#!/bin/sh set -eu EXT="flac" # default errecho() { echo "$*" >&2 ; } die() { errecho "$*" && exit 1 ; } clean() { trap 'exit' INT HUP QUIT EXIT [ -f "${DEFAULT_PICTURE:-}" ] && rm -f "$DEFAULT_PICTURE" [ -f "${COVER_PICTURE:-}" ] && rm -f "$COVER_PICTURE" [ -f "${JSON:-}" ] && rm -f "$JSON" } # wrapper to grab a specific tag from flac/opus/mp3 grab_tag() { case ${1##*.} in flac) metaflac --show-tag="$2" -- "$1" | cut -d '=' -f 2- ;; opus) opusinfo -- "$1" | awk -v RS='\n' -v FS='=' -v OFS='' "/^\\t$2=/"'{$1=""; print $0}' ;; mp3) case "$2" in ARTIST) id3 -q '%_a' -- "$1" ;; ALBUM) id3 -q '%_A' -- "$1" ;; TRACKNUMBER) id3 -q '%_###T' -- "$1" ;; TITLE) id3 -q '%_t' -- "$1" ;; ALBUMARTIST) id3 -2 -q "%|%{TPE2}||%{TXXX:ALBUM ARTIST}|?" -- "$1" ;; *) printf 'unknown tag used for mp3' ;; esac ;; *) die 'unknown filetype' esac } # grab track info, make tracknumber 3-padded just in case for long albums # (no album has > 999 tracks. I think.) grab_info() { set +e DURATION="$(ffprobe -v quiet -of csv=p=0 -show_entries format=duration "$1")" ARTIST="$(grab_tag "$1" ARTIST)" ALBUM="$(grab_tag "$1" ALBUM)" TITLE="$(grab_tag "$1" TITLE)" ALBUMARTIST="$(grab_tag "$1" ALBUMARTIST)" TRACKNUMBER="$(printf '%03g' "$(grab_tag "$1" TRACKNUMBER)")" set -e } convert_vid() { ffmpeg -nostdin ${VERBOSE--loglevel error} -y -loop 1 -framerate 4 -f image2 -i "$1" -i "$2" \ -t "$DURATION" \ -pix_fmt yuv420p \ ${acopy:--c:a libopus -b:a 256k} \ -r 4 \ -b:v 500k \ -c:v libvpx \ -deadline realtime \ "$3" errecho "Successfully converted $2" } # extracts cover from $1 to $2 # returns 0 if $2 (output) exists, 1 if not extract_cover() { set +e { case ${1##*.} in flac) metaflac --export-picture-to="$2" -- "$1" ;; opus) opustags --output-cover "$2" -- "$1" ;; mp3) ffmpeg -nostdin -i "$1" -map "0:v:0" -c:v copy "$2" ;; *) die 'unknown filetype' ;; esac } >/dev/null 2>&1 set -e # resize within the bounds of 1280x720 # if it's square then make it 722x720 so youtube doesn't make it a short [ -f "$2" ] && vipsthumbnail -o "$2" -s '722x720' -c "$2" || true } # sets COVER_PIC for use in convert_vid set_cover() { COVER_PICTURE="$(mktemp -u -t XXXXXX.png)" extract_cover "$1" "$COVER_PICTURE" if [ -f "$COVER_PICTURE" ]; then : elif [ -f "track$TRACKNUMBER.png" ]; then cp "track$TRACKNUMBER.png" "$COVER_PICTURE" else cp "$DEFAULT_PICTURE" "$COVER_PICTURE" fi } # SCRIPT START # trap 'clean' INT HUP QUIT EXIT SONG="" BANDCAMP="" NO_CONVERT="" OUTDIR="/tmp/albumsetup/${PWD##*/}" while getopts :vnd:e:p:s:b: OPT; do case "$OPT" in v) VERBOSE="" ;; # can use ${var-rep} for this d) cd "$OPTARG" && OUTDIR="${OUTDIR%/*}/${PWD##*/}" ;; e) EXT="$OPTARG" ;; p) tmpcover="$OPTARG" ;; s) SONG="$OPTARG" && OUTDIR="${OUTDIR%/*}" ;; # individual song b) BANDCAMP="$OPTARG" ;; # bandcamp link to extract data n) NO_CONVERT=1 ;; # don't convert, just get metadata.txt (only affects default albumsetup) *) die "albumsetup: invalid option: -$OPTARG" ;; esac done shift "$((OPTIND - 1))" [ ! -f "$tmpcover" ] && die "Default cover not specified! (-p IMG)" mkdir -p "$OUTDIR" # default cover image, fallback if tracks don't have embedded art DEFAULT_PICTURE="$(mktemp -u -t XXXXXX.png)" vipsthumbnail -o "$DEFAULT_PICTURE" -s '722x720' -c "$tmpcover" # copy audio codec if opus since output codec is opus [ "$EXT" = "opus" ] && acopy="-c:a copy" # INDIVIDUAL SONG # if [ -n "$SONG" ]; then grab_info "$SONG" set_cover "$SONG" # lots of %%/* as to not accidentally create directories convert_vid "$COVER_PICTURE" "$SONG" "$OUTDIR/SONG ${ARTIST%%/*} - ${TITLE%%/*}.webm" # INDIVIDUAL TRACKS FOR FULL ALBUM + COMPLETE VID # else # make a sorted list of the files by tracknumber first # so that we don't have to do it individually for everything later for f in ./*."$EXT"; do grab_info "$f" printf '%s\t%s\n' "$TRACKNUMBER" "$f" done | sort | cut -f 2- > "$OUTDIR/tmp_sorted_list" total_time=0 # this var helps for more precise timestamps while read -r f; do grab_info "$f" set_cover "$f" output_file="$OUTDIR/$TRACKNUMBER ${ARTIST%%/*} - ${TITLE%%/*}.webm" # STUFF FOR INDIVIDUAL VID # errecho "Converting $f" [ -z "$NO_CONVERT" ] && convert_vid "$COVER_PICTURE" "$f" "$output_file" rm -f "$COVER_PICTURE" # metadata print to send to metadata.txt printf '%s - %s - %s\n' "${ARTIST:-unknown artist}" "${ALBUM:-unknown album}" "${TITLE:-unknown title}" \ >> "$OUTDIR/metadata.txt" # build ffmpeg concat metadata # make safe filename for ffmpeg concat; replace all ' with '\'' sf="$(printf '%s' "$output_file" | sed "s/'/'\\\\''/g")" # tracklist printf '%02d:%02d:%02d - %s\n' \ "$((${total_time%%.*} / 3600))" "$((${total_time%%.*} % 3600 / 60))" "$((${total_time%%.*} % 60))" \ "$ARTIST - $TITLE" >> "$OUTDIR/metadata_fullvid.txt" total_time="$(printf '%s\n' "$total_time + $DURATION" | bc)" printf "file '%s'\n" "$sf" >> "$OUTDIR/ffmpeg_tracklist.txt" done < "$OUTDIR/tmp_sorted_list" [ ! -f "$OUTDIR/metadata.txt" ] && die "No files found!" if [ -z "$NO_CONVERT" ]; then errecho "Building full video..." ffmpeg -nostdin -safe 0 -f concat -i "$OUTDIR/ffmpeg_tracklist.txt" -c copy "$OUTDIR/${ALBUMARTIST%%/*} - ${ALBUM%%/*}.webm" fi rm "$OUTDIR/ffmpeg_tracklist.txt" # Bandcamp check and info retrieval if [ -n "$BANDCAMP" ]; then JSON="$(mktemp -t "ALBUMSETUP_JSON.XXXX")" curl -L -s -o - "$BANDCAMP" | pup 'script[type="application/ld+json"]' 'text{}' > "$JSON" albumartist="$(jq -r '.byArtist.name' < "$JSON")" date="$(jq -r '.datePublished' < "$JSON")" date="$(date -d "$date" -u +'%Y-%m-%d')" desc="$(jq -r 'if (.description) then .description | gsub("[\\r]"; "") else empty end' <"$JSON")" creds="$(jq -r 'if (.creditText) then .creditText | gsub("[\\r]"; "") else empty end' <"$JSON")" { printf '%s - %s\n\n' "$albumartist" "$ALBUM" cat "$OUTDIR/metadata.txt" printf '\nReleased %s\nDOWNLOAD: %s\n\n' "$date" "$BANDCAMP" [ -n "$desc" ] || [ -n "$creds" ] && printf '%s\n' 'Release notes:' printf '%s\n\n%s' "$desc" "$creds" | sed -e 's/ "$OUTDIR/metadata2.txt" mv "$OUTDIR/metadata2.txt" "$OUTDIR/metadata.txt" rm -f "$JSON" fi rm -f "$DEFAULT_PICTURE" fi