mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-30 13:19:44 +02:00
fix(download): align native metadata preparation and ReplayGain support
This commit is contained in:
1 parent
c0997de19c
commit
e61e54f93f
11 files changed
+288
-117
No files matched your search
@@ -3,6 +3,8 @@ package com.zarz.spotiflac
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import java.io.File
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
@@ -12,6 +14,76 @@ import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class NativeDownloadContainerTest {
|
||||
private lateinit var backend: CoreBackend
|
||||
private lateinit var backendRoot: File
|
||||
|
||||
@Before
|
||||
fun initializeBackend() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
backendRoot = File(context.filesDir, "finalizer-test-${System.nanoTime()}").apply { mkdirs() }
|
||||
backend = createCoreBackend(context)
|
||||
backend.invokeApplication("initExtensionSystem", mapOf(
|
||||
"extensions_dir" to File(backendRoot, "sources").apply { mkdirs() }.path,
|
||||
"data_dir" to File(backendRoot, "data").apply { mkdirs() }.path,
|
||||
"master_key" to "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=",
|
||||
"allowed_directories" to listOf(context.cacheDir.path),
|
||||
))
|
||||
}
|
||||
|
||||
@After
|
||||
fun closeBackend() {
|
||||
backend.invokeApplication("cleanupExtensions", emptyMap<String, Any>())
|
||||
backendRoot.deleteRecursively()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nativeFormatsPreserveTagsLyricsAndReplayGain() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
val root = File(context.cacheDir, "finalizer-parity-${System.nanoTime()}").apply { mkdirs() }
|
||||
try {
|
||||
for ((extension, encoder) in listOf("mp3" to "libmp3lame", "opus" to "libopus", "flac" to "flac", "m4a" to "aac")) {
|
||||
val input = File(root, "track.$extension")
|
||||
val fixture = NativeDownloadFinalizer.runFFmpegArguments(arrayOf(
|
||||
"-v", "error", "-f", "lavfi", "-i", "sine=frequency=997:sample_rate=48000",
|
||||
"-t", "1.5", "-c:a", encoder, input.path,
|
||||
))
|
||||
assertTrue(fixture.second, fixture.first)
|
||||
val request = JSONObject()
|
||||
.put("contract_version", 1).put("item_id", "example-$extension")
|
||||
.put("service", "example-provider").put("track_name", "Parity test")
|
||||
.put("artist_name", "Example artist").put("album_name", "Example album")
|
||||
.put("album_artist", "Album Artist").put("track_number", 2).put("total_tracks", 10)
|
||||
.put("quality", "LOSSLESS").put("storage_mode", "app")
|
||||
.put("output_ext", ".$extension").put("embed_metadata", true)
|
||||
.put("embed_lyrics", true).put("lyrics_mode", "both")
|
||||
.put("embed_replaygain", true).put("duration_ms", 1500)
|
||||
val result = NativeDownloadFinalizer.finalize(
|
||||
context, "example-$extension", request.toString(), "{}",
|
||||
JSONObject().put("success", true).put("file_path", input.path)
|
||||
.put("file_name", input.name).put("lyrics_lrc", "[00:00.00]Example line"),
|
||||
"{\"save_download_history\":false}",
|
||||
)
|
||||
assertTrue(result.toString(), result.getBoolean("success"))
|
||||
assertFalse(result.toString(), result.has("replaygain_warning"))
|
||||
assertEquals(1.5, result.getJSONObject("replaygain").getDouble("duration_secs"), 0.001)
|
||||
val output = File(result.getString("file_path"))
|
||||
assertEquals(extension, output.extension)
|
||||
assertEquals("[00:00.00]Example line", File(root, "track.lrc").readText())
|
||||
File(root, "track.lrc").delete()
|
||||
val probe = NativeDownloadFinalizer.runFFmpegArguments(arrayOf(
|
||||
"-hide_banner", "-i", output.path, "-map", "0:a:0", "-f", "null", "-",
|
||||
))
|
||||
assertTrue(probe.second, probe.first)
|
||||
val gainTag = if (extension == "opus") "r128_track_gain" else "replaygain_track_gain"
|
||||
for (tag in listOf("Parity test", "Album Artist", "Example line", gainTag)) {
|
||||
assertTrue("$extension: missing $tag\n${probe.second}", probe.second.contains(tag, ignoreCase = true))
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
root.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun misnamedMp4IsTaggedAndPublishedAsM4aWithoutOverwritingExistingAudio() {
|
||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
|
||||
@@ -1269,11 +1269,11 @@ object NativeDownloadFinalizer {
|
||||
}
|
||||
|
||||
private fun replayGainDurationSeconds(input: FinalizeInput): Double {
|
||||
val duration = input.request.optInt("duration_ms", 0).let {
|
||||
if (it > 0) it else trackInt(input, "duration", 0)
|
||||
}
|
||||
if (duration <= 0) return 1.0
|
||||
return if (duration > 10000) duration / 1000.0 else duration.toDouble()
|
||||
val duration = NativeFinalizationPolicy.durationMilliseconds(
|
||||
input.request.optLong("duration_ms", 0L),
|
||||
trackInt(input, "duration", 0).toLong(),
|
||||
)
|
||||
return if (duration > 0L) duration / 1000.0 else 1.0
|
||||
}
|
||||
|
||||
private fun buildHistoryRow(input: FinalizeInput, state: FinalizeState): ContentValues {
|
||||
|
||||
@@ -11,6 +11,11 @@ import kotlin.math.roundToInt
|
||||
* finalizer's I/O-heavy orchestration.
|
||||
*/
|
||||
internal object NativeFinalizationPolicy {
|
||||
fun durationMilliseconds(requestMilliseconds: Long, trackSeconds: Long): Long {
|
||||
if (requestMilliseconds > 0) return requestMilliseconds
|
||||
return trackSeconds.coerceIn(0, Long.MAX_VALUE / 1000) * 1000
|
||||
}
|
||||
|
||||
fun resolvedAlbumRelativeDirectory(
|
||||
relativeDirectory: String,
|
||||
albumFolderTemplate: String,
|
||||
|
||||
@@ -245,9 +245,7 @@ internal fun NativeDownloadFinalizer.resolveLyricsLrc(context: Context, input: N
|
||||
internal fun NativeDownloadFinalizer.lyricsDurationMs(input: NativeDownloadFinalizer.FinalizeInput): Long {
|
||||
val requestDuration = input.request.optLong("duration_ms", 0L)
|
||||
val trackDuration = trackInt(input, "duration", 0).toLong()
|
||||
val duration = if (requestDuration > 0L) requestDuration else trackDuration
|
||||
if (duration <= 0L) return 0L
|
||||
return if (duration > 10000L) duration else duration * 1000L
|
||||
return NativeFinalizationPolicy.durationMilliseconds(requestDuration, trackDuration)
|
||||
}
|
||||
|
||||
internal fun nativeMetadataEditHandled(response: String): Boolean {
|
||||
|
||||
@@ -7,6 +7,17 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
|
||||
class NativeFinalizationPolicyTest {
|
||||
@Test
|
||||
fun durationUsesDeclaredUnitsForShortTracksAndLongPerformances() {
|
||||
assertEquals(250L, NativeFinalizationPolicy.durationMilliseconds(250, 0))
|
||||
assertEquals(8000L, NativeFinalizationPolicy.durationMilliseconds(8000, 8))
|
||||
assertEquals(10000L, NativeFinalizationPolicy.durationMilliseconds(10000, 10))
|
||||
assertEquals(180000L, NativeFinalizationPolicy.durationMilliseconds(180000, 0))
|
||||
assertEquals(8000L, NativeFinalizationPolicy.durationMilliseconds(0, 8))
|
||||
assertEquals(14400000L, NativeFinalizationPolicy.durationMilliseconds(0, 14400))
|
||||
assertEquals(0L, NativeFinalizationPolicy.durationMilliseconds(0, -1))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun lateAlbumMetadataResolvesOnlyThePendingFolderLeaf() {
|
||||
assertEquals(
|
||||
|
||||
Reference in new issue
Block a user