mutagen
mutagen copied to clipboard
Chapter tags write sequence
I'm trying to use mutagen to write chapters info into an audiobook. Example code is below.
import logging
import shutil
import datetime
from mutagen.mp3 import MP3
from mutagen.id3 import (
ID3, TIT2, CHAP, CTOC, CTOCFlags, Encoding,
)
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
SOURCE_MP3 = 'example.mp3'
CHAPTERS_MP3 = 'example_with_chapters.mp3'
def run():
chapters = [
{
"id": "ch01",
"text": "ABCD",
"start_time": 0,
"end_time": 10000
},
{
"id": "ch02",
"text": "ABCDEF",
"start_time": 10000,
"end_time": 20000
},
{
"id": "ch03",
"text": "ABC",
"start_time": 20000,
"end_time": 30000
},
{
"id": "ch04",
"text": "A",
"start_time": 30000,
"end_time": 40000
}
]
# make a copy of the source file
shutil.copyfile(SOURCE_MP3, CHAPTERS_MP3)
# write chapter tags to mp3
audio_book = MP3(CHAPTERS_MP3, ID3=ID3)
for m in chapters:
audio_book.tags.add(
CHAP(element_id=m['id'], start_time=m['start_time'], end_time=m['end_time'],
sub_frames=[TIT2(encoding=Encoding.UTF8, text=[m['text']])]))
start_time = datetime.timedelta(milliseconds=m['start_time'])
end_time = datetime.timedelta(milliseconds=m['end_time'])
logger.debug(
'Added chap tag => "{}": {}-{} "{}"'.format(
m['id'],
start_time, end_time,
m['text']),
)
audio_book.tags.add(
CTOC(element_id='toc', flags=CTOCFlags.TOP_LEVEL | CTOCFlags.ORDERED,
child_element_ids=[ch['id'] for ch in chapters],
sub_frames=[TIT2(encoding=Encoding.UTF8, text=['Table of Contents'])]))
logger.debug('CTOC child_element_ids: %s', [ch['id'] for ch in chapters])
audio_book.save()
if __name__ == '__main__':
run()
The log output looks fine.
DEBUG:__main__:Added chap tag => "ch01": 0:00:00-0:00:10 "ABCD"
DEBUG:__main__:Added chap tag => "ch02": 0:00:10-0:00:20 "ABCDEF"
DEBUG:__main__:Added chap tag => "ch03": 0:00:20-0:00:30 "ABC"
DEBUG:__main__:Added chap tag => "ch04": 0:00:30-0:00:40 "A"
DEBUG:__main__:CTOC child_element_ids: ['ch01', 'ch02', 'ch03', 'ch04']
A check with ffprobe ffprobe -hide_banner example_with_chapters.mp3
reveals
Chapter #0:0: start 30.000000, end 40.000000
Metadata:
title : A
Chapter #0:1: start 20.000000, end 30.000000
Metadata:
title : ABC
Chapter #0:2: start 0.000000, end 10.000000
Metadata:
title : ABCD
Chapter #0:3: start 10.000000, end 20.000000
Metadata:
title : ABCDEF
It looks like to me that the chapters tags are being written to file in sequence according to the length of the title text.
This causes an issue when I try to use ffmpeg to convert the mp3 into an m4b.
ffmpeg -i example_with_chapters.mp3 -map 0:a -c:a aac -b:a 64k example_with_chapters.m4b
ffprobe of the resultant m4b show the chapters sequence and timings become completely messed up:
Chapter #0:0: start 0.000000, end 19.950000
Metadata:
title : A
Chapter #0:1: start 19.950000, end 19.951000
Metadata:
title : ABC
Chapter #0:2: start 19.951000, end 19.952000
Metadata:
title : ABCD
Chapter #0:3: start 19.952000, end 29.952000
Metadata:
title : ABCDEF
As an additional note, similar code using eyed3 worked flawlessly. The chapters were written out in expected sequence and coversion with ffmpeg to m4b had no issue.
Am I doing something wrong with mutagen? Or is this expected behaviour?
The order is defined in CTOC. In id3 the order of how frames appear in the file is not significant.
I guess ffmpeg doesn't support it fully.
We might be able to sort it... I guess based on start time?
Thanks for the response.
I'm not familiar with the internal workings of mutagen, but yes, sorting based on start time sounds like a pretty reasonable choice.
Hello @lazka,
I also came accross this issue in ffmpeg recently when writing metadata with mutagen.
It would be great to have a solution in mutagen to sort the chapter marks by start time when writing CHAP
tags like you suggested.
From what I could understand of the code the sorting is made here : https://github.com/quodlibet/mutagen/blob/30f373fa6d56e1afa17d48a0c6f3e111267fbd32/mutagen/id3/_tags.py#L199
Hello!
Just stumbled upon this as well. Is there anything we can do to support? A hint on how you wish this to be fixed would be appreciated, so one could get things going.
Thanks!
@lazka I took a shot at fixing this. Appreciate if you can take a look at the PR.
thanks
It seems if CHAP frames aren't ordered according to start_time
, Apple Podcasts simply won't render them if though according to the spec the order should be defined by CTOC.