mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 04:42:02 +02:00
fix(download): detect mislabeled MP4 before native metadata writes
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
package com.zarz.spotiflac
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import java.io.File
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NativeDownloadContainerTest {
|
||||
@Test
|
||||
fun misnamedMp4IsTaggedAndPublishedAsM4aWithoutOverwritingExistingAudio() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val root = File(context.cacheDir, "container-test-${System.nanoTime()}").apply { mkdirs() }
|
||||
try {
|
||||
val input = File(root, "track.flac")
|
||||
val existing = File(root, "track.m4a").apply { writeText("existing download") }
|
||||
val fixture = NativeDownloadFinalizer.runFFmpegArguments(arrayOf(
|
||||
"-v", "error", "-f", "lavfi", "-i", "sine=frequency=997:sample_rate=44100",
|
||||
"-t", "0.2", "-c:a", "aac", "-f", "mp4", input.path,
|
||||
))
|
||||
assertTrue(fixture.second, fixture.first)
|
||||
assertTrue(isMP4ContainerFile(input.path))
|
||||
val request = JSONObject()
|
||||
.put("contract_version", 1).put("item_id", "example-track")
|
||||
.put("service", "example-provider").put("track_name", "Container test")
|
||||
.put("artist_name", "Example artist").put("album_name", "Example album")
|
||||
.put("quality", "LOSSLESS").put("storage_mode", "app")
|
||||
.put("output_ext", ".flac").put("embed_metadata", true)
|
||||
val result = NativeDownloadFinalizer.finalize(
|
||||
context, "example-track", request.toString(), "{}",
|
||||
JSONObject().put("success", true).put("file_path", input.path)
|
||||
.put("file_name", input.name),
|
||||
"{\"save_download_history\":false}",
|
||||
)
|
||||
assertTrue(result.toString(), result.getBoolean("success"))
|
||||
assertTrue(result.getBoolean("native_finalized"))
|
||||
val output = File(result.getString("file_path"))
|
||||
assertEquals("m4a", output.extension)
|
||||
assertTrue(output.length() > 0)
|
||||
assertTrue(isMP4ContainerFile(output.path))
|
||||
assertFalse(input.exists())
|
||||
assertEquals("existing download", existing.readText())
|
||||
val probe = NativeDownloadFinalizer.runFFmpegArguments(arrayOf(
|
||||
"-hide_banner", "-i", output.path, "-map", "0:a:0", "-f", "null", "-",
|
||||
))
|
||||
assertTrue(probe.second, probe.first)
|
||||
assertTrue(probe.second, probe.second.contains("Container test"))
|
||||
assertTrue(probe.second, probe.second.contains("Audio: aac"))
|
||||
} finally {
|
||||
root.deleteRecursively()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -760,6 +760,7 @@ object NativeDownloadFinalizer {
|
||||
if (!forceContainerConversion && requestedDecryptionExt.isNotBlank() && requestedDecryptionExt != ".flac") return
|
||||
val mayNeedContainerConversion = forceContainerConversion ||
|
||||
looksLikeM4a(state.filePath, state.fileName) ||
|
||||
isMP4ContainerFile(state.filePath) ||
|
||||
state.filePath.startsWith("content://")
|
||||
if (!mayNeedContainerConversion) return
|
||||
|
||||
@@ -768,6 +769,7 @@ object NativeDownloadFinalizer {
|
||||
val output = buildOutputPath(localInput, ".flac")
|
||||
val stagedOutput = stagedConversionPath(output)
|
||||
var adoptedOutput = false
|
||||
var createdOutput = false
|
||||
try {
|
||||
val codec = probePrimaryAudioCodec(localInput, shouldCancel)
|
||||
val isAlreadyNativeFlac = codec == "flac" && isNativeFlacFile(localInput)
|
||||
@@ -794,6 +796,7 @@ object NativeDownloadFinalizer {
|
||||
if (!promoteStagedConversion(stagedOutput, output)) {
|
||||
throw IllegalStateException("failed to publish native FLAC output")
|
||||
}
|
||||
createdOutput = true
|
||||
output
|
||||
}
|
||||
embedBasicMetadata(context, nativeFlacOutput, input, "flac")
|
||||
@@ -811,6 +814,7 @@ object NativeDownloadFinalizer {
|
||||
if (!promoteStagedConversion(stagedOutput, output)) {
|
||||
throw IllegalStateException("failed to publish container conversion output")
|
||||
}
|
||||
createdOutput = true
|
||||
// Keep metadata failures before adoption so the source survives
|
||||
// and the unsuccessful output is removed by the local cleanup.
|
||||
embedBasicMetadata(context, output, input, "flac")
|
||||
@@ -819,7 +823,7 @@ object NativeDownloadFinalizer {
|
||||
} finally {
|
||||
if (!adoptedOutput) {
|
||||
File(stagedOutput).delete()
|
||||
File(output).delete()
|
||||
if (createdOutput && output != localInput) File(output).delete()
|
||||
}
|
||||
if (deleteLocalInput) File(localInput).delete()
|
||||
}
|
||||
@@ -846,14 +850,7 @@ object NativeDownloadFinalizer {
|
||||
codec == "opus" -> ".opus"
|
||||
else -> return
|
||||
}
|
||||
val renamed = File(
|
||||
currentFile.parentFile,
|
||||
currentFile.name.dropLast(".flac".length) + newExt,
|
||||
)
|
||||
if (renamed.exists() && !renamed.delete()) {
|
||||
Log.w(TAG, "Cannot adopt container extension; ${renamed.name} already exists")
|
||||
return
|
||||
}
|
||||
val renamed = File(uniqueAutoConversionOutputPath(currentFile.path, newExt))
|
||||
if (!currentFile.renameTo(renamed)) {
|
||||
Log.w(TAG, "Failed to rename preserved container to ${renamed.name}")
|
||||
return
|
||||
|
||||
@@ -35,7 +35,7 @@ import kotlin.math.pow
|
||||
|
||||
// FFmpeg execution, probing, and container helpers for NativeDownloadFinalizer.
|
||||
|
||||
internal fun NativeDownloadFinalizer.isMP4ContainerFile(path: String): Boolean {
|
||||
internal fun isMP4ContainerFile(path: String): Boolean {
|
||||
return try {
|
||||
File(path).inputStream().use { stream ->
|
||||
val header = ByteArray(12)
|
||||
|
||||
Reference in New Issue
Block a user