diff options
Diffstat (limited to 'cli')
-rwxr-xr-x | cli/ape2id3.py | 145 | ||||
-rwxr-xr-x | cli/split2flac | 752 | ||||
-rwxr-xr-x | cli/wma2mp3.sh | 20 |
3 files changed, 917 insertions, 0 deletions
diff --git a/cli/ape2id3.py b/cli/ape2id3.py new file mode 100755 index 0000000..8651a2f --- /dev/null +++ b/cli/ape2id3.py @@ -0,0 +1,145 @@ +#! /usr/bin/env python +import sys +from optparse import OptionParser +import mutagen +from mutagen.apev2 import APEv2 +from mutagen.id3 import ID3, TXXX + +def convert_gain(gain): + if gain[-3:] == " dB": + gain = gain[:-3] + try: + gain = float(gain) + except ValueError: + raise ValueError, "invalid gain value" + return "%.2f dB" % gain +def convert_peak(peak): + try: + peak = float(peak) + except ValueError: + raise ValueError, "invalid peak value" + return "%.6f" % peak + +REPLAYGAIN_TAGS = ( + ("mp3gain_album_minmax", None), + ("mp3gain_minmax", None), + ("replaygain_album_gain", convert_gain), + ("replaygain_album_peak", convert_peak), + ("replaygain_track_gain", convert_gain), + ("replaygain_track_peak", convert_peak), +) + + +class Logger(object): + def __init__(self, log_level, prog_name): + self.log_level = log_level + self.prog_name = prog_name + self.filename = None + def prefix(self, msg): + if self.filename is None: + return msg + return "%s: %s" % (self.filename, msg) + def debug(self, msg): + if self.log_level >= 4: + print self.prefix(msg) + def info(self, msg): + if self.log_level >= 3: + print self.prefix(msg) + def warning(self, msg): + if self.log_level >= 2: + print self.prefix("WARNING: %s" % msg) + def error(self, msg): + if self.log_level >= 1: + sys.stderr.write("%s: %s\n" % (self.prog_name, msg)) + def critical(self, msg, retval=1): + self.error(msg) + sys.exit(retval) + +class Ape2Id3(object): + def __init__(self, logger, force=False): + self.log = logger + self.force = force + def convert_tag(self, name, value): + pass + def copy_replaygain_tag(self, apev2, id3, name, converter=None): + self.log.debug("processing '%s' tag" % name) + if name not in apev2: + self.log.info("no APEv2 '%s' tag found, skipping tag" % name) + return False + if not self.force and ("TXXX:%s" % name) in id3: + self.log.info("ID3 '%s' tag already exists, skpping tag" % name) + return False + value = str(apev2[name]) + if callable(converter): + self.log.debug("converting APEv2 '%s' tag from '%s'" % + (name, value)) + try: + value = converter(value) + except ValueError: + self.log.warning("invalid value for APEv2 '%s' tag" % name) + return False + self.log.debug("converted APEv2 '%s' tag to '%s'" % (name, value)) + id3.add(TXXX(encoding=1, desc=name, text=value)) + self.log.info("added ID3 '%s' tag with value '%s'" % (name, value)) + return True + def copy_replaygain_tags(self, filename): + self.log.filename = filename + self.log.debug("begin processing file") + try: + apev2 = APEv2(filename) + except mutagen.apev2.error: + self.log.info("no APEv2 tag found, skipping file") + return + except IOError: + e = sys.exc_info() + self.log.error("%s" % e[1]) + return + try: + id3 = ID3(filename) + except mutagen.id3.error: + self.log.info("no ID3 tag found, creating one") + id3 = ID3() + modified = False + for name, converter in REPLAYGAIN_TAGS: + copied = self.copy_replaygain_tag(apev2, id3, name, converter) + if copied: + modified = True + if modified: + self.log.debug("saving modified ID3 tag") + id3.save(filename) + self.log.debug("done processing file") + self.log.filename = None + +def main(prog_name, options, args): + logger = Logger(options.log_level, prog_name) + ape2id3 = Ape2Id3(logger, force=options.force) + for filename in args: + ape2id3.copy_replaygain_tags(filename) + +if __name__ == "__main__": + parser = OptionParser(version="0.1", usage="%prog [OPTION]... FILE...", + description="Copy APEv2 ReplayGain tags on " + "FILE(s) to ID3v2.") + parser.add_option("-q", "--quiet", dest="log_level", + action="store_const", const=0, default=1, + help="do not output error messages") + parser.add_option("-v", "--verbose", dest="log_level", + action="store_const", const=3, + help="output warnings and informational messages") + parser.add_option("-d", "--debug", dest="log_level", + action="store_const", const=4, + help="output debug messages") + parser.add_option("-f", "--force", dest="force", + action="store_true", default=False, + help="force overwriting of existing ID3v2 " + "ReplayGain tags") + prog_name = parser.get_prog_name() + options, args = parser.parse_args() + if len(args) < 1: + parser.error("no files specified") + try: + main(prog_name, options, args) + except KeyboardInterrupt: + pass + +# vim: set expandtab shiftwidth=4 softtabstop=4 textwidth=79: diff --git a/cli/split2flac b/cli/split2flac new file mode 100755 index 0000000..6622262 --- /dev/null +++ b/cli/split2flac @@ -0,0 +1,752 @@ +#!/bin/sh +# Copyright (c) 2009-2015 Serge "ftrvxmtrx" Ziryukin +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Dependencies: +# shntool, cuetools +# SPLIT: flac, wavpack, mac +# CONVERT: flac/flake, faac, libmp4v2, id3lib/mutagen, lame, vorbis-tools +# ART: ImageMagick +# CHARSET: iconv, enca +# GAIN: flac, aacgain, mp3gain, vorbisgain + +# Exit codes: +# 0 - success +# 1 - error in arguments +# 2 - file or path is not accessible +# 3 - something has failed + +[ -n "${XDG_CONFIG_HOME}" ] && CONFIG="${XDG_CONFIG_HOME}/split2flac/split2flac.conf" +[ -r "${CONFIG}" ] || CONFIG="${HOME}/.split2flac" +TMPPIC="${HOME}/.split2flac_cover.jpg" +FAILED="split_failed.txt" + +NOSUBDIRS=0 +NOPIC=0 +REMOVE=0 +NOCOLORS=0 +PIC_SIZE="192x192" +REPLAY_GAIN=0 +FORMAT="${0##*split2}" +DIR="." +OUTPATTERN="@artist/{@year - }@album/@track - @title.@ext" +COPYMASKS="[Cc]overs \*.log \*.txt \*.jpg \*.cbr" +COPYFILES=1 +ENCA_ARGS="" + +# codecs default arguments +ENCARGS_flac="-8" +ENCARGS_m4a="-q 500" +ENCARGS_mp3="--preset standard" +ENCARGS_ogg="-q 5" +ENCARGS_wav="" + +# load settings +eval $(cat "${CONFIG}" 2>/dev/null) +DRY=0 +SAVE=0 +NASK=0 +unset PIC INPATH CUE CHARSET +FORCE=0 + +# do not forget to update before commit +VERSION=121 + +HELP="\${cG}split2flac version: ${VERSION} +Splits one big \${cU}APE/FLAC/WV/WAV\$cZ\$cG audio image (or a collection) into \${cU}FLAC/M4A/MP3/OGG_VORBIS/WAV\$cZ\$cG tracks with tagging and renaming. + +Usage: \${cZ}split2\${FORMAT} [\${cU}OPTIONS\$cZ] \${cU}FILE\$cZ [\${cU}OPTIONS\$cZ]\$cZ + \${cZ}split2\${FORMAT} [\${cU}OPTIONS\$cZ] \${cU}DIR\$cZ [\${cU}OPTIONS\$cZ]\$cZ + \$cG-p\$cZ - dry run + \$cG-o \${cU}DIRECTORY\$cZ \$cR*\$cZ - set output directory (current is \$cP\${DIR}\$cZ) + \$cG-of \${cU}'PATTERN'\$cZ \$cR*\$cZ - use specific output naming pattern (current is \$cP'\${OUTPATTERN}'\$cZ) + \$cG-cue \${cU}FILE\$cZ - use file as a cue sheet (does not work with \${cU}DIR\$cZ) + \$cG-cuecharset \${cU}CHARSET\$cZ - convert cue sheet from CHARSET to UTF-8 (no conversion by default) + \$cG-nask\$cZ - do not ask to enter proper charset of a cue sheet (default is to ask) + \$cG-f \${cU}FORMAT\$cZ - use specific output format (current is \$cP\${FORMAT}\$cZ) + \$cG-e \${cU}'ARG1 ARG2'\$cZ \$cR*\$cZ - encoder arguments (current is \$cP'\${ENCARGS}'\$cZ) + \$cG-eh\$cZ - show help for current encoder and exit\$cZ + \$cG-enca \${cU}'ARG1 ARG2'\$cZ \$cR*\$cZ - enca additional arguments (current is \$cP'\${ENCA_ARGS}'\$cZ) + \$cG-c \${cU}FILE\$cZ \$cR*\$cZ - use file as a cover image (does not work with \${cU}DIR\$cZ) + \$cG-nc \${cR}*\$cZ - do not set any cover images + \$cG-C \${cU}MASKS\$cZ \$cR*\$cZ - specify wildcards for files to copy over (current is \$cP'\${COPYMASKS}'\$cZ) + \$cG-nC \${cR}*\$cZ - do not copy any files + \$cG-cs \${cU}WxH\$cZ \$cR*\$cZ - set cover image size (current is \$cP\${PIC_SIZE}\$cZ) + \$cG-d \$cR*\$cZ - create artist/album subdirs (default) + \$cG-nd \$cR*\$cZ - do not create any subdirs + \$cG-D \$cR*\$cZ - delete original file + \$cG-nD \$cR*\$cZ - do not remove the original (default) + \$cG-F\$cZ - force deletion without asking + \$cG-colors\$cZ \$cR*\$cZ - colorized output (default) + \$cG-nocolors\$cZ \$cR*\$cZ - turn off colors + \$cG-g\$cZ \$cR*\$cZ - adjust audio gain + \$cG-ng\$cZ \$cR*\$cZ - do not adjust audio gain (default) + \$cG-s\$cZ - save configuration to \$cP\"\${CONFIG}\"\$cZ + \$cG-h\$cZ - print this message + \$cG-v\$cZ - print version + +\$cR*\$cZ - option affects configuration if \$cP'-s'\$cZ option passed. +\${cP}NOTE: \$cG'-c some_file.jpg -s'\$cP only \${cU}allows\$cZ\$cP cover images, it doesn't set a default one. +\${cZ}Supported \$cU\${cG}FORMATs\${cZ}: flac, m4a, mp3, ogg, wav. +Supported tags for \$cU\${cG}PATTERN\${cZ}: @artist, @album, @year, @track, @performer, @title, @genre, @ext. +@performer tag is useful with 'various artists' albums, when you want to add +each artist's name to the track filename. It works as @artist if track performer is undefined. +Special \"underscored\" tags are also supported (@_artist, @_album, etc). If used, spaces will be replaced with +underscores. It's useful if you want to have filenames without spaces. + +It's better to pass \$cP'-p'\$cZ option to see what will happen when actually splitting tracks. +You may want to pass \$cP'-s'\$cZ option for the first run to save default configuration +(output dir, cover image size, etc.) so you won't need to pass a lot of options +every time, just a filename. Script will try to find CUE sheet if it wasn't specified. +It also supports internal CUE sheets (FLAC, APE and WV).\n" + +msg="printf" + +emsg () { + $msg "${cR}$1${cZ}" +} + +SKIP_UPDATE_ENCARGS=0 + +update_encargs () { + if [ ${SKIP_UPDATE_ENCARGS} -eq 0 ]; then + e="\${ENCARGS_${FORMAT}}" + ENCARGS=`eval echo "$e"` + ENCHELP=0 + fi +} + +update_colors () { + if [ "${NOCOLORS}" -eq 0 ]; then + cR="\033[31m" + cG="\033[32m" + cC="\033[35m" + cP="\033[36m" + cU="\033[4m" + cZ="\033[0m" + else + unset cR cG cC cP cU cZ + fi +} + +update_encargs +update_colors + +# parse arguments +while [ "$1" ]; do + case "$1" in + -o) DIR=$2; shift;; + -of) OUTPATTERN=$2; shift;; + -cue) CUE=$2; shift;; + -cuecharset) CHARSET=$2; shift;; + -nask) NASK=1;; + -f) FORMAT=$2; update_encargs; shift;; + -e) ENCARGS=$2; SKIP_UPDATE_ENCARGS=1; shift;; + -eh) ENCHELP=1;; + -enca) ENCA_ARGS=$2; shift;; + -c) NOPIC=0; PIC=$2; shift;; + -nc) NOPIC=1;; + -C) COPYMASKS=$2; COPYFILES=1; shift;; + -nC) COPYFILES=0;; + -cs) PIC_SIZE=$2; shift;; + -d) NOSUBDIRS=0;; + -nd) NOSUBDIRS=1;; + -p) DRY=1;; + -D) REMOVE=1;; + -nD) REMOVE=0;; + -F) FORCE=1;; + -colors) NOCOLORS=0; update_colors;; + -nocolors) NOCOLORS=1; update_colors;; + -g) REPLAY_GAIN=1;; + -ng) REPLAY_GAIN=0;; + -s) SAVE=1;; + -h|--help|-help) eval "$msg \"${HELP}\""; exit 0;; + -v|--version) + $msg "split2${FORMAT} version: ${VERSION}\n\n"; + shntool -v 2>&1 | grep '^shntool'; + flac --version 2>/dev/null; + wavpack --help 2>&1 | grep 'Version'; + mac 2>&1 | grep '(v '; + faac -h 2>&1 | grep '^FAAC'; + oggenc --version 2>/dev/null; + lame --version | grep '^LAME'; + exit 0;; + -*) eval "$msg \"${HELP}\""; emsg "\nUnknown option $1\n"; exit 1;; + *) + if [ -n "${INPATH}" ]; then + eval "$msg \"${HELP}\"" + emsg "\nUnknown option $1\n" + exit 1 + elif [ ! -r "$1" ]; then + emsg "Unable to read $1\n" + exit 2 + else + INPATH="$1" + fi;; + esac + shift +done + +eval "export ENCARGS_${FORMAT}=\"${ENCARGS}\"" + +# save configuration if needed +if [ ${SAVE} -eq 1 ]; then + echo "DIR=\"${DIR}\"" > "${CONFIG}" + echo "OUTPATTERN=\"${OUTPATTERN}\"" >> "${CONFIG}" + echo "COPYMASKS=\"${COPYMASKS}\"" >> "${CONFIG}" + echo "COPYFILES=${COPYFILES}" >> "${CONFIG}" + echo "NOSUBDIRS=${NOSUBDIRS}" >> "${CONFIG}" + echo "NOPIC=${NOPIC}" >> "${CONFIG}" + echo "REMOVE=${REMOVE}" >> "${CONFIG}" + echo "PIC_SIZE=${PIC_SIZE}" >> "${CONFIG}" + echo "NOCOLORS=${NOCOLORS}" >> "${CONFIG}" + echo "REPLAY_GAIN=${REPLAY_GAIN}" >> "${CONFIG}" + echo "ENCARGS_flac=\"${ENCARGS_flac}\"" >> "${CONFIG}" + echo "ENCARGS_m4a=\"${ENCARGS_m4a}\"" >> "${CONFIG}" + echo "ENCARGS_mp3=\"${ENCARGS_mp3}\"" >> "${CONFIG}" + echo "ENCARGS_ogg=\"${ENCARGS_ogg}\"" >> "${CONFIG}" + echo "ENCARGS_wav=\"${ENCARGS_wav}\"" >> "${CONFIG}" + echo "ENCA_ARGS=\"${ENCA_ARGS}\"" >> "${CONFIG}" + $msg "${cP}Configuration saved$cZ\n" +fi + +# use flake if possible +command -v flake >/dev/null && FLAC_ENCODER="flake" || FLAC_ENCODER="flac" + +METAFLAC="metaflac --no-utf8-convert" +VORBISCOMMENT="vorbiscomment -R -a" +command -v mid3v2 >/dev/null && ID3TAG="mid3v2" || ID3TAG="id3tag -2" +MP4TAGS="mp4tags" +GETTAG="cueprint -n 1 -t" +VALIDATE="sed s/[^][[:space:][:alnum:]&_#,.'\"\(\)!-]//g" + +# check & print output format +msg_format="${cG}Output format :$cZ" +case ${FORMAT} in + flac) $msg "$msg_format FLAC [using ${FLAC_ENCODER} tool]"; enc_help="${FLAC_ENCODER} -h";; + m4a) $msg "$msg_format M4A"; enc_help="faac --help";; + mp3) $msg "$msg_format MP3"; enc_help="lame --help";; + ogg) $msg "$msg_format OGG VORBIS"; enc_help="oggenc --help";; + wav) $msg "$msg_format WAVE"; enc_help="echo Sorry, no arguments available for this encoder";; + *) emsg "Unknown output format \"${FORMAT}\"\n"; exit 1;; +esac + +$msg " (${ENCARGS})\n" + +if [ ${ENCHELP} -eq 1 ]; then + ${enc_help} + exit 0 +fi + +$msg "${cG}Output dir :$cZ ${DIR:?Output directory was not set}\n" + +# replaces a tag name with the value of the tag. $1=pattern $2=tag_name $3=tag_value +update_pattern_aux () { + tag_name="$2" + tag_value="$3" + expr_match="@${tag_name}" + expr_match_opt="[{]\([^}{]*\)${expr_match}\([^}]*\)[}]" + + echo "$1" | { [ "${tag_value}" ] \ + && sed "s/${expr_match_opt}/\1${tag_value}\2/g;s/${expr_match}/${tag_value}/g" \ + || sed "s/${expr_match_opt}//g;s/${expr_match}//g"; } +} + +# replaces a tag name with the value of the tag. $1=pattern $2=tag_name $3=tag_value +update_pattern () { + # replace '/' with '\' and '&' with '\&' for proper sed call + tag_name=$(echo "$2" | sed 's,/,\\\\,g;s,&,\\&,g') + tag_value=$(echo "$3" | sed 's,/,\\\\,g;s,&,\\&,g') + + v=$(update_pattern_aux "$1" "${tag_name}" "${tag_value}") + update_pattern_aux "$v" "_${tag_name}" $(echo "${tag_value}" | sed "s/ /_/g") +} + +# splits a file +split_file () { + TMPCUE="${HOME}/.split2flac_XXXXX.cue" + FILE="$1" + + if [ ! -r "${FILE}" ]; then + emsg "Can not read the file\n" + return 1 + fi + + # search for a cue sheet if not specified + if [ -z "${CUE}" ]; then + CUE="${FILE}.cue" + if [ ! -r "${CUE}" ]; then + CUE="${FILE%.*}.cue" + if [ ! -r "${CUE}" ]; then + # try to extract internal one + CUESHEET=$(${METAFLAC} --show-tag=CUESHEET "${FILE}" 2>/dev/null | sed 's/^cuesheet=//;s/^CUESHEET=//') + + # try WV internal cue sheet + [ -z "${CUESHEET}" ] && CUESHEET=$(wvunpack -q -c "${FILE}" 2>/dev/null) + + # try APE internal cue sheet (omfg!) + if [ -z "${CUESHEET}" ]; then + APETAGEX=$(tail -c 32 "$1" | cut -b 1-8 2>/dev/null) + if [ "${APETAGEX}" = "APETAGEX" ]; then + LENGTH=$(tail -c 32 "$1" | cut -b 13-16 | od -t u4 | awk '{printf $2}') 2>/dev/null + tail -c ${LENGTH} "$1" | grep -a CUESHEET >/dev/null 2>&1 + if [ $? -eq 0 ]; then + CUESHEET=$(tail -c ${LENGTH} "$1" | sed 's/.*CUESHEET.//g' 2>/dev/null) + [ $? -ne 0 ] && CUESHEET="" + fi + fi + fi + + if [ -n "${CUESHEET}" ]; then + $msg "${cP}Found internal cue sheet$cZ\n" + TMPCUE=$(mktemp "${TMPCUE}") + CUE="${TMPCUE}" + echo "${CUESHEET}" > "${CUE}" + TMPCUE="${HOME}/.split2flac_XXXXX.cue" + + if [ $? -ne 0 ]; then + emsg "Unable to save internal cue sheet\n" + return 1 + fi + else + unset CUE + fi + fi + fi + fi + + # print cue sheet filename + if [ -z "${CUE}" ]; then + emsg "No cue sheet\n" + return 1 + fi + + # cue sheet charset + [ -z "${CHARSET}" ] && CHARSET="utf-8" || $msg "${cG}Cue charset : $cP${CHARSET} -> utf-8$cZ\n" + + CUESHEET=$(iconv -f "${CHARSET}" -t utf-8 "${CUE}" 2>/dev/null) + + # try to guess the charset using enca + if [ $? -ne 0 ]; then + CUESHEET=$(enconv ${ENCA_ARGS} -x utf8 < "${CUE}" 2>/dev/null) + fi + + if [ $? -ne 0 ]; then + [ "${CHARSET}" = "utf-8" ] \ + && emsg "Cue sheet is not utf-8\n" \ + || emsg "Unable to convert cue sheet from ${CHARSET} to utf-8\n" + + if [ ${NASK} -eq 0 ]; then + while [ 1 ]; do + echo -n "Please enter the charset (or just press ENTER to ignore) > " + read CHARSET + + [ -z "${CHARSET}" ] && break + $msg "${cG}Converted cue sheet:$cZ\n" + iconv -f "${CHARSET}" -t utf-8 "${CUE}" || continue + + echo -n "Is this right? [Y/n] > " + read YEP + [ -z "${YEP}" -o "${YEP}" = "y" -o "${YEP}" = "Y" ] && break + done + + CUESHEET=$(iconv -f "${CHARSET}" -t utf-8 "${CUE}" 2>/dev/null) + fi + fi + + # save converted cue sheet + TMPCUE=$(mktemp "${TMPCUE}") + CUE="${TMPCUE}" + echo "${CUESHEET}" > "${CUE}" + + if [ $? -ne 0 ]; then + emsg "Unable to save converted cue sheet\n" + return 1 + fi + + SDIR=$(dirname "${FILE}") + + # search for a front cover image + if [ ${NOPIC} -eq 1 ]; then + unset PIC + elif [ -z "${PIC}" ]; then + # try common names + for i in *[Cc]over*.jpg *[Ff]older*.jpg */*[Cc]over*.jpg */*[Ff]older*.jpg; do + if [ -r "${SDIR}/$i" ]; then + PIC="${SDIR}/$i" + break + fi + done + + # try to extract internal one + if [ -z "${PIC}" ]; then + ${METAFLAC} --export-picture-to="${TMPPIC}" "${FILE}" 2>/dev/null + if [ $? -ne 0 ]; then + unset PIC + else + PIC="${TMPPIC}" + fi + fi + fi + + $msg "${cG}Cue sheet :$cZ ${CUE}\n" + $msg "${cG}Cover image :$cZ ${PIC:-not set}\n" + + # file removal warning + if [ ${REMOVE} -eq 1 ]; then + msg_removal="\n${cR}Also remove original" + [ ${FORCE} -eq 1 ] \ + && $msg "$msg_removal (WITHOUT ASKING)$cZ\n" \ + || $msg "$msg_removal if user says 'y'$cZ\n" + fi + + # files to copy over + if [ ${COPYFILES} -eq 1 -a -n "${COPYMASKS}" ]; then + $msg "${cG}Copy over :$cZ ${COPYMASKS}\n" + fi + + # get common tags + TAG_ARTIST=$(${GETTAG} %P "${CUE}" 2>/dev/null) + TAG_ALBUM=$(${GETTAG} %T "${CUE}" 2>/dev/null) + TRACKS_NUM=$(${GETTAG} %N "${CUE}" 2>/dev/null) + + # some cue sheets may have non-audio tracks + # we can check the difference between what cuebreakpoints and cueprint gives us + BREAKPOINTS_NUM=$(($(cuebreakpoints "${CUE}" 2>/dev/null | wc -l) + 1)) + + # too bad, we can't fix that in a _right_ way + if [ ${BREAKPOINTS_NUM} -lt ${TRACKS_NUM} ]; then + emsg "'cueprint' tool reported ${TRACKS_NUM} tracks, " + emsg "but there seem to be only ${BREAKPOINTS_NUM} audio ones\n" + emsg "Sorry, there is no any helpful options in the 'cueprint' tool for this problem.\n" + emsg "You probably remove non-audio tracks from the cue sheet (\"${CUE}\") by hand.\n" + return 1 + fi + + if [ -z "${TRACKS_NUM}" ]; then + emsg "Failed to get number of tracks from CUE sheet.\n" + emsg "There may be an error in the sheet.\n" + emsg "Running ${GETTAG} %N \"${CUE}\" produces this:\n" + ${GETTAG} %N "${CUE}" + return 1 + fi + + TAG_GENRE=$(grep 'REM[ \t]\+GENRE[ \t]\+' "${CUE}" | head -1 | sed 's/REM[ \t]\+GENRE[ \t]\+//;s/^"\(.*\)"$/\1/') + + YEAR=$(awk '{ if (/REM[ \t]+DATE/) { printf "%i", $3; exit } }' < "${CUE}") + YEAR=$(echo ${YEAR} | tr -d -c '[:digit:]') + + unset TAG_DATE + + if [ -n "${YEAR}" ]; then + [ ${YEAR} -ne 0 ] && TAG_DATE="${YEAR}" + fi + + $msg "\n${cG}Artist :$cZ ${TAG_ARTIST}\n" + $msg "${cG}Album :$cZ ${TAG_ALBUM}\n" + [ "${TAG_GENRE}" ] && $msg "${cG}Genre :$cZ ${TAG_GENRE}\n" + [ "${TAG_DATE}" ] && $msg "${cG}Year :$cZ ${TAG_DATE}\n" + $msg "${cG}Tracks :$cZ ${TRACKS_NUM}\n\n" + + # those tags won't change, so update the pattern now + DIR_ARTIST=$(echo "${TAG_ARTIST}" | ${VALIDATE}) + DIR_ALBUM=$(echo "${TAG_ALBUM}" | ${VALIDATE}) + PATTERN=$(update_pattern "${OUTPATTERN}" "artist" "${DIR_ARTIST}") + PATTERN=$(update_pattern "${PATTERN}" "album" "${DIR_ALBUM}") + PATTERN=$(update_pattern "${PATTERN}" "genre" "${TAG_GENRE}") + PATTERN=$(update_pattern "${PATTERN}" "year" "${TAG_DATE}") + PATTERN=$(update_pattern "${PATTERN}" "ext" "${FORMAT}") + + # construct output directory name + OUT="${DIR}" + + if [ ${NOSUBDIRS} -eq 0 ]; then + # add path from the pattern + path=$(dirname "${PATTERN}") + [ "${path}" != "${PATTERN}" ] && OUT="${OUT}/${path}" + fi + + # shnsplit is retarded enough to break on double slash + OUT=$(echo "${OUT}" | sed s,/[/]*,/,g) + + # remove path from the pattern + PATTERN=$(basename "${PATTERN}") + + $msg "${cP}Saving tracks to $cZ\"${OUT}\"\n" + + # split to tracks + if [ ${DRY} -ne 1 ]; then + # remove if empty and create output dir + if [ ${NOSUBDIRS} -eq 0 ]; then + rmdir "${OUT}" 2>/dev/null + mkdir -p "${OUT}" + [ $? -ne 0 ] && { emsg "Failed to create output directory ${OUT} (already split?)\n"; return 1; } + fi + + case ${FORMAT} in + flac) ENC="flac ${FLAC_ENCODER} ${ENCARGS} - -o %f"; RG="metaflac --add-replay-gain";; + m4a) ENC="cust ext=m4a faac ${ENCARGS} -o %f -"; RG="aacgain";; + mp3) ENC="cust ext=mp3 lame ${ENCARGS} - %f"; RG="mp3gain";; + ogg) ENC="cust ext=ogg oggenc ${ENCARGS} - -o %f"; RG="vorbisgain -a";; + wav) ENC="wav ${ENCARGS}"; REPLAY_GAIN=0;; + *) emsg "Unknown output format ${FORMAT}\n"; exit 1;; + esac + + # split to tracks + # sed expression is a fix for "shnsplit: error: m:ss.ff format can only be used with CD-quality files" + cuebreakpoints "${CUE}" 2>/dev/null | \ + sed 's/$/0/' | \ + shnsplit -O never -o "${ENC}" -d "${OUT}" -t "%n" "${FILE}" + if [ $? -ne 0 ]; then + emsg "Failed to split\n" + return 1 + fi + + # prepare cover image + if [ -n "${PIC}" ]; then + convert "${PIC}" -resize "${PIC_SIZE}" "${TMPPIC}" + if [ $? -eq 0 ]; then + PIC="${TMPPIC}" + else + $msg "${cR}Failed to convert cover image$cZ\n" + unset PIC + fi + fi + fi + + # set tags and rename + $msg "\n${cP}Setting tags$cZ\n" + + i=1 + while [ $i -le ${TRACKS_NUM} ]; do + TAG_TITLE=$(cueprint -n $i -t %t "${CUE}" 2>/dev/null) + FILE_TRACK="$(printf %02i $i)" + FILE_TITLE=$(echo "${TAG_TITLE}" | ${VALIDATE}) + f="${OUT}/${FILE_TRACK}.${FORMAT}" + + TAG_PERFORMER=$(cueprint -n $i -t %p "${CUE}" 2>/dev/null) + + if [ -n "${TAG_PERFORMER}" -a "${TAG_PERFORMER}" != "${TAG_ARTIST}" ]; then + $msg "$i: $cG${TAG_PERFORMER} - ${TAG_TITLE}$cZ\n" + else + TAG_PERFORMER="${TAG_ARTIST}" + $msg "$i: $cG${TAG_TITLE}$cZ\n" + fi + + FINAL=$(update_pattern "${OUT}/${PATTERN}" "title" "${FILE_TITLE}") + FINAL=$(update_pattern "${FINAL}" "performer" "${TAG_PERFORMER}") + FINAL=$(update_pattern "${FINAL}" "track" "${FILE_TRACK}") + + if [ ${DRY} -ne 1 -a "$f" != "${FINAL}" ]; then + mv "$f" "${FINAL}" + if [ $? -ne 0 ]; then + emsg "Failed to rename track file\n" + return 1 + fi + fi + + if [ ${DRY} -ne 1 ]; then + case ${FORMAT} in + flac) + ${METAFLAC} --remove-all-tags \ + --set-tag="ARTIST=${TAG_PERFORMER}" \ + --set-tag="ALBUM=${TAG_ALBUM}" \ + --set-tag="TITLE=${TAG_TITLE}" \ + --set-tag="TRACKNUMBER=$i" \ + --set-tag="TRACKTOTAL=${TRACKS_NUM}" \ + "${FINAL}" >/dev/null + RES=$? + + [ "${TAG_GENRE}" ] && { ${METAFLAC} --set-tag="GENRE=${TAG_GENRE}" "${FINAL}" >/dev/null; RES=$RES$?; } + [ "${TAG_DATE}" ] && { ${METAFLAC} --set-tag="DATE=${TAG_DATE}" "${FINAL}" >/dev/null; RES=$RES$?; } + [ "${PIC}" ] && { ${METAFLAC} --import-picture-from="${PIC}" "${FINAL}" >/dev/null; RES=$RES$?; } + ;; + + mp3) + ${ID3TAG} --artist="${TAG_PERFORMER}" \ + --album="${TAG_ALBUM}" \ + --song="${TAG_TITLE}" \ + --track="$i" \ + "${FINAL}" >/dev/null + RES=$? + + [ "${TAG_GENRE}" ] && { ${ID3TAG} --genre="${TAG_GENRE}" "${FINAL}" >/dev/null; RES=$RES$?; } + [ "${TAG_DATE}" ] && { ${ID3TAG} --year="${TAG_DATE}" "${FINAL}" >/dev/null; RES=$RES$?; } + ;; + + ogg) + ${VORBISCOMMENT} "${FINAL}" \ + -t "ARTIST=${TAG_PERFORMER}" \ + -t "ALBUM=${TAG_ALBUM}" \ + -t "TITLE=${TAG_TITLE}" \ + -t "TRACKNUMBER=$i" \ + -t "TRACKTOTAL=${TRACKS_NUM}" >/dev/null + RES=$? + + [ "${TAG_GENRE}" ] && { ${VORBISCOMMENT} "${FINAL}" -t "GENRE=${TAG_GENRE}" >/dev/null; RES=$RES$?; } + [ "${TAG_DATE}" ] && { ${VORBISCOMMENT} "${FINAL}" -t "DATE=${TAG_DATE}" >/dev/null; RES=$RES$?; } + ;; + + m4a) + ${MP4TAGS} "${FINAL}" \ + -a "${TAG_PERFORMER}" \ + -A "${TAG_ALBUM}" \ + -s "${TAG_TITLE}" \ + -t "$i" \ + -T "${TRACKS_NUM}" >/dev/null + RES=$? + + [ "${TAG_GENRE}" ] && { ${MP4TAGS} "${FINAL}" -g "${TAG_GENRE}" >/dev/null; RES=$RES$?; } + [ "${TAG_DATE}" ] && { ${MP4TAGS} "${FINAL}" -y "${TAG_DATE}" >/dev/null; RES=$RES$?; } + [ "${PIC}" ] && { ${MP4TAGS} "${FINAL}" -P "${PIC}" >/dev/null; RES=$RES$?; } + ;; + + wav) + RES=0 + ;; + + *) + emsg "Unknown output format ${FORMAT}\n" + return 1 + ;; + esac + + if [ ${RES} -ne 0 ]; then + emsg "Failed to set tags for track\n" + return 1 + fi + fi + + $msg " -> ${cP}${FINAL}$cZ\n" + + i=$(($i + 1)) + done + + # adjust gain + if [ ${REPLAY_GAIN} -ne 0 ]; then + $msg "\n${cP}Adjusting gain$cZ\n" + + if [ ${DRY} -ne 1 ]; then + ${RG} "${OUT}/"*.${FORMAT} >/dev/null + + if [ $? -ne 0 ]; then + emsg "Failed to adjust gain for track\n" + return 1 + fi + fi + fi + + # copy files + if [ ${COPYFILES} -eq 1 -a "${COPYMASKS}" ]; then + old=`pwd` + cd "${SDIR}" + $msg "\n${cG}Copying files:$cZ\n" + eval "for i in ${COPYMASKS}; do \ + test -r \"\$i\" && \ + echo \" +> \$i\" 2>/dev/null; done" + cd "${old}" + if [ ${DRY} -ne 1 ]; then + eval "for i in ${COPYMASKS}; do \ + test -r/\"${SDIR}/\$i\" && \ + cp -r \"${SDIR}/\$i\" \"\${OUT}/\"; done" + fi + fi + + rm -f "${TMPPIC}" + rm -f "${TMPCUE}" + + if [ ${DRY} -ne 1 -a ${REMOVE} -eq 1 ]; then + YEP="n" + + if [ ${FORCE} -ne 1 ]; then + echo -n "Are you sure you want to delete original? [y/N] > " + read YEP + fi + + [ "${YEP}" = "y" -o "${YEP}" = "Y" -o ${FORCE} -eq 1 ] && rm -f "${FILE}" + fi + + return 0 +} + +# searches for files in a directory and splits them +split_collection () { + rm -f "${FAILED}" + NUM_FAILED=0 + OLDIFS=${IFS} + OLDCHARSET="${CHARSET}" + # set IFS to newline. we do not use 'read' here because we may want to ask user for input + IFS=" +" + + for FILE in `find "$1" -iname '*.flac' -o -iname '*.ape' -o -iname '*.tta' -o -iname '*.wv' -o -iname '*.wav'`; do + IFS=${OLDIFS} + CHARSET=${OLDCHARSET} + $msg "$cG>> $cC\"${FILE}\"$cZ\n" + unset PIC CUE + split_file "${FILE}" + + if [ ! $? -eq 0 ]; then + emsg "Failed to split \"${FILE}\"\n" + echo "${FILE}" >> "${FAILED}" + NUM_FAILED=$((${NUM_FAILED} + 1)) + fi + + echo + done + + if [ ${NUM_FAILED} -ne 0 ]; then + emsg "${NUM_FAILED} file(s) failed to split (already split?):\n" + $msg "${cR}\n" + sort "${FAILED}" -o "${FAILED}" + cat "${FAILED}" + emsg "\nThese files are also listed in ${FAILED}.\n" + return 1 + fi + + return 0 +} + +if [ -d "${INPATH}" ]; then + if [ ! -x "${INPATH}" ]; then + emsg "Directory \"${INPATH}\" is not accessible\n" + exit 2 + fi + $msg "${cG}Input dir :$cZ ${INPATH}$cZ\n\n" + split_collection "${INPATH}" +elif [ -n "${INPATH}" ]; then + split_file "${INPATH}" +else + emsg "No input filename given. Use -h for help.\n" + exit 1 +fi + +# exit code of split_collection or split_file +STATUS=$? + +$msg "\n${cP}Finished$cZ\n" + +[ ${STATUS} -ne 0 ] && exit 3 || exit 0 + +### Local Variables: *** +### mode:sh *** +### tab-width:4 *** +### End: *** diff --git a/cli/wma2mp3.sh b/cli/wma2mp3.sh new file mode 100755 index 0000000..db51e56 --- /dev/null +++ b/cli/wma2mp3.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# convert *.wma to *.mp3 + +current_directory=$( pwd ) + +#remove spaces +for i in *.wma; do mv "$i" `echo $i | tr ' ' '_'`; done + +#remove uppercase +for i in *.[Ww][Mm][Aa]; do mv "$i" `echo $i | tr '[A-Z]' '[a-z]'`; done + +#Rip with Mplayer / encode with LAME +for i in *.wma ; do mplayer -vo null -vc dummy -af resample=44100 -ao pcm + -waveheader $i && lame -m s audiodump.wav -o $i; done + +#convert file names +for i in *.wma; do mv "$i" "`basename "$i" .wma`.mp3"; done + +rm audiodump.wav + |