Build and publish container / build (pull_request) Successful in 2m18s
Two changes for playing the mirror on a Rockbox iPod, where the device is FAT32 and Rockbox reads a plain directory tree rather than a database. --fat32-safe names mirror files acceptably: the reserved characters and control characters become underscores, trailing dots and spaces are stripped because FAT eats them silently and the name then round-trips as a different one, and a component left empty becomes an underscore. Names differing only in case are detected as collisions, since two files here are one file there and the second would silently overwrite the first. "Kick Out the Epic Motherf**ker" is a real example from a real library, and without this it simply never arrives. Off by default. It renames files, and that should be a decision rather than a surprise on somebody's next pass. Turning it on does not re-encode anything. Every track whose name held a reserved character changes path, and encoding those again would be hours of work producing files that already exist byte for byte, so the run moves them instead and logs each one. Prune then finds nothing left behind. Album art is now also copied into the mirror as cover.jpg beside the tracks. Rockbox searches the filesystem for art -- cover.jpg, folder.jpg and the rest, in the track's directory or its parent -- and that search never looks at the picture embedded in the tag, so a mirror that only embeds art displays none of it on the device. Embedding continues for the Apple firmware; both are now satisfied. A cover whose tracks have all been pruned is removed too, or its directory would never look empty and never go. Adds tools/check_fat32.py, which reports unacceptable paths before a copy rather than during one: rsync reports them too, but scattered through fifty thousand files where they are easy to lose. It exits non-zero so it can gate a script. The README documents the rsync invocation, including why --modify-window=2 is required against FAT and why Rhythmbox must be kept out of the transfer -- rb_ipod_helpers_is_ipod() reads access-protocols from media-player-info and returns true on the USB id alone, without looking at the filesystem, so removing iPod_Control changes nothing.
34 lines
1.2 KiB
Docker
34 lines
1.2 KiB
Docker
# Alpine rather than Debian slim purely because of ffmpeg's dependencies.
|
|
# Debian's ffmpeg depends on libavdevice, which can render to X11, Wayland and
|
|
# OpenGL, so it drags in Mesa and with it LLVM (127 MB) and Z3 (27 MB) to
|
|
# encode an MP3 on a headless NAS. Alpine's build brings none of that: 576 MB
|
|
# down to 189 MB.
|
|
FROM python:3.13-alpine AS runtime
|
|
|
|
ENV PYTHONUNBUFFERED=1
|
|
|
|
# ffmpeg does the encoding; the application itself has no Python dependencies.
|
|
RUN apk add --no-cache ffmpeg
|
|
|
|
WORKDIR /app
|
|
COPY pyproject.toml README.md ./
|
|
COPY music_mirror.py ./
|
|
RUN pip install --no-cache-dir . \
|
|
&& rm -rf build music_mirror.egg-info
|
|
|
|
# Runs as root by default so a bind-mounted dataset of any ownership is
|
|
# writable. Override with `user:` in compose to run as the dataset's owner.
|
|
ENTRYPOINT ["music-mirror"]
|
|
|
|
# Test stage: the suite runs against this image's own ffmpeg, which is the one
|
|
# that ships. Build it with `--target test`; a failing test fails the build.
|
|
# The published image is the `runtime` stage above and carries none of this.
|
|
FROM runtime AS test
|
|
|
|
RUN pip install --no-cache-dir pytest
|
|
COPY pytest.ini ./
|
|
# Host-side tools; not in the runtime image, but the suite covers them.
|
|
COPY tools ./tools
|
|
COPY tests ./tests
|
|
RUN python -m pytest
|