b135f11557
Security (P0): - Remove committed session-secret default; auto-generate and persist a random secret to the config volume when SESSION_SECRET is unset (prevents forgeable session cookies / auth bypass). - Validate playlist names to a safe charset and render sldl configs via literal Python substitution instead of sed (closes a command-injection and path-traversal path through playlist names). - shlex-quote credential values written to shell-sourced env files, and strip newlines from values patched into .conf files. - Render playlist .conf files 0600; warn at startup if the master key is co-located with the config volume; document keeping it separate. Portability: - Configurable timezone via TZ (default UTC) instead of hardcoded Edmonton. - Remove personal defaults (navidrome user "andrew", ephemeral.club URLs). - Ship generic example seeds; move the cross-album dedup keep-list and the legacy playlist import to editable config files; drop the personal _upgrade.csv. - Generic VPN reference in docker-compose.snippet.yml. First-run experience: - Redirect to /setup instead of 500 when OIDC is unconfigured; surface a missing master key inline; entrypoint exits with an actionable message when the config folder is not writable. - Add unauthenticated /health (JSON) and /setup (checklist) diagnostics. Docs: - Write docs/ARCHITECTURE.md and docs/MIGRATION.md (previously referenced but missing); expand README with ownership, backups, advanced settings, and migration guidance. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
110 lines
3.8 KiB
Bash
Executable File
110 lines
3.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# Find audio files in the library that have no ALBUM tag, set ALBUM to
|
|
# "{TITLE} - Single" — matches Spotify / Apple Music's convention for
|
|
# single-track releases. Otherwise these tracks land as [Unknown Album]
|
|
# in Navidrome.
|
|
#
|
|
# Usage:
|
|
# fix-empty-album.sh # dry run (default)
|
|
# fix-empty-album.sh --apply # actually rewrite tags
|
|
#
|
|
# Bandcamp single-track downloads are the most common source of this
|
|
# issue (Bandcamp serves single tracks with no ALBUM tag). New downloads
|
|
# via sync-bandcamp.py are auto-tagged; this script handles backfill.
|
|
|
|
set -u
|
|
APPLY=0
|
|
[[ "${1:-}" == "--apply" ]] && APPLY=1
|
|
|
|
LOG=${ALEMBIC_CONFIG_DIR:-/config}/logs/fix-empty-album-$(date +%Y%m%d-%H%M%S).log
|
|
mkdir -p "$(dirname "$LOG")"
|
|
|
|
NAVIDROME_ENV="${ALEMBIC_CONFIG_DIR:-/config}/pipeline/navidrome/admin.env"
|
|
[ -f "$NAVIDROME_ENV" ] && source "$NAVIDROME_ENV"
|
|
ND_BASE="${ND_BASE:-http://navidrome:4533}"
|
|
ND_USER="${ND_USER:-}"
|
|
ND_PASS="${ND_PASS:-}"
|
|
log() { echo "[$(date -Iseconds)] $*" | tee -a "$LOG"; }
|
|
|
|
if [[ $APPLY -eq 1 ]]; then
|
|
log "=== FIX EMPTY ALBUM (APPLY) ==="
|
|
else
|
|
log "=== FIX EMPTY ALBUM (DRY RUN — pass --apply to write) ==="
|
|
fi
|
|
|
|
FIXED=0
|
|
SKIPPED=0
|
|
|
|
while IFS= read -r -d '' f; do
|
|
ext="${f,,}"; ext="${ext##*.}"
|
|
album="" ; title=""
|
|
case "$ext" in
|
|
flac)
|
|
album=$(metaflac --show-tag=ALBUM "$f" 2>/dev/null | sed -n 's/^[^=]*=//p' | head -1)
|
|
title=$(metaflac --show-tag=TITLE "$f" 2>/dev/null | sed -n 's/^[^=]*=//p' | head -1)
|
|
;;
|
|
mp3)
|
|
album=$(id3v2 -l "$f" 2>/dev/null | sed -n 's/^TALB[^:]*: //p' | head -1)
|
|
title=$(id3v2 -l "$f" 2>/dev/null | sed -n 's/^TIT2[^:]*: //p' | head -1)
|
|
;;
|
|
*) continue ;;
|
|
esac
|
|
|
|
if [[ -n "$album" ]]; then
|
|
continue # already has an album, leave it alone
|
|
fi
|
|
if [[ -z "$title" ]]; then
|
|
log "[skip-no-title] $f"
|
|
SKIPPED=$((SKIPPED + 1))
|
|
continue
|
|
fi
|
|
|
|
# Some titles have a leading "NN " track-number prefix baked in from a
|
|
# bad filename parse (e.g. title="01 Domestic Violence"). If we use the
|
|
# title as-is, the album becomes "01 Domestic Violence - Single" which
|
|
# is broken. Strip leading 1-3 digits + space, and also fix the title
|
|
# tag in place so it isn't shown with the prefix in clients.
|
|
clean_title=$(echo "$title" | sed -E 's/^[[:space:]]*[0-9]{1,3}[[:space:]]+//')
|
|
if [[ "$clean_title" != "$title" && -n "$clean_title" ]]; then
|
|
log " trimmed leading track-number from title: \"$title\" -> \"$clean_title\""
|
|
if [[ $APPLY -eq 1 ]]; then
|
|
case "$ext" in
|
|
flac) metaflac --remove-tag=TITLE --set-tag="TITLE=${clean_title}" "$f" 2>>"$LOG" ;;
|
|
mp3) id3v2 --TIT2 "${clean_title}" "$f" 2>>"$LOG" ;;
|
|
esac
|
|
fi
|
|
title="$clean_title"
|
|
fi
|
|
|
|
new_album="${title} - Single"
|
|
log "[$( [[ $APPLY -eq 1 ]] && echo SET || echo WOULD-SET )] album=\"${new_album}\" - $f"
|
|
|
|
if [[ $APPLY -eq 1 ]]; then
|
|
case "$ext" in
|
|
flac) metaflac --set-tag="ALBUM=${new_album}" "$f" 2>>"$LOG" ;;
|
|
mp3) id3v2 --TALB "${new_album}" "$f" 2>>"$LOG" ;;
|
|
esac
|
|
fi
|
|
FIXED=$((FIXED + 1))
|
|
done < <(find ${MUSIC_DATA_DIR:-/data/music}/Library -type f \( -iname "*.flac" -o -iname "*.mp3" \) -print0 2>/dev/null)
|
|
|
|
log ""
|
|
log "=== Summary: $FIXED fixed, $SKIPPED skipped (no title) ==="
|
|
|
|
if [[ $APPLY -eq 1 && $FIXED -gt 0 ]]; then
|
|
log "Syncing beets DB from new tags..."
|
|
beet update 2>&1 | tail -3 >> "$LOG"
|
|
log "Moving files to reflect new album in path templates..."
|
|
beet move 2>&1 | tail -3 >> "$LOG"
|
|
log "Triggering Navidrome rescan..."
|
|
curl -s -G "$ND_BASE/rest/startScan.view" \
|
|
--data-urlencode "u=$ND_USER" \
|
|
--data-urlencode "p=$ND_PASS" \
|
|
--data-urlencode 'v=1.16.0' \
|
|
--data-urlencode 'c=fix-empty-album' \
|
|
--data-urlencode 'f=json' \
|
|
--data-urlencode 'fullScan=true' >> "$LOG" 2>&1
|
|
fi
|
|
|
|
log "=== Done. Log: $LOG ==="
|