|
|
|
@ -1,21 +1,19 @@
|
|
|
|
|
#!/bin/sh
|
|
|
|
|
set -euf
|
|
|
|
|
set -eu
|
|
|
|
|
|
|
|
|
|
EXT="flac" # default
|
|
|
|
|
NL="
|
|
|
|
|
"
|
|
|
|
|
IFS="$NL"
|
|
|
|
|
|
|
|
|
|
errecho() { echo "$*" >&2 ; }
|
|
|
|
|
die() { errecho "$*" && exit 1 ; }
|
|
|
|
|
clean() {
|
|
|
|
|
trap 'exit' INT HUP QUIT
|
|
|
|
|
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
|
|
|
|
|
grabtag() {
|
|
|
|
|
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}' ;;
|
|
|
|
@ -35,20 +33,19 @@ grabtag() {
|
|
|
|
|
|
|
|
|
|
# grab track info, make tracknumber 3-padded just in case for long albums
|
|
|
|
|
# (no album has > 999 tracks. I think.)
|
|
|
|
|
grabinfo() {
|
|
|
|
|
grab_info() {
|
|
|
|
|
set +e
|
|
|
|
|
DURATION="$(ffprobe -v quiet -of csv=p=0 -show_entries format=duration "$1")"
|
|
|
|
|
ARTIST="$(grabtag "$1" ARTIST)"
|
|
|
|
|
ALBUM="$(grabtag "$1" ALBUM)"
|
|
|
|
|
TITLE="$(grabtag "$1" TITLE)"
|
|
|
|
|
TRACKNUMBER="$(printf '%03g' "$(grabtag "$1" TRACKNUMBER)")"
|
|
|
|
|
ARTIST="$(grab_tag "$1" ARTIST)"
|
|
|
|
|
ALBUM="$(grab_tag "$1" ALBUM)"
|
|
|
|
|
TITLE="$(grab_tag "$1" TITLE)"
|
|
|
|
|
TRACKNUMBER="$(printf '%03g' "$(grab_tag "$1" TRACKNUMBER)")"
|
|
|
|
|
set -e
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ffconv() {
|
|
|
|
|
IFS=" "
|
|
|
|
|
ffmpeg ${VERBOSE--loglevel error} -y -loop 1 -framerate 4 -f image2 -i "$1" ${FULLALBUM:+-safe 0 -f concat} -i "$2" \
|
|
|
|
|
-t "${TOTALTIME:-$DURATION}" \
|
|
|
|
|
convert_vid() {
|
|
|
|
|
ffmpeg ${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 \
|
|
|
|
@ -60,6 +57,8 @@ ffconv() {
|
|
|
|
|
errecho "Successfully converted $2"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# extracts cover from $1 to $2
|
|
|
|
|
# returns 0 if $2 (output) exists, 1 if not
|
|
|
|
|
extract_cover() {
|
|
|
|
|
set +e
|
|
|
|
|
{
|
|
|
|
@ -70,21 +69,37 @@ extract_cover() {
|
|
|
|
|
*) die 'unknown filetype' ;;
|
|
|
|
|
esac
|
|
|
|
|
} >/dev/null
|
|
|
|
|
[ -f "$2" ] && magick mogrify -resize 1280x720 -background black -gravity center -extent 722x720 "$2"
|
|
|
|
|
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" ] && magick mogrify -resize 1280x720 -background black -gravity center -extent 722x720 "$2"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# GET OPTIONS #
|
|
|
|
|
# sets COVER_PIC for use in convert_vid
|
|
|
|
|
set_cover() {
|
|
|
|
|
tmp="$(mktemp -u -t ALBUMSETUP_COVER_XXXX)"
|
|
|
|
|
extract_cover "$1" "$tmp"
|
|
|
|
|
if [ -f "$tmp" ]; then
|
|
|
|
|
COVER_PICTURE="$tmp"
|
|
|
|
|
elif [ -f "track$TRACKNUMBER.png" ]; then
|
|
|
|
|
COVER_PICTURE="track$TRACKNUMBER.png"
|
|
|
|
|
else
|
|
|
|
|
COVER_PICTURE="$DEFAULT_PICTURE"
|
|
|
|
|
fi
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# SCRIPT START #
|
|
|
|
|
|
|
|
|
|
trap 'clean' INT HUP QUIT EXIT
|
|
|
|
|
|
|
|
|
|
SONG="" FULLALBUM="" BANDCAMP="" NO_CONVERT=""
|
|
|
|
|
OUTDIR="/tmp/albumsetup/${PWD##*/}"
|
|
|
|
|
while getopts :ovnd:e:p:s:b: OPT; do
|
|
|
|
|
while getopts :vnd:e:p:s:b: OPT; do
|
|
|
|
|
case "$OPT" in
|
|
|
|
|
o) FULLALBUM=1 ;;
|
|
|
|
|
v) VERBOSE="" ;; # can use ${var-rep} for this
|
|
|
|
|
d) cd "$OPTARG" && OUTDIR="${OUTDIR%/*}/${PWD##*/}" ;;
|
|
|
|
|
e) EXT="$OPTARG" ;;
|
|
|
|
|
p) tmpimg="$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)
|
|
|
|
@ -93,69 +108,69 @@ while getopts :ovnd:e:p:s:b: OPT; do
|
|
|
|
|
done
|
|
|
|
|
shift "$((OPTIND - 1))"
|
|
|
|
|
|
|
|
|
|
[ ! -f "$tmpimg" ] && die "Default image not specified!"
|
|
|
|
|
[ ! -f "$tmpcover" ] && die "Default cover not specified! (-p IMG)"
|
|
|
|
|
|
|
|
|
|
mkdir -p "$OUTDIR"
|
|
|
|
|
|
|
|
|
|
# convert cover image. it has to be wider than 1:1 so youtube doesn't convert it to a short
|
|
|
|
|
# if image is wider than 1:1, just resize height. if not, pad with black
|
|
|
|
|
# default cover image, fallback if tracks don't have embedded art
|
|
|
|
|
DEFAULT_PICTURE="$(mktemp -u ALBUMSETUP_DEFAULT_PIC.XXXX)"
|
|
|
|
|
magick convert "$tmpimg" -resize 1280x720 -background black -gravity center -extent 722x720 "$DEFAULT_PICTURE"
|
|
|
|
|
magick convert "$tmpcover" -resize 1280x720 -background black -gravity center -extent 722x720 "$DEFAULT_PICTURE"
|
|
|
|
|
|
|
|
|
|
trap 'clean' INT HUP QUIT
|
|
|
|
|
[ "$EXT" = "opus" ] && acopy="-c:a copy" # copy audio codec if opus since output codec is opus
|
|
|
|
|
# copy audio codec if opus since output codec is opus
|
|
|
|
|
[ "$EXT" = "opus" ] && acopy="-c:a copy"
|
|
|
|
|
|
|
|
|
|
# INDIVIDUAL SONG #
|
|
|
|
|
if [ -n "$SONG" ]; then
|
|
|
|
|
grabinfo "$SONG"
|
|
|
|
|
ffconv "$DEFAULT_PICTURE" "$SONG" "$OUTDIR/SONG ${ARTIST%%/*} - ${TITLE%%/*}.webm" # substitution mods are to not create directories
|
|
|
|
|
grab_info "$SONG"
|
|
|
|
|
set_cover "$SONG"
|
|
|
|
|
|
|
|
|
|
# CONTINUOUS VIDEO OF WHOLE ALBUM #
|
|
|
|
|
elif [ -n "$FULLALBUM" ]; then
|
|
|
|
|
TOTALTIME=0 # keeping track of timestamps
|
|
|
|
|
for f in $(fd -d 1 -e "$EXT"); do
|
|
|
|
|
grabinfo "$f"
|
|
|
|
|
# lots of %%/* as to not accidentally create directories
|
|
|
|
|
convert_vid "$COVER_PICTURE" "$SONG" "$OUTDIR/SONG ${ARTIST%%/*} - ${TITLE%%/*}.webm"
|
|
|
|
|
|
|
|
|
|
# make timestamp
|
|
|
|
|
printf '%02d:%02d:%02d - %s\n' \
|
|
|
|
|
"$((${TOTALTIME%%.*} / 3600))" "$((${TOTALTIME%%.*} % 3600 / 60))" "$((${TOTALTIME%%.*} % 60))" \
|
|
|
|
|
"$ARTIST - $TITLE" >> "$OUTDIR/metadata.txt"
|
|
|
|
|
# add to total time, but this is as a float remember that
|
|
|
|
|
TOTALTIME="$(printf '%s' "$TOTALTIME + $DURATION" | bc)"
|
|
|
|
|
# build ffmpeg concat metadata
|
|
|
|
|
sf="$(printf '%s' "$f" | sed "s/'/'\\\\''/g")" # make safe filename for ffmpeg concat; replace all ' with '\''
|
|
|
|
|
echo "file '${PWD}/$sf'" >> "$OUTDIR/ffmpeg_tracklist.txt"
|
|
|
|
|
done
|
|
|
|
|
[ ! -f "$OUTDIR/ffmpeg_tracklist.txt" ] && die "No files found!"
|
|
|
|
|
ffconv "$DEFAULT_PICTURE" "$OUTDIR/tracklist.txt" "$OUTDIR/$ARTIST - $ALBUM.webm"
|
|
|
|
|
rm "$OUTDIR/ffmpeg_tracklist.txt"
|
|
|
|
|
|
|
|
|
|
# INDIVIDUAL TRACKS FOR FULL ALBUM (default) #
|
|
|
|
|
# INDIVIDUAL TRACKS FOR FULL ALBUM + COMPLETE VID #
|
|
|
|
|
else
|
|
|
|
|
for f in $(fd -d 1 -e "$EXT"); do
|
|
|
|
|
grabinfo "$f"
|
|
|
|
|
total_time=0 # this var helps for more precise timestamps
|
|
|
|
|
for f in ./*."$EXT"; do
|
|
|
|
|
grab_info "$f"
|
|
|
|
|
set_cover "$f"
|
|
|
|
|
output_file="$OUTDIR/$TRACKNUMBER ${ARTIST%%/*} - ${TITLE%%/*}.webm"
|
|
|
|
|
|
|
|
|
|
# metadata print
|
|
|
|
|
# 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 - %s\n' "${TRACKNUMBER:-000}" "${ARTIST:-unknown artist}" "${ALBUM:-unknown album}" "${TITLE:-unknown title}"
|
|
|
|
|
|
|
|
|
|
# try making auto pic
|
|
|
|
|
PICTURE="$(mktemp -u -t ALBUMSETUP_COVER_XXXX)"
|
|
|
|
|
extract_cover "$f" "$PICTURE"
|
|
|
|
|
[ ! -f "$PICTURE" ] && PICTURE="$(fd -e jpg -e png "^track$TRACKNUMBER")"
|
|
|
|
|
[ ! -f "$PICTURE" ] && PICTURE="$DEFAULT_PICTURE"
|
|
|
|
|
# STUFF FOR FULL VID #
|
|
|
|
|
# metadata for full vid
|
|
|
|
|
printf '%02d:%02d:%02d - %s\n' \
|
|
|
|
|
"$((${total_time%%.*} / 3600))" "$((${total_time%%.*} % 3600 / 60))" "$((${total_time%%.*} % 60))" \
|
|
|
|
|
"$ARTIST - $TITLE" >> "$OUTDIR/metadata_fullvid.txt"
|
|
|
|
|
# add to total time, but this is as a float remember that
|
|
|
|
|
total_time="$(printf '%s\n' "$total_time + $DURATION" | bc)"
|
|
|
|
|
|
|
|
|
|
errecho "Converting $f"
|
|
|
|
|
[ -z "$NO_CONVERT" ] && ffconv "$PICTURE" "$f" "$OUTDIR/$TRACKNUMBER ${ARTIST%%/*} - ${TITLE%%/*}.webm"
|
|
|
|
|
|
|
|
|
|
rm -f "$PICTURE"
|
|
|
|
|
# build ffmpeg concat metadata
|
|
|
|
|
# make safe filename for ffmpeg concat; replace all ' with '\''
|
|
|
|
|
sf="$(printf '%s' "$output_file" | sed "s/'/'\\\\''/g")"
|
|
|
|
|
echo "file '$sf'" >> "$OUTDIR/ffmpeg_tracklist.txt"
|
|
|
|
|
done > "$OUTDIR/metadata.txt"
|
|
|
|
|
|
|
|
|
|
[ ! -f "$OUTDIR/metadata.txt" ] && die "No files found!"
|
|
|
|
|
|
|
|
|
|
# sort by correct track numbers then remove track numbers later
|
|
|
|
|
sort -o "$OUTDIR/metadata.txt.sorted" "$OUTDIR/metadata.txt"
|
|
|
|
|
while read -r line; do
|
|
|
|
|
printf '%s' "$line" | cut -d ' ' -f 2-
|
|
|
|
|
while read -r track name; do
|
|
|
|
|
printf '%s\n' "$name"
|
|
|
|
|
done <"$OUTDIR/metadata.txt.sorted" >"$OUTDIR/metadata.txt"
|
|
|
|
|
rm "$OUTDIR/metadata.txt.sorted"
|
|
|
|
|
|
|
|
|
|
if [ -z "$NO_CONVERT" ]; then
|
|
|
|
|
errecho "Building full video..."
|
|
|
|
|
ffmpeg -safe 0 -f concat -i "$OUTDIR/ffmpeg_tracklist.txt" -c copy "$OUTDIR/$ARTIST - $ALBUM.webm"
|
|
|
|
|
fi
|
|
|
|
|
rm "$OUTDIR/ffmpeg_tracklist.txt"
|
|
|
|
|
|
|
|
|
|
# Bandcamp check and info retrieval
|
|
|
|
|
if [ -n "$BANDCAMP" ]; then
|
|
|
|
|