lyncd

A better way to losslessly join MP3 files

I recently revisited a previous how-to I wrote up two years ago, thinking that there must be a better way to losslessly combine multiple MP3 files than to use 3 separate utilities.

The good news is, I think I’ve found the one true method. The bad news is, it still uses 3 programs. But, just like last time, the commands are short and sweet, and it’s easy to throw them into a shell script.

To summarize from before, we want to:

  • Losslessly join multiple files together (no re-encoding).
  • Correctly copy metadata (ID3 tags, including cover art etc.) to the final file, while stripping the metadata of the other input files (i.e., we don’t want to just do cat 1.mp3 2.mp3 > all.mp3 and end up with leftover tags in the middle of the MPEG data stream).
  • Either remove the VBR (aka Xing) header or else correct it so that file duration is correct and seeking works in iTunes and on iPods, among others.

So, what’s new? The old way produces a file with no VBR header, while the new way corrects the VBR header. Should you care about this? Well, if you ask me, this Xing header should go the way of the company that spawned it back in the 1990s. But, until then … both files work the same in i(Tunes|Pod). There are some other programs (VLC is one) that display the wrong file duration with the old method, although playback and seeking aren’t affected.

I think the internals of the file created by the new method are cleaner, and including the VBR header can improve seek performance. But for you, the choice might only come down to which set of utility programs you’d rather use.

The revised method

First, we use mp3cat, which takes a bunch of mp3 files and joins their data frames together, dumping everything else. Then, like last time, we’ll use id3cp from id3lib to copy the metadata we want onto the joined file. Finally, VBRFix will re-sync the VBR header to the actual size of the resulting file.

In this example, 1.mp3 and 2.mp3 are merged into all.mp3, and metadata is copied from 1.mp3:

cat 1.mp3 2.mp3 | mp3cat - - > tmp.mp3
id3cp 1.mp3 tmp.mp3
vbrfixc --XingFrameCrcProtectIfCan tmp.mp3 all.mp3 && rm tmp.mp3

That’s it! I’ve tested this on both a Linux system and my trusty Powerbook, so it should work on any reasonable *nix.

Some tips: You can also use mp3cat to operate on a directory of files. The command-line version of VBRFix is a little hard to find (I did an svn export of the source, then ran setupNonQtBuild.sh). I also tried mp3tool for this purpose, and while iTunes liked the result end, VLC displayed an incorrect file duration.

Let me know what you think.

Filed under: Systems.  Tagged: , .

8 comments »

  • […] 3 separate utilities. The good news is, I think I’ve found the one true method. The bad… [full post] sticks lyncd codemp3unix 0 0 0 0 0 [16 Mar […]

  • just fyi, `vbrfix` will remove non-mp3 data frames for you too, so there’s actually no need to invoke `mp3cat` (this is handy for me, as on my Debian system `mp3cat` is the only bit I had to compile from source).

    From the wrapper script I created:

    cat “$@” > tmp.mp3
    vbrfix -ri1 -ri2 -always tmp.mp3 “$output” && rm tmp.mp3
    id3cp “$1″ “$output”

    :)

  • Thanks for this. I turned it into a full script.
    This saved me a lot of hassle.. Thanks to Simon for the code aswell, it made making the script easier(I’m rubbish at bash lol). Sadly though Simon’s script didn’t work for me.

    On my PC the command is VbrfixConsole, but I left it as vbrfix in the script. I used a rpm from rpmseek.com, so maybe that’s why.

    Save as “mp3merge”

    #!/bin/bash

    if [ $# -gt 2 ]; then
    output=$1
    shift
    first_part=$1

    cat "$@" | mp3cat - - > tmp.mp3
    id3cp $first_part tmp.mp3
    vbrfix --XingFrameCrcProtectIfCan tmp.mp3 $output && rm tmp.mp3
    else
    echo "You need to enter 3 files as arguments."
    echo "The first argument is the output file."
    echo "The rest of the arguments are the files to be merged."
    echo "E.g."
    echo -e "\tmp3merge final_output.mp3 part1.mp3 part2.mp3 part3.mp3"
    fi

    • Lol, I told you I am bad at bash scripts.
      change the line
      vbrfix –XingFrameCrcProtectIfCan tmp.mp3 $output && rm tmp.mp3
      vbrfix –XingFrameCrcProtectIfCan tmp.mp3 “$output” && rm tmp.mp3

      I missed the double quotes from $output :).

  • Thanks for This Post.
    mp3wrap command works very well instead of mp3cat (which I couldn’t find) and avoids cat command;

    Thanks @CSLATE for the script.
    Here is my modified script to handle everything.

    Save as mp3merge


    #!/bin/bash
    # Author: http://lyncd.com/2011/03/lossless-combine-mp3s/
    # Purpose: Merge Multiple MP3 Files
    # Date: 14-Oct-2011

    if [ $# -gt 2 ]; then
      output=$1
      shift
      first_part=$1
    else
      echo "You need to enter at least 3 files as arguments."
      echo "The first argument is the output file."
      echo "The rest of the arguments are the files to be merged."
      echo "E.g."
      echo -e "->33[1;36mmp3merge final_output.mp3 \part1.mp3 part2.mp3 part3.mp333[0m"
      exit 1
    fi

    check_prereqs(){
      x=0
      # Check for mp3cat
      [[ ! -x `which mp3wrap` ]] && \
      echo -e "33[1;31mPlese install mp3wrap33[0m" && x=1
      
      # Check for id3cp
      [[ ! -x `which id3cp` ]] && \
      echo -e "33[1;31mPlease install libid3-tools33[0m" && x=1
      
      # Check for vbrfix
      [[ ! -x `which vbrfix` ]] && \
      echo -e "33[1;31mPlease install vbrfix33[0m" && x=1
      
      # Exit if id3cp or vbrfix is missing
      [[ $x -eq 1 ]] && echo '-> Exiting' && exit $x
    }
    # Main
    check_prereqs
    mp3wrap -v tmp.mp3 "$@"

    # mp3wrap prefers to rename tmp.mp3 to tmp_MP3WRAP.mp3 (this is annoying)
    id3cp $first_part tmp_MP3WRAP.mp3

    # vbrfix --XingFrameCrcProtectIfCan tmp.mp3 "$output" && rm tmp.mp3

    # In Ubuntu/Debian the option '--XingFrameCrcProtectIfCan' is meaningless
    vbrfix tmp_MP3WRAP.mp3 "$output"

    # Figure out how vbrfix faired
    if [ $? -eq 255 ]; then
      # file is not Not VBR (we move our tmp_MP3WRAP.mp3 to $output)
      mv tmp_MP3WRAP.mp3 "$output"
    elif [ $? -eq 0 ]; then
      rm tmp_MP3WRAP.mp3
    fi

  • This was a very helpful resource, a quick and easy sollution to my question. It doesn’t get any better!

    Thank you.

  • I’m using this method on Mac OS X lion and it seems to work well. itunes AND vlc media player 2.0.3 show proper information and play just fine. At least with the two sets of tracks I wrapped into two containers.

    Thanks for the updated procedure.

Add a comment

You can also log in (or register) for easier commenting on lyncd.