197 lines
6.2 KiB
Bash
Executable File
197 lines
6.2 KiB
Bash
Executable File
#!/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/</‹/g'
|
||
} > "$OUTDIR/metadata2.txt"
|
||
mv "$OUTDIR/metadata2.txt" "$OUTDIR/metadata.txt"
|
||
rm -f "$JSON"
|
||
fi
|
||
|
||
rm -f "$DEFAULT_PICTURE"
|
||
fi
|