aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Anthony <sam@samanthony.xyz>2026-03-20 09:12:17 -0400
committerSam Anthony <sam@samanthony.xyz>2026-03-20 09:12:17 -0400
commitbb9fd9a7e0cda9893e7d8573e5f64ff695caff39 (patch)
treedf81a5dcb91c11ce73a830d5003c7829b6e72a8a
parent4e1f21f9d882d392c8636a1ce095f9ec145a74ab (diff)
downloadbin-master.zip
tagopusHEADmaster
-rwxr-xr-xtagopus93
1 files changed, 93 insertions, 0 deletions
diff --git a/tagopus b/tagopus
new file mode 100755
index 0000000..165cdda
--- /dev/null
+++ b/tagopus
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+usage() {
+ cat >&2 <<'EOF'
+Usage: tagopus [OPTIONS] FILE...
+
+Options:
+ -a ARTIST Set artist
+ -b ALBUM Set album
+ -y DATE Set date (year)
+ -D Infer artist/album from directory structure (artist/album/NN title.opus)
+ Explicit -a/-b flags override inferred values.
+
+Track number and title are inferred from each filename's leading numeric prefix.
+EOF
+ exit 1
+}
+
+artist=
+album=
+date=
+infer_dirs=0
+
+while getopts 'a:b:y:D' opt; do
+ case $opt in
+ a) artist=$OPTARG ;;
+ b) album=$OPTARG ;;
+ y) date=$OPTARG ;;
+ D) infer_dirs=1 ;;
+ *) usage ;;
+ esac
+done
+shift $((OPTIND - 1))
+
+[ $# -eq 0 ] && usage
+
+# Validate fields that must come from flags and are always required
+err=0
+[ -z "$date" ] && { printf 'Error: date is required (-y)\n' >&2; err=1; }
+
+# Without -D, artist/album must be supplied via flags
+if [ $infer_dirs -eq 0 ]; then
+ [ -z "$artist" ] && { printf 'Error: artist is required (-a or -D)\n' >&2; err=1; }
+ [ -z "$album" ] && { printf 'Error: album is required (-b or -D)\n' >&2; err=1; }
+fi
+
+[ $err -eq 1 ] && exit 1
+
+numtracks=$#
+
+for f in "$@"; do
+ base=$(basename "$f")
+ name="${base%.opus}"
+
+ # Extract leading numeric prefix as track number (variable width)
+ tracknum=$(printf '%s' "$name" | sed -n 's/^\([0-9][0-9]*\).*/\1/p')
+ if [ -z "$tracknum" ]; then
+ printf 'Error: cannot parse track number from filename: %s\n' "$f" >&2
+ exit 1
+ fi
+
+ # Title is everything after the numeric prefix and any whitespace
+ title=$(printf '%s' "$name" | sed 's/^[0-9][0-9]*[[:space:]]*//')
+ if [ -z "$title" ]; then
+ printf 'Error: cannot parse title from filename: %s\n' "$f" >&2
+ exit 1
+ fi
+
+ # Start with flag values; fill in from directory structure if -D and not already set
+ file_artist=$artist
+ file_album=$album
+
+ if [ $infer_dirs -eq 1 ]; then
+ album_dir=$(pwd)
+ artist_dir=$(dirname "$album_dir")
+ [ -z "$file_album" ] && file_album=$(basename "$album_dir")
+ [ -z "$file_artist" ] && file_artist=$(basename "$artist_dir")
+ fi
+
+ # Final check — -D may have been given but paths might not resolve usefully
+ err=0
+ [ -z "$file_artist" ] && { printf 'Error: could not determine artist for: %s\n' "$f" >&2; err=1; }
+ [ -z "$file_album" ] && { printf 'Error: could not determine album for: %s\n' "$f" >&2; err=1; }
+ [ $err -eq 1 ] && exit 1
+
+ opustags -i "$f" \
+ -s "artist=$file_artist" \
+ -s "album=$file_album" \
+ -s "date=$date" \
+ -s "tracknumber=$tracknum" \
+ -s "tracktotal=$numtracks" \
+ -s "title=$title"
+done