diff --git a/chapters.txt b/chapters.txt deleted file mode 100644 index cdccca7..0000000 --- a/chapters.txt +++ /dev/null @@ -1,4 +0,0 @@ -0:00:00.000 Intro -0:01:00.000 First Minute -0:02:10.000 Two Minutes and ten seconds -0:02:20.420 Two Minutes and twenty seconds, and then some diff --git a/example.yaml b/example.yaml new file mode 100644 index 0000000..2f36c22 --- /dev/null +++ b/example.yaml @@ -0,0 +1,13 @@ +title: "A Test" +artist: "Tony the tester" +date: "2017-01-14" +comment: "This is comment" +language: "de" + +cover: "cover.jpg" + +chapters: + "0:00:00.000" : "Intro" + "0:01:00.000" : "First Minute" + "0:02:10.000" : "Two Minutes and ten seconds" + "0:02:20.420" : "Two Minutes and twenty seconds, and then some" diff --git a/multitag.py b/multitag.py index b8ef313..0862d80 100644 --- a/multitag.py +++ b/multitag.py @@ -4,9 +4,14 @@ import sys import os import subprocess +import yaml + import mutagen from mutagen.id3 import ID3, CTOC, CHAP, TIT2, CTOCFlags +from mutagen.easyid3 import EasyID3 +from mutagen.easymp4 import EasyMP4 +YAML_KEYS = ["title", "artist", "date", "comment", "cover", "language", "chapters"] def time_to_milliseconds(time_str): time_parts = list(map(lambda t: float(t), time_str.split(':'))) @@ -18,23 +23,36 @@ def time_to_milliseconds(time_str): return time - -def read_chapter_file(chapter_file): - chapters = [] - +def read_tag_file(chapter_file): if not os.path.isfile(chapter_file): raise RuntimeError("Chapter file %s does not exist" % chapter_file) - for line in open(chapter_file, "r"): - line = line.strip() - if line != "": - time, name = line.split(maxsplit=1) - chapters += [(time, name)] + tags = yaml.load(open(chapter_file, 'r')) - return chapters + for key in YAML_KEYS: + if not key in tags: + raise RuntimeError("Expected key %s not found in yaml" % key) -def make_mp3_chapters(chapters, audio): + tags['chapters'] = sorted(tags['chapters'].items(), key=lambda x: x[0]) + + return tags + + +def make_mp3_tags(tags, path): + id3 = EasyID3(path) + + id3['title'] = tags['title'] + id3['artist'] = tags['artist'] + id3['date'] = tags['date'] + id3['language'] = tags['language'] + + id3.save() + + +def make_mp3_chapters(chapters, path): + audio = mutagen.File(path) + file_length = int(audio.info.length * 1000) element_ids = [(u"ch%d" % i) for i in range(0, len(chapters))] @@ -60,6 +78,16 @@ def make_mp3_chapters(chapters, audio): ])) + +def make_mp4_tags(tags, path): + mp4 = EasyMP4(path) + + mp4['title'] = tags['title'] + mp4['artist'] = tags['artist'] + mp4['date'] = tags['date'] + + mp4.save() + def make_mp4_chapters(chapters, path): chap_path = "%s.chapters.txt" % os.path.splitext(path)[0] chapter_file = open(chap_path, 'w') @@ -72,39 +100,50 @@ def make_mp4_chapters(chapters, path): os.remove(chap_path) +def make_ogg_tags(tags, audio): + audio.tags['TITLE'] = tags['title'] + audio.tags['ARTIST'] = tags['artist'] + audio.tags['DATE'] = tags['date'] + audio.tags['LANGUAGE'] = tags['language'] + audio.tags['COMMENT'] = tags['comment'] + + audio.save() def make_ogg_chapters(chapters, audio): + for i in range(0, len(chapters)): num = str(i).zfill(3) audio.tags['CHAPTER%s' % num] = chapters[i][0] audio.tags['CHAPTER%sNAME' % num] = chapters[i][1] + audio.save() def main(): if len(sys.argv) < 3: - print("Usage: %s chapterfile mediafile1 mediafile2 mediafile3 ..." % sys.argv[0]) + print("Usage: %s tagfile mediafile1 mediafile2 mediafile3 ..." % sys.argv[0]) sys.exit(0) - chapter_file_path = sys.argv[1] - media_files = sys.argv[1:] + tag_file_path = sys.argv[1] + media_files = sys.argv[2:] - - chapters = read_chapter_file(chapter_file_path) + tags = read_tag_file(tag_file_path) for path in media_files: print("Adding chapters to: %s" % path) + audio = mutagen.File(path) if isinstance(audio, mutagen.mp3.MP3): - make_mp3_chapters(chapters, audio) - audio.save() + make_mp3_tags(tags, path) + make_mp3_chapters(tags['chapters'], path) elif isinstance(audio, mutagen.mp4.MP4): - make_mp4_chapters(chapters, path) + make_mp4_tags(tags, path) + make_mp4_chapters(tags['chapters'], path) elif isinstance(audio, mutagen.oggvorbis.OggVorbis) or isinstance(audio, mutagen.oggopus.OggOpus): - make_ogg_chapters(chapters, audio) - audio.save() + make_ogg_tags(tags, audio) + make_ogg_chapters(tags['chapters'], audio) else: print("Skipping unsupported file: %s" % path)