songattr/songattr
2024-01-29 14:31:16 -06:00

180 lines
4.6 KiB
Bash
Executable File

#!/bin/sh
# --------------------------------------
# name: music2attr
# main: jadedctrl<@posteo.at>
# lisc: MIT
# date: 2020-11
# --------------------------------------
# ======================================
# FFMPEG PARSING
# ======================================
# prints metadata output from FFMPEG of the given file
function song_metadata {
local file="$1"
ffmpeg -i "$file" 2>&1 \
| sed '1,/Metadata/d' \
| head -n -1 \
| sed 's% : %:%'
}
# get value of song's metadata key, from it's `song_metadata` output
function get_value {
local trait="$1"
sed -n "/^ *${trait}.*:/I,\$p" \
| sed "s/$trait//i" \
| print_until_nonempty
}
# ======================================
# ATTRIBUTES
# ======================================
# set a song's metadata attribute, if file contains it
# pipe in output from `song_metadata`
function set_attr {
local file="$1"
local key="$2"
local attr="$3"
local type="$4"
local value="$(get_value "$key")"
if test "$type" = "time"; then set_time_attr "$file" "$attr" "$value"; return; fi
if echo "$type"|grep -q "^int";then set_int_attr "$file" "$attr" "$value" "$type";return;fi
if test -n "$value"; then
addattr -t "$type" "$attr" "$value" "$file"
fi
}
# we have some special sanitization to do for time attributes (helper of 'set_attr')
function set_time_attr {
local file="$1"; local attr="$2"; local value="$3"
if test 5 -eq "$(echo "$value" | wc -c)"; then
value="${value}-01-01"
fi
if test -n "$value"; then
addattr -t "time" "$attr" "$value" "$file"
fi
}
# we have some special sanitization to do for ints, too (helper of 'set_attr')
function set_int_attr {
local file="$1"; local attr="$2"; local value="$3"; local type="$4"
value="$(echo "$value" | sed 's%[A-z/, -].*%%')"
if test -n "$value"; then
addattr -t "$type" "$attr" "$value" "$file"
fi
}
# set a file's entire all song-related attributes
function set_song_attrs {
local file="$1"
meta="$(song_metadata "$file")"
echo "$meta" | set_title_attr "$file" "Media:title"
echo "$meta" | set_year_attr "$file" "Media:Year"
echo "$meta" | set_lyrics_attr "$file" "Audio:Lyrics"
echo "$meta" | set_artist_attr "$file" "Audio:Artist"
echo "$meta" | set_album_attr "$file" "Audio:Album"
echo "$meta" | set_track_attr "$file" "Audio:Track"
}
# set all of an album's necessary attributes
function set_album_attrs {
song="$1"
album="$(dirname "$song")"
meta="$(song_metadata "$song")"
addattr -t mime "BEOS:TYPE" "audio/x-album" "$album"
echo "$meta" | set_artist_attr "$album" "Album:Artist"
echo "$meta" | set_year_attr "$album" "Media:Year"
}
function set_title_attr { set_attr "$1" "TITLE" "$2" "string"; }
function set_year_attr { set_attr "$1" "DATE" "$2" "int32"; }
function set_lyrics_attr { set_attr "$1" "LYRICS" "$2" "string"; }
function set_artist_attr { set_attr "$1" "ARTIST" "$2" "string"; }
function set_album_attr { set_attr "$1" "ALBUM" "$2" "string"; }
function set_track_attr { set_attr "$1" "TRACK" "$2" "int32"; }
# ======================================
# UTIL
# ======================================
# selects all fields except the first, until a a line's first field is non-empty
# delimiter used is a colon.
function print_until_nonempty {
awk -F ':' \
'{ lines[NR] = $0 }
END { i = 1
split( lines[i], L )
while ( i <= NR && match( L[1], /^( )+$/ ) ) {
for ( j = 2; j <= length(L); j = j + 1 ) {
if ( j > 2 )
printf ":"
printf L[j]
}
printf "\n"
i = i + 1
split( lines[i], L )
}
}'
}
# ======================================
# INVOCATION
# ======================================
function help {
echo "usage: $(basename $0) [-hi] [-A|a] file"
echo
echo \
"Use a song file's metadata to populate related attributes. The attributes set
are 'Media:Title', 'Audio:Artist', 'Audio:Album', 'Audio:Track', and 'Media:year'.
In addition, the parent directory is assumed to be the 'album' folder, its
type is set to 'audio/x-album', and has its attributes populated with relevant
metadata. The attributes set to 'album' folders are 'Album:Artist' and
'Media:Year', based on the song files' metadata.
$(basename $0) requires 'ffmpeg' to be installed.
-a don't edit the parent directory's attributes
-A only edit the parent directory's attributes
-h prints this message."
exit 1
}
ALBUM=1
SONG=1
while getopts ":haAi" arg; do
case $arg in
a) ALBUM=0;;
A) ALBUM=1; SONG=0;;
h) help;;
esac
done
shift $(($OPTIND - 1))
if test -z "$1"; then help; fi
if test ! -e /bin/ffmpeg; then help; fi
if test "$SONG" -eq 1; then
for song in "$@"; do
set_song_attrs "$song"
done
fi
if test "$ALBUM" -eq 1; then
for song in "$@"; do
set_album_attrs "$@"
done
fi