forked from recloudstream/cloudstream
fix downloaded subtitles
This commit is contained in:
parent
e6a85855b1
commit
9b639a06fb
3 changed files with 94 additions and 31 deletions
|
@ -528,7 +528,7 @@ class ResultFragment : Fragment() {
|
||||||
val downloadList = ctx.getDownloadSubsLanguageISO639_1()
|
val downloadList = ctx.getDownloadSubsLanguageISO639_1()
|
||||||
main {
|
main {
|
||||||
subs?.let { subsList ->
|
subs?.let { subsList ->
|
||||||
subsList.filter { downloadList.contains(SubtitleHelper.fromLanguageToTwoLetters(it.lang)) }
|
subsList.filter { downloadList.contains(SubtitleHelper.fromLanguageToTwoLetters(it.lang, true)) }
|
||||||
.map { ExtractorSubtitleLink(it.lang, it.url, "") }
|
.map { ExtractorSubtitleLink(it.lang, it.url, "") }
|
||||||
.forEach { link ->
|
.forEach { link ->
|
||||||
val epName = meta.name ?: "${context?.getString(R.string.episode)} ${meta.episode}"
|
val epName = meta.name ?: "${context?.getString(R.string.episode)} ${meta.episode}"
|
||||||
|
|
|
@ -39,17 +39,29 @@ object SubtitleHelper {
|
||||||
println("ISO CREATED:\n$text")
|
println("ISO CREATED:\n$text")
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
/** lang -> ISO_639_1*/
|
/** lang -> ISO_639_1
|
||||||
fun fromLanguageToTwoLetters(input: String): String? {
|
* @param looseCheck will use .contains in addition to .equals
|
||||||
for (lang in languages) {
|
* */
|
||||||
if (lang.languageName == input || lang.nativeName == input) {
|
fun fromLanguageToTwoLetters(input: String, looseCheck: Boolean): String? {
|
||||||
return lang.ISO_639_1
|
|
||||||
|
languages.forEach {
|
||||||
|
if (it.languageName.equals(input, ignoreCase = true)
|
||||||
|
|| it.nativeName.equals(input, ignoreCase = true)
|
||||||
|
) return it.ISO_639_1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Runs as a separate loop as to prioritize fully matching languages.
|
||||||
|
if (looseCheck)
|
||||||
|
languages.forEach {
|
||||||
|
if (input.contains(it.languageName, ignoreCase = true)
|
||||||
|
|| input.contains(it.nativeName, ignoreCase = true)
|
||||||
|
) return it.ISO_639_1
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private var ISO_639_1Map : HashMap<String, String> = hashMapOf()
|
private var ISO_639_1Map: HashMap<String, String> = hashMapOf()
|
||||||
private fun initISO6391Map() {
|
private fun initISO6391Map() {
|
||||||
for (lang in languages) {
|
for (lang in languages) {
|
||||||
ISO_639_1Map[lang.ISO_639_1] = lang.languageName
|
ISO_639_1Map[lang.ISO_639_1] = lang.languageName
|
||||||
|
@ -59,7 +71,7 @@ object SubtitleHelper {
|
||||||
/** ISO_639_1 -> lang*/
|
/** ISO_639_1 -> lang*/
|
||||||
fun fromTwoLettersToLanguage(input: String): String? {
|
fun fromTwoLettersToLanguage(input: String): String? {
|
||||||
if (input.length != 2) return null
|
if (input.length != 2) return null
|
||||||
if(ISO_639_1Map.isEmpty()) {
|
if (ISO_639_1Map.isEmpty()) {
|
||||||
initISO6391Map()
|
initISO6391Map()
|
||||||
}
|
}
|
||||||
val comparison = input.toLowerCase(Locale.ROOT)
|
val comparison = input.toLowerCase(Locale.ROOT)
|
||||||
|
|
|
@ -150,7 +150,8 @@ object VideoDownloadManager {
|
||||||
private const val SUCCESS_DOWNLOAD_DONE = 1
|
private const val SUCCESS_DOWNLOAD_DONE = 1
|
||||||
private const val SUCCESS_STREAM = 3
|
private const val SUCCESS_STREAM = 3
|
||||||
private const val SUCCESS_STOPPED = 2
|
private const val SUCCESS_STOPPED = 2
|
||||||
private const val ERROR_DELETING_FILE = 3 // will not download the next one, but is still classified as an error
|
private const val ERROR_DELETING_FILE =
|
||||||
|
3 // will not download the next one, but is still classified as an error
|
||||||
private const val ERROR_CREATE_FILE = -2
|
private const val ERROR_CREATE_FILE = -2
|
||||||
private const val ERROR_UNKNOWN = -10
|
private const val ERROR_UNKNOWN = -10
|
||||||
private const val ERROR_OPEN_FILE = -3
|
private const val ERROR_OPEN_FILE = -3
|
||||||
|
@ -422,7 +423,8 @@ object VideoDownloadManager {
|
||||||
c.moveToFirst()
|
c.moveToFirst()
|
||||||
while (true) {
|
while (true) {
|
||||||
val id = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
|
val id = c.getLong(c.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
|
||||||
val name = c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME))
|
val name =
|
||||||
|
c.getString(c.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME))
|
||||||
val uri = ContentUris.withAppendedId(
|
val uri = ContentUris.withAppendedId(
|
||||||
MediaStore.Downloads.EXTERNAL_CONTENT_URI, id
|
MediaStore.Downloads.EXTERNAL_CONTENT_URI, id
|
||||||
)
|
)
|
||||||
|
@ -450,7 +452,11 @@ object VideoDownloadManager {
|
||||||
* Used for getting video player subs.
|
* Used for getting video player subs.
|
||||||
* @return List of pairs for the files in this format: <Name, Uri>
|
* @return List of pairs for the files in this format: <Name, Uri>
|
||||||
* */
|
* */
|
||||||
fun getFolder(context: Context, relativePath: String, basePath: String?): List<Pair<String, Uri>>? {
|
fun getFolder(
|
||||||
|
context: Context,
|
||||||
|
relativePath: String,
|
||||||
|
basePath: String?
|
||||||
|
): List<Pair<String, Uri>>? {
|
||||||
val base = basePathToFile(context, basePath)
|
val base = basePathToFile(context, basePath)
|
||||||
val folder = base?.gotoDir(relativePath, false)
|
val folder = base?.gotoDir(relativePath, false)
|
||||||
|
|
||||||
|
@ -472,7 +478,10 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.Q)
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
private fun ContentResolver.getExistingDownloadUriOrNullQ(relativePath: String, displayName: String): Uri? {
|
private fun ContentResolver.getExistingDownloadUriOrNullQ(
|
||||||
|
relativePath: String,
|
||||||
|
displayName: String
|
||||||
|
): Uri? {
|
||||||
try {
|
try {
|
||||||
val projection = arrayOf(
|
val projection = arrayOf(
|
||||||
MediaStore.MediaColumns._ID,
|
MediaStore.MediaColumns._ID,
|
||||||
|
@ -558,10 +567,15 @@ object VideoDownloadManager {
|
||||||
val cr = context.contentResolver ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND)
|
val cr = context.contentResolver ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND)
|
||||||
|
|
||||||
val currentExistingFile =
|
val currentExistingFile =
|
||||||
cr.getExistingDownloadUriOrNullQ(folder ?: "", displayName) // CURRENT FILE WITH THE SAME PATH
|
cr.getExistingDownloadUriOrNullQ(
|
||||||
|
folder ?: "",
|
||||||
|
displayName
|
||||||
|
) // CURRENT FILE WITH THE SAME PATH
|
||||||
|
|
||||||
fileLength =
|
fileLength =
|
||||||
if (currentExistingFile == null || !resume) 0 else (cr.getFileLength(currentExistingFile)
|
if (currentExistingFile == null || !resume) 0 else (cr.getFileLength(
|
||||||
|
currentExistingFile
|
||||||
|
)
|
||||||
?: 0)// IF NOT RESUME THEN 0, OTHERWISE THE CURRENT FILE SIZE
|
?: 0)// IF NOT RESUME THEN 0, OTHERWISE THE CURRENT FILE SIZE
|
||||||
|
|
||||||
if (!resume && currentExistingFile != null) { // DELETE FILE IF FILE EXITS AND NOT RESUME
|
if (!resume && currentExistingFile != null) { // DELETE FILE IF FILE EXITS AND NOT RESUME
|
||||||
|
@ -580,9 +594,13 @@ object VideoDownloadManager {
|
||||||
MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
MediaStore.Downloads.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) // USE INSTEAD OF MediaStore.Downloads.EXTERNAL_CONTENT_URI
|
||||||
//val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
//val currentMimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||||
val currentMimeType = when (extension) {
|
val currentMimeType = when (extension) {
|
||||||
"vtt" -> "text/vtt"
|
|
||||||
|
// Absolutely ridiculous, if text/vtt is used as mimetype scoped storage prevents
|
||||||
|
// downloading to /Downloads yet it works with null
|
||||||
|
|
||||||
|
"vtt" -> null // "text/vtt"
|
||||||
"mp4" -> "video/mp4"
|
"mp4" -> "video/mp4"
|
||||||
"srt" -> "application/x-subrip"//"text/plain"
|
"srt" -> null // "application/x-subrip"//"text/plain"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
val newFile = ContentValues().apply {
|
val newFile = ContentValues().apply {
|
||||||
|
@ -616,7 +634,8 @@ object VideoDownloadManager {
|
||||||
if (subDir.createFile(displayName) == null) return StreamData(ERROR_CREATE_FILE)
|
if (subDir.createFile(displayName) == null) return StreamData(ERROR_CREATE_FILE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fileStream = (subDir.findFile(displayName) ?: subDir.createFile(displayName))!!.openOutputStream()
|
fileStream = (subDir.findFile(displayName)
|
||||||
|
?: subDir.createFile(displayName))!!.openOutputStream()
|
||||||
// fileStream = FileOutputStream(rFile, false)
|
// fileStream = FileOutputStream(rFile, false)
|
||||||
if (fileLength == 0L) resume = false
|
if (fileLength == 0L) resume = false
|
||||||
}
|
}
|
||||||
|
@ -640,7 +659,8 @@ object VideoDownloadManager {
|
||||||
val basePath = context.getBasePath()
|
val basePath = context.getBasePath()
|
||||||
|
|
||||||
val displayName = getDisplayName(name, extension)
|
val displayName = getDisplayName(name, extension)
|
||||||
val relativePath = if (isScopedStorage && basePath.first.isDownloadDir()) getRelativePath(folder) else folder
|
val relativePath =
|
||||||
|
if (isScopedStorage && basePath.first.isDownloadDir()) getRelativePath(folder) else folder
|
||||||
|
|
||||||
fun deleteFile(): Int {
|
fun deleteFile(): Int {
|
||||||
return delete(context, name, relativePath, extension, parentId, basePath.first)
|
return delete(context, name, relativePath, extension, parentId, basePath.first)
|
||||||
|
@ -690,7 +710,8 @@ object VideoDownloadManager {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // fuck android
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // fuck android
|
||||||
connection.contentLengthLong
|
connection.contentLengthLong
|
||||||
} else {
|
} else {
|
||||||
connection.getHeaderField("content-length").toLongOrNull() ?: connection.contentLength.toLong()
|
connection.getHeaderField("content-length").toLongOrNull()
|
||||||
|
?: connection.contentLength.toLong()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logError(e)
|
logError(e)
|
||||||
|
@ -753,7 +774,13 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createNotificationCallback.invoke(CreateNotificationMetadata(type, bytesDownloaded, bytesTotal))
|
createNotificationCallback.invoke(
|
||||||
|
CreateNotificationMetadata(
|
||||||
|
type,
|
||||||
|
bytesDownloaded,
|
||||||
|
bytesTotal
|
||||||
|
)
|
||||||
|
)
|
||||||
/*createNotification(
|
/*createNotification(
|
||||||
context,
|
context,
|
||||||
source,
|
source,
|
||||||
|
@ -853,7 +880,15 @@ object VideoDownloadManager {
|
||||||
deleteFile()
|
deleteFile()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
parentId?.let { id -> downloadProgressEvent.invoke(Triple(id, bytesDownloaded, bytesTotal)) }
|
parentId?.let { id ->
|
||||||
|
downloadProgressEvent.invoke(
|
||||||
|
Triple(
|
||||||
|
id,
|
||||||
|
bytesDownloaded,
|
||||||
|
bytesTotal
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
isDone = true
|
isDone = true
|
||||||
updateNotification()
|
updateNotification()
|
||||||
SUCCESS_DOWNLOAD_DONE
|
SUCCESS_DOWNLOAD_DONE
|
||||||
|
@ -871,7 +906,10 @@ object VideoDownloadManager {
|
||||||
* @param directoryName if null will use the current path.
|
* @param directoryName if null will use the current path.
|
||||||
* @return UniFile / null if createMissingDirectories = false and folder is not found.
|
* @return UniFile / null if createMissingDirectories = false and folder is not found.
|
||||||
* */
|
* */
|
||||||
private fun UniFile.gotoDir(directoryName: String?, createMissingDirectories: Boolean = true): UniFile? {
|
private fun UniFile.gotoDir(
|
||||||
|
directoryName: String?,
|
||||||
|
createMissingDirectories: Boolean = true
|
||||||
|
): UniFile? {
|
||||||
|
|
||||||
// May give this error on scoped storage.
|
// May give this error on scoped storage.
|
||||||
// W/DocumentsContract: Failed to create document
|
// W/DocumentsContract: Failed to create document
|
||||||
|
@ -945,7 +983,10 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
@Deprecated("TODO fix UniFile to work with download directory.")
|
@Deprecated("TODO fix UniFile to work with download directory.")
|
||||||
private fun getRelativePath(folder: String?): String {
|
private fun getRelativePath(folder: String?): String {
|
||||||
return (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace('/', File.separatorChar)
|
return (Environment.DIRECTORY_DOWNLOADS + '/' + folder + '/').replace(
|
||||||
|
'/',
|
||||||
|
File.separatorChar
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -988,7 +1029,8 @@ object VideoDownloadManager {
|
||||||
// If scoped storage and using download dir (not accessible with UniFile)
|
// If scoped storage and using download dir (not accessible with UniFile)
|
||||||
if (isScopedStorage && basePath.isDownloadDir()) {
|
if (isScopedStorage && basePath.isDownloadDir()) {
|
||||||
val relativePath = getRelativePath(folder)
|
val relativePath = getRelativePath(folder)
|
||||||
val lastContent = context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName)
|
val lastContent =
|
||||||
|
context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName)
|
||||||
if (lastContent != null) {
|
if (lastContent != null) {
|
||||||
context.contentResolver.delete(lastContent, null, null)
|
context.contentResolver.delete(lastContent, null, null)
|
||||||
}
|
}
|
||||||
|
@ -1040,7 +1082,8 @@ object VideoDownloadManager {
|
||||||
var realIndex = startIndex ?: 0
|
var realIndex = startIndex ?: 0
|
||||||
val basePath = context.getBasePath()
|
val basePath = context.getBasePath()
|
||||||
|
|
||||||
val relativePath = if (isScopedStorage && basePath.first.isDownloadDir()) getRelativePath(folder) else folder
|
val relativePath =
|
||||||
|
if (isScopedStorage && basePath.first.isDownloadDir()) getRelativePath(folder) else folder
|
||||||
|
|
||||||
val stream = setupStream(context, name, relativePath, extension, realIndex > 0)
|
val stream = setupStream(context, name, relativePath, extension, realIndex > 0)
|
||||||
if (stream.errorCode != SUCCESS_STREAM) return stream.errorCode
|
if (stream.errorCode != SUCCESS_STREAM) return stream.errorCode
|
||||||
|
@ -1258,7 +1301,8 @@ object VideoDownloadManager {
|
||||||
notificationCallback: (Int, Notification) -> Unit,
|
notificationCallback: (Int, Notification) -> Unit,
|
||||||
tryResume: Boolean = false,
|
tryResume: Boolean = false,
|
||||||
): Int {
|
): Int {
|
||||||
val name = sanitizeFilename(ep.name ?: "${context.getString(R.string.episode)} ${ep.episode}")
|
val name =
|
||||||
|
sanitizeFilename(ep.name ?: "${context.getString(R.string.episode)} ${ep.episode}")
|
||||||
|
|
||||||
if (link.isM3u8 || URI(link.url).path.endsWith(".m3u8")) {
|
if (link.isM3u8 || URI(link.url).path.endsWith(".m3u8")) {
|
||||||
val startIndex = if (tryResume) {
|
val startIndex = if (tryResume) {
|
||||||
|
@ -1323,7 +1367,11 @@ object VideoDownloadManager {
|
||||||
val link = item.links[index]
|
val link = item.links[index]
|
||||||
val resume = pkg.linkIndex == index
|
val resume = pkg.linkIndex == index
|
||||||
|
|
||||||
context.setKey(KEY_RESUME_PACKAGES, id.toString(), DownloadResumePackage(item, index))
|
context.setKey(
|
||||||
|
KEY_RESUME_PACKAGES,
|
||||||
|
id.toString(),
|
||||||
|
DownloadResumePackage(item, index)
|
||||||
|
)
|
||||||
val connectionResult = withContext(Dispatchers.IO) {
|
val connectionResult = withContext(Dispatchers.IO) {
|
||||||
normalSafeApiCall {
|
normalSafeApiCall {
|
||||||
downloadSingleEpisode(
|
downloadSingleEpisode(
|
||||||
|
@ -1361,7 +1409,8 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDownloadFileInfo(context: Context, id: Int): DownloadedFileInfoResult? {
|
private fun getDownloadFileInfo(context: Context, id: Int): DownloadedFileInfoResult? {
|
||||||
val info = context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return null
|
val info =
|
||||||
|
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return null
|
||||||
val base = basePathToFile(context, info.basePath)
|
val base = basePathToFile(context, info.basePath)
|
||||||
|
|
||||||
if (isScopedStorage && base.isDownloadDir()) {
|
if (isScopedStorage && base.isDownloadDir()) {
|
||||||
|
@ -1404,7 +1453,8 @@ object VideoDownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteFile(context: Context, id: Int): Boolean {
|
private fun deleteFile(context: Context, id: Int): Boolean {
|
||||||
val info = context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return false
|
val info =
|
||||||
|
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return false
|
||||||
downloadEvent.invoke(Pair(id, DownloadActionType.Stop))
|
downloadEvent.invoke(Pair(id, DownloadActionType.Stop))
|
||||||
downloadProgressEvent.invoke(Triple(id, 0, 0))
|
downloadProgressEvent.invoke(Triple(id, 0, 0))
|
||||||
downloadStatusEvent.invoke(Pair(id, DownloadType.IsStopped))
|
downloadStatusEvent.invoke(Pair(id, DownloadType.IsStopped))
|
||||||
|
@ -1468,7 +1518,8 @@ object VideoDownloadManager {
|
||||||
|
|
||||||
private fun saveQueue(context: Context) {
|
private fun saveQueue(context: Context) {
|
||||||
val dQueue =
|
val dQueue =
|
||||||
downloadQueue.toList().mapIndexed { index, any -> DownloadQueueResumePackage(index, any) }
|
downloadQueue.toList()
|
||||||
|
.mapIndexed { index, any -> DownloadQueueResumePackage(index, any) }
|
||||||
.toTypedArray()
|
.toTypedArray()
|
||||||
context.setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
|
context.setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue