Skip to content

Commit

Permalink
Feat: Add subtitle charset detection for network files (#1012)
Browse files Browse the repository at this point in the history
* add charset detection for http subtitles

* run ktlintFormat
  • Loading branch information
anilbeesetti authored Jul 27, 2024
1 parent e0267bc commit 9b94a68
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,11 @@ import android.provider.MediaStore
import android.provider.OpenableColumns
import android.util.Log
import android.util.TypedValue
import android.widget.Toast
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.core.text.isDigitsOnly
import java.io.BufferedInputStream
import java.io.BufferedReader
import java.io.BufferedWriter
import java.io.File
import java.io.FileWriter
import java.io.InputStreamReader
import java.io.InputStream
import java.net.URL
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import kotlin.coroutines.suspendCoroutine
Expand Down Expand Up @@ -201,10 +196,6 @@ fun Context.getMediaContentUri(uri: Uri): Uri? {
return null
}

fun Context.showToast(string: String, duration: Int = Toast.LENGTH_SHORT) {
Toast.makeText(this, string, duration).show()
}

suspend fun Context.scanPaths(paths: List<String>): Boolean = suspendCoroutine { continuation ->
try {
MediaScannerConnection.scanFile(
Expand Down Expand Up @@ -242,71 +233,84 @@ suspend fun Context.scanStorage(
}
}

fun Context.convertToUTF8(uri: Uri, charset: Charset?): Uri {
suspend fun Context.convertToUTF8(uri: Uri, charset: Charset? = null): Uri = withContext(Dispatchers.IO) {
try {
// TODO: handle network uri
if (uri.scheme?.lowercase()?.startsWith("http") == true) return uri
contentResolver.openInputStream(uri)?.use { inputStream ->
val bufferedInputStream = BufferedInputStream(inputStream)
val detectedCharset = charset ?: detectCharset(bufferedInputStream)
return convertToUTF8(uri, bufferedInputStream.reader(detectedCharset))
when {
uri.scheme?.let { it in listOf("http", "https", "ftp") } == true -> {
val url = URL(uri.toString())
val detectedCharset = charset ?: detectCharset(url)
if (detectedCharset == StandardCharsets.UTF_8) {
uri
} else {
convertNetworkUriToUTF8(url = url, sourceCharset = detectedCharset)
}
}
else -> {
val detectedCharset = charset ?: detectCharset(uri = uri, context = this@convertToUTF8)
if (detectedCharset == StandardCharsets.UTF_8) {
uri
} else {
convertLocalUriToUTF8(uri = uri, sourceCharset = detectedCharset)
}
}
}
} catch (exception: Exception) {
exception.printStackTrace()
uri
}
return uri
}

fun detectCharset(inputStream: BufferedInputStream): Charset {
val charsetDetector = UniversalDetector()
charsetDetector.handleData(inputStream.readBytes())
charsetDetector.dataEnd()

val encoding = charsetDetector.detectedCharset

Log.d("TAG", "detectCharset: $encoding")

return encoding?.let { Charset.forName(encoding) } ?: StandardCharsets.UTF_8
private fun detectCharset(uri: Uri, context: Context): Charset {
return context.contentResolver.openInputStream(uri)?.use { inputStream ->
detectCharsetFromStream(inputStream)
} ?: StandardCharsets.UTF_8
}

fun Context.convertToUTF8(inputUri: Uri, inputStreamReader: InputStreamReader): Uri {
if (!StandardCharsets.UTF_8.displayName().equals(inputStreamReader.encoding)) {
val fileName = getFilenameFromUri(inputUri)
val file = File(subtitleCacheDir, fileName)
val bufferedReader = BufferedReader(inputStreamReader)
val bufferedWriter = BufferedWriter(FileWriter(file))

val buffer = CharArray(512)
var bytesRead: Int
private fun detectCharset(url: URL): Charset {
return url.openStream().use { inputStream ->
detectCharsetFromStream(inputStream)
}
}

while (bufferedReader.read(buffer).also { bytesRead = it } != -1) {
bufferedWriter.write(buffer, 0, bytesRead)
private fun detectCharsetFromStream(inputStream: InputStream): Charset {
return BufferedInputStream(inputStream).use { bufferedStream ->
val data = bufferedStream.readBytes()
UniversalDetector(null).run {
handleData(data, 0, data.size)
dataEnd()
Charset.forName(detectedCharset ?: StandardCharsets.UTF_8.name())
}
}
}

bufferedWriter.close()
bufferedReader.close()
private fun Context.convertLocalUriToUTF8(uri: Uri, sourceCharset: Charset): Uri {
val fileName = getFilenameFromUri(uri)
val file = File(subtitleCacheDir, fileName)

return Uri.fromFile(file)
contentResolver.openInputStream(uri)?.use { inputStream ->
inputStream.reader(sourceCharset).buffered().use { reader ->
file.outputStream().writer(StandardCharsets.UTF_8).buffered().use { writer ->
reader.copyTo(writer)
}
}
}
return inputUri

return Uri.fromFile(file)
}

/**
* For this to work set android:requestLegacyExternalStorage=true in AndroidManifest.xml
*/
suspend fun Context.deleteFiles(uris: List<Uri>, intentSenderLauncher: ActivityResultLauncher<IntentSenderRequest>) = withContext(Dispatchers.IO) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val intentSender = MediaStore.createDeleteRequest(contentResolver, uris).intentSender
intentSenderLauncher.launch(IntentSenderRequest.Builder(intentSender).build())
} else {
for (uri in uris) {
contentResolver.delete(uri, null, null)
private fun Context.convertNetworkUriToUTF8(url: URL, sourceCharset: Charset): Uri {
val fileName = url.path.substringAfterLast('/')
val file = File(subtitleCacheDir, fileName)

url.openStream().use { inputStream ->
inputStream.reader(sourceCharset).buffered().use { reader ->
file.outputStream().writer(StandardCharsets.UTF_8).buffered().use { writer ->
reader.copyTo(writer)
}
}
} catch (e: Exception) {
Log.d("CONTEXT", "deleteFiles: ${e.printStackTrace()}")
}

return Uri.fromFile(file)
}

fun Context.isDeviceTvBox(): Boolean {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -965,7 +965,7 @@ class PlayerActivity : AppCompatActivity() {
.setUri(uri)
.build()

private fun createExternalSubtitleStreams(subtitles: List<Subtitle>): List<MediaItem.SubtitleConfiguration> {
private suspend fun createExternalSubtitleStreams(subtitles: List<Subtitle>): List<MediaItem.SubtitleConfiguration> {
return subtitles.map {
val charset = if (with(playerPreferences.subtitleTextEncoding) { isNotEmpty() && Charset.isSupported(this) }) {
Charset.forName(playerPreferences.subtitleTextEncoding)
Expand Down

0 comments on commit 9b94a68

Please sign in to comment.