I fixed another Nix graalvm issue. Some mp4s wouldn’t be tagged properly, so I fixed that.
I fixed another Nix graalvm issue. Some mp4s wouldn’t be tagged properly, so I fixed that.
I fixed the binary in Nix, it works now, also updated the asciinema cast
it was missing some reachability metadata and required LD_LIBRARY_PATH to be set to the jdk’s libraries.
Tiny change, I switched from scrimage to ImageIO. It works much better and should be less error prone as scrimage would vendor in webp binaries, which is not cross platform.
Squashed more bugs, updated NativeMetadataMain.kt so that reachability metadata includes image i/o. This makes the Graal native image actually work when having to convert from webp to png. This will be a non issue when I can find an image library that is written in pure Kotlin.
The last shipwright to review my project tried syncing a mix, which is infinite. So I added an error for that and made sure no one will run into unexplainable errors again.
I added GraalVM native image support. The actual implementation is boring but this makes the program’s cold starts faster and makes it feel more native. Not sure if this helps with battery life, but it might. This definitely helps with memory usage, the program only uses ~100mb syncing my 400 track playlist.
I restricted the native image to Nix because the releases are supposed to always work on all platforms.
I finally silenced those pesky slf4j and unsafe memory access warnings. Just added the slf4j-nop library and a JVM flag.
I redid the cli structure to group all the playlist commands under one common playlist parent command. I also implemented a clean up command to help people save disk space because SSDs are expensive right now.
For example:
music-syncer-kotlin -p path init -> music-syncer-kotlin playlist -p path init
This was a necessary step so I could implement the clean up command, which deletes audio and thumbnail files that are not used by any playlists in a container. A container is the folder holding all the databases.
Here’s everything I did, stolen from my commit message:
feature: add clean-container command
- refactor: add hasTrackInDatabase to the Playlist class
- refactor: move knownFinal{Audio,Thumbnail}Extension out of class and
make it const
- refactor: nest all playlist commands as one subcommand
- refactor: make cli help text more helpful
- refactor: always print something to the console to let the user know
that everything went well
- refactor: do not use PlaylistHolder, use
currentContext.registerJvmCloseable to clean up playlist
- fix: when thumbnail is newly created, retag should silently fail
I watched a Kotlin talk about Flows and I was inspired to rewrite my program using them.
This made my program more efficient, idiomatic and also helped me fix the annoying progress bar refresh bug (multiple old bars shown). It also let me actually properly sort by position across all 4 status types (added, removed, moved, errored).
I also made the downloading functions use one underlying common function to clean up the codebase. I also removed sqlx4k because it wasn’t actually parallel and was causing issues with init.
Here, in the screenshot I am showing the new elegant code for the logging of track statuses. It used to be many individual async calls, but flows allow me to group them up and sort them.
I now detect invalid playlist ids and print out an error telling the user how to get a valid playlist id.
I made position diffing aware of insertions and deletions, so that output is less bloated with useless move info such as 0->1, 1->2, etc.
I also did some optimization by using database transactions wherever possible. I can’t do it in the main sync body because it is bounded to its own coroutine scope.
I added duration tracking to the database. The M3U generation now doesn’t hardcode 0 as the duration.
I made it so that thumbnails should now be even higher quality, it does take more storage but the difference is quite noticeable on some music players.
Also small hotfix, if you accidentally pass in another unrelated SQLite database it will now not run migrations on it before erroring.
To help users migrate from the old Rust implementation, I made it so that a sync will re-tag the track if the track’s thumbnail does not exist on disk.
The Kotlin implementation always gets higher resolution images, so this allows for much higher resolution track covers.
In my attempt to make the streamExtractor lazily loaded, I caused a deadlock. I have fixed the deadlock and this fixed version is at v0.2.7.
The fix was pretty simple, it was to not use CoroutineStart.Lazy because a lazy coroutine may never run, meaning the parent coroutineScope will hang forever.
I also switched the backend driver to sqlx4k as it provides a connection pool but more importantly makes the suspend code flow cleaner.
I wont switch to Exposed (the official SQL driver) because it doesn’t have the same parallel capabilities as sql4k.
I forgot to add error handling from the original Rust project, so I did that.
I also fixed the parallelism by using a Semaphore as limitedParallelism still wakes all the coroutines. Using a Semaphore only allows N permits to be handed out at once. This means that only N tasks would be running at once, given that each task takes one permit. I set N to 8 as that should not cause any throttling but still allow multiple tracks to be downloaded at once.
I also implemented better lazy fetching than the old Rust implementation. Now only pieces that must be fetched are fetched, and if they don’t need to be fetched, a StreamExtractor is never fetched. This causes a huge speed up, allowing my ~400 track playlist to be incrementally synced in 8 seconds.
Other notable quality of life features (taken from the git log):
- refactor: make output use terminal.theme.*
- refactor: make output more human readable by using title instead of youtube_playlist_id
- fix: reorder items in subtasks so that the text doesn't move everything else
Shortly after I shipped, I found a better library for progress bars and displaying to the terminal in general.
I also made sure that the exists locally log line would be printed out in order.
I decided to use it, and I am really happy with the result!!!!
Preparing to ship the project, so here’s a list of everything I did:
music-player-kotlin instead of cli
added a progress bar and actually actually fixed the deadlock this time
I fixed position tracking, so that moved tracks will output a line telling you they have moved.
I attempted to fix a deadlock that was happening, and fixed some database queries targetting the wrong table.