637 lines
16 KiB
Bash
637 lines
16 KiB
Bash
#!/bin/bash
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
#
|
||
# Music Tools Library
|
||
#
|
||
# Copyright (C) 2016-2026 Arnaud G. GIBERT
|
||
# mailto:arnaud@rx3.net
|
||
#
|
||
# This is free software: you can redistribute it and/or modify it
|
||
# under the terms of the GNU Lesser General Public License as published
|
||
# by the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU Lesser General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU Lesser General Public
|
||
# License along with this program; If not, see
|
||
# <https://www.gnu.org/licenses/>.
|
||
#
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
if [[ "${MUSIC_TOOLS_LIB}" != "" ]]
|
||
then
|
||
return
|
||
else
|
||
declare -g MUSIC_TOOLS_LIB=1
|
||
fi
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Includes
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
: "${RX3_LIB_DIR:=/usr/lib/rx3}"
|
||
. "${RX3_LIB_DIR}/base.bash"
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Global Variable
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
declare -Ag MT_FLAC_2_MP3_TAB=(
|
||
[TRACKNUMBER]=TRCK
|
||
[TRACKTOTAL]=TRCK
|
||
[TITLE]=TIT2
|
||
[ALBUM]=TALB
|
||
[ARTIST]=TPE1
|
||
[COMPOSER]=TCOM
|
||
[ARRANGER]=IPLS
|
||
[CONDUCTOR]=TPE3
|
||
[REMIXER]=TPE4
|
||
[AUTHOR]=TEXT
|
||
[WRITER]=TEXT
|
||
[LYRICIST]=TEXT
|
||
[ALBUMARTIST]=TPE2
|
||
[DATE]=TDRC
|
||
[GENRE]=TCON
|
||
[COMPILATION]=TCMP
|
||
[DISCTOTAL]=TPOS
|
||
[DISCNUMBER]=TPOS
|
||
[LABEL]=XXXX
|
||
[PUBLISHER]=TPUB
|
||
[ISRC]=TSRC
|
||
[BARCODE]=TXXX
|
||
[CATALOGNUMBER]=TXXX
|
||
[COPYRIGHT]=TCOP
|
||
[LICENSE]=USER
|
||
[COMMENT]=COMM
|
||
[DESCRIPTION]=TIT3
|
||
[ENCODER]=TSSE
|
||
[MEDIA]=TMED
|
||
[LANGUAGE]=TLAN
|
||
[LYRICS]=USLT
|
||
)
|
||
|
||
declare -Ag MT_MP3_2_FLAC_TAB=(
|
||
[TRCK]=TRACKNUMBER:TRACKTOTAL
|
||
[TIT2]=TITLE
|
||
[TALB]=ALBUM
|
||
[TPE1]=ARTIST
|
||
[TCOM]=COMPOSER
|
||
[IPLS]=ARRANGER
|
||
[TPE3]=CONDUCTOR
|
||
[TPE4]=REMIXER
|
||
[TEXT]=AUTHOR:WRITER:LYRICIST
|
||
[TPE2]=ALBUMARTIST
|
||
[TDRC]=DATE
|
||
[TCON]=GENRE
|
||
[TCMP]=COMPILATION
|
||
[TPOS]=DISCNUMBER:DISCTOTAL
|
||
[TPUB]=PUBLISHER
|
||
[TSRC]=ISRC
|
||
[TXXX]=BARCODE:CATALOGNUMBER
|
||
[TCOP]=COPYRIGHT
|
||
[USER]=LICENSE
|
||
[COMM]=COMMENT
|
||
[TIT3]=DESCRIPTION
|
||
[TSSE]=ENCODER
|
||
[TMED]=MEDIA
|
||
[TLAN]=LANGUAGE
|
||
[USLT]=LYRICS
|
||
)
|
||
|
||
declare -g MT_FLAC_TAG_LIST="TRACKNUMBER TRACKTOTAL TITLE ALBUM ARTIST COMPOSER ARRANGER CONDUCTOR REMIXER AUTHOR WRITER LYRICIST ALBUMARTIST DATE GENRE COMPILATION LABEL PUBLISHER ISRC BARCODE UPC COPYRIGHT LICENSE COMMENT DESCRIPTION ENCODER MEDIA LANGUAGE LYRICS"
|
||
declare -g MT_FLAC_TAG_COND=$(echo '('${MT_FLAC_TAG_LIST}')' | sed -e 's/ /|/g')
|
||
|
||
declare -g MT_NODEF_TAG_LIST="TRACKNUMBER TITLE ISRC LYRICS"
|
||
declare -g MT_NODEF_TAG_COND=$(echo '('${MT_NODEF_TAG_LIST}')' | sed -e 's/ /|/g')
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Fix Name
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Standardise Name
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# $1: Input Name
|
||
# $2: SED Substitute Patern
|
||
# Out: Output Name
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_fix_name()
|
||
{
|
||
local input_name="$1"
|
||
local ssp="$2"
|
||
|
||
|
||
if [[ "${ssp}" != "" ]]
|
||
then
|
||
sse="s/${ssp}"
|
||
else
|
||
sse="s/^//"
|
||
fi
|
||
|
||
echo ${input_name} |
|
||
sed -e 's/[ ,!/?:]/_/g' \
|
||
-e "s/['’´]/_/g" \
|
||
-e 's/\[/-/g' -e 's/\]//g' \
|
||
-e 's/(/-/g' -e 's/)//g' \
|
||
-e 's/__/_/g' -e 's/_\././g' -e 's/_-/-/g' -e 's/--/-/g' -e 's/-_/-/g' \
|
||
-e 's/à/a/g' -e 's/á/a/g' -e 's/ä/a/g' -e 's/â/a/g' -e 's/ã/a/g' -e 's/é/e/g' -e 's/è/e/g' -e 's/ê/e/g' -e 's/ë/e/g' -e 's/ï/i/g' -e 's/î/i/g' -e 's/í/i/g' -e 's/ô/o/g' -e 's/ö/o/g' -e 's/ó/o/g' -e 's/ù/u/g' -e 's/ü/u/g' -e 's/û/u/g' -e 's/ú/u/g' -e 's/ç/c/g' -e 's/ñ/n/g' \
|
||
-e 's/À/a/g' -e 's/Á/a/g' -e 's/Ä/a/g' -e 's/Â/a/g' -e 's/Ã/a/g' -e 's/É/e/g' -e 's/È/e/g' -e 's/Ê/e/g' -e 's/Ë/e/g' -e 's/Ï/i/g' -e 's/Î/i/g' -e 's/Í/i/g' -e 's/Ô/o/g' -e 's/Ö/o/g' -e 's/Ó/o/g' -e 's/Ù/u/g' -e 's/Ü/u/g' -e 's/Û/u/g' -e 's/Ú/u/g' -e 's/Ç/c/g' -e 's/Ñ/n/g' \
|
||
-e 's/¿//g' \
|
||
-e 's/[+&]/and/g' -e 's/"/_inch/g' \
|
||
-e 's/#//g' \
|
||
-e 's/^[_-]*//' \
|
||
-e 's/[_-]*$//' \
|
||
-e "${sse}" |
|
||
tr '[:upper:]' '[:lower:]'
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Fix File Name
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Standardise File Name
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# $1: Input File Name
|
||
# $2: SED Substitute Patern
|
||
# Out: Output File Name
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_fix_file_name()
|
||
{
|
||
local input_file_name="$1"
|
||
local ssp="$2"
|
||
|
||
|
||
mt_fix_name "${input_file_name}" "${ssp}" |
|
||
sed -e 's/\(^[0-9]*\)./\1-/'
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# TagTab Alloc
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tagtab_alloc()
|
||
{
|
||
unset tagtab
|
||
declare -Ag tagtab
|
||
|
||
declare -g taglist
|
||
taglist=":"
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Tag Exist
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tag_exist()
|
||
{
|
||
local tag="$1"
|
||
|
||
|
||
echo ${taglist} | grep -e ":${tag}:"
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# TagTab Read
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tagtab_read()
|
||
{
|
||
local track_id="$1"
|
||
local track_file="$2"
|
||
local mode="$3"
|
||
|
||
|
||
tmp_file=$(mktemp)
|
||
|
||
case "${track_file}"
|
||
in
|
||
*.flac)
|
||
metaflac --export-tags-to=- "${track_file}" | sed -e 's/^[^=]*=/\U&\E/' > ${tmp_file}
|
||
|
||
while read line
|
||
do
|
||
tag="${line/=*}"
|
||
|
||
if [[ ${line/*=*/=} == "=" ]] && [[ "${tag}" != "" ]]
|
||
then
|
||
value="$(mt_tag_read "${track_file}" "${tag}")"
|
||
|
||
if [[ "$(mt_tag_exist ${tag})" == "" ]]
|
||
then
|
||
taglist="${taglist}${tag}:"
|
||
fi
|
||
|
||
if [[ ( "${mode}" != "FACTORING" ) || ( "${tag}" =~ ^${MT_NODEF_TAG_COND}$ ) || ( "${tagtab["0,${tag}"]}" != "" ) ]]
|
||
then
|
||
if [[ "${tagtab["0,${tag}"]}" != "${value}" ]]
|
||
then
|
||
tagtab["${track_id},${tag}"]="$value"
|
||
fi
|
||
else
|
||
tagtab["0,${tag}"]="${value}"
|
||
fi
|
||
fi
|
||
done < ${tmp_file}
|
||
;;
|
||
|
||
*.mp3)
|
||
mid3v2 --list "${track_file}" | tail -n +2 | sed -e 's/^[^=]*=/\U&\E/' > ${tmp_file}
|
||
|
||
while read line
|
||
do
|
||
mp3tag="${line/=*}"
|
||
|
||
value="$(mt_tag_read "${track_file}" "${mp3tag}")"
|
||
|
||
if [[ "${line/*=*/=}" == "=" ]] && [[ "${mp3tag}" != "" ]] && [[ "${MT_MP3_2_FLAC_TAB["${mp3tag}"]}" != "" ]]
|
||
then
|
||
tag="${MT_MP3_2_FLAC_TAB["${mp3tag}"]}"
|
||
|
||
if [[ "$(mt_tag_exist ${tag})" == "" ]]
|
||
then
|
||
taglist="${taglist}${tag}:"
|
||
fi
|
||
|
||
if [[ ( "${mode}" != "FACTORING" ) || ( "${tag}" =~ ^${MT_NODEF_TAG_COND}$ ) || ( "${tagtab["0,${tag}"]}" != "" ) ]]
|
||
then
|
||
if [[ "${tagtab["0,${tag}"]}" != "${value}" ]]
|
||
then
|
||
tagtab["${track_id},${tag}"]="$value"
|
||
fi
|
||
else
|
||
tagtab["0,${tag}"]="${value}"
|
||
fi
|
||
fi
|
||
done < ${tmp_file}
|
||
;;
|
||
|
||
*)
|
||
echo_error "mt_tagtab_read: Unknown file format: ${track_file}"
|
||
;;
|
||
esac
|
||
|
||
if [[ ${taglist} == ":" ]]
|
||
then
|
||
taglist=""
|
||
fi
|
||
|
||
\rm -f ${tmp_file}
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# TagTab Load
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tagtab_load()
|
||
{
|
||
local metadata_file="$1"
|
||
local mode="$2"
|
||
|
||
|
||
track_id=0
|
||
eoa="false"
|
||
|
||
while read -r line && [[ "${eoa}" != "true" ]]
|
||
do
|
||
tag="${line/=*}"
|
||
value="${line/${tag}=}"
|
||
|
||
case "${tag}"
|
||
in
|
||
@${MT_FLAC_TAG_COND})
|
||
tagtab["${track_id},${tag}"]="$value"
|
||
;;
|
||
|
||
DEF)
|
||
;;
|
||
|
||
SOT)
|
||
track_id=$((${track_id} + 1))
|
||
;;
|
||
|
||
EOT)
|
||
;;
|
||
|
||
EOA)
|
||
eoa="true"
|
||
;;
|
||
|
||
*)
|
||
echo_error "mt_tagtab_load: Unkwown tag: '${tag}'"
|
||
;;
|
||
esac
|
||
done < "${metadata_file}"
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# TagTab Dump
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tagtab_dump()
|
||
{
|
||
local track_id="$1"
|
||
local dump_mode="$2"
|
||
|
||
|
||
case "${dump_mode}" in
|
||
"STANDARD")
|
||
dump_list="${MT_FLAC_TAG_LIST}"
|
||
;;
|
||
|
||
"PASSTHROUGH")
|
||
dump_list="${taglist//:/ }"
|
||
;;
|
||
|
||
"CUSTOM")
|
||
dump_list="$3"
|
||
;;
|
||
|
||
esac
|
||
|
||
|
||
|
||
for tag in ${dump_list}
|
||
do
|
||
if [[ "${tagtab["${track_id},${tag}"]}" != "" ]]
|
||
then
|
||
echo "${tag}=${tagtab["${track_id},${tag}"]}"
|
||
fi
|
||
done
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Tag Get
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tag_get()
|
||
{
|
||
local declare -n return=$1
|
||
local track_id=$2
|
||
local tag=$3
|
||
|
||
|
||
return="${tagtab["${track_id},${tag}"]}"
|
||
|
||
if [[ "${return}" == "" ]]
|
||
then
|
||
return="${tagtab["0,${tag}"]}"
|
||
|
||
if [[ "${return}" == "" ]] && [[ "${tag}" == "TRACKNUMBER" ]]
|
||
then
|
||
return="$(printf "%02d" ${track_id})"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Tag Save
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tag_save()
|
||
{
|
||
local target_file=$1
|
||
local track_id=$2
|
||
local mode=$3
|
||
|
||
|
||
if [[ "${mode}" == "CLEAN" ]]
|
||
then
|
||
mt_tag_all_delete "${target_file}" ""
|
||
fi
|
||
|
||
for tag in ${MT_FLAC_TAG_LIST}
|
||
do
|
||
# if [[ "${tagtab["${track_id},${tag}"]}" == "" ]]
|
||
# then
|
||
# value="${tagtab["0,${tag}"]}"
|
||
#
|
||
# if [[ "${value}" == "" ]] && [[ "${tag}" == "TRACKNUMBER" ]]
|
||
# then
|
||
# value="$(printf "%02d" ${track_id})"
|
||
# fi
|
||
# else
|
||
# value="${tagtab["${track_id},${tag}"]}"
|
||
# fi
|
||
|
||
mt_tag_get value "${track_id}" "${tag}"
|
||
|
||
mt_tag_delete "${target_file}" "${tag}"
|
||
|
||
if [[ "${value}" != "" ]]
|
||
then
|
||
mt_tag_write "${target_file}" "${tag}" "${value}"
|
||
fi
|
||
done
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Tag Read
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tag_read()
|
||
{
|
||
local track_file="$1"
|
||
local tag="$2"
|
||
|
||
|
||
case "${track_file}"
|
||
in
|
||
*.flac)
|
||
sh_exec "metaflac --show-tag=\"${tag}\" \"${track_file}\" | sed -e 's/^[^=]*=//' -e ':a' -e 'N' -e '"'$!'"ba' -e 's/\n/\\n/g'"
|
||
;;
|
||
|
||
*.mp3)
|
||
# sh_exec "mid3v2 --list \"${track_file}\" | tail -n +2 | sed -e 's/^[^=]*=/\U&\E/' | grep -e \"^${MT_FLAC_2_MP3_TAB[\"${tag}\"]}\" | sed -e 's/^[^=]*=//' -e ':a' -e 'N' -e '"'$!'"ba' -e 's/\n/\\n/g'"
|
||
sh_exec "mid3v2 --list \"${track_file}\" | tail -n +2 | grep -e \"^${tag}\" | sed -e 's/^[^=]*=//' -e ':a' -e 'N' -e '"'$!'"ba' -e 's/\n/\\n/g'"
|
||
;;
|
||
|
||
*)
|
||
echo_error "mt_tag_read: Unknown file format: ${track_file}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Tag Write
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tag_write()
|
||
{
|
||
local track_file="$1"
|
||
local tag="$2"
|
||
local value="$3"
|
||
|
||
|
||
value="$(echo -e "$(str_escape "${value}")")"
|
||
|
||
case "${track_file}"
|
||
in
|
||
*.flac)
|
||
cmd_exec metaflac --set-tag="${tag}=${value}" "${track_file}"
|
||
;;
|
||
|
||
*.mp3)
|
||
cmd_exec mid3v2 --"${MT_FLAC_2_MP3_TAB["${tag}"]}" "${value}" "${track_file}"
|
||
;;
|
||
|
||
*)
|
||
echo_error "mt_tag_write: Unknown file format: ${track_file}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Tag Delete
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tag_delete()
|
||
{
|
||
local track_file="$1"
|
||
local tag="$2"
|
||
|
||
|
||
case "${track_file}"
|
||
in
|
||
*.flac)
|
||
cmd_exec metaflac --remove-tag="${tag}" "${track_file}"
|
||
;;
|
||
|
||
*.mp3)
|
||
cmd_exec mid3v2 --delete-frames="${MT_FLAC_2_MP3_TAB["${tag}"]}" "${track_file}"
|
||
;;
|
||
|
||
*)
|
||
echo_error "mt_tag_delete: Unknown file format: ${track_file}"
|
||
;;
|
||
esac
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Tag All Delete
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_tag_all_delete()
|
||
{
|
||
local track_file="$1"
|
||
local header_type="$2"
|
||
|
||
|
||
if [[ "${header_type}" == "" ]]
|
||
then
|
||
case "${track_file##*.}"
|
||
in
|
||
flac)
|
||
header_type="ogg"
|
||
;;
|
||
|
||
mp3)
|
||
header_type="id3"
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
if [[ ( "${header_type}" == "ogg") && ( "${track_file##*.}" == "mp3") ]]
|
||
then
|
||
echo_error "mt_tag_all_delete: Can't remove ogg tags from mp3 file!"
|
||
else
|
||
case "${header_type}"
|
||
in
|
||
ogg)
|
||
cmd_exec metaflac --remove-all-tags "${track_file}"
|
||
;;
|
||
|
||
id3)
|
||
cmd_exec mid3v2 --delete-all "${track_file}"
|
||
;;
|
||
|
||
*)
|
||
echo_error "mt_tag_all_delete: Unknown header type: ${header_type}"
|
||
;;
|
||
esac
|
||
fi
|
||
|
||
}
|
||
|
||
|
||
|
||
|
||
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
# Album Type Get
|
||
#-----------------------------------------------------------------------------------------------------------------------------------
|
||
|
||
mt_album_type_get()
|
||
{
|
||
local track_file="$1"
|
||
|
||
|
||
for type in various_artists tribute original_soundtrack child
|
||
do
|
||
if [[ ${track_file} == */${type}/* ]]
|
||
then
|
||
echo "${type}"
|
||
fi
|
||
done
|
||
}
|