Perl Script to Convert an Audiobook (m4b) to mp3 files splitting on the chapters

I have an Nissan Altima with a BOSE radio that allows me to hook up an USB thumbdrive containing mp3 files. The problem is most of my audiobooks are in m4b format. Previously I’ve used tools like mp3splt and tried to split on ‘silence’ or timed increments (say 15 min) but I was getting mp3 files that would be split in midsentence and sometimes midword. It became very annoying after awhile.

So, I came up with a really simple script to convert an audiobook (m4b) into mp3 files splitting on the chapters. We are dependent on FFmpeg::Command and a modified FFprobe Perl module.

In the following example, we are converting a Ben Bova audiobook but we are going to specify to start the track numbering at “10” because the 2nd file ended with track “9”.

jason@jason-Inspiron-1545 ~/bin $ ./test_mp4_info.pl -i "/home/jason/Audiobooks/Ben Bova/Mars/Mars 3.m4b" -o mp3 -a "Mars" -t 10
Converting "Mars 3.m4b" to "mp3/010 Mars.mp3"...
	album: Mars
	artist: Ben Bova
	title: 010 - Mars
	genre: Audiobook
	track: 10
	... COMPLETE
Converting "Mars 3.m4b" to "mp3/011 Mars.mp3"...
	album: Mars
	artist: Ben Bova
	title: 011 - Mars
	genre: Audiobook
	track: 11
	... COMPLETE
Converting "Mars 3.m4b" to "mp3/012 Mars.mp3"...
	album: Mars
	artist: Ben Bova
	title: 012 - Mars
	genre: Audiobook
	track: 12
	... COMPLETE
Converting "Mars 3.m4b" to "mp3/013 Mars.mp3"...
	album: Mars
	artist: Ben Bova
	title: 013 - Mars
	genre: Audiobook
	track: 13
	... COMPLETE
Converting "Mars 3.m4b" to "mp3/014 Mars.mp3"...
	album: Mars
	artist: Ben Bova
	title: 014 - Mars
	genre: Audiobook
	track: 14
	... COMPLETE

Source code:

#!/usr/bin/perl

use strict;
use warnings;

use lib qw(/home/jason/bin);

use Getopt::Std;
use File::Basename;
use FFmpeg::Command;
use FFprobe;

$|++;

###############################
sub _encode_mp3 {
  my ($input_file, $output_dir, $album, $starting_track) = @_;

  my %tags = ();
  my $track_number;
  my $mp4 = FFprobe->probe_file($input_file);
  my $base_output_file = basename($input_file);
  $base_output_file =~ s/\.\w+$//;

  if (exists $mp4->{format}->{'TAG:comment'}) {
    $tags{genre} = $mp4->{format}->{'TAG:comment'};
    $tags{genre} =~ s/("')//g;
  }

  if (exists $mp4->{format}->{'TAG:genre'}) {
    $tags{genre} = $mp4->{format}->{'TAG:genre'};
    $tags{genre} =~ s/("')//g;
  }

  if (exists $mp4->{format}->{'TAG:artist'}) {
    $tags{artist} = $mp4->{format}->{'TAG:artist'};
    $tags{artist} =~ s/("')//g;
  }

  if ($album) {
    $tags{album} = $album;
  } elsif (exists $mp4->{format}->{'TAG:album'}) {
    $tags{album} = $mp4->{format}->{'TAG:album'};
  }

  $tags{album} =~ s/("')//g;
  $track_number = $starting_track if $starting_track;

  foreach my $chapter (sort keys %{$mp4->{chapters}}) {
    unless ($starting_track) {
      $track_number = $chapter;
    }

    my $output_file = sprintf "%s/%03d %s.mp3", $output_dir, $track_number, $tags{album};
    my $start = $mp4->{chapters}->{$chapter}->{start};
    my $duration = $mp4->{chapters}->{$chapter}->{end} - $start;
    my @options = ();

    if ($album) {
      $tags{title} = sprintf "%03d - %s", $track_number, $album;
    } else {
      if (exists $mp4->{format}->{'TAG:title'}) {
        $tags{title} = sprintf "%03d - %s", $track_number, $mp4->{format}->{'TAG:title'};
      } else {
        $tags{title} = sprintf "%03d - %s", $track_number, $base_output_file;
      }
    }

    $tags{title} =~ s/("')//g;

    my $ffmpeg = FFmpeg::Command->new;

    $ffmpeg->input_options({
        file => $input_file,
     });

    $ffmpeg->output_options({
     'file' => $output_file,
     'audio_codec' => 'libmp3lame',
     'audio_bit_rate' => 64,
     });

    printf "Converting \"%s\" to \"%s\"...\n", basename($input_file), $output_file;

Share Button