Home » OS » Linux » Audiobook chapter support for FFprobe (Perl) module

Audiobook chapter support for FFprobe (Perl) module

I have multiple audiobook files (m4b) that ffprobe is able to retrieve the chapters from just fine… except the chapter information is printed to stderr and never in the formatted (STDOUT) output. The Perl module FFprobe doesn’t handle the chapters so I submitted feature request #73803

Feature request is to format the chapter output.

jason@jason-Inspiron-1545 ~/bin $ ffprobe "/home/jason/Audiobooks/Ben Bova/Mars/Mars 1.m4b" 1>/dev/null
  libavutil    51.  7. 0 / 51.  7. 0
  libavcodec   53.  5. 0 / 53.  5. 0
  libavformat  53.  2. 0 / 53.  2. 0
  libavdevice  53.  0. 0 / 53.  0. 0
  libavfilter   2.  4. 0 /  2.  4. 0
  libswscale    2.  0. 0 /  2.  0. 0
  libpostproc  52.  0. 0 / 52.  0. 0
[mov,mp4,m4a,3gp,3g2,mj2 @ 0xddfac0] max_analyze_duration reached
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from '/home/jason/Audiobooks/Ben Bova/Mars/Mars 1.m4b':
  Metadata:
    major_brand     : M4B 
    minor_version   : 0
    compatible_brands: M4B mp42isom
    creation_time   : 2009-09-08 16:19:29
    album           : Mars
    artist          : Ben Bova
    genre           : Audiobook
  Duration: 03:51:23.41, start: 0.000000, bitrate: 81 kb/s
    Chapter #0.0: start 0.000000, end 2779.567914
    Metadata:
      title           : Mars - 01 of 24
    Chapter #0.1: start 2779.567914, end 5555.049161
    Metadata:
      title           : Mars - 02 of 24
    Chapter #0.2: start 5555.049161, end 8334.617075
    Metadata:
      title           : Mars - 03 of 24
    Chapter #0.3: start 8334.617075, end 11110.098322
    Metadata:
      title           : Mars - 04 of 24
    Chapter #0.4: start 11110.098322, end 13883.419864
    Metadata:
      title           : Mars - 05 of 24
    Stream #0.0(und): Audio: aac, 44100 Hz, stereo, s16, 80 kb/s
    Metadata:
      creation_time   : 2009-09-08 16:19:29
    Stream #0.1(eng): Subtitle: text / 0x74786574
    Metadata:
      creation_time   : 2009-09-08 17:31:00
Unsupported codec with id 94213 for input stream 1

patch to add m4b chapter support:

82c82
< my ($tree, $branch, $tag, $stream);
---
>     my ($tree, $branch, $tag, $stream, $chapter);
100c100,108
< }
---
> 	} elsif ($line =~ m/Chapter \#(\d+\.*\d+): start (\d+\.*\d+)\, end (\d+\.*\d+)/i) {
>       my ($start, $end) = ($2, $3);
>       $chapter = $1;
>       $chapter =~ s/\.//g;
>       $chapter =~ s/^0+(\d)/$1/;
> 
>       $$tree{chapters}{$chapter} = { start => $start, end => $end };
>     } elsif ($line =~ /title\s+: (.+)$/) {
>       $$tree{chapters}{$chapter}{title} = $1;
101a110
>   }
Share Button

Comments

  1. Marty says:

    In case it helps the next person, I slapped together a shell script to do the same using only ffmpeg. It surely does not handle every scenario, but it should be enough either to work or get someone started. It puts the chapters in a directory named the same as the input file (but lacking the m4b extension).


    #!/usr/bin/ksh

    # pass in parm for file name

    # generic usage func
    function usage
    {
    echo usage: $0 mkv_file
    exit 1
    }

    # check parms
    [ $# -eq 1 ] || usage

    # set parms to friendly names
    src_fn=${1}
    dir_fn=${1%%.m4b}
    met_fn=${1%%.m4b}.meta

    # can we even read the input file?
    if [ -r "${src_fn}" ]; then; else echo cannot read ${src_fn}; exit 1; fi

    # make a dir if missing
    if [ -d "${dir_fn}" ]; then; else mkdir "${dir_fn}"; fi

    # get the metadata
    rm -f "${met_fn}"
    ffmpeg -i "${src_fn}" -f ffmetadata "${met_fn}" 2> /dev/null

    # get a chapter count
    numchap=$(grep "^.CHAPTER" "${met_fn}" | wc -l | sed "s/ //g")
    echo Chapters: ${numchap}

    # get time base
    timebase=$(grep "^TIMEBASE" "${met_fn}" | head -1 | cut -d/ -f2)
    echo Time base: ${timebase}

    # iterate each chapter to find title, start, stop
    for i in $(seq 1 ${numchap})
    do
    # find the title, but skip the first which is the complete title
    # as a hack, tail the last $numchap titles
    chap_title=$(grep "^title=" "${met_fn}" | tail -${numchap} | head -${i} | tail -1 | cut -d= -f2-)
    chap_start=$(echo "12 k" $(grep "^START=" "${met_fn}" | tail -${numchap} | head -${i} | tail -1 | cut -d= -f2-) ${timebase} "/pq" | dc)
    chap_end=$(echo "12 k" $(grep "^END=" "${met_fn}" | tail -${numchap} | head -${i} | tail -1 | cut -d= -f2-) ${timebase} "/pq" | dc)
    chap_dur=$(echo "12 k" ${chap_end} ${chap_start} "-pq" | dc)
    chap_track="${i}/${numchap}"
    echo Chapter: ${i} "${chap_title}" ${chap_start} ${chap_end} ${chap_dur} ${chap_track}

    # get a printable number prefix
    i_p=$(printf "%02d" ${i})

    # cut it out
    ffmpeg -i "${src_fn}" -metadata title="${chap_title}" -metadata track="${chap_track}" -ss ${chap_start} -t ${chap_dur} "${dir_fn}/${i_p} ${chap_title}".mp3
    done

  2. Marty says:

    In case it helps the next person, I put together a quick shell script which breaks out chapters to mp3 using only ffmpeg.


    #!/usr/bin/ksh

    # pass in parm for file name

    # generic usage func
    function usage
    {
    echo usage: $0 mkv_file
    exit 1
    }

    # check parms
    [ $# -eq 1 ] || usage

    # set parms to friendly names
    src_fn=${1}
    dir_fn=${1%%.m4b}
    met_fn=${1%%.m4b}.meta

    # can we even read the input file?
    if [ -r "${src_fn}" ]; then; else echo cannot read ${src_fn}; exit 1; fi

    # make a dir if missing
    if [ -d "${dir_fn}" ]; then; else mkdir "${dir_fn}"; fi

    # get the metadata
    rm -f "${met_fn}"
    ffmpeg -i "${src_fn}" -f ffmetadata "${met_fn}" 2> /dev/null

    # get a chapter count
    numchap=$(grep "^.CHAPTER" "${met_fn}" | wc -l | sed "s/ //g")
    echo Chapters: ${numchap}

    # get time base
    timebase=$(grep "^TIMEBASE" "${met_fn}" | head -1 | cut -d/ -f2)
    echo Time base: ${timebase}

    # iterate each chapter to find title, start, stop
    for i in $(seq 1 ${numchap})
    do
    # find the title, but skip the first which is the complete title
    # as a hack, tail the last $numchap titles
    chap_title=$(grep "^title=" "${met_fn}" | tail -${numchap} | head -${i} | tail -1 | cut -d= -f2-)
    chap_start=$(echo "12 k" $(grep "^START=" "${met_fn}" | tail -${numchap} | head -${i} | tail -1 | cut -d= -f2-) ${timebase} "/pq" | dc)
    chap_end=$(echo "12 k" $(grep "^END=" "${met_fn}" | tail -${numchap} | head -${i} | tail -1 | cut -d= -f2-) ${timebase} "/pq" | dc)
    chap_dur=$(echo "12 k" ${chap_end} ${chap_start} "-pq" | dc)
    chap_track="${i}/${numchap}"
    echo Chapter: ${i} "${chap_title}" ${chap_start} ${chap_end} ${chap_dur} ${chap_track}

    # get a printable number prefix
    i_p=$(printf "%02d" ${i})

    # cut it out
    ffmpeg -i "${src_fn}" -metadata title="${chap_title}" -metadata track="${chap_track}" -ss ${chap_start} -t ${chap_dur} "${dir_fn}/${i_p} ${chap_title}".mp3
    done

Leave a Reply

Your email address will not be published. Required fields are marked *

*
*

Facebook login by WP-FB-AutoConnect