fix(metadata): preserve original artwork resolution for SAF files

This commit is contained in:
zarzet committed 2026-10-09 10:34:49 +07:00
1 parent 9f2062d495
commit c7b1d0b0ec
5 files changed
+179 -14

No files matched your search

@@ -106,6 +106,54 @@ class NativeDownloadContainerTest {
} finally { covers.deleteRecursively() }
}
@Test
fun descriptorArtworkExportPreservesOriginalResolutionAndBytes() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
val directory = File(context.cacheDir, "original-artwork-${System.nanoTime()}").apply { mkdirs() }
try {
for ((width, height, format) in listOf(
Triple(3000, 3000, android.graphics.Bitmap.CompressFormat.JPEG),
Triple(4500, 4500, android.graphics.Bitmap.CompressFormat.PNG),
Triple(3000, 1500, android.graphics.Bitmap.CompressFormat.PNG),
)) {
val artwork = File(directory, "cover-$width-$height.img")
val bitmap = android.graphics.Bitmap.createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888)
try {
bitmap.eraseColor(android.graphics.Color.GREEN)
artwork.outputStream().use { assertTrue(bitmap.compress(format, 95, it)) }
} finally { bitmap.recycle() }
val original = artwork.readBytes()
val input = File(directory, "track-$width-$height.flac")
val fixture = NativeDownloadFinalizer.runFFmpegArguments(arrayOf(
"-v", "error", "-f", "lavfi", "-i", "sine=frequency=997:sample_rate=48000",
"-i", artwork.path, "-t", "0.1", "-map", "0:a", "-map", "1:v",
"-c:a", "flac", "-c:v", "copy", "-disposition:v", "attached_pic", input.path,
))
assertTrue(fixture.second, fixture.first)
val before = java.security.MessageDigest.getInstance("SHA-256").digest(input.readBytes())
val exported = File(directory, "export-$width-$height.img")
android.os.ParcelFileDescriptor.open(input, android.os.ParcelFileDescriptor.MODE_READ_ONLY).use { descriptor ->
backend.extractCoverToFile("/proc/self/fd/${descriptor.fd}", exported.path, input.name)
assertTrue("$width x $height artwork must remain byte-identical", exported.readBytes().contentEquals(original))
val bounds = android.graphics.BitmapFactory.Options().apply { inJustDecodeBounds = true }
android.graphics.BitmapFactory.decodeFile(exported.path, bounds)
assertEquals(width, bounds.outWidth)
assertEquals(height, bounds.outHeight)
assertTrue(descriptor.statSize > 0)
if (width == 3000) {
val preview = com.spotiflac.backend.readLibraryMetadataFromDescriptor(
descriptor.fd, input.name, "2026-10-09T00:00:00Z", true, null,
).coverBytes
android.graphics.BitmapFactory.decodeByteArray(preview, 0, preview.size, bounds)
assertEquals(800, bounds.outWidth)
assertEquals(if (height == 3000) 800 else 400, bounds.outHeight)
}
}
assertTrue(before.contentEquals(java.security.MessageDigest.getInstance("SHA-256").digest(input.readBytes())))
}
} finally { directory.deleteRecursively() }
}
@Test
fun nativeFormatsPreserveTagsLyricsAndReplayGain() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
@@ -451,14 +451,14 @@ internal object RustCoreBackend : CoreBackend {
audioPath.removePrefix("/proc/self/fd/").toIntOrNull()
} else null
if (descriptor != null) {
val result = com.spotiflac.backend.readLibraryMetadataFromDescriptor(
descriptor, hint, java.time.Instant.now().toString(), true, null,
val artwork = com.spotiflac.backend.extractCoverFromDescriptor(
descriptor, hint, null,
)
check(result.coverBytes.isNotEmpty()) { "No embedded artwork" }
check(artwork.isNotEmpty()) { "No embedded artwork" }
val output = File(outputPath)
val staged = File.createTempFile("cover_", ".tmp", output.parentFile)
try {
staged.writeBytes(result.coverBytes)
staged.writeBytes(artwork)
check(staged.renameTo(output)) { "Could not publish artwork" }
} finally { staged.delete() }
return