#!/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