Here is a small Bash script that converts any supported ffmpeg video format; such as .MKV, .MP4 or .MOV and extracts the audio to an .MP3 file, It will also split that MP3 file into chunks and put them in a convenient directory. You will need to install ffmpeg and mp3splt for your particular platform.
Example Usage:
1 |
./mkv2mp3 "big fat file.mkv" |
This uses ffmpeg to convert “big fat file.mkv” to “big fat file.mp3” and then uses mp3splt to create a directory “big fat file” containing the files 01 – big fat file.mp3, 02 – big fat file.mp3, etc. The MP3 files will be encoded at 128k Constant Bit Rate and each file will be around 50 minutes in length. To install in Debian/Ubuntu use: sudo apt-get install ffmpeg mp3splt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#!/bin/bash INFILE=$1 BITRATE="128k" LENGTH="50.0" FILENAME=$(basename "$INFILE" | cut -d. -f1) if [[ -z "${FILENAME// }" ]]; then echo "filename missing" exit 1 fi echo "$FILENAME" ffmpeg -i "$INFILE" -vn -acodec libmp3lame -b:a "$BITRATE" "$FILENAME".mp3 mp3splt -t "$LENGTH" -d "$FILENAME" -a -o @n+-+@f "$FILENAME".mp3 |
mp3splt can find the audio in a quiet region near where the split is desired rather than midway through a word, this should make for much cleaner playback across tracks.
Alternative Method
This script gives the same results but uses ffmpeg to split the large MP3 file and then adds track numbering metadata using id3v2. To install in Debian/Ubuntu use: sudo apt-get install ffmpeg id3v2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#!/bin/bash INFILE="$1" BITRATE="128k" LENGTH="3000" # 50 minutes in seconds FILENAME=$(basename "$INFILE" | cut -d. -f1) if [[ -z "${FILENAME// }" ]]; then echo "filename missing" exit 1 fi echo "$FILENAME" MP3FILE="$FILENAME".mp3 ffmpeg -y -i "$INFILE" -vn -acodec libmp3lame -b:a "$BITRATE" "$MP3FILE" OUTDIR="$FILENAME" mkdir "$OUTDIR" ffmpeg -y -i "$MP3FILE" -vn -f segment -segment_time "$LENGTH" -c copy "$OUTDIR"/%02d-"$MP3FILE" TRACK=1 IFS=$'\n' FILES=($(find "$OUTDIR" -name "*.mp3" | sort -n)) for file in "${FILES[@]}"; do TN=$(printf %02d $TRACK) id3v2 --track "$TN" --TIT2 "$FILENAME" "$file" TRACK=$((TRACK+1)) done |
Creating an Audiobook
Taking this further, I was thinking that it would be nice to have these converted into the M4B Audiobook format for use on my elderly iPod. The script below assumes that you have processed the files as above and have added metadata tags using a tool like mp3tag (yes I know this is for Windows).
To complete this we need to: Combine the multiple MP3 files into one big file, or read the original big file then convert that to M4B format at 96K bit and add chapter marks every ten minutes. For this I have used ffmpeg v3.2.12 and libmp4v2 (for the mp4chaps utility), to install in Debian/Ubuntu use: sudo apt-get install libmp4v2-dev mp4v2-utils ffmpeg
This script works best from a single MP3 file rather than from those that have been re-combined back into a single file, recombining the files caused ffmpeg to exclaim “invalid packet size” and “invalid data” errors. It is able to tell the difference between a directory and a single MP3 and processes the file accordingly, don’t forget to add metadata tags and cover art before you run the script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 |
#!/bin/bash INFILE="$1" BITRATE="96k" MP3TITLE="" MP3ARTIST="" MP3ALBUM="" MP3COMMENT="" MP3YEAR="" MP3PERFORMER="" #### #### FUNCTIONS #### function setMP3variables() { TAG="$1" VALUE="$2" case "$TAG" in title) MP3TITLE="$VALUE" ;; artist) MP3ARTIST="$VALUE" ;; comment) MP3COMMENT="$VALUE" ;; album_artist) MP3PERFORMER="$VALUE" ;; album) MP3ALBUM="$VALUE" ;; date) MP3YEAR="$VALUE" ;; esac } ## get the tag text from the metadata ## if the line contains an equals (=) then split by the first equals. function getMP3tags() { MP3METADATA="$1" while read -r line do case "$line" in *=*) TAG=${line%%"="*} VALUE=${line#*"="} setMP3variables "$TAG" "$VALUE" ;; esac done < "$MP3METADATA" if [[ ! -z "${MP3TITLE// }" ]]; then OUTFILE="$MP3TITLE".mp3 M4BFILE="$MP3TITLE".m4b fi } #### #### BEGINS #### FILENAME=$(basename "$INFILE" | cut -d. -f1) if [[ -z "${FILENAME// }" ]]; then echo "filename missing" exit 1 fi OLDIFS=$IFS OUTFILE="$FILENAME"_out.mp3 M4BFILE="${OUTFILE:0:-4}".m4b METADATA="${OUTFILE:0:-4}".metadata JPGFILE="${OUTFILE:0:-4}".jpg ## check if input is a diretory if [ -d "$INFILE" ]; then # get the name of the first MP3 file in the directory FIRSTFILE=$(find "$INFILE" -name "*.mp3" | sort -n | head -1) FIRSTFILE=$(basename "$FIRSTFILE") if [[ -z "${FIRSTFILE// }" ]]; then echo "no mp3 files in directory" exit 1 fi ## save the MP3 tag metadata from the first file ffmpeg -y -i "$INFILE/$FIRSTFILE" -f ffmetadata "$METADATA" ## save out the cver art image ffmpeg -y -i "$INFILE/$FIRSTFILE" -an -vcodec copy "$JPGFILE" ## process the tag metadata - put it into the $MP3* strings, set $OUTFILE and $LOGFILE from the title getMP3tags "$METADATA" ## make a bar seperated list of the MP3 files that are to be combined MP3FILES="" IFS=$'\n' FILES=($(find "$INFILE" -name "*.mp3" | sort -n)) for files in "${FILES[@]}"; do MP3FILES+="$files|" done MP3FILES=${MP3FILES:0:-1} ## combine all the MP3 files into one big file ## $OUTFILE is set using either the $FILENAME or $MP3TITLE if that has been used ffmpeg -y -i "concat:$MP3FILES" -vn -vsync 2 -acodec copy "$OUTFILE" else ## process a single MP3 file ## save the MP3 tag metadata from the first file ffmpeg -y -i "$INFILE" -f ffmetadata "$METADATA" ffmpeg -y -i "$INFILE" -an -vcodec copy "$JPGFILE" ## process the tag metadata - put it into the $MP3* strings, set $OUTFILE and $LOGFILE from the title getMP3tags "$METADATA" #ffmpeg -y -i "$INFILE" -vn -vsync 2 -acodec copy "$OUTFILE" OUTFILE="$INFILE" fi ## if there is a cover image, these are are the options for adding it back in WITHJPG=() if [ -e "$JPGFILE" ]; then IFS=$OLDIFS WITHJPG=(-i "$JPGFILE" -metadata:s:v comment="Cover (front)") fi ## create the audiobook M4B file ## and write the metadata into the output file ffmpeg -y -i "$OUTFILE" -i "$METADATA" "${WITHJPG[@]}" -map_metadata 1 -id3v2_version 3 -acodec aac -b:a "$BITRATE" -strict -2 -f mp4 "$M4BFILE" ## add the chapters every 10 minutes mp4chaps -e 600 "$M4BFILE" ## tidy up rm "$MP3METADATA" if [ -e "$JPGFILE" ]; then rm "$JPGFILE" fi #rm "$OUTFILE" echo "finished" |
When encoding to the M4B using a re-combined file I saw a few of these errors from ffmpeg:
1 2 |
[mp3 @ 0x7fffc3bc9ea0] Header missing Error while decoding stream #0:0: Invalid data found when processing input |
These appear to be caused by the mp3splt program from when the original MP3 file was being split into 50 minute chunks, but I can’t hear any effect on the output.
Lots of information about the file can be gotten using mediainfo, to install in Debian/Ubuntu use: sudo apt-get install mediainfo, example use:
1 |
mediainfo --fullscan "big fat file.m4b" |
Links and References
- ffmpeg, encode AAC: https://trac.ffmpeg.org/wiki/Encode/AAC
- ArchLinux audiobook howto