music: fix m3u export
Wasn't correctly writing and also naively relative-izing paths. Those should be fixed now, I hope.
This commit is contained in:
parent
d59230be6d
commit
c3f67d4dc5
2 changed files with 47 additions and 23 deletions
|
@ -20,19 +20,19 @@ package org.oxycblt.auxio.music.external
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import org.oxycblt.auxio.music.Playlist
|
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
import java.io.BufferedWriter
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
|
import java.io.OutputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import org.oxycblt.auxio.music.Playlist
|
||||||
import org.oxycblt.auxio.music.fs.Components
|
import org.oxycblt.auxio.music.fs.Components
|
||||||
import org.oxycblt.auxio.music.fs.Path
|
import org.oxycblt.auxio.music.fs.Path
|
||||||
import org.oxycblt.auxio.music.metadata.correctWhitespace
|
import org.oxycblt.auxio.music.metadata.correctWhitespace
|
||||||
import org.oxycblt.auxio.music.resolveNames
|
import org.oxycblt.auxio.music.resolveNames
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
import java.io.BufferedWriter
|
|
||||||
import java.io.OutputStream
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Minimal M3U file format implementation.
|
* Minimal M3U file format implementation.
|
||||||
|
@ -53,6 +53,7 @@ interface M3U {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes the given [playlist] to the given [outputStream] in the M3U format,.
|
* Writes the given [playlist] to the given [outputStream] in the M3U format,.
|
||||||
|
*
|
||||||
* @param playlist The playlist to write.
|
* @param playlist The playlist to write.
|
||||||
* @param outputStream The stream to write the M3U file to.
|
* @param outputStream The stream to write the M3U file to.
|
||||||
* @param workingDirectory The directory that the M3U file is contained in. This is used to
|
* @param workingDirectory The directory that the M3U file is contained in. This is used to
|
||||||
|
@ -128,17 +129,13 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun write(
|
override fun write(playlist: Playlist, outputStream: OutputStream, workingDirectory: Path) {
|
||||||
playlist: Playlist,
|
|
||||||
outputStream: OutputStream,
|
|
||||||
workingDirectory: Path
|
|
||||||
) {
|
|
||||||
val writer = outputStream.bufferedWriter()
|
val writer = outputStream.bufferedWriter()
|
||||||
// Try to be as compliant to the spec as possible while also cramming it full of extensions
|
// Try to be as compliant to the spec as possible while also cramming it full of extensions
|
||||||
// I imagine other players will use.
|
// I imagine other players will use.
|
||||||
writer.writeLine("#EXTM3U")
|
writer.writeLine("#EXTM3U")
|
||||||
writer.writeLine("#EXTENC:UTF-8")
|
writer.writeLine("#EXTENC:UTF-8")
|
||||||
writer.writeLine("#PLAYLIST:${playlist.name}")
|
writer.writeLine("#PLAYLIST:${playlist.name.resolve(context)}")
|
||||||
for (song in playlist.songs) {
|
for (song in playlist.songs) {
|
||||||
val relativePath = song.path.components.relativeTo(workingDirectory.components)
|
val relativePath = song.path.components.relativeTo(workingDirectory.components)
|
||||||
writer.writeLine("#EXTINF:${song.durationMs},${song.name.resolve(context)}")
|
writer.writeLine("#EXTINF:${song.durationMs},${song.name.resolve(context)}")
|
||||||
|
@ -147,6 +144,7 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte
|
||||||
writer.writeLine("#EXTGEN:${song.genres.resolveNames(context)}")
|
writer.writeLine("#EXTGEN:${song.genres.resolveNames(context)}")
|
||||||
writer.writeLine(relativePath.toString())
|
writer.writeLine(relativePath.toString())
|
||||||
}
|
}
|
||||||
|
writer.flush()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BufferedWriter.writeLine(line: String) {
|
private fun BufferedWriter.writeLine(line: String) {
|
||||||
|
@ -154,9 +152,7 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte
|
||||||
newLine()
|
newLine()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Components.absoluteTo(
|
private fun Components.absoluteTo(workingDirectory: Components): Components {
|
||||||
workingDirectory: Components
|
|
||||||
): Components {
|
|
||||||
var absoluteComponents = workingDirectory
|
var absoluteComponents = workingDirectory
|
||||||
for (component in components) {
|
for (component in components) {
|
||||||
when (component) {
|
when (component) {
|
||||||
|
@ -172,18 +168,44 @@ class M3UImpl @Inject constructor(@ApplicationContext private val context: Conte
|
||||||
return absoluteComponents
|
return absoluteComponents
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Components.relativeTo(
|
private fun Components.relativeTo(workingDirectory: Components): Components {
|
||||||
workingDirectory: Components
|
// We want to find the common prefix of the working directory and path, and then
|
||||||
): Components {
|
// and them combine them with the correct relative elements to make sure they
|
||||||
var relativeComponents = Components.parse(".")
|
// resolve the same.
|
||||||
var commonIndex = 0
|
var commonIndex = 0
|
||||||
while (commonIndex < components.size &&
|
while (commonIndex < components.size &&
|
||||||
commonIndex < workingDirectory.components.size &&
|
commonIndex < workingDirectory.components.size &&
|
||||||
components[commonIndex] == workingDirectory.components[commonIndex]) {
|
components[commonIndex] == workingDirectory.components[commonIndex]) {
|
||||||
++commonIndex
|
++commonIndex
|
||||||
relativeComponents = relativeComponents.child("..")
|
|
||||||
}
|
|
||||||
return relativeComponents.concat(depth(commonIndex))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var relativeComponents = Components.parse(".")
|
||||||
|
|
||||||
|
// TODO: Simplify this logic
|
||||||
|
when {
|
||||||
|
commonIndex == components.size && commonIndex == workingDirectory.components.size -> {
|
||||||
|
// The paths are the same. This shouldn't occur.
|
||||||
|
}
|
||||||
|
commonIndex == components.size -> {
|
||||||
|
// The working directory is deeper in the path, backtrack.
|
||||||
|
for (i in 0..workingDirectory.components.size - commonIndex - 1) {
|
||||||
|
relativeComponents = relativeComponents.child("..")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
commonIndex == workingDirectory.components.size -> {
|
||||||
|
// Working directory is shallower than the path, can just append the
|
||||||
|
// non-common remainder of the path
|
||||||
|
relativeComponents = relativeComponents.child(depth(commonIndex))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
// The paths are siblings. Backtrack and append as needed.
|
||||||
|
for (i in 0..workingDirectory.components.size - commonIndex - 1) {
|
||||||
|
relativeComponents = relativeComponents.child("..")
|
||||||
|
}
|
||||||
|
relativeComponents = relativeComponents.child(depth(commonIndex))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return relativeComponents
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,8 +122,9 @@ value class Components private constructor(val components: List<String>) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the first [n] elements of the path, effectively resulting in a path that is n
|
* Removes the first [n] elements of the path, effectively resulting in a path that is n levels
|
||||||
* levels deep.
|
* deep.
|
||||||
|
*
|
||||||
* @param n The number of elements to remove.
|
* @param n The number of elements to remove.
|
||||||
* @return The new [Components] instance.
|
* @return The new [Components] instance.
|
||||||
*/
|
*/
|
||||||
|
@ -131,10 +132,11 @@ value class Components private constructor(val components: List<String>) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenates this [Components] instance with another.
|
* Concatenates this [Components] instance with another.
|
||||||
|
*
|
||||||
* @param other The [Components] instance to concatenate with.
|
* @param other The [Components] instance to concatenate with.
|
||||||
* @return The new [Components] instance.
|
* @return The new [Components] instance.
|
||||||
*/
|
*/
|
||||||
fun concat(other: Components) = Components(components + other.components)
|
fun child(other: Components) = Components(components + other.components)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue