68cb007e4c
Moves all ~30 pipeline scripts, configs, and the vendored sldl binary into this repo (source /opt/sldl left untouched). Removes all docker exec/docker compose dependencies now that beets and sldl run in-process/as a subprocess of this container instead of via soulbeet/on-demand sldl containers. Replaces hardcoded host paths, Navidrome credentials, and Spotify credential sourcing with env-var-driven paths and shared credential loaders. Adds Dockerfile, entrypoint.sh, requirements.txt, docker-compose.snippet.yml, and the initial app DB schema.
123 lines
4.9 KiB
Bash
Executable File
123 lines
4.9 KiB
Bash
Executable File
#!/bin/bash
|
|
# Wrapper: sync new Bandcamp purchases, then push them through the standard
|
|
# import-track.sh pipeline so they end up in the Navidrome library tagged
|
|
# the same way as everything else.
|
|
#
|
|
# Reads $ALEMBIC_CONFIG_DIR/pipeline/bandcamp/config.env for username, format pref, etc.
|
|
# Run as root (cron). Logs to ${ALEMBIC_CONFIG_DIR:-/config}/logs/bandcamp-YYYYMMDD.log.
|
|
|
|
set -u
|
|
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
export PATH
|
|
|
|
CONFIG="${ALEMBIC_CONFIG_DIR:-/config}/pipeline/bandcamp/config.env"
|
|
LOG=${ALEMBIC_CONFIG_DIR:-/config}/logs/bandcamp-$(date +%Y%m%d).log
|
|
SYNC_PY=${PIPELINE_DIR:-/app/pipeline}/lib/sync-bandcamp.py
|
|
IMPORT_ME=${MUSIC_DATA_DIR:-/data/music}/import-me
|
|
|
|
mkdir -p "$(dirname "$LOG")"
|
|
log() { echo "[$(date -Iseconds)] $*" | tee -a "$LOG"; }
|
|
|
|
if [[ ! -f "$CONFIG" ]]; then
|
|
log "ERROR: $CONFIG not found"
|
|
exit 1
|
|
fi
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG"
|
|
|
|
if [[ ! -f "$BANDCAMP_COOKIES" ]]; then
|
|
log "ERROR: cookies file missing at $BANDCAMP_COOKIES"
|
|
log " Export Bandcamp cookies in Netscape format (Get cookies.txt LOCALLY"
|
|
log " extension, while logged into bandcamp.com), save to that path."
|
|
exit 1
|
|
fi
|
|
|
|
# Refuse to run against a dead/empty share: mkdir -p below would otherwise
|
|
# silently create shadow directories inside the container, downloads would
|
|
# land there invisibly, and state.json would mark those purchases as done —
|
|
# permanently skipping them after the mount comes back. `mountpoint -q` (the
|
|
# original host-side check) doesn't apply to a bind-mounted container path;
|
|
# a canary file + minimum artist-dir count is the container-compatible
|
|
# equivalent (same pattern as navidrome-scan.sh's share-health gate).
|
|
LIB="${MUSIC_DATA_DIR:-/data/music}/Library"
|
|
CANARY="$LIB/.navidrome-canary"
|
|
MIN_ARTIST_DIRS=500
|
|
if [[ ! -r "$CANARY" ]]; then
|
|
log "ERROR: canary $CANARY missing/unreadable — share looks unhealthy — aborting sync"
|
|
exit 1
|
|
fi
|
|
n=$(find "$LIB" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | wc -l)
|
|
if [[ "$n" -lt "$MIN_ARTIST_DIRS" ]]; then
|
|
log "ERROR: only $n artist dirs under $LIB (< $MIN_ARTIST_DIRS) — library looks truncated — aborting sync"
|
|
exit 1
|
|
fi
|
|
|
|
mkdir -p "$BANDCAMP_STAGING" "$IMPORT_ME"
|
|
|
|
log "=== Bandcamp sync starting ==="
|
|
log "user=$BANDCAMP_USERNAME staging=$BANDCAMP_STAGING format=$BANDCAMP_FORMAT_PREF"
|
|
|
|
if ! python3 "$SYNC_PY" \
|
|
"$BANDCAMP_USERNAME" \
|
|
"$BANDCAMP_COOKIES" \
|
|
"$BANDCAMP_STATE" \
|
|
"$BANDCAMP_STAGING" \
|
|
"$BANDCAMP_FORMAT_PREF" >> "$LOG" 2>&1; then
|
|
log "WARN: sync-bandcamp.py exited non-zero (likely auth issue or network)"
|
|
fi
|
|
|
|
# Anything new? If staging dir has audio, hand it off to import-track.sh.
|
|
NEW_COUNT=$(find "$BANDCAMP_STAGING" -type f \( -iname "*.flac" -o -iname "*.mp3" -o -iname "*.wav" -o -iname "*.m4a" \) 2>/dev/null | wc -l)
|
|
if [[ "$NEW_COUNT" -eq 0 ]]; then
|
|
log "No new files to import"
|
|
log "=== Bandcamp sync done (no-op) ==="
|
|
exit 0
|
|
fi
|
|
|
|
log "$NEW_COUNT new audio file(s) staged; moving to $IMPORT_ME for import"
|
|
|
|
# Move everything from staging into import-me. Preserve the band/album folder
|
|
# structure since import-track.sh handles directories fine.
|
|
shopt -s dotglob nullglob
|
|
for entry in "$BANDCAMP_STAGING"/*; do
|
|
[[ -e "$entry" ]] || continue
|
|
mv "$entry" "$IMPORT_ME/" 2>>"$LOG"
|
|
done
|
|
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.
|
|
log "Calling import-track.sh"
|
|
if ${PIPELINE_DIR:-/app/pipeline}/bin/import-track.sh >> "$LOG" 2>&1; then
|
|
log "import-track.sh OK"
|
|
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
|