aboutsummaryrefslogtreecommitdiffstats
path: root/cli
diff options
context:
space:
mode:
Diffstat (limited to 'cli')
-rwxr-xr-xcli/ape2id3.py145
-rwxr-xr-xcli/split2flac752
-rwxr-xr-xcli/wma2mp3.sh20
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
+