Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
[Script] Convert flac to (almost) anything
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Multimedia
View previous topic :: View next topic  
Author Message
Akkara
Administrator
Administrator


Joined: 28 Mar 2006
Posts: 3715
Location: &akkara

PostPosted: Thu Apr 19, 2007 9:28 am    Post subject: [Script] Convert flac to (almost) anything Reply with quote

I have been ripping CDs into the lossless flac format to have the original music preserved intact and available to encode to whatever new audio formats become popular as the years go by.

Which raises the question of how to maintain a mirror database of music encoded in whatever format one's portable music player requires - even if the player supports flac, a high bit-rate lossy format might be preferable to fit more music into it, and it would be nice to automatically make mp3's or oggs or whatever as you rip new flacs, and automatically update them as you notice and fix tagging errors.

This script is what I'm currently using to do the job. It is an outgrowth of various ideas and tidbits I had found in these forums along with my own coding to solve these common problems. I'm sharing it here because it might be helpful to someone else, and to return something to the community.

It is a longish script so it'll be put in a followup post. (If there's a better way to make these things available please let me know!)


Requirements

Needs flac, metaflac, lame (only if you want to encode to mp3), faac (only if encoding to aac), oggenc (only if encoding to ogg).


Installation

To install, copy the code in my 2nd post below into a file named /usr/local/bin/flac2any and mark it executable. Then make the following symlinks. As root:
Code:
cd /usr/local/bin
chmod +x flac2any
ln -s flac2any flac2ogg
ln -s flac2any flac2mp3
ln -s flac2any flac2aac


Use

This script examines its name to know what conversion to perform. (That's why the symlinks when installing it.)

As usual, --help gives a short usage reminder:
Code:
flac2ogg --help

To encode a flac file to ogg:
Code:
flac2ogg file.flac
This makes file.ogg in the current directory. flac2mp3 and flac2aac works similarly in this and subsequent examples. (Note, however, that flac2aac makes m4a files)

To encode a flac file to ogg but give it a specific name:
Code:
flac2ogg -o newname.ogg file.flac

To encode a bunch of flac files and put the ogg files in a new directory:
Code:
flac2ogg -dir /place/for/ogg/files *.flac

Adding the -r flag causes it to recursively descend into subdirectories in search for flac files, mirroring the subdirectory structure in the .ogg destination:
Code:
flac2ogg -r -dir /place/for/ogg/files *
or
Code:
flac2ogg -r -dir /place/for/ogg/files  /main/flac/collection/.

Giving the -u flag turns on update mode: if the .ogg is already present, it will only re-encode if the .flac file is newer. This is useful when new music is added to your collection or tags changed.

Finally, there's the -n "no, don't do it" flag. It prints out what would be done without actually doing anything. This is good if you just want to see what would be done (like if you use the -u flag, you can see what's been updated since last time).

But -n is also very useful in conjection with this parallel-shell script if you have a multi-core machine, to keep all the cores busy with encoding:
Code:
time flac2aac -r -n -u --quality 180 -dir /your/aac/mirror  /your/flac/files/* | nice -20 parallel-sh -v


Anyway, here it is. Oh, and I appreciate comments, if you notice a better way to do something, or something I overlooked, please let us know!
Back to top
View user's profile Send private message
Akkara
Administrator
Administrator


Joined: 28 Mar 2006
Posts: 3715
Location: &akkara

PostPosted: Thu Apr 19, 2007 9:29 am    Post subject: Reply with quote

The script:

Code:
#!/bin/bash
#-------------------------------------------------------------------------------
# This script converts flac files to mp3, ogg, or aac.
#
# Written by Akkara based on ideas from supermihi and tkdfighter
# Reference:   http://forums.gentoo.org/viewtopic-t-314704.html
#      http://forums.gentoo.org/viewtopic-p-4016618.html
#
# Known Bugs:
#       - If the script is interrupted (ctrl-C) the file being encoded is left
#         in a partially-done state.  A subsequent invocation with --update
#         will incorrectly skip this file, thinking it is current.
#
#       - There is no way I know of to put the aac encoder into a 'quiet' mode
#         so currently its output is redirected to /dev/null.  But that means
#         any encoder errors are also not seen.
#
#       - mp3 tags only support genre chosen from a small set of official genres.
#         If the flac's genre is not in this set, encoding will quietly fail.
#         Ideally flac->mp3 should examine the genre and if not in the accepted
#         set, map it to something like "Other" before encoding.  For future work.

THIS=`basename $0`


# Encoders
OGG="/usr/bin/oggenc"
AAC="/usr/bin/faac"
MP3="/usr/bin/lame"


# flac decoder options:
# -d   decode
# -s   silent
# -o -   send output to stdout
FLACOPTS="-d -s -o -";
FLAC="/usr/bin/flac"
METAFLAC="/usr/bin/metaflac"


# Tags to extract
ALLTAGS="ARTIST ALBUM TITLE DISCNUMBER TRACKNUMBER GENRE YEAR DATE"


# sed expression to enclose its input in single-quotes, surrounding any single
# quotes in the input with double quotes.
QUOTING_EXPR="-e s;';'"'"'"'"'"'"';g -e s;.*;'&';"
QUOTEQUOTES="sed $QUOTING_EXPR"


# Examine this program's name to determine which conversion to perform
#
# Variables are set according to what our encoder expects:
#    EXTN   is the output file extension
#    ENCODE   is the encoder command to use.
#    ENCOPTS   is the options and command-line to pass the encoder.
#      A # character tells where to put the tag-setting options.
#      A @ character tells where to put the output filename.  Due to
#      a limitation in the script, # must occur before @.
#    TAG_*   is the encoder option that sets the named tag.
#    TAG_DISK   supports two special values for encoding to formats that do
#      not have a disc-number tag.
#          +TRACK+   indicates that disc-numbers should be combined
#            with the track number.  For example a flac
#            tagged as disc 2 track 3 can be encoded as an
#            ogg tagged with track 203
#          +ALBUM+   indicates that disc-numbers should be appended
#            to the album-name as "(Disc N)".  This option
#            is necessary when encoding to mp3 because mp3
#            has a maximum limit on track numbers of 255,
#            which makes the previous option unusable on
#            collections of more than two discs.
#
case "$THIS" in
    *ogg)
        EXTN="ogg"
        ENCODE="$OGG"
        ENCOPTS="-Q # -o @ -"      # -Q  quiet
        OPT_QUALITY="--quality"
        TAG_DISC="+TRACK+"
        TAG_TRACK="--tracknum"
        TAG_TITLE="--title"
        TAG_ALBUM="--album"
        TAG_ARTIST="--artist"
        TAG_DATE="--date"
        TAG_GENRE="--genre"
        TAG_COMMENT="--comment"
        ;;
    *aac)
        EXTN="m4a"
        ENCODE="$AAC"
        ENCOPTS="-w # -o @ - 2>/dev/null"   # -w  wrap AAC data in MP4 container.
        OPT_QUALITY="-q"
        TAG_DISC="--disc"
        TAG_TRACK="--track"
        TAG_TITLE="--title"
        TAG_ALBUM="--album"
        TAG_ARTIST="--artist"
        TAG_DATE="--year"
        TAG_GENRE="--genre"
        TAG_COMMENT="--comment"
        # (also supports --writer  --compilation  --cover-art )
        ;;
    *mp3)
        EXTN="mp3"
        ENCODE="$MP3"
        ENCOPTS="--silent # - @"
        OPT_QUALITY="-V"
        TAG_DISC="+ALBUM+"
        TAG_TRACK="--tn"
        TAG_TITLE="--tt"
        TAG_ALBUM="--tl"
        TAG_ARTIST="--ta"
        TAG_DATE="--ty"
        TAG_GENRE="--tg"
        TAG_COMMENT="--tc"
        ;;
    *)
        echo "$THIS: Unknown conversion" >&2
        exit 1
        ;;
esac


if [[ "$1" == "" ]]; then
    echo "Usage: $THIS [--help] [--quality Q] [-dir D] [-u] [-r] [-n] file1.flac [file2.flac...]" >&2
    exit 0
fi

if [[ ! -x "$ENCODE" ]]; then
    echo "$THIS: Encoder $ENCODER is not installed" >&2
    exit 2
fi
if [[ ! -x "$FLAC" ]]; then
    echo "$THIS: Flac decoder $FLAC is not installed" >&2
    exit 2
fi
if [[ ! -x "$METAFLAC" ]]; then
    echo "$THIS: Flac tags extractor $METAFLAC is not installed" >&2
    exit 2
fi


ENC1="$ENCODE "`echo "$ENCOPTS" | sed 's;\(.*[^ ]\) *# *\(.*[^ ]\) *@ *\(.*\);\1;'`
ENC2=`echo "$ENCOPTS" | sed 's;\(.*[^ ]\) *# *\(.*[^ ]\) *@ *\(.*\);\2;'`
ENC3=`echo "$ENCOPTS" | sed 's;\(.*[^ ]\) *# *\(.*[^ ]\) *@ *\(.*\);\3;'`


UPDATE="false"
DEBUG="false"
DOIT="eval"
VERBOSE="false"
RECURSIVE="false"
RECURSE="$0"

QUALITY=""
DESTFILE=""
DESTDIR=""
MAKE_DESTDIR="false"
SUCCESS="true"


#
# Parse command-line options
#
while :; do case "$1" in
    --help)
        echo "Usage: `basename $0` [options] file1.flac [file2.flac...]" >&2
        echo >&2
        echo   "  Converts audio files from flac format to $EXTN.  Resulting file(s) are written" >&2
        echo   "  to the current directory with .$EXTN replacing .flac in the filename." >&2
        echo   "  (** Note: due to technical limitations, any options (especially -o and -odir)" >&2
        echo   "  must be given before the filename(s) to which they apply.)" >&2
        echo >&2
        echo   "  Options:" >&2
        echo   "    -o dest   Place ogg output in dest."
        echo   "    -dir dest   Place output files(s) in directory dest." >&2
        echo   "    -r      Recurse into subdirectories." >&2
        echo   "    -n      Show what would be done but don't do anything." >&2
        echo   "    -u      Rewrite an existing ogg only if the flac file is newer." >&2
        echo   "    -v      Verbose.  Gives more detail of what's happening." >&2
        echo   "    --quality Q   Encode with quality Q (its meaning depends on encoder)" >&2
        echo   "    --encopts O   Pass additional options to encoder.  Example, -encopts "'"-B 192"'
        echo   "    --help   Display this message" >&2
        exit 0
        ;;
    -o)
        DESTFILE="$2"
        shift 2
        ;;
    -dir)
        DESTDIR="$2"
        if [[ ! -d "$DESTDIR" ]]; then
            MAKE_DESTDIR=true
        fi
        shift 2
        ;;
    -n)
        RECURSE="$RECURSE -n"
        DOIT="echo"
        shift 1
        ;;
    -r)
        RECURSE="$RECURSE -r"
        RECURSIVE="true"
        shift 1
        ;;
    -v)
        RECURSE="$RECURSE -v"
        VERBOSE="true"
        shift 1
        ;;
    --quality)
        RECURSE="$RECURSE --quality $2"
        QUALITY="$OPT_QUALITY $2"
        shift 2
        ;;
    --encopts)
        RECURSE="$RECURSE --encopts '$2'"
        ENC1="$ENC1 $2"
        shift 2
        ;;
    -u)
        RECURSE="$RECURSE -u"
        UPDATE="true"
        shift 1
        ;;
    --debug)
        RECURSE="$RECURSE --debug"
        DEBUG="true"
        VERBOSE="true"
        shift 1
        ;;
    "")      # No more args - done
        break
        ;;
    *)
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        # process a file
        #
        IN="$1"
        shift 1
        OUT="$DESTFILE"
        DESTFILE=""      # DESTFILE must be unset for subsequent loops

        if [[ ! -r "$IN" ]]; then
            echo Cannot read "$IN" >&2
            SUCCESS=false

        elif [[ -d "$IN" ]]; then
            if $RECURSIVE; then
                NEWDEST=`basename "$IN"`
                if [[ "$DESTDIR" != "" ]]; then
                    NEWDEST="$DESTDIR/$NEWDEST"
                fi
                $VERBOSE  &&  echo "Entering directory:" "$IN"
                $RECURSE -dir "$NEWDEST" "$IN"/*  ||  SUCCESS="false"
                $VERBOSE  &&  echo "Leaving directory:" "$IN"
            else
                echo "Skipping directory:" "$IN" >&2
            fi

        elif [[ `dd bs=4 count=1 if="$IN" 2>/dev/null` != "fLaC" ]]; then
            echo "Skipping non-flac file:" "$IN" >&2

        else
            if [[ "$OUT" == "" ]]; then
                OUT=`basename "$IN" .flac`.$EXTN
            fi
            if [[ "$DESTDIR" != "" ]]; then
                OUT="$DESTDIR/$OUT"
                if $MAKE_DESTDIR; then
                    MAKE_DESTDIR=false
                    $VERBOSE  &&  echo "Making directory:" "$DESTDIR" >&2
                    CMD="mkdir -p "`echo "$DESTDIR" | $QUOTEQUOTES`
                    $DOIT "$CMD"
                fi
            fi

            if [[ "$UPDATE" == "true"  &&  "$IN" -ot "$OUT" ]]; then
                $VERBOSE  &&  echo "Destination is newer, skipping:" "$IN" >&2
                continue
            fi

            $VERBOSE  &&  echo "Processing: " "$IN"   >&2

            # Extract meta-tags.  Ensure all variables corresponding to
            # tags that are not present get assigned to "".  Also wrap the tag
            # values in single quotes to prevent script-injection via the eval.
            MD5SUM=`metaflac --show-md5sum "$IN" | sed 's;.*:\([0-9a-fA-F]*\)$;\1;'`
            for tag in $ALLTAGS ; do
                settag=`$METAFLAC --no-utf8-convert --show-tag=$tag "$IN" |  \
                    sed -e "s;^$tag=;;" $QUOTING_EXPR -e "s;^;$tag=;"`
                $DEBUG  &&  echo "$settag"
                eval "$tag=''"
                eval "$settag"
            done

            if $DEBUG; then
                echo   "$IN"            >&2
                echo   "  Outfile:   $OUT"      >&2
                echo   "  Tags:"         >&2
                echo   "    Artist:   $ARTIST"   >&2
                echo   "    Album:   $ALBUM"      >&2
                echo   "    Title:   $TITLE"      >&2
                echo   "    Disc:   $DISCNUMBER"   >&2
                echo   "    Track:   $TRACKNUMBER"   >&2
                echo   "    Genre:   $GENRE"      >&2
                echo   "    Year:   $YEAR"      >&2
                echo   "    Date:   $DATE"      >&2
            fi

            # Process discnumber for fileformats that don't support that tag
            if [[ $DISCNUMBER != "" ]]; then
                if [[ "$TAG_DISC" == "+TRACK+" ]]; then
                    TRACKNUMBER=$DISCNUMBER`echo "000$TRACKNUMBER" | sed 's/0*\(..\)$/\1/'`
                    DISCNUMBER=""
                elif [[ "$TAG_DISC" == "+ALBUM+" ]]; then
                    ALBUM="$ALBUM (Disc $DISCNUMBER)"
                    DISCNUMBER=""
                fi
            fi

            # Assemble tag options.
            TAGS=""
            if [[ $ARTIST != "" ]]; then
                TAGS="$TAGS  $TAG_ARTIST "`echo $ARTIST | $QUOTEQUOTES`
            fi
            if [[ $ALBUM != "" ]]; then
                TAGS="$TAGS  $TAG_ALBUM "`echo $ALBUM | $QUOTEQUOTES`
            fi
            if [[ $DISCNUMBER != "" ]]; then
                TAGS="$TAGS  $TAG_DISC "`echo $DISCNUMBER | $QUOTEQUOTES`
            fi
            if [[ $TRACKNUMBER != "" ]]; then
                TAGS="$TAGS  $TAG_TRACK "`echo $TRACKNUMBER | $QUOTEQUOTES`
            fi
            if [[ $TITLE != "" ]]; then
                TAGS="$TAGS  $TAG_TITLE "`echo $TITLE | $QUOTEQUOTES`
            fi

            # Some flacs are tagged with DATE, others with YEAR.  Pick one.
            if [[ $DATE != "" ]]; then
                TAGS="$TAGS  $TAG_DATE "`echo $DATE | $QUOTEQUOTES`
            elif [[ $YEAR != "" ]]; then
                TAGS="$TAGS  $TAG_DATE "`echo $YEAR | $QUOTEQUOTES`
            fi
            if [[ $GENRE != "" ]]; then
                TAGS="$TAGS  $TAG_GENRE "`echo $GENRE | $QUOTEQUOTES`
            fi
            if [[ $COMMENT != "" ]]; then
                TAGS="$TAGS  $TAG_COMMENT "`echo $COMMENT | $QUOTEQUOTES`
            fi

            $DEBUG  &&  echo "    Tagline:   $TAGS"   >&2

            QUOTE_IN=`echo "$IN" | $QUOTEQUOTES`
            QUOTE_OUT=`echo "$OUT" | $QUOTEQUOTES`
            $DOIT "$FLAC $FLACOPTS $QUOTE_IN | $ENC1 $QUALITY $TAGS $ENC2 $QUOTE_OUT $ENC3"  ||  SUCCESS="false"
        fi

        #
        # end process a file
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
        ;;
esac done

$SUCCESS
exit $?
Back to top
View user's profile Send private message
faceman
Apprentice
Apprentice


Joined: 10 Jan 2005
Posts: 200
Location: inter terram et caelo

PostPosted: Thu May 03, 2007 4:17 pm    Post subject: Reply with quote

If you use mp3s, you could also use mp3fs.
_________________
www.impressusart.com
Back to top
View user's profile Send private message
PraetorZero
Apprentice
Apprentice


Joined: 11 Dec 2004
Posts: 236
Location: /home

PostPosted: Sat Jun 02, 2007 1:42 am    Post subject: Reply with quote

Perhaps this should be moved to Tips & Tricks? Awesome script, I've been using it when adding files to my wife's ipod via gtkpod.
Back to top
View user's profile Send private message
Dirk.R.Gently
Guru
Guru


Joined: 29 Jan 2007
Posts: 546
Location: Titan

PostPosted: Tue Aug 21, 2007 11:51 pm    Post subject: Reply with quote

Tips and Tricks sure. I think this needs to be in portage! Freakin' awesome! To an average lay person, I tried to convert flac to ogg, how does this convert, using oggenc? I tried previously to use oggenc without success:
Code:
oggenc in.flac -q 6 -o out.ogg

and got:
Code:
ERROR: Input file "in.flac" is not a supported format

Actually this is a bit strange, I've seen it work on other sites, but with your script it works. Also it could be useful to name the dependencies in your script, i.e. vorbis-tools...
_________________
Helpful Linux Tidbits
Back to top
View user's profile Send private message
.brum
n00b
n00b


Joined: 02 Sep 2007
Posts: 1

PostPosted: Sun Sep 02, 2007 3:56 pm    Post subject: Re: [Script] Convert flac to (almost) anything Reply with quote

Hi,

Thank you for sharing this with us, it is appreciated. I have tried the script to convert to aac and gotten it basically to work as it should. There are, however, two questions:

(1) The resulting m4a -files seem not to have a track number at all. When running the script with -v -n switches, tracknumber does not show up. I verified that the FLACs do have a track number, and that they've converted correctly when previously using Foobar2000. Any idea what I could check?

(2) This script uses the faac encoder, but as far as I understand, Nero Digital's encoder is supposed to produce better audio quality. Previously one needed to use the windows version via wine, but now there's a native Linux version. I tried just changing the encoder reference, and got the script to pass the information to neroAacEnc, but then came errors complaining about the tag data being unknown parameters. Perhaps the windows version via wine would work, but uinfortunately I'm not good enough with scripting. Any thoughts?

Rgds,

.brum
Back to top
View user's profile Send private message
nuke
n00b
n00b


Joined: 14 Sep 2003
Posts: 30

PostPosted: Sun Dec 09, 2007 8:31 pm    Post subject: Reply with quote

Nifty script, and written very maintainably. Thanks!

I did find that the regex you provide resulted in one more level of quoting than was necessary, and I've also got a few pathologically tagged files, so I modified it slightly:
Code:

--- flac2any    2007/12/09 19:27:40     1.1                                                                                         
+++ flac2any    2007/12/09 20:13:54                                                                                                 
@@ -44,7 +44,7 @@ ALLTAGS="ARTIST ALBUM TITLE DISCNUMBER T
                                                                                                                                   
 # sed expression to enclose its input in single-quotes, surrounding any single                                                     
 # quotes in the input with double quotes.                                                                                         
-QUOTING_EXPR="-e s;';'"'"'"'"'"'"';g -e s;.*;'&';"                                                                                 
+QUOTING_EXPR="-e s;^'\(.*\)'$;\1; -e s;';'"'"'"'"'"'"';g -e s;\`;; -e s;.*;'&';"                                                   
 QUOTEQUOTES="sed $QUOTING_EXPR"                                                                                                   
                                                                                                                                   
                                                                                                                                   
Back to top
View user's profile Send private message
Zucca
Apprentice
Apprentice


Joined: 14 Jun 2007
Posts: 201
Location: Helsinki, Finland

PostPosted: Wed Feb 11, 2009 3:28 pm    Post subject: Reply with quote

Dirk.R.Gently wrote:
Tips and Tricks sure. I think this needs to be in portage! Freakin' awesome! To an average lay person, I tried to convert flac to ogg, how does this convert, using oggenc? I tried previously to use oggenc without success:
Code:
oggenc in.flac -q 6 -o out.ogg

and got:
Code:
ERROR: Input file "in.flac" is not a supported format

Actually this is a bit strange, I've seen it work on other sites, but with your script it works. Also it could be useful to name the dependencies in your script, i.e. vorbis-tools...
Try piping: flac <opts> | oggenc <opts>
(I haven't read oggenc man pages, so I'm not sure if this works.)

Also ffmpeg could do the thing...
_________________
Threading support for your bash scripts.
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Multimedia All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum