Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom metadata decoder #567

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .phrasey/schema.toml
Original file line number Diff line number Diff line change
Expand Up @@ -680,3 +680,6 @@ name = "Pink"

[[keys]]
name = "Rose"

[[keys]]
name = "MediaFolders"
16 changes: 12 additions & 4 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
plugins {
alias(libs.plugins.android.app)
alias(libs.plugins.android.kotlin)
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlin.serialization)
}

android {
Expand Down Expand Up @@ -63,15 +65,17 @@ android {
buildConfig = true
}

composeOptions {
kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get()
}

packaging {
resources {
excludes.add("/META-INF/{AL2.0,LGPL2.1}")
}
}

testOptions {
unitTests.all {
it.useJUnitPlatform()
}
}
}

dependencies {
Expand All @@ -84,12 +88,16 @@ dependencies {
implementation(libs.compose.ui.tooling.preview)
implementation(libs.core)
implementation(libs.core.splashscreen)
implementation(libs.documentfile)
implementation(libs.fuzzywuzzy)
implementation(libs.jaudiotagger)
implementation(libs.kotlinx.serialization.json)
implementation(libs.lifecycle.runtime)
implementation(libs.media)
implementation(libs.okhttp3)

debugImplementation(libs.compose.ui.tooling)
debugImplementation(libs.compose.ui.test.manifest)

testImplementation(libs.junit.jupiter)
}
37 changes: 37 additions & 0 deletions app/src/main/java/io/github/zyrouge/metaphony/Artwork.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.github.zyrouge.metaphony

data class Artwork(
val format: Format,
val data: ByteArray,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Artwork
if (format != other.format) return false
if (!data.contentEquals(other.data)) return false
return true
}

override fun hashCode(): Int {
var result = format.hashCode()
result = 31 * result + data.contentHashCode()
return result
}

enum class Format(val extension: String, val mimeType: String) {
Jpeg("jpg", "image/jpg"),
Png("png", "image/png"),
Gif("gif", "image/gif"),
Unknown("", "");

companion object {
fun fromMimeType(value: String) = when (value) {
Jpeg.mimeType, "image/jpeg" -> Jpeg
Png.mimeType -> Png
Gif.mimeType -> Gif
else -> Unknown
}
}
}
}
38 changes: 38 additions & 0 deletions app/src/main/java/io/github/zyrouge/metaphony/Metadata.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.github.zyrouge.metaphony

import io.github.zyrouge.metaphony.flac.Flac
import io.github.zyrouge.metaphony.mp3.Mp3
import io.github.zyrouge.metaphony.mpeg4.Mpeg4
import io.github.zyrouge.metaphony.ogg.Ogg
import java.io.InputStream
import java.time.LocalDate

interface Metadata {
val title: String?
val artists: Set<String>
val album: String?
val albumArtists: Set<String>
val composer: Set<String>
val genres: Set<String>
val year: Int?
val date: LocalDate?
val trackNumber: Int?
val trackTotal: Int?
val discNumber: Int?
val discTotal: Int?
val lyrics: String?
val comments: Set<String>
val artworks: List<Artwork>

companion object {
fun read(input: InputStream, mimeType: String): Metadata? {
return when (mimeType) {
"audio/flac" -> Flac.read(input)
"audio/mpeg" -> Mp3.read(input)
"audio/mp4" -> Mpeg4.read(input)
"audio/ogg" -> Ogg.read(input)
else -> null
}
}
}
}
43 changes: 43 additions & 0 deletions app/src/main/java/io/github/zyrouge/metaphony/flac/Flac.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.github.zyrouge.metaphony.flac

import io.github.zyrouge.metaphony.utils.xDecodeToUInt
import io.github.zyrouge.metaphony.utils.xReadByte
import io.github.zyrouge.metaphony.utils.xReadInt
import io.github.zyrouge.metaphony.utils.xReadString
import io.github.zyrouge.metaphony.utils.xSkipBytes
import io.github.zyrouge.metaphony.vorbis.VorbisMetadata
import io.github.zyrouge.metaphony.vorbis.readVorbisComments
import io.github.zyrouge.metaphony.vorbis.readVorbisPicture
import java.io.InputStream
import kotlin.experimental.and

object Flac {
fun read(input: InputStream): VorbisMetadata {
val flac = input.xReadString(4)
if (flac != "fLaC") {
throw Exception("Missing 'fLaC' header")
}
val builder = VorbisMetadata.Builder()
while (true) {
val last = readFlacBlock(input, builder)
if (last) break
}
return builder.done()
}

private fun readFlacBlock(
input: InputStream,
builder: VorbisMetadata.Builder,
): Boolean {
val blockHeader = input.xReadByte()
val last = (blockHeader and 0x80.toByte()) == 0x80.toByte()
val blockId = blockHeader and 0x7f.toByte()
val blockLen = input.xReadInt(3)
when (blockId.xDecodeToUInt()) {
4 -> builder.readVorbisComments(input)
6 -> builder.readVorbisPicture(input)
else -> input.xSkipBytes(blockLen)
}
return last
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.github.zyrouge.metaphony.id3v2

import io.github.zyrouge.metaphony.utils.xBitSetAt
import io.github.zyrouge.metaphony.utils.xReadByte
import io.github.zyrouge.metaphony.utils.xSkipBytes
import java.io.InputStream

internal data class ID3v2FrameFlags(
val flagsSize: Int,
val compression: Boolean,
val encryption: Boolean,
val unsynchronization: Boolean,
val dataLengthIndicator: Boolean,
) {
companion object {
internal fun readID3v2FrameFlags(
input: InputStream,
version: ID3v2Version,
): ID3v2FrameFlags? {
return when (version) {
ID3v2Version.V3 -> readID3v2r3FrameFlags(input)
ID3v2Version.V4 -> readID3v2r4FrameFlags(input)
else -> null
}
}

private fun readID3v2r3FrameFlags(input: InputStream): ID3v2FrameFlags {
input.xSkipBytes(1)
val format = input.xReadByte()
return ID3v2FrameFlags(
flagsSize = 2,
compression = format.xBitSetAt(7),
encryption = format.xBitSetAt(6),
unsynchronization = false,
dataLengthIndicator = false,
)
}

private fun readID3v2r4FrameFlags(input: InputStream): ID3v2FrameFlags {
input.xSkipBytes(1)
val format = input.xReadByte()
return ID3v2FrameFlags(
flagsSize = 2,
compression = format.xBitSetAt(3),
encryption = format.xBitSetAt(2),
unsynchronization = format.xBitSetAt(1),
dataLengthIndicator = format.xBitSetAt(0),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.github.zyrouge.metaphony.id3v2

import io.github.zyrouge.metaphony.utils.xReadInt
import io.github.zyrouge.metaphony.utils.xReadString
import java.io.InputStream

internal data class ID3v2FrameHeader(
val name: String,
val size: Int,
val headerSize: Int,
) {
companion object {
internal fun readID3v2FrameHeader(
input: InputStream,
version: ID3v2Version,
): ID3v2FrameHeader {
return when (version) {
ID3v2Version.V2 -> readID3v2r2FrameHeader(input)
ID3v2Version.V3 -> readID3v2r3FrameHeader(input)
ID3v2Version.V4 -> readID3v2r4FrameHeader(input)
}
}

private fun readID3v2r2FrameHeader(input: InputStream): ID3v2FrameHeader {
val name = input.xReadString(3)
val size = input.xReadInt(3)
return ID3v2FrameHeader(
name = name,
size = size,
headerSize = 6,
)
}

private fun readID3v2r3FrameHeader(input: InputStream): ID3v2FrameHeader {
val name = input.xReadString(4)
val size = input.xReadInt(4)
return ID3v2FrameHeader(
name = name,
size = size,
headerSize = 8,
)
}

private fun readID3v2r4FrameHeader(input: InputStream): ID3v2FrameHeader {
val name = input.xReadString(4)
val size = input.xReadInt(4, 7)
return ID3v2FrameHeader(
name = name,
size = size,
headerSize = 8,
)
}
}
}
Loading