git/intake/main

test: fixtures for the shapes downloads actually arrive in

Twenty-two cases built from what real rips look like rather than what a tidy
library looks like: nested album folders, CD1 and CD2 folders, junk cover art,
truncated and empty files, text files wearing an audio extension, every sidecar a
rip ships with, AppleDouble files, emoji and zero-width characters, names that
are only punctuation, over-long names, slashes in album titles, and tags that
disagree with each other.

Each has an EDGE.md saying what is odd about it and what intake should do, so the
expectation is written down next to the case rather than in someone's head.
diff --git a/scripts/make-edge-fixtures.sh b/scripts/make-edge-fixtures.sh
new file mode 100755
index 0000000..a14930f
--- /dev/null
+++ b/scripts/make-edge-fixtures.sh
@@ -0,0 +1,387 @@
+#!/usr/bin/env bash
+# Generate pathological albums: the shapes downloads actually arrive in.
+#
+# Kept apart from make-fixtures.sh because these are about surviving bad input
+# rather than exercising the normal paths. Each directory gets an EDGE.md saying
+# what is odd about it and what intake should do with it.
+#
+# Usage: scripts/make-edge-fixtures.sh [STAGING_DIR] (default ~/music/staging)
+
+set -euo pipefail
+
+STAGING="${1:-$HOME/music/staging}"
+POOL="$(mktemp -d)"
+trap 'rm -rf "$POOL"' EXIT
+
+command -v ffmpeg >/dev/null || { echo "need ffmpeg" >&2; exit 1; }
+command -v metaflac >/dev/null || { echo "need metaflac" >&2; exit 1; }
+
+echo "==> staging dir: $STAGING"
+mkdir -p "$STAGING"
+
+tone() { # tone <freq> <path> [extra ffmpeg args...]
+ local freq="$1" out="$2"; shift 2
+ ffmpeg -hide_banner -loglevel error -y -f lavfi -i "sine=frequency=$freq:duration=1" "$@" "$out"
+}
+
+for i in 1 2 3 4 5 6; do
+ tone $((300 + i * 90)) "$POOL/t$i.flac" -ac 2 -ar 44100
+done
+
+tag() { # tag <file> <key=value>...
+ local f="$1"; shift
+ if [ "${f##*.}" = "flac" ]; then
+ printf '%s\n' "$@" > "$f.tags"
+ metaflac --remove-all-tags --import-tags-from="$f.tags" "$f"
+ rm -f "$f.tags"
+ return
+ fi
+ local args=()
+ local kv
+ for kv in "$@"; do args+=(-metadata "$kv"); done
+ # the temp name keeps the extension, or ffmpeg cannot pick a muxer
+ local tmp="$f.tagged.${f##*.}"
+ ffmpeg -hide_banner -loglevel error -y -i "$f" "${args[@]}" -c copy "$tmp"
+ mv "$tmp" "$f"
+}
+
+note() { # note <album-dir> <text>
+ mkdir -p "$STAGING/$1"
+ printf '%s\n' "$2" > "$STAGING/$1/EDGE.md"
+}
+
+album() { mkdir -p "$STAGING/$1"; }
+
+echo "==> building edge cases"
+
+# --------------------------------------------------------------------------
+# naming and structure
+# --------------------------------------------------------------------------
+
+# A folder with no artist separator, so nothing can be inferred from it.
+album "e01-no-hint-anywhere"
+cp "$POOL/t1.flac" "$STAGING/e01-no-hint-anywhere/01 one.flac"
+cp "$POOL/t2.flac" "$STAGING/e01-no-hint-anywhere/02 two.flac"
+note "e01-no-hint-anywhere" "# no artist or album anywhere
+
+Files are untagged and the folder name carries no separator, so neither tag nor
+name gives an artist.
+
+Expected: refuse. This is the case the name editor exists for, and guessing
+'one' as the artist would be worse than stopping."
+
+# An album folder nested inside another folder of the same name.
+album "e02-nested/An Album"
+for i in 1 2; do
+ cp "$POOL/t$i.flac" "$STAGING/e02-nested/An Album/0$i track.flac"
+ tag "$STAGING/e02-nested/An Album/0$i track.flac" \
+ "TITLE=Track $i" "ALBUM=An Album" "ARTIST=Nested Artist" \
+ "ALBUMARTIST=Nested Artist" "TRACKNUMBER=$i" "DISCNUMBER=1" "DATE=2010"
+done
+note "e02-nested" "# album nested one level down
+
+The album is inside a wrapper folder rather than directly in staging.
+
+Expected: either file the inner album correctly or say clearly that the wrapper
+holds no audio. It must not file the wrapper as an album with zero tracks."
+
+# Files sitting directly in the staging root, belonging to no album.
+mkdir -p "$STAGING/e03-loose-files"
+cp "$POOL/t1.flac" "$STAGING/e03-loose-files/loose one.flac"
+tag "$STAGING/e03-loose-files/loose one.flac" \
+ "TITLE=Loose" "ALBUM=Loose Album" "ARTIST=Loose Artist" \
+ "ALBUMARTIST=Loose Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+rm -f "$STAGING/e03-loose-files/EDGE.md"
+note "e03-loose-files" "# a release of exactly one track
+
+A single audio file with full tags and nothing else. (Files sitting loose in the
+staging root, not in any folder, are a separate case: they are ignored, so that
+staging itself is never treated as an album. That behaviour does not fit the
+fixture layout here, which is one directory per case, so this fixture covers the
+one-track release instead.)
+
+Expected: files normally. A single-track release is not an error."
+
+# "CD1" / "CD2" folders rather than tagged disc numbers.
+album "e04-cd-folders/CD1"
+album "e04-cd-folders/CD2"
+for d in 1 2; do
+ for t in 1 2; do
+ f="$STAGING/e04-cd-folders/CD$d/$t track.flac"
+ cp "$POOL/t$((d * 2 + t - 2)).flac" "$f"
+ tag "$f" "TITLE=CD$d Track $t" "ALBUM=CD Folders" "ARTIST=CD Artist" \
+ "ALBUMARTIST=CD Artist" "TRACKNUMBER=$t" "DATE=2005"
+ done
+done
+note "e04-cd-folders" "# discs as CD1 / CD2 subfolders, no DISCNUMBER tags
+
+Expected: either infer disc 1 and 2 from the folder names, or file everything as
+disc 1. What it must not do is invent track numbers that collide, or drop a disc."
+
+# --------------------------------------------------------------------------
+# tags
+# --------------------------------------------------------------------------
+
+# ALBUM differs from track to track.
+album "e05-inconsistent-album"
+for i in 1 2 3; do
+ f="$STAGING/e05-inconsistent-album/0$i track.flac"
+ cp "$POOL/t$i.flac" "$f"
+ # the second track is mistagged, which is the ordinary version of this
+ alb="Right Album"
+ [ "$i" = "2" ] && alb="Rong Album"
+ tag "$f" "TITLE=Inconsistent $i" "ALBUM=$alb" "ARTIST=Inc Artist" \
+ "ALBUMARTIST=Inc Artist" "TRACKNUMBER=$i" "DISCNUMBER=1"
+done
+note "e05-inconsistent-album" "# one track out of three has a mistyped ALBUM tag
+
+Expected: two of three agree, so the majority is used and the disagreement is
+reported. A single bad tag should not stop an otherwise fine album."
+
+# Duplicate track numbers within one disc.
+album "e06-duplicate-numbers"
+for i in 1 2; do
+ f="$STAGING/e06-duplicate-numbers/0$i track.flac"
+ cp "$POOL/t$i.flac" "$f"
+ tag "$f" "TITLE=Duplicate $i" "ALBUM=Dupe Numbers" "ARTIST=Dupe Artist" \
+ "ALBUMARTIST=Dupe Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+done
+note "e06-duplicate-numbers" "# two tracks both numbered 1
+
+Expected: the target names differ because the titles do, so this files. Two
+files claiming track 1 is a bad rip rather than an intake problem, but it is
+worth a warning."
+
+# Track numbers that are not numbers.
+album "e07-junk-track-numbers"
+for n in "A" "B"; do
+ f="$STAGING/e07-junk-track-numbers/$n song.flac"
+ cp "$POOL/t1.flac" "$f"
+ tag "$f" "TITLE=$n Song" "ALBUM=Junk Numbers" "ARTIST=Junk Artist" \
+ "ALBUMARTIST=Junk Artist" "TRACKNUMBER=$n" "DISCNUMBER=1"
+done
+note "e07-junk-track-numbers" "# TRACKNUMBER=A and TRACKNUMBER=B
+
+Expected: fall back to numbering in file order, and say it did."
+
+# Titles that normalise to the same name.
+album "e08-title-collision"
+i=1
+for t in "Song!" "Song?"; do
+ f="$STAGING/e08-title-collision/track $i.flac"
+ cp "$POOL/t1.flac" "$f"
+ tag "$f" "TITLE=$t" "ALBUM=Collide" "ARTIST=Collide Artist" \
+ "ALBUMARTIST=Collide Artist" "TRACKNUMBER=$i" "DISCNUMBER=1"
+ i=$((i + 1))
+done
+note "e08-title-collision" "# 'Song!' and 'Song?' normalise to the same name
+
+Expected: refuse with a hard issue naming both offending files, before anything
+is written. Silent clobbering here would lose a track."
+
+# An artist whose name is entirely symbols.
+album "e09-symbol-artist"
+cp "$POOL/t1.flac" "$STAGING/e09-symbol-artist/01 track.flac"
+tag "$STAGING/e09-symbol-artist/01 track.flac" \
+ "TITLE=Symbol Song" "ALBUM=Symbol Album" "ARTIST=★★★" "ALBUMARTIST=★★★" \
+ "TRACKNUMBER=1" "DISCNUMBER=1"
+note "e09-symbol-artist" "# ALBUMARTIST is entirely non-ASCII symbols
+
+Expected: refuse. A folder name cannot be made from it, and the editor is the
+way out. It must not create a directory of punctuation."
+
+# Windows reserved device names as titles.
+album "e10-reserved-names"
+for t in con nul aux; do
+ f="$STAGING/e10-reserved-names/$t.flac"
+ cp "$POOL/t1.flac" "$f"
+ tag "$f" "TITLE=$t" "ALBUM=Reserved" "ARTIST=Reserved Artist" \
+ "ALBUMARTIST=Reserved Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+done
+note "e10-reserved-names" "# titles are con, nul and aux
+
+These are device names on Windows and cannot be used as file names there.
+
+Expected: decide and document. On Linux they are legal, so filing them is
+defensible, but a library meant to be portable will hit trouble copying it to a
+Windows machine."
+
+# --------------------------------------------------------------------------
+# files
+# --------------------------------------------------------------------------
+
+# A mixture of formats in one album.
+album "e11-mixed-formats"
+cp "$POOL/t1.flac" "$STAGING/e11-mixed-formats/01 lossless.flac"
+tone 700 "$STAGING/e11-mixed-formats/02 lossy.mp3" -ac 2 -ar 44100 -b:a 128k
+tag "$STAGING/e11-mixed-formats/01 lossless.flac" \
+ "TITLE=Lossless" "ALBUM=Mixed Formats" "ARTIST=Mixed Artist" \
+ "ALBUMARTIST=Mixed Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+tag "$STAGING/e11-mixed-formats/02 lossy.mp3" \
+ "TITLE=Lossy" "ALBUM=Mixed Formats" "ARTIST=Mixed Artist" \
+ "ALBUMARTIST=Mixed Artist" "TRACKNUMBER=2" "DISCNUMBER=1"
+note "e11-mixed-formats" "# one flac and one mp3 in the same album
+
+Expected: both are filed, each keeping its own extension. Transcoding a lossy
+file into the library should not pretend it is lossless, and the warning is
+worth having."
+
+# A truncated file: valid header, no data.
+album "e12-truncated"
+head -c 4096 "$POOL/t1.flac" > "$STAGING/e12-truncated/01 truncated.flac"
+tag "$STAGING/e12-truncated/01 truncated.flac" \
+ "TITLE=Truncated" "ALBUM=Broken" "ARTIST=Broken Artist" \
+ "ALBUMARTIST=Broken Artist" "TRACKNUMBER=1" "DISCNUMBER=1" 2>/dev/null || true
+note "e12-truncated" "# a flac truncated to its first 4KB
+
+Expected: refuse before promoting anything, and leave staging untouched. A
+half-downloaded file is the most common bad file on Soulseek."
+
+# A file that is not audio at all, wearing an audio extension.
+album "e13-not-audio"
+printf 'this is not audio, it is a text file\n' > "$STAGING/e13-not-audio/01 fake.flac"
+note "e13-not-audio" "# text file named .flac
+
+Expected: refuse. It must not be copied into the archive and presented as a
+master just because it has the right extension."
+
+# A zero byte file.
+album "e14-empty-file"
+: > "$STAGING/e14-empty-file/01 empty.flac"
+note "e14-empty-file" "# zero byte flac
+
+Expected: refuse."
+
+# High resolution, mono and a very short track in one album.
+album "e15-unusual-audio"
+tone 400 "$STAGING/e15-unusual-audio/01 hires.flac" -ac 2 -ar 96000 -sample_fmt s32
+tone 500 "$STAGING/e15-unusual-audio/02 mono.flac" -ac 1 -ar 22050
+tone 600 "$STAGING/e15-unusual-audio/03 short.flac" -ac 2 -ar 44100 -t 0.3
+i=1
+for f in "$STAGING/e15-unusual-audio"/*.flac; do
+ tag "$f" "TITLE=Unusual $i" "ALBUM=Unusual Audio" "ARTIST=Unusual Artist" \
+ "ALBUMARTIST=Unusual Artist" "TRACKNUMBER=$i" "DISCNUMBER=1"
+ i=$((i + 1))
+done
+note "e15-unusual-audio" "# 96kHz 32-bit, mono, and a 0.3 second track
+
+Expected: all three file. Nothing about the sample rate, channel count or
+duration should cause a refusal."
+
+# --------------------------------------------------------------------------
+# clutter and sidecars
+# --------------------------------------------------------------------------
+
+album "e16-clutter"
+for i in 1 2; do
+ f="$STAGING/e16-clutter/0$i track.flac"
+ cp "$POOL/t$i.flac" "$f"
+ tag "$f" "TITLE=Clutter $i" "ALBUM=Clutter" "ARTIST=Clutter Artist" \
+ "ALBUMARTIST=Clutter Artist" "TRACKNUMBER=$i" "DISCNUMBER=1"
+done
+printf 'PERFORMER "Clutter Artist"\nFILE "01 track.flac" WAVE\n' > "$STAGING/e16-clutter/album.cue"
+printf 'Exact Audio Copy log\n' > "$STAGING/e16-clutter/album.log"
+printf 'release info\n' > "$STAGING/e16-clutter/album.nfo"
+printf 'x\n' > "$STAGING/e16-clutter/album.sfv"
+printf '#EXTM3U\n' > "$STAGING/e16-clutter/album.m3u"
+printf 'partial download\n' > "$STAGING/e16-clutter/03 track.flac.part"
+printf 'junk\n' > "$STAGING/e16-clutter/.DS_Store"
+printf 'junk\n' > "$STAGING/e16-clutter/._01 track.flac"
+printf 'junk\n' > "$STAGING/e16-clutter/Thumbs.db"
+printf 'junk\n' > "$STAGING/e16-clutter/desktop.ini"
+note "e16-clutter" "# every sidecar a rip comes with
+
+cue, log, nfo, sfv, m3u, a .part, and the hidden files macOS and Windows leave
+behind.
+
+Expected: audio is filed, all of it is removed from staging, and the removals are
+reported. Nothing here should be mistaken for a track or for cover art."
+
+# A single file album plus a cue sheet, which is how a lot of rips arrive.
+album "e17-cue-single-file"
+cp "$POOL/t1.flac" "$STAGING/e17-cue-single-file/whole album.flac"
+tag "$STAGING/e17-cue-single-file/whole album.flac" \
+ "TITLE=Whole Album" "ALBUM=Cue Album" "ARTIST=Cue Artist" \
+ "ALBUMARTIST=Cue Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+printf 'FILE "whole album.flac" WAVE\n TRACK 01 AUDIO\n TITLE "One"\n TRACK 02 AUDIO\n TITLE "Two"\n' \
+ > "$STAGING/e17-cue-single-file/album.cue"
+note "e17-cue-single-file" "# one long file plus a cue sheet describing many tracks
+
+Expected: this is NOT supported, and intake should say so rather than filing the
+whole thing as a one-track album without comment. Splitting on a cue sheet is a
+separate feature. The important part is that the user is told, because otherwise
+they end up with an album whose track list is wrong."
+
+# --------------------------------------------------------------------------
+# cover art
+# --------------------------------------------------------------------------
+
+# Art that is not an image.
+album "e18-broken-art"
+cp "$POOL/t1.flac" "$STAGING/e18-broken-art/01 track.flac"
+tag "$STAGING/e18-broken-art/01 track.flac" \
+ "TITLE=Broken Art" "ALBUM=Broken Art" "ARTIST=Art Artist" \
+ "ALBUMARTIST=Art Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+printf 'not an image at all\n' > "$STAGING/e18-broken-art/cover.jpg"
+note "e18-broken-art" "# cover.jpg is a text file
+
+Expected: ignore it as artwork and carry on, falling back to a placeholder or to
+another source. A bad image file must not fail the album."
+
+# Art in a subfolder, which is how scans usually arrive.
+album "e19-art-in-subfolder/Scans"
+cp "$POOL/t1.flac" "$STAGING/e19-art-in-subfolder/01 track.flac"
+tag "$STAGING/e19-art-in-subfolder/01 track.flac" \
+ "TITLE=Scanned" "ALBUM=Scanned Album" "ARTIST=Scan Artist" \
+ "ALBUMARTIST=Scan Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+tone 800 "$POOL/art.flac"
+ffmpeg -hide_banner -loglevel error -y -f lavfi -i "color=c=blue:s=600x600:d=1" -frames:v 1 \
+ "$STAGING/e19-art-in-subfolder/Scans/front.jpg"
+note "e19-art-in-subfolder" "# the only artwork is Scans/front.jpg
+
+Expected: ideally found and offered. If not, it falls back to a placeholder,
+which is acceptable but worth knowing."
+
+# --------------------------------------------------------------------------
+# text
+# --------------------------------------------------------------------------
+
+# Emoji, zero width space and a right-to-left mark in the names.
+album "e20-odd-text"
+cp "$POOL/t1.flac" "$STAGING/e20-odd-text/01 track.flac"
+tag "$STAGING/e20-odd-text/01 track.flac" \
+ "TITLE=Song 🎵 With Emoji" "ALBUM=Album​With Zero Width" \
+ "ARTIST=RTL ‏Artist" "ALBUMARTIST=RTL ‏Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+note "e20-odd-text" "# emoji and zero-width characters in tags
+
+Expected: they are stripped or kept consistently and never produce an empty or
+invisible name. A zero-width space surviving into a file name is the failure
+mode worth catching."
+
+# A very long album name, and one containing a slash.
+album "e21-long-names"
+cp "$POOL/t1.flac" "$STAGING/e21-long-names/01 track.flac"
+tag "$STAGING/e21-long-names/01 track.flac" \
+ "TITLE=A Song" "ALBUM=$(printf 'Very Long Album Name %.0s' {1..20})" \
+ "ARTIST=Long Artist" "ALBUMARTIST=Long Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+note "e21-long-names" "# an album name far past 200 bytes
+
+Expected: the directory name is truncated to something legal, and the shortening
+is reported rather than happening silently."
+
+album "e22-slash-in-name"
+cp "$POOL/t1.flac" "$STAGING/e22-slash-in-name/01 track.flac"
+tag "$STAGING/e22-slash-in-name/01 track.flac" \
+ "TITLE=Slash Song" "ALBUM=AC/DC Live" "ARTIST=Slash Artist" \
+ "ALBUMARTIST=Slash Artist" "TRACKNUMBER=1" "DISCNUMBER=1"
+note "e22-slash-in-name" "# the album name contains a slash
+
+Expected: the slash must not become a directory separator, or the album is
+written into a tree one level deeper than intended. It should be replaced."
+
+echo
+echo "==> done. Edge cases in $STAGING"
+for d in "$STAGING"/e*/; do
+ n=$(find "$d" -name '*.flac' -o -name '*.mp3' 2>/dev/null | wc -l)
+ printf ' %-28s %3s audio file(s)\n' "$(basename "$d")" "$n"
+done