mirror of
https://github.com/videolan/vlc-android
synced 2024-11-23 09:56:36 +08:00
Subtitle download using the new API
This commit is contained in:
parent
d2aa35e233
commit
7f09ad5715
@ -1,7 +1,9 @@
|
||||
package org.videolan.resources.opensubtitles
|
||||
|
||||
import org.videolan.resources.opensubtitles.OpenSubV1
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface IOpenSubtitleService {
|
||||
@ -16,6 +18,9 @@ interface IOpenSubtitleService {
|
||||
@Query("season_number") season: Int? = null,
|
||||
): OpenSubV1
|
||||
|
||||
@POST("download")
|
||||
suspend fun queryDownloadUrl( @Body downloadLinkBody: DownloadLinkBody): Response<DownloadLink>
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
package org.videolan.resources.opensubtitles
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
data class QueryParameters(
|
||||
@field:Json(name = "query") val query: String,
|
||||
@field:Json(name = "episode") val episode: String,
|
||||
@field:Json(name = "season") val season: String
|
||||
@field:Json(name = "query") val query: String,
|
||||
@field:Json(name = "episode") val episode: String,
|
||||
@field:Json(name = "season") val season: String
|
||||
)
|
||||
|
||||
|
||||
@ -123,7 +124,7 @@ data class File(
|
||||
@field:Json(name = "cd_number")
|
||||
val cdNumber: Int?,
|
||||
@field:Json(name = "file_id")
|
||||
val fileId: Int,
|
||||
val fileId: Long,
|
||||
@field:Json(name = "file_name")
|
||||
val fileName: String
|
||||
)
|
||||
@ -148,3 +149,25 @@ data class Uploader(
|
||||
val uploaderId: Int?
|
||||
)
|
||||
|
||||
data class DownloadLink(
|
||||
@field:Json(name = "file_name")
|
||||
val fileName: String,
|
||||
@field:Json(name = "link")
|
||||
val link: String,
|
||||
@field:Json(name = "message")
|
||||
val message: String,
|
||||
@field:Json(name = "remaining")
|
||||
val remaining: Int,
|
||||
@field:Json(name = "requests")
|
||||
val requests: Int,
|
||||
@field:Json(name = "reset_time")
|
||||
val resetTime: String,
|
||||
@field:Json(name = "reset_time_utc")
|
||||
val resetTimeUtc: String
|
||||
)
|
||||
|
||||
data class DownloadLinkBody(
|
||||
@field:Json(name = "file_id")
|
||||
val fileId: Long,
|
||||
)
|
||||
|
||||
|
@ -40,6 +40,11 @@ class OpenSubtitleRepository(private val openSubtitleService: IOpenSubtitleServi
|
||||
|
||||
}
|
||||
|
||||
suspend fun getDownloadLink(fileId: Long): DownloadLink {
|
||||
val query = openSubtitleService.queryDownloadUrl(DownloadLinkBody(fileId))
|
||||
return query.body() ?: throw Exception("No body")
|
||||
}
|
||||
|
||||
companion object {
|
||||
// To ensure the instance can be overridden in tests.
|
||||
var instance = lazy { OpenSubtitleRepository(OpenSubtitleClient.instance) }
|
||||
|
@ -17,7 +17,7 @@ import java.util.concurrent.TimeUnit
|
||||
|
||||
|
||||
private const val BASE_URL = "https://api.opensubtitles.com/api/v1/"
|
||||
private const val USER_AGENT = "VLSub v0.9"
|
||||
const val USER_AGENT = "VLSub v0.9"
|
||||
|
||||
private fun buildClient() = Retrofit.Builder()
|
||||
.baseUrl(BASE_URL)
|
||||
@ -46,6 +46,7 @@ private class UserAgentInterceptor(val userAgent: String): Interceptor {
|
||||
val userAgentRequest: Request = request.newBuilder()
|
||||
.header("User-Agent", userAgent)
|
||||
.header("Api-Key", BuildConfig.VLC_OPEN_SUBTITLES_API_KEY)
|
||||
.header("Accept", "application/json")
|
||||
.build()
|
||||
return chain.proceed(userAgentRequest)
|
||||
}
|
||||
@ -54,6 +55,5 @@ private class UserAgentInterceptor(val userAgent: String): Interceptor {
|
||||
interface OpenSubtitleClient {
|
||||
companion object {
|
||||
val instance: IOpenSubtitleService by lazy { buildClient() }
|
||||
fun getDownloadLink(id:Int) = "${BASE_URL}download?file_id=$id"
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,11 @@ import android.content.DialogInterface
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.*
|
||||
import android.view.Gravity
|
||||
import android.view.KeyEvent
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
@ -14,9 +18,12 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
import kotlinx.coroutines.channels.actor
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.videolan.resources.opensubtitles.OpenSubtitleRepository
|
||||
import org.videolan.resources.util.parcelableList
|
||||
import org.videolan.vlc.R
|
||||
import org.videolan.vlc.databinding.SubtitleDownloaderDialogBinding
|
||||
@ -56,7 +63,15 @@ class SubtitleDownloaderDialogFragment : VLCBottomSheetDialogFragment() {
|
||||
private val listEventActor = lifecycleScope.actor<SubtitleEvent> {
|
||||
for (subtitleEvent in channel) if (isActive) when (subtitleEvent) {
|
||||
is SubtitleClick -> when (subtitleEvent.item.state) {
|
||||
State.NotDownloaded -> VLCDownloadManager.download(requireActivity(), subtitleEvent.item)
|
||||
State.NotDownloaded -> {
|
||||
withContext(Dispatchers.IO) {
|
||||
val downloadLink = OpenSubtitleRepository.getInstance()
|
||||
.getDownloadLink(subtitleEvent.item.fileId)
|
||||
subtitleEvent.item.zipDownloadLink = downloadLink.link
|
||||
subtitleEvent.item.fileName = downloadLink.fileName
|
||||
}
|
||||
VLCDownloadManager.download(requireActivity(), subtitleEvent.item, true)
|
||||
}
|
||||
State.Downloaded -> deleteSubtitleDialog(requireActivity(), DialogInterface.OnClickListener { _, _ ->
|
||||
subtitleEvent.item.mediaUri.path?.let { viewModel.deleteSubtitle(it, subtitleEvent.item.idSubtitle) }
|
||||
}
|
||||
@ -153,7 +168,7 @@ class SubtitleDownloaderDialogFragment : VLCBottomSheetDialogFragment() {
|
||||
}
|
||||
})
|
||||
//todo
|
||||
viewModel.observableSearchHearingImpaired.set(true)
|
||||
viewModel.observableSearchHearingImpaired.set(false)
|
||||
|
||||
binding.retryButton.setOnClickListener {
|
||||
viewModel.onRefresh()
|
||||
|
@ -5,14 +5,16 @@ import org.videolan.tools.readableNumber
|
||||
|
||||
data class SubtitleItem(
|
||||
val idSubtitle: String,
|
||||
val fileId: Long,
|
||||
val mediaUri: Uri,
|
||||
val subLanguageID: String,
|
||||
val movieReleaseName: String,
|
||||
val state: State,
|
||||
val zipDownloadLink: String,
|
||||
var zipDownloadLink: String,
|
||||
val hearingImpaired: Boolean,
|
||||
val rating: Int,
|
||||
val downloadNumber: Long
|
||||
val downloadNumber: Long,
|
||||
var fileName: String = ""
|
||||
) {
|
||||
fun getReadableDownloadNumber() = downloadNumber.readableNumber()
|
||||
}
|
||||
|
@ -194,6 +194,14 @@ object FileUtils {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun copyFile(src: String, dst: String): String? {
|
||||
return if (copyFile(File(src), File(dst)))
|
||||
dst
|
||||
else
|
||||
null
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
fun copyFile(src: File, dst: File): Boolean {
|
||||
var ret = true
|
||||
@ -217,7 +225,8 @@ object FileUtils {
|
||||
len = inputStream.read(buf)
|
||||
}
|
||||
return true
|
||||
} catch (ignored: IOException) {
|
||||
} catch (exception: IOException) {
|
||||
Log.e(TAG, exception.message, exception)
|
||||
} finally {
|
||||
CloseableUtils.close(inputStream)
|
||||
CloseableUtils.close(out)
|
||||
@ -453,51 +462,6 @@ object FileUtils {
|
||||
return volumeDescription
|
||||
}
|
||||
|
||||
suspend fun unpackZip(path: String, unzipDirectory: String): ArrayList<String> = withContext(Dispatchers.IO) {
|
||||
val fis: InputStream
|
||||
val zis: ZipInputStream
|
||||
val unzippedFiles = ArrayList<String>()
|
||||
File(unzipDirectory).mkdirs()
|
||||
try {
|
||||
fis = FileInputStream(path)
|
||||
zis = ZipInputStream(BufferedInputStream(fis))
|
||||
var ze = zis.nextEntry
|
||||
|
||||
while (ze != null) {
|
||||
val baos = ByteArrayOutputStream()
|
||||
val buffer = ByteArray(1024)
|
||||
var count = zis.read(buffer)
|
||||
|
||||
val filename = ze.name.replace('/', ' ')
|
||||
if (filename.endsWith(".nfo")) {
|
||||
zis.closeEntry()
|
||||
ze = zis.nextEntry
|
||||
continue
|
||||
}
|
||||
val fileToUnzip = File(unzipDirectory, filename)
|
||||
val fout = FileOutputStream(fileToUnzip)
|
||||
|
||||
// reading and writing
|
||||
while (count != -1) {
|
||||
baos.write(buffer, 0, count)
|
||||
val bytes = baos.toByteArray()
|
||||
fout.write(bytes)
|
||||
baos.reset()
|
||||
count = zis.read(buffer)
|
||||
}
|
||||
|
||||
unzippedFiles.add(fileToUnzip.absolutePath)
|
||||
fout.close()
|
||||
zis.closeEntry()
|
||||
ze = zis.nextEntry
|
||||
}
|
||||
zis.close()
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
unzippedFiles
|
||||
}
|
||||
|
||||
const val BUFFER = 2048
|
||||
fun zip(files: Array<String>, zipFileName: String):Boolean {
|
||||
return try {
|
||||
|
@ -5,6 +5,7 @@ import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.net.toUri
|
||||
@ -17,12 +18,15 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.videolan.libvlc.util.Extensions
|
||||
import org.videolan.resources.AppContextProvider
|
||||
import org.videolan.resources.opensubtitles.USER_AGENT
|
||||
import org.videolan.resources.util.registerReceiverCompat
|
||||
import org.videolan.tools.isStarted
|
||||
import org.videolan.vlc.BuildConfig
|
||||
import org.videolan.vlc.R
|
||||
import org.videolan.vlc.gui.dialogs.SubtitleItem
|
||||
import org.videolan.vlc.gui.helpers.hf.getExtWritePermission
|
||||
import org.videolan.vlc.repository.ExternalSubRepository
|
||||
import java.io.File
|
||||
|
||||
|
||||
object VLCDownloadManager: BroadcastReceiver(), DefaultLifecycleObserver {
|
||||
@ -62,10 +66,12 @@ object VLCDownloadManager: BroadcastReceiver(), DefaultLifecycleObserver {
|
||||
AppContextProvider.appContext.applicationContext.unregisterReceiver(this)
|
||||
}
|
||||
|
||||
suspend fun download(context: FragmentActivity, subtitleItem: SubtitleItem) {
|
||||
suspend fun download(context: FragmentActivity, subtitleItem: SubtitleItem, useOpenSubtitlesHeader: Boolean = false) {
|
||||
val request = DownloadManager.Request(subtitleItem.zipDownloadLink.toUri())
|
||||
request.setDescription(subtitleItem.movieReleaseName)
|
||||
request.setTitle(context.resources.getString(R.string.download_subtitle_title))
|
||||
if (useOpenSubtitlesHeader) request.addRequestHeader("User-Agent", USER_AGENT)
|
||||
if (useOpenSubtitlesHeader) request.addRequestHeader("Api-Key", org.videolan.resources.BuildConfig.VLC_OPEN_SUBTITLES_API_KEY)
|
||||
request.setDestinationInExternalFilesDir(context, getDownloadPath(subtitleItem), "")
|
||||
val id = downloadManager.enqueue(request)
|
||||
val deferred = CompletableDeferred<SubDlResult>().also { dlDeferred = it }
|
||||
@ -78,14 +84,13 @@ object VLCDownloadManager: BroadcastReceiver(), DefaultLifecycleObserver {
|
||||
|
||||
private suspend fun downloadSuccessful(id:Long, subtitleItem: SubtitleItem, localUri: String, context: FragmentActivity) {
|
||||
val extractDirectory = getFinalDirectory(context, subtitleItem) ?: return
|
||||
val downloadedPaths = FileUtils.unpackZip(localUri, extractDirectory)
|
||||
subtitleItem.run {
|
||||
ExternalSubRepository.getInstance(context).removeDownloadingItem(id)
|
||||
downloadedPaths.forEach {
|
||||
if (Extensions.SUBTITLES.contains(".${it.split('.').last()}")) {
|
||||
FileUtils.copyFile(localUri, "$extractDirectory/${subtitleItem.fileName}")?.let {dest ->
|
||||
subtitleItem.run {
|
||||
ExternalSubRepository.getInstance(context).removeDownloadingItem(id)
|
||||
if (Extensions.SUBTITLES.contains(".${dest.split('.').last()}")) {
|
||||
ExternalSubRepository.getInstance(context).saveDownloadedSubtitle(
|
||||
idSubtitle,
|
||||
it,
|
||||
dest,
|
||||
mediaUri.path!!,
|
||||
subLanguageID,
|
||||
movieReleaseName,
|
||||
@ -94,7 +99,8 @@ object VLCDownloadManager: BroadcastReceiver(), DefaultLifecycleObserver {
|
||||
}
|
||||
else
|
||||
Toast.makeText(context, R.string.subtitles_download_failed, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.IO) { FileUtils.deleteFile(localUri) }
|
||||
}
|
||||
}
|
||||
@ -113,13 +119,18 @@ object VLCDownloadManager: BroadcastReceiver(), DefaultLifecycleObserver {
|
||||
ExternalSubRepository.getInstance(context).removeDownloadingItem(id)
|
||||
}
|
||||
|
||||
private fun getDownloadPath(subtitleItem: SubtitleItem) = "VLC/${subtitleItem.movieReleaseName}_${subtitleItem.idSubtitle}.zip"
|
||||
private fun getDownloadPath(subtitleItem: SubtitleItem) = "VLC/${subtitleItem.movieReleaseName}_${subtitleItem.fileName}.zip"
|
||||
|
||||
private fun getDownloadState(downloadId: Long): Pair<Int, String> {
|
||||
val query = DownloadManager.Query()
|
||||
query.setFilterById(downloadId)
|
||||
val cursor = downloadManager.query(query)
|
||||
cursor.moveToFirst()
|
||||
val reasonIndex = cursor.getColumnIndex(DownloadManager.COLUMN_REASON)
|
||||
val reason = cursor.getInt(reasonIndex)
|
||||
if (BuildConfig.DEBUG) Log.d("VLCDownloadManager", "Reason: $reason")
|
||||
|
||||
|
||||
val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
||||
|
||||
val status = if (statusIndex != -1)
|
||||
|
@ -67,7 +67,7 @@ class SubtitlesModel(private val context: Context, private val mediaUri: Uri, pr
|
||||
|
||||
private val apiResultLiveData: MutableLiveData<List<Data>> = MutableLiveData()
|
||||
private val downloadedLiveData = ExternalSubRepository.getInstance(context).getDownloadedSubtitles(mediaUri).map { list ->
|
||||
list.map { SubtitleItem(it.idSubtitle, mediaUri, it.subLanguageID, it.movieReleaseName, State.Downloaded, "", it.hearingImpaired, 0, 0) }
|
||||
list.map { SubtitleItem(it.idSubtitle, -1, mediaUri, it.subLanguageID, it.movieReleaseName, State.Downloaded, "", it.hearingImpaired, 0, 0) }
|
||||
}
|
||||
|
||||
private val downloadingLiveData = ExternalSubRepository.getInstance(context).downloadingSubtitles
|
||||
@ -130,11 +130,12 @@ class SubtitlesModel(private val context: Context, private val mediaUri: Uri, pr
|
||||
list.add(
|
||||
SubtitleItem(
|
||||
openSubtitle.attributes.subtitleId,
|
||||
openSubtitle.attributes.files.first().fileId,
|
||||
mediaUri,
|
||||
openSubtitle.attributes.language,
|
||||
openSubtitle.attributes.featureDetails.title,
|
||||
state,
|
||||
OpenSubtitleClient.getDownloadLink(openSubtitle.attributes.files.first().fileId),
|
||||
"",
|
||||
openSubtitle.attributes.hearingImpaired,
|
||||
openSubtitle.attributes.ratings,
|
||||
openSubtitle.attributes.downloadCount
|
||||
|
Loading…
Reference in New Issue
Block a user