Skip to content

Commit

Permalink
For single-file audiobooks prefer the title over the album for the bo…
Browse files Browse the repository at this point in the history
…ok name (#2261)
  • Loading branch information
PaulWoitaschek committed Jan 19, 2024
1 parent 2dd86ed commit ee0e43c
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,12 @@ class MediaAnalyzerInstrumentationTest {

@Test
fun intactFile() {
analyze(R.raw.intact) shouldBe MediaAnalyzer.Metadata(
analyze(R.raw.intact)!!.copy(fileName = "") shouldBe MediaAnalyzer.Metadata(
duration = 119040,
author = "Auphonic",
bookName = "Auphonic Examples",
chapterName = "Auphonic Chapter Marks Demo",
artist = "Auphonic",
album = "Auphonic Examples",
fileName = "",
title = "Auphonic Chapter Marks Demo",
chapters = listOf(
MarkData(0, "Intro"),
MarkData(15_000, "Creating a new production"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private fun FolderOverviewView(
@Suppress("ktlint:compose:preview-public-check")
@Composable
@Preview
private fun FolderOverviewPreview() {
fun FolderOverviewPreview() {
FolderOverviewView(
viewState = FolderPickerViewState(
items = listOf(
Expand Down
20 changes: 18 additions & 2 deletions scanner/src/main/kotlin/voice/app/scanner/BookParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,31 @@ class BookParser

val migratedPlaybackPosition = migrationSettings?.let { findMigratedPlaybackPosition(it, chapters) }

buildList {
add(null)
add("")
}.filterNotNull().firstOrNull()
BookContent(
id = id,
isActive = true,
addedAt = migrationMetaData?.addedAtMillis?.let(Instant::ofEpochMilli)
?: Instant.now(),
author = analyzed?.author,
author = analyzed?.artist,
lastPlayedAt = migrationSettings?.lastPlayedAtMillis?.let(Instant::ofEpochMilli)
?: Instant.EPOCH,
name = migrationMetaData?.name ?: analyzed?.bookName ?: analyzed?.chapterName ?: file.bookName(),
name = buildList {
add(migrationMetaData?.name)
if (chapters.size == 1) {
// for single file audiobooks, the title should have higher precedence.
// see https://github.com/PaulWoitaschek/Voice/issues/2171
add(analyzed?.title)
add(analyzed?.album)
} else {
add(analyzed?.album)
add(analyzed?.title)
}
}
.filterNotNull().firstOrNull() ?: file.bookName(),
playbackSpeed = migrationSettings?.playbackSpeed
?: 1F,
skipSilence = migrationSettings?.skipSilence
Expand Down
2 changes: 1 addition & 1 deletion scanner/src/main/kotlin/voice/app/scanner/ChapterParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class ChapterParser
id = id,
duration = metaData.duration,
fileLastModified = Instant.ofEpochMilli(file.lastModified),
name = metaData.chapterName,
name = metaData.title ?: metaData.fileName,
markData = metaData.chapters,
)
}
Expand Down
7 changes: 4 additions & 3 deletions scanner/src/main/kotlin/voice/app/scanner/FFProbeAnalyze.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class FFProbeAnalyze
allowStructuredMapKeys = true
}

suspend fun analyze(file: CachedDocumentFile): MetaDataScanResult? {
internal suspend fun analyze(file: CachedDocumentFile): MetaDataScanResult? {
val tagKeys = TagType.entries.flatMap { it.keys }.toSet().joinToString(",")
val result = ffprobe(
input = file.uri,
context = context,
Expand All @@ -27,8 +28,8 @@ class FFProbeAnalyze
"-show_chapters",
"-loglevel", "quiet",
"-show_entries", "format=duration",
"-show_entries", "format_tags=artist,title,album",
"-show_entries", "stream_tags=artist,title,album",
"-show_entries", "format_tags=$tagKeys",
"-show_entries", "stream_tags=$tagKeys",
// only select the audio stream
"-select_streams", "a",
),
Expand Down
14 changes: 8 additions & 6 deletions scanner/src/main/kotlin/voice/app/scanner/MediaAnalyzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ class MediaAnalyzer
return if (duration != null && duration > 0 && result.streams.isNotEmpty()) {
Metadata(
duration = duration.seconds.inWholeMilliseconds,
chapterName = result.findTag(TagType.Title) ?: file.nameWithoutExtension(),
author = result.findTag(TagType.Artist),
bookName = result.findTag(TagType.Album),
fileName = file.nameWithoutExtension(),
artist = result.findTag(TagType.Artist),
album = result.findTag(TagType.Album),
title = result.findTag(TagType.Title),
chapters = result.chapters.mapIndexed { index, metaDataChapter ->
MarkData(
startMs = metaDataChapter.start.inWholeMilliseconds,
Expand All @@ -36,9 +37,10 @@ class MediaAnalyzer

data class Metadata(
val duration: Long,
val chapterName: String?,
val author: String?,
val bookName: String?,
val artist: String?,
val album: String?,
val title: String?,
val fileName: String,
val chapters: List<MarkData>,
)
}
31 changes: 14 additions & 17 deletions scanner/src/main/kotlin/voice/app/scanner/Metadata.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@Serializable
data class MetaDataScanResult(
internal data class MetaDataScanResult(
val streams: List<MetaDataStream> = emptyList(),
val chapters: List<MetaDataChapter> = emptyList(),
val format: MetaDataFormat? = null,
)

enum class TagType {
Title,
Artist,
Album,
internal enum class TagType(val keys: List<String>) {
Title(listOf("title")),
Artist(listOf("author", "artist", "album_artist")),
Album(listOf("album")),
}

fun MetaDataScanResult.findTag(tagType: TagType): String? {
internal fun MetaDataScanResult.findTag(tagType: TagType): String? {
format?.tags?.find(tagType)?.let { return it }
streams.forEach { stream ->
stream.tags?.find(tagType)?.let { return it }
Expand All @@ -29,27 +29,24 @@ fun MetaDataScanResult.findTag(tagType: TagType): String? {
return null
}

fun Map<String, String>.find(tagType: TagType): String? {
val targetKey = when (tagType) {
TagType.Title -> "title"
TagType.Artist -> "artist"
TagType.Album -> "album"
}
internal fun Map<String, String>.find(tagType: TagType): String? {
forEach { (key, value) ->
if (key.equals(targetKey, ignoreCase = true) && value.isNotEmpty()) {
return value
tagType.keys.forEach { targetKey ->
if (key.equals(targetKey, ignoreCase = true) && value.isNotEmpty()) {
return value
}
}
}
return null
}

@Serializable
data class MetaDataStream(
internal data class MetaDataStream(
val tags: Map<String, String>? = null,
)

@Serializable
data class MetaDataChapter(
internal data class MetaDataChapter(
@SerialName("start_time")
private val startInSeconds: Double,
val tags: Map<String, String>? = null,
Expand All @@ -59,7 +56,7 @@ data class MetaDataChapter(
}

@Serializable
data class MetaDataFormat(
internal data class MetaDataFormat(
val duration: Double? = null,
val tags: Map<String, String>? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ class ChapterParserTest {
val file = firstArg<CachedDocumentFile>()
MediaAnalyzer.Metadata(
duration = 1000,
chapterName = file.nameWithoutExtension(),
author = null,
bookName = null,
fileName = file.nameWithoutExtension(),
artist = null,
album = null,
chapters = emptyList(),
title = null,
)
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ internal class MediaAnalyzerTest {
duration = 123.45,
),
)
analyzer.analyze(file)!!.chapterName shouldBe "MyTitle"
analyzer.analyze(file)!!.title shouldBe "MyTitle"
}

@Test
Expand All @@ -39,6 +39,6 @@ internal class MediaAnalyzerTest {
duration = 123.45,
),
)
analyzer.analyze(file)!!.chapterName shouldBe "mybook"
analyzer.analyze(file)!!.fileName shouldBe "mybook"
}
}
7 changes: 4 additions & 3 deletions scanner/src/test/kotlin/voice/app/scanner/MediaScannerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -252,10 +252,11 @@ class MediaScannerTest {
coEvery { mediaAnalyzer.analyze(any()) } coAnswers {
MediaAnalyzer.Metadata(
duration = 1000L,
author = "Author",
bookName = "Book Name",
chapterName = "Chapter",
artist = "Author",
album = "Book Name",
fileName = "Chapter",
chapters = emptyList(),
title = "Title",
)
}
}
Expand Down

0 comments on commit ee0e43c

Please sign in to comment.