Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1c7cf83a1b | |||
| 55a059b6da |
@@ -73,10 +73,10 @@ Optional, add these later if you want them:
|
||||
Pull the prebuilt image onto your Docker host:
|
||||
|
||||
```bash
|
||||
docker pull git.kretzer.club/andrew/alembic:0.6.13
|
||||
docker pull git.kretzer.club/andrew/alembic:0.6.14
|
||||
```
|
||||
|
||||
That is the whole install. You do not need to download the source or build anything. The `0.6.13` is the version; you can pin to it so nothing changes under you, or use `latest` to always get the newest.
|
||||
That is the whole install. You do not need to download the source or build anything. The `0.6.14` is the version; you can pin to it so nothing changes under you, or use `latest` to always get the newest.
|
||||
|
||||
(If you would rather build it yourself from source, you can, but you do not need to.)
|
||||
|
||||
@@ -136,7 +136,7 @@ Create a file called `docker-compose.yml` on your server (put it wherever you ke
|
||||
```yaml
|
||||
services:
|
||||
alembic:
|
||||
image: git.kretzer.club/andrew/alembic:0.6.13
|
||||
image: git.kretzer.club/andrew/alembic:0.6.14
|
||||
container_name: alembic
|
||||
ports:
|
||||
- "8420:8420"
|
||||
|
||||
@@ -242,6 +242,30 @@ BEETS_EXIT=0
|
||||
beet import -q -s "$DEST_DIR" >> "$LOG" 2>&1 || BEETS_EXIT=$?
|
||||
log "beets import finished with exit code $BEETS_EXIT"
|
||||
|
||||
# ==== Safety net: clean up stragglers + exact duplicates ====
|
||||
# With duplicate_action=keep, beets always imports rather than rejecting a
|
||||
# real conflict (see beets/config.yaml) -- any file whose (albumartist,
|
||||
# album, title) already exists in the library gets imported anyway as a
|
||||
# `.1.ext` sibling. That's true whether this run came from the web UI, an
|
||||
# SMB drop, or sync-bandcamp.sh, so the cleanup has to run here rather than
|
||||
# per-caller (a 2026-07-23 incident: a Bandcamp sync's own call into this
|
||||
# script failed and silently stranded ~150 already-owned files in import-me/
|
||||
# for two weeks; a later unrelated manual import swept them back in and
|
||||
# duplicated them, and nothing had deduped since).
|
||||
log "Running replace-with-better safety pass on /downloads stragglers"
|
||||
if ${PIPELINE_DIR:-/app/pipeline}/lib/replace-with-better.sh --apply >> "$LOG" 2>&1; then
|
||||
log "replace-with-better OK"
|
||||
else
|
||||
log "WARN: replace-with-better.sh exited non-zero (exit $?)"
|
||||
fi
|
||||
|
||||
log "Running dedup pass (exact-match duplicates only; FLAC > MP3, then largest file)"
|
||||
if ${PIPELINE_DIR:-/app/pipeline}/lib/dedup-library.sh --apply >> "$LOG" 2>&1; then
|
||||
log "dedup OK"
|
||||
else
|
||||
log "WARN: dedup-library.sh exited non-zero (exit $?)"
|
||||
fi
|
||||
|
||||
# ==== Regenerate M3U if playlist was specified ====
|
||||
if [[ -n "$PLAYLIST_NAME" ]]; then
|
||||
log "Regenerating M3U for $PLAYLIST_NAME"
|
||||
|
||||
@@ -20,7 +20,9 @@
|
||||
# Returns exit 0 on send-OK, non-zero otherwise.
|
||||
|
||||
set -euo pipefail
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
# /opt/venv/bin leads PATH for consistency with the other pipeline scripts,
|
||||
# even though this one only shells out to curl.
|
||||
PATH=/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
|
||||
PARSE_MODE=""
|
||||
if [[ "${1:-}" == "--html" ]]; then
|
||||
|
||||
@@ -7,7 +7,15 @@
|
||||
# Run as root (cron). Logs to ${ALEMBIC_CONFIG_DIR:-/config}/logs/bandcamp-YYYYMMDD.log.
|
||||
|
||||
set -euo pipefail
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
# /opt/venv/bin must lead PATH -- that's where `beet` lives (the app's own
|
||||
# subprocess env puts it there too, see pipeline_runner._subprocess_env()).
|
||||
# This script used to run as a bare root cron job (pre-2026-07-08 cutover to
|
||||
# the app scheduler) where a minimal hardened PATH made sense; omitting the
|
||||
# venv here silently broke every downstream `beet import` call inside
|
||||
# import-track.sh once this started running through the app instead --
|
||||
# import-track.sh swallows that failure and still reports OK, so a purchase
|
||||
# would sit unimported until someone noticed it missing from Navidrome.
|
||||
PATH=/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
export PATH
|
||||
|
||||
CONFIG="${ALEMBIC_CONFIG_DIR:-/config}/pipeline/bandcamp/config.env"
|
||||
@@ -87,7 +95,10 @@ shopt -u dotglob nullglob
|
||||
|
||||
# Hand off to the standard manual-import pipeline. No playlist tag — Bandcamp
|
||||
# purchases aren't part of any Spotify playlist. The import-track.sh guard,
|
||||
# albumartist fallback, beets import, and Navidrome scan all kick in normally.
|
||||
# albumartist fallback, beets import, straggler/dedup safety net, and
|
||||
# Navidrome scan all kick in normally (import-track.sh runs the
|
||||
# replace-with-better + dedup-library safety net itself now, for every
|
||||
# caller, not just this one).
|
||||
log "Calling import-track.sh"
|
||||
if ${PIPELINE_DIR:-/app/pipeline}/bin/import-track.sh >> "$LOG" 2>&1; then
|
||||
log "import-track.sh OK"
|
||||
@@ -95,28 +106,5 @@ else
|
||||
log "WARN: import-track.sh exited non-zero (exit $?)"
|
||||
fi
|
||||
|
||||
# Safety net: if anything is stranded in ${MUSIC_DATA_DIR:-/data/music}/downloads (rare with
|
||||
# duplicate_action=keep set in beets config, but possible for files beets
|
||||
# couldn't process), find each one's library counterpart and replace if the
|
||||
# new file is higher quality, OR import as new if there's no counterpart.
|
||||
log "Running replace-with-better safety pass on /downloads stragglers"
|
||||
if ${PIPELINE_DIR:-/app/pipeline}/lib/replace-with-better.sh --apply >> "$LOG" 2>&1; then
|
||||
log "replace-with-better OK"
|
||||
else
|
||||
log "WARN: replace-with-better.sh exited non-zero (exit $?)"
|
||||
fi
|
||||
|
||||
# Now dedupe across the library. With duplicate_action=keep beets imported
|
||||
# every Bandcamp file even when it conflicts with an existing track at the
|
||||
# same path (it creates `.1.ext` siblings). dedup-library.sh's policy is
|
||||
# "FLAC > MP3, then largest file" — Bandcamp version wins, soulseek version
|
||||
# is removed from both the beets DB and disk.
|
||||
log "Running dedup pass (Bandcamp FLAC wins over older Soulseek copies)"
|
||||
if ${PIPELINE_DIR:-/app/pipeline}/lib/dedup-library.sh --apply >> "$LOG" 2>&1; then
|
||||
log "dedup OK"
|
||||
else
|
||||
log "WARN: dedup-library.sh exited non-zero (exit $?)"
|
||||
fi
|
||||
|
||||
log "=== Bandcamp sync done ==="
|
||||
exit 0
|
||||
|
||||
@@ -19,6 +19,42 @@
|
||||
# which is the lesser of two evils vs. corrupting real band names.
|
||||
_PRIMARY_ARTIST_SEP_PATTERN=' / | feat\. | feat | ft\. | ft |; '
|
||||
|
||||
# Read one ID3 text frame (e.g. TPE1, TIT2, TPE2) via mutagen instead of the
|
||||
# `id3v2` CLI. id3v2 is id3lib-3.8.3 (last released 2013) and cannot parse
|
||||
# ID3v2.4 tags at all -- it reports "No ID3 tag" on a file that has complete,
|
||||
# valid tags, just written in the modern version most taggers (Mp3tag,
|
||||
# yt-dlp, foobar2000) default to. That false negative used to send fully-
|
||||
# tagged mp3s to quarantine. Usage: _mp3_tag <file> <frame>
|
||||
_mp3_tag() {
|
||||
python3 - "$1" "$2" 2>/dev/null <<'PY'
|
||||
import sys
|
||||
from mutagen.id3 import ID3
|
||||
try:
|
||||
tags = ID3(sys.argv[1])
|
||||
frame = tags.get(sys.argv[2])
|
||||
print(str(frame) if frame else "")
|
||||
except Exception:
|
||||
print("")
|
||||
PY
|
||||
}
|
||||
|
||||
# Set one ID3 text frame via mutagen, preserving every other frame on the
|
||||
# file. The `id3v2` CLI cannot be used for this: writing even a single frame
|
||||
# with it to a file that already has an ID3v2.4 tag silently drops every
|
||||
# other frame (title, album, genre, date, artwork -- all of it), because
|
||||
# id3lib doesn't understand v2.4 and rewrites the whole tag as v2.3 with only
|
||||
# the frame(s) it was told to set. Usage: _mp3_set_tag <file> <frame> <value>
|
||||
_mp3_set_tag() {
|
||||
python3 - "$1" "$2" "$3" <<'PY'
|
||||
import sys
|
||||
from mutagen.id3 import ID3, Frames
|
||||
tags = ID3(sys.argv[1])
|
||||
frame_cls = Frames[sys.argv[2]]
|
||||
tags.setall(sys.argv[2], [frame_cls(encoding=3, text=[sys.argv[3]])])
|
||||
tags.save(sys.argv[1])
|
||||
PY
|
||||
}
|
||||
|
||||
# For each audio file under $scan_dir that has no ALBUMARTIST tag, set
|
||||
# ALBUMARTIST to the primary artist (everything before the first separator
|
||||
# in the ARTIST tag). Prevents Navidrome from coining ghost album-artists
|
||||
@@ -45,12 +81,12 @@ set_albumartist_fallback() {
|
||||
;;
|
||||
mp3|MP3)
|
||||
local cur_aa artist primary
|
||||
cur_aa=$(id3v2 -l "$file" 2>/dev/null | sed -n 's/^TPE2[^:]*: //p' | head -1)
|
||||
cur_aa=$(_mp3_tag "$file" TPE2)
|
||||
[[ -n "$cur_aa" ]] && continue
|
||||
artist=$(id3v2 -l "$file" 2>/dev/null | sed -n 's/^TPE1[^:]*: //p' | head -1)
|
||||
artist=$(_mp3_tag "$file" TPE1)
|
||||
[[ -z "$artist" ]] && continue
|
||||
primary=$(echo "$artist" | sed -E "s#(${_PRIMARY_ARTIST_SEP_PATTERN}).*##")
|
||||
id3v2 --TPE2 "$primary" "$file" 2>>"$logfile"
|
||||
_mp3_set_tag "$file" TPE2 "$primary" 2>>"$logfile"
|
||||
count=$((count + 1))
|
||||
;;
|
||||
esac
|
||||
@@ -76,8 +112,8 @@ quarantine_untagged() {
|
||||
|
||||
_has_mp3_tags() {
|
||||
local artist title
|
||||
artist=$(id3v2 -l "$1" 2>/dev/null | sed -n 's/^TPE1[^:]*: //p' | head -1)
|
||||
title=$(id3v2 -l "$1" 2>/dev/null | sed -n 's/^TIT2[^:]*: //p' | head -1)
|
||||
artist=$(_mp3_tag "$1" TPE1)
|
||||
title=$(_mp3_tag "$1" TIT2)
|
||||
[[ -n "$artist" && -n "$title" ]]
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
# upgrade-mp3-to-flac.sh --csv-only # just write the CSV; don't run sldl
|
||||
|
||||
set -euo pipefail
|
||||
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
# /opt/venv/bin must lead PATH -- `beet` (used heavily below) lives there.
|
||||
PATH=/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
export PATH
|
||||
|
||||
# These helpers are the same ones import-track.sh sources and runs under its
|
||||
|
||||
Reference in New Issue
Block a user