#!/bin/bash
# *** LICENSE ***
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
# *** CONTRIBUTORS ***
# Contributor: Changaco
# *** Changelog ***
# 0.2.1: minor fixes
# 0.2:
# - added length comparison after the conversion
# - fixed some regexp
# - switched from id3info to mp3info for bitrate, frequency, channels, length and genre, we can't use it for the other tags because it doesn't support ID3v2
# - improved temp files handling
# - added --debug, -a, -n, -s, --tags-charset and --listen
# - fixed double "--raw"
# 0.1.1:
# - "-d" now works when conversion is skipped
# - renamed mp32ogg-file to mp32ogg_file and mp32ogg-dir to mp32ogg_dir
# 0.1: First release
# Version
version=0.2
# Colors
ESC="\033"
RESET=$ESC"[39;49;00m"
RED=$ESC"[31;01m"
GREEN=$ESC"[32;01m"
YELLOW=$ESC"[33;01m"
BLUE=$ESC"[34;01m"
MAGENTA=$ESC"[35;01m"
CYAN=$ESC"[36;01m"
# Output functions
echo1(){ echo -e "$@"; }
echox(){ if [ $verbose -ge "$1" -o $debug = 1 ]; then shift; echo -e $@; fi; }
echo2(){ echox 2 $@; }
echo3(){ echox 3 $@; }
debug(){ if [ $debug = 1 ]; then echo -e "$@"; return 0; else return 1; fi; }
deletemp3(){
if [ "$delete" = 1 ]; then
if [ $no_confirm = 1 ]; then
echo2 $MAGENTA"==> Deleting $CYAN$i"$RESET
rm "$1"
else
rm -i "$1"
fi
fi
}
mp32ogg_file(){
skip_these_tags=$skip_tags
# Check that file name ends up with .mp3
if [ "${1:0-4}" = ".mp3" ]; then
oggfile="${1/.mp3/.ogg}"
else
echo3 $MAGENTA"==> Skipping $CYAN$1$RESET (not an .mp3 file)"$RESET
return 0
fi
# Check that the .ogg does not already exist
if [ $force = 1 ]; then
echo -e $GREEN"==> Converting $CYAN$1"$RESET
elif [ -s "$oggfile" ]; then
echo2 -n "\r"$YELLOW"==> $CYAN${oggfile}$YELLOW already exists, checking it with ogginfo ... "$RESET
ogginfo "$oggfile" 1>$tmpdir/ogginfo 2>&1
ogginfo_ret=$?
if [ $ogginfo_ret -le 1 ]; then
rm $tmpdir/ogginfo
echo2 $GREEN"OK, Skipping file$RESET (use -f to force)"
deletemp3 "$1"
return 0
else
if [ $verbose -ge 3 ]; then
echo -e $RED"Error - ogginfo returned $ogginfo_ret, output is below:"$RESET
cat $tmpdir/ogginfo
fi
rm $tmpdir/ogginfo
echo -e "\r"$GREEN"==> Converting $CYAN$1$GREEN (.ogg exists but seems to be corrupted (ogginfo returned $ogginfo_ret))"$RESET
fi
else
if [ -e "$oggfile" ]; then
echo -e $GREEN"==> Converting $CYAN$1$GREEN (.ogg exists but is empty)"$RESET
else
echo -e $GREEN"==> Converting $CYAN$1"$RESET
fi
fi
# Try to find out the charset of the tags, we try UTF8 and ISO88591
if [ $skip_tags = 0 ]; then
these_tags_charset=""
id3info "$1" 2>/dev/null > "$tmpdir/$1.infos"
if [ "$tags_charset" != "" ] && iconv -f "$tags_charset" -t UTF8 "$tmpdir/$1.infos" > "$tmpdir/$1.infos.utf8" 2>/dev/null; then
these_tags_charset="$tags_charset"
fi
if [ "$these_tags_charset" = "" ]; then
if ! iconv -t UTF8 "$tmpdir/$1.infos" > "$tmpdir/$1.infos.utf8" 2>/dev/null; then
if ! iconv -f ISO88591 -t UTF8 "$tmpdir/$1.infos" > "$tmpdir/$1.infos.utf8" 2>/dev/null; then
if [ $automatic = 1 ]; then
echo2 $RED"Warning: tags are neither in $( [ "$tags_charset" != "" ] && echo "$tags_charset nor " )UTF8 nor ISO88591, automatic mode on -> skipping tags"$RESET
else
echo1 $RED"Warning: tags are neither in $( [ "$tags_charset" != "" ] && echo "$tags_charset nor " )UTF8 nor ISO88591, you can:$RESET\n- enter a charset to try\n- enter \"c\" to continue the conversion process without the tags\n- enter \"l\" to see the list of charsets supported by iconv"
while read ans
do
case "$ans" in
"c" )
skip_these_tags=1
break
;;
"l" )
iconv -l | less
;;
* )
if iconv -l | grep -e "^$ans$" || iconv -l | grep -e "^$ans//$"; then
if ! iconv -f "$ans" -t UTF8 "$tmpdir/$1.infos" > "$tmpdir/$1.infos.utf8" 2>/dev/null; then
echo "Not the good charset, try another one or enter \"c\" to continue the conversion process without the tags."
else
echo "Charset seems good, it doesn't mean it really is though."
these_tags_charset="$ans"
break
fi
else
echo1 "Not a valid charset or command, enter \"l\" to see the list of charsets or \"c\" to continue the conversion process without the tags."
fi
esac
done
fi
else
these_tags_charset="ISO88591"
fi
else
these_tags_charset="UTF8"
fi
fi
convtags="iconv -f $these_tags_charset -t UTF8"
if [ $debug = 1 ]; then
echo "*** Debug info: id3info output ***"
cat "$tmpdir/$1.infos"
echo "*** Debug info: id3info output converted with \"$convtags\" ***"
cat "$tmpdir/$1.infos.utf8"
fi
fi
# Build the command line for conversion
enc="mpg123 -q -s \"$1\" 2>/dev/null | oggenc -o \"${oggfile}\" --raw "
bitrate="$(mp3info "$1" -p "%r" 2>/dev/null)"
if [ "$bitrate" != "" -a "$bitrate" != "Variable" ]; then
enc+="-b $bitrate "
fi
freq="$(mp3info "$1" -p "%Q" 2>/dev/null)"
if [ "$freq" != "" ]; then
enc+="--raw-rate $freq "
fi
channels="$(mp3info "$1" -p "%o" 2>/dev/null)"
if [ "$channels" = "mono" ]; then
enc+="-C 1 "
fi
# Get tags
if [ $skip_tags = 0 -a $skip_these_tags = 0 ]; then
genre="$(mp3info "$1" -p "%g" 2>/dev/null | $convtags)"
if [ "$genre" != "" ]; then
enc+="--genre $(printf %q "$genre") "
fi
while read line
do
case "${line:4:4}" in
"TIT2" )
enc+="--title $(printf %q "$(echo "$line" | sed -e 's/[^:]*: \?//')") "
;;
"TPE1" )
enc+="--artist $(printf %q "$(echo "$line" | sed -e 's/[^:]*: \?//')") "
;;
"TALB" )
enc+="--album $(printf %q "$(echo "$line" | sed -e 's/[^:]*: \?//')") "
;;
"TYER" )
enc+="--date $(printf %q "$(echo "$line" | sed -e 's/[^:]*: \?//')") "
;;
"TRCK" )
enc+="--tracknum $(printf %q "$(echo "$line" | sed -e 's/[^:]*: \?//')") "
;;
esac
done < "$tmpdir/$1.infos.utf8"
rm "$tmpdir/$1.infos" "$tmpdir/$1.infos.utf8"
fi
# Output handling
if [ $show_progress = 0 ]; then
enc+="1>$tmpdir/oggenc 2>&1"
fi
echo3 $YELLOW"Command is: $enc"$RESET
# Execute the command and treat the result
eval "$enc -"
oggenc_ret=$?
if [ $oggenc_ret != 0 ]; then
echo -e $RED"Error: oggenc returned $oggenc_ret"
if [ $verbose -le 2 ]; then
echo -e $RED"Command was:$RESET $enc -"
fi
if [ $show_progress = 0 ]; then
cat $tmpdir/oggenc
rm $tmpdir/oggenc
fi
if [ -e "$oggfile" ]; then rm -f "$oggfile"; fi
return 1
fi
# Check that the length of the ogg is equal to the length of the mp3
if [ "$(ogginfo "$oggfile" | grep "Playback length:" -c)" = "1" ]; then
declare -i oggminutes="10#$(ogginfo "$oggfile" | grep "Playback length:" | sed -e 's/[^0-9]*\([0-9]\+\)m:\([0-9]\+\).*/\1/')"
declare -i oggseconds="10#$(ogginfo "$oggfile" | grep "Playback length:" | sed -e 's/[^0-9]*\([0-9]\+\)m:\([0-9]\+\).*/\2/')"
debug "*** Debug: oggminutes=$oggminutes oggseconds=$oggseconds ***"
ogglength=$(($oggminutes*60+$oggseconds))
declare -i mp3length="10#$(mp3info "$1" -p "%S" | $convtags)"
if [ "$ogglength" != "$mp3length" ]; then
if [ $(($ogglength+1)) != $mp3length ] && [ $(($ogglength-1)) != $mp3length ]; then
echo2 $RED"Warning: the length of the mp3 and ogg files don't seem to match (respectively $mp3length and $ogglength), this may be the sign of a bad conversion."$RESET
if [ $automatic = 1 ]; then
echo2 "Automatic mode enabled, ignoring error."
else
askdel=0
ret=0
if [ "$listen_command" != "" ]; then
echo1 $YELLOW"Do you want to listen to the ogg file to make sure that the conversion was correct ? [Y/n]"$RESET
read ans
if [ "$ans" != "n" -a "$ans" != "N" ]; then
$listen_command "$oggfile"
echo1 $YELLOW"Is the ogg ok ? [Y/n]"$RESET
read ans
if ! [ "$ans" != "n" -a "$ans" != "N" ]; then
echo1 $YELLOW"Sorry, I can't do anything more ..."$RESET
ret=1
askdel=1
fi
fi
else
echo2 "No ogg player was found on your system, ignoring error (use --listen= to specify a player)"
askdel=1
fi
if [ $askdel = 1 ]; then
echo1 $YELLOW"Do you want to delete the .ogg file ? [Y/n]"$RESET
read ans
if [ "$ans" != "n" -a "$ans" != "N" ]; then
rm "$oggfile"
fi
fi
if [ $ret != 0 ]; then
return $ret
fi
fi
fi
fi
else
echo2 "Couldn't check ogg lenght"
fi
if [ $show_progress = 0 ]; then
rm $tmpdir/oggenc
fi
# Delete mp3 if asked
deletemp3 "$1"
return 0
}
mp32ogg_dir(){
cd "$1"
echo3 $BLUE"===> Entering $CYAN$1"$RESET
for i in *
do
if [ -d "$i" ]; then
if [ $recursive = -1 ]; then
mp32ogg_dir "$i" "-1"
elif [ $recursive -gt 0 ]; then
mp32ogg_dir "$i" $(($recursive-1))
fi
else
mp32ogg_file "$i" || echo "$1/$i" >> "$tmpdir/failed-list"
fi
done
cd ..
return 0
}
show_help(){
echo \
"mp32ogg-bash $version
Usage: mp32ogg-bash [-fqvpdr] dir1 dir2 file1 file2 ...
Options:
-f force the conversion even if a valid ogg is found (validity is checked with ogginfo)
-q quiet mode, show less details
-v verbose mode, show more details
-p show encoding progress (independent of the verbosity)
-d delete the mp3 file(s) if conversion is successfull (use with caution)
-r when the argument is a directory, search recursively without a depth limit
-n don't ask before deleting the .mp3 file (use with caution)
-a automatic mode, skip questions (doesn't include -n)
-s skip tags retreival
Other Options:
--recursive= when the argument is a directory, search recursively with a maximum depth of , use -r if you don't want a depth limit
--debug use this when you want to report a bug
--listen= use when listening to the created ogg file
--tags-charset= try to convert tags from first (use 'iconv -l' to get the list of available charsets)
Classic options:
-h --help show this help
--version show version"
}
# Create a unique temp dir
tmpdir=/tmp/mp32ogg-bash_$RANDOM
while [ -d $tmpdir ]
do
tmpdir=/tmp/mp32ogg-bash_$RANDOM
done
mkdir $tmpdir
verbose=2
delete=0
force=0
show_progress=0
recursive=0
no_confirm=0
debug=0
automatic=0
skip_tags=0
tags_charset=""
listen_command=""
if which vlc 1>/dev/null 2>&1; then
listen_command="vlc"
elif which mplayer 1>/dev/null 2>&1; then
listen_command="mplayer"
fi
for arg in "$@"
do
if [ "${arg:0:1}" = "-" ]; then
if [ "${arg:1:1}" != "-" ]; then
echo -n ${arg:1} > $tmpdir/arg
while read -n1 c
do
case "$c" in
"f" )
force=1
;;
"q" )
verbose=1
;;
"v" )
verbose=3
;;
"d" )
delete=1
;;
"p" )
show_progress=1
;;
"r" )
recursive=-1
;;
"n" )
no_confirm=1
;;
"a" )
automatic=1
;;
"s" )
skip_tags=1
;;
"h" )
show_help
exit 0
;;
* )
echo "Unknow option \"$c\""
esac
done < $tmpdir/arg
rm $tmpdir/arg
else
case "$arg" in
"--help" )
show_help
exit 0
;;
"--version" )
echo "$version"
exit 0
;;
"--debug" )
debug=1
;;
--recursive=* )
declare -i recursive="$(echo "$arg" | sed -e 's/--recursive=//')"
;;
--listen=* )
listen_command="$(echo "$arg" | sed -e 's/--listen=//')"
;;
--tags_charset=* )
tags_charset="$(echo "$arg" | sed -e 's/--tags_charset=//')"
;;
* )
echo "Unknow argument \"$arg\""
esac
fi
fi
done
debug \
"version=$version
command=$@
verbose=$verbose
delete=$delete
force=$force
show_progress=$show_progress
recursive=$recursive
no_confirm=$no_confirm
automatic=$automatic
skip_tags=$skip_tags
tags_charset=$tags_charset
listen_command=$listen_command
tmpdir=$tmpdir"
for arg in "$@"
do
if [ "${arg:0:1}" != "-" ]; then
if [ -d "$arg" ]; then
mp32ogg_dir "$arg" $recursive
elif [ -e "$arg" ]; then
mp32ogg_file "$arg" || echo "$arg" >> "$tmpdir/failed-list"
else
echo "$arg not found"
fi
fi
done
# Show the failed list if it exists
if [ -s "$tmpdir/failed-list" ]; then
echo
echo1 $YELLOW"=> List of files that weren't converted due to an error:"$RESET
cat "$tmpdir/failed-list"
fi
# Clean temp files
rm -rf --preserve-root $tmpdir
exit 0