#!/bin/sh ################################################################################ ## User options ## You can easily set output folder to current folder with OUTPUT_ROOT=".". [ -z "$OUTPUT_ROOT" ] && OUTPUT_ROOT="$HOME/musics" ## End of user options ################################################################################ if [ -z "$(command -v ffmpeg)" ]; then echo "ffmpeg required for transcoding." exit fi if [ -z "$(command -v realpath)" ]; then echo "realpath required to get input file folder." exit fi OUTPUT_ROOT="$(realpath "$OUTPUT_ROOT")" if [ ! -d "$OUTPUT_ROOT" ]; then echo "Output folder '$OUTPUT_ROOT' does not exist." exit fi TITLECASE_SCRIPT="${0%/*}/titlecase.awk" if [ ! -f "$TITLECASE_SCRIPT" ]; then echo "AWK titlecase script required." exit fi ## For the sake of simplicity we convert everything to OGG. ## OGG quality ranges from -1 to 10. ## -q-1 45 kbit/s ## -q0 64 kbit/s ## -q1 80 kbit/s ## -q2 96 kbit/s ## -q3 112 kbit/s ## -q4 128 kbit/s ## -q5 160 kbit/s ## -q6 192 kbit/s ## -q7 224 kbit/s ## -q8 256 kbit/s ## -q9 320 kbit/s ## -q10 500 kbit/s ## Artists: we use same value for artist and album_artist. ## Genres: since this is not universal by nature, we do not put a genre in tags, ## except for special cases like Soundtrack. ## Composer: not universal neither, we prefer ARTIST over COMPOSER, so COMPOSER ## will be empty. ## This script is supposed to work on a per-album basis. Folders will not be ## accepted as argument. This function will work best if all tracks of a folder ## belong to the same album. There's is no way to handle covers reliably, so you ## should remove the undesired covers from the working folder. ## TODO: cover resolution check. Should be > 100x100. Above 1000x1000 should ## give a warning. ## TODO: extract cover from tags if checksum does not match any from current folder. _printhelp () { cat <&1) if [ -z "$(echo $STREAM | grep "Stream")" ]; then echo "ERROR: Non-audio file [$1]." exit fi METADATA=$(echo "$STREAM" | sed -n '/Metadata/ ! d; /Metada/{b cont}; :cont ; {n;p;b cont}') ## Filename without extension nor path. INPUT_FILE="${1%.*}" INPUT_FILE="${INPUT_FILE##*/}" ## Folder of the file. Needed for cover. INPUT_FOLDER="$(realpath "$1")" INPUT_FOLDER="${INPUT_FOLDER%/*}" INPUT_EXT="${1##*.}" INPUT_BITRATE=$(echo "$STREAM" | sed -n '/Duration/ {s|.* \([[:digit:]]\+\) kb/s|\1|;p;q}') ## CODEC is unused for now. # CODEC=$(echo "$STREAM" | sed -n '/Stream.*Audio:/ {s/.*Audio: \([^,]*\),.*/\1/;p}') ## Extension needs to be set in case we skip encoding so that ffmpeg will not be ## disturbed by unappropriate extension. if $SKIP &&[ -z "$INPUT_EXT" ]; then echo "ERROR: Extension missing [$1]." exit fi ## This function greps for one match only, so if several metadata are present, ## this may not be the desired values. _metadata_filter() { echo "$METADATA" | grep -im1 "\<$1\>" | sed 's/[^:]* : //g' } INPUT_TITLE=$(_metadata_filter "title") INPUT_ARTIST=$(_metadata_filter "artist") INPUT_ALBUM=$(_metadata_filter "album") INPUT_ALBUM_ARTIST=$(_metadata_filter "album_artist") INPUT_COMPOSER=$(_metadata_filter "composer") INPUT_DISC=$(_metadata_filter "disc") INPUT_GENRE=$(_metadata_filter "genre") INPUT_TRACK=$(_metadata_filter "track") INPUT_DATE=$(_metadata_filter "date") INPUT_TYER=$(_metadata_filter "TYER") ##============================================================================== ## Variable cleansing. ## We use the AWK script to set title case. The script contains ## exceptions that can be configured. We fix some chars with sed. # ’ => ' # : => - # / => - _string_cleanser() { echo "$@" | awk -v capital=$CAPITAL -f "$TITLECASE_SCRIPT" \ | sed -n -e "s/’/'/g ; s/ *: */ - /g ; s| */ *| - |g; p; q" } ## These are the user-accessible variables. TITLE=$(_string_cleanser "$INPUT_TITLE") ARTIST=$(_string_cleanser "$INPUT_ARTIST") ALBUM=$(_string_cleanser "$INPUT_ALBUM") COMPOSER=$(_string_cleanser "$INPUT_COMPOSER") DISC=$(_string_cleanser "$INPUT_DISC") GENRE=$(_string_cleanser "$INPUT_GENRE") TRACK=$(_string_cleanser "$INPUT_TRACK") DATE=$(_string_cleanser "$INPUT_DATE") TYER=$(_string_cleanser "$INPUT_TYER") ALBUM_ARTIST=$(_string_cleanser "$INPUT_ALBUM_ARTIST") ##================================================================================ ## OUTPUT variables. ## In this part we prepend backslashes to parenthese when there is an 'eval' ## call to prevent parsing error. OUTPUT_TITLE=$(eval echo $(echo ${OUTPUT_TITLE:-Unknown Title} | sed 's/[()]/\\&/g')) OUTPUT_ALBUM=$(eval echo $(echo ${OUTPUT_ALBUM:-Unknown Album} | sed 's/[()]/\\&/g')) ## We use album artist if artist is empty. [ -z "$OUTPUT_ARTIST" ] && [ -n "$ALBUM_ARTIST" ] && OUTPUT_ARTIST="$ALBUM_ARTIST" OUTPUT_ARTIST=$(eval echo $(echo ${OUTPUT_ARTIST:-Unknown Artist} | sed 's/[()]/\\&/g')) ## If OUTPUT_GENRE is set from command-line parameters, we clease the ## string. Otherwise we put GENRE in lower case and underscore to ease matching. ## If it matches, we use the Title Case match. If it does not, we set it to ## empty. if [ -n "$OUTPUT_GENRE" ]; then OUTPUT_GENRE=$(eval _string_cleanser $(echo $OUTPUT_GENRE | sed 's/[()]/\\&/g')) else ## We also convert space to underscore. GENRE=$(echo "$GENRE" | tr '[:upper:] ' '[:lower:]_') case $GENRE in ost) OUTPUT_GENRE="Soundtrack" ;; soundtrack) OUTPUT_GENRE="Soundtrack";; original_soundtrack) OUTPUT_GENRE="Soundtrack";; classical) OUTPUT_GENRE="Classical";; classics) OUTPUT_GENRE="Classical";; classic) OUTPUT_GENRE="Classical";; humour) OUTPUT_GENRE="Humour";; *) OUTPUT_GENRE="";; esac fi ## We remove the track count if any, we suppress leading zeros, we suppress all ## non-digit characters. OUTPUT_TRACK=$(eval _string_cleanser $(echo $OUTPUT_TRACK | sed 's/[()]/\\&/g') | sed -e 's/^0*//' -e 's|[^[:digit:]].*||') ## We extract the four-digits number from the date. OUTPUT_DATE=$(eval _string_cleanser $(echo $OUTPUT_DATE | sed 's/[()]/\\&/g')) OUTPUT_DATE=$(echo "$OUTPUT_DATE" | sed -n 's/.*\([[:digit:]]\{4\}\).*/\1/p') ## If DATE is not a year, we use TYER if it is a year. TYER_REG=$(echo "$TYER" | sed -n 's/.*\([[:digit:]]\{4\}\).*/\1/p') if [ ${#DATE} -ne 4 ] && [ ${#TYER_REG} -eq 4 ]; then OUTPUT_DATE="$TYER_REG" fi ## QUALITY ## If bitrate argument is not provided, we use INPUT_BITRATE value. [ $OUTPUT_BITRATE -eq 0 ] && OUTPUT_BITRATE=${INPUT_BITRATE} ## If OUTPUT_BITRATE is beyond OGG's limit, we trim it. [ $OUTPUT_BITRATE -gt 500 ] && OUTPUT_BITRATE=500 ## Only reencode if not in OGG and if SKIP not set. OGG_PARAM="-c:a libvorbis -b:a ${OUTPUT_BITRATE}k" INPUT_EXT_LOW="$(echo $INPUT_EXT | tr [:upper:] [:lower:])" if $SKIP; then OGG_PARAM="-c:a copy" OUTPUT_EXT="$INPUT_EXT_LOW" fi [ "$INPUT_EXT_LOW" = "ogg" ] && OGG_PARAM="-c:a copy" ## Make sure track number has two digits for file name only. OUTPUT_PADDEDTRACK=$OUTPUT_TRACK if [ -n "$OUTPUT_PADDEDTRACK" ]; then [ ${OUTPUT_PADDEDTRACK} -lt 10 ] && OUTPUT_PADDEDTRACK="0$OUTPUT_PADDEDTRACK" fi OUTPUT_FOLDER=$(eval echo $OUTPUT_FOLDER) OUTPUT_FILE=$(eval echo $OUTPUT_FILE) unset OUTPUT_FILE_ORIGINAL if [ -e "$OUTPUT_FOLDER/$OUTPUT_FILE.$OUTPUT_EXT" ]; then if [ $OVERWRITE = "-n" ]; then ## If file exist, we append a unique timestamp to the name. OUTPUT_FILE="$OUTPUT_FILE-$(date '+%F-%H%M%S')" OUTPUT_MSG="$(tput setf 1)$(tput bold)(Warning: destination exists, appending timestamp.)$(tput sgr0)" else ## WARNING: here it is important that no folder are suffixed by slashes. if [ "$INPUT_FOLDER/$INPUT_FILE.$INPUT_EXT" = "$OUTPUT_FOLDER/$OUTPUT_FILE.$OUTPUT_EXT" ]; then OUTPUT_FILE_ORIGINAL="$OUTPUT_FILE" OUTPUT_FILE="$OUTPUT_FILE-$(date '+%F-%H%M%S')" fi OUTPUT_MSG="$(tput setf 4)$(tput bold)(Warning: overwriting destination!)$(tput sgr0)" fi fi ##============================================================================== ## PREVIEW ATTR_WIDTH="%-13.13s" # Length of longest attribute +2 ## INPUT_WIDTH = COLUNMS - ATTR_WIDTH -2 (for |)) INPUT_WIDTH=$((($(tput cols)-15)/2)) INPUT_WIDTH="%$INPUT_WIDTH.${INPUT_WIDTH}s" echo $INPUT_WIDTH cat <