From 57933a18153ba276027c240853b94169f50f8a12 Mon Sep 17 00:00:00 2001 From: LongHairedHacker Date: Sun, 15 Jan 2017 04:12:45 +0100 Subject: [PATCH] Added coverart support Added Readme Added requirements.txt --- .gitignore | 5 +++++ README.md | 42 ++++++++++++++++++++++++++++++++++++++ cover.jpg | Bin 0 -> 11281 bytes example.yaml | 1 - multitag.py | 52 +++++++++++++++++++++++++++++++++++++++++++++-- requirements.txt | 2 ++ 6 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 cover.jpg create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..023050d --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.ogg +*.opus +*.mp3 +*.mp4 +/virtenv diff --git a/README.md b/README.md new file mode 100644 index 0000000..8482c67 --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +Multitag +======== + +A simple tool for adding metadata to multiple audio files in different formats. +Metadata is read from a yaml file and an additional jpg (for coverart) +and can be applied to multiple files at once. +It was developed with linux in mind, but might work on other platforms as well. + + +**Warning:** This is tool is a quick and dirty one day hack and has only been tested using mpv. +Expect bugs and other random weirdness. + + +Features +--------- + +Support audio formats: +* mp3 +* mp4 +* ogg vorbis +* ogg opus + +Supported metadata entries: +* Title +* Artist +* Date +* Language +* Coverart (only jpg format supported) +* Chapters + + +Usage +----- +`multitag.py metadatafile audiofile1 audiofile2 ... audiofileN` + + +Dependencies +------------ +* python3 +* pyyaml +* mutagen +* libm4v2 (for the m4chaps utility) diff --git a/cover.jpg b/cover.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1817594b710bfe0f85fe7b02043b311ec004c72b GIT binary patch literal 11281 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 bG!W%&%fJFA6&M9;3Q5apH5JHvo)-TL`Fr{i literal 0 HcmV?d00001 diff --git a/example.yaml b/example.yaml index 2f36c22..aad27a3 100644 --- a/example.yaml +++ b/example.yaml @@ -3,7 +3,6 @@ artist: "Tony the tester" date: "2017-01-14" comment: "This is comment" language: "de" - cover: "cover.jpg" chapters: diff --git a/multitag.py b/multitag.py index 0862d80..7a9a079 100644 --- a/multitag.py +++ b/multitag.py @@ -3,16 +3,19 @@ import sys import os import subprocess +import base64 import yaml import mutagen -from mutagen.id3 import ID3, CTOC, CHAP, TIT2, CTOCFlags +from mutagen.id3 import ID3, CTOC, CHAP, TIT2, CTOCFlags, APIC 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(':'))) time = 0 @@ -23,6 +26,7 @@ def time_to_milliseconds(time_str): return time + def read_tag_file(chapter_file): if not os.path.isfile(chapter_file): raise RuntimeError("Chapter file %s does not exist" % chapter_file) @@ -50,6 +54,16 @@ def make_mp3_tags(tags, path): id3.save() +def make_mp3_cover(cover, path): + audio = mutagen.File(path) + + jpeg_data = open(cover, "rb").read() + image_tag = APIC(3, 'image/jpeg', 3, 'Cover', jpeg_data) + audio[image_tag.HashKey] = image_tag + + audio.save() + + def make_mp3_chapters(chapters, path): audio = mutagen.File(path) @@ -88,6 +102,17 @@ def make_mp4_tags(tags, path): mp4.save() + +def make_mp4_cover(cover, path): + jpeg_data = open(cover, "rb").read() + cover = mutagen.mp4.MP4Cover(jpeg_data) + + mp4 = mutagen.mp4.MP4(path) + mp4.tags['covr'] = [cover] + + mp4.save() + + def make_mp4_chapters(chapters, path): chap_path = "%s.chapters.txt" % os.path.splitext(path)[0] chapter_file = open(chap_path, 'w') @@ -100,6 +125,8 @@ 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'] @@ -109,8 +136,19 @@ def make_ogg_tags(tags, audio): audio.save() -def make_ogg_chapters(chapters, audio): +def make_ogg_cover(cover, audio): + jpeg_data = open(cover, "rb").read() + + audio['coverartmime'] = 'image/jpeg' + audio['coverartdescription'] = 'Cover' + audio['coverarttype'] = '3' + audio['coverart'] = base64.b64encode(jpeg_data).decode("utf-8") + + 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] @@ -119,6 +157,7 @@ def make_ogg_chapters(chapters, audio): audio.save() + def main(): if len(sys.argv) < 3: @@ -130,6 +169,12 @@ def main(): tags = read_tag_file(tag_file_path) + if not os.path.isfile(tags['cover']): + raise RuntimeError("%s does not exist" % cover) + + if os.path.splitext(tags['cover'])[1] != ".jpg": + raise RuntimeError("Only jpgs are supported as covers") + for path in media_files: print("Adding chapters to: %s" % path) @@ -137,12 +182,15 @@ def main(): if isinstance(audio, mutagen.mp3.MP3): make_mp3_tags(tags, path) + make_mp3_cover(tags['cover'], path) make_mp3_chapters(tags['chapters'], path) elif isinstance(audio, mutagen.mp4.MP4): make_mp4_tags(tags, path) + make_mp4_cover(tags['cover'], path) make_mp4_chapters(tags['chapters'], path) elif isinstance(audio, mutagen.oggvorbis.OggVorbis) or isinstance(audio, mutagen.oggopus.OggOpus): make_ogg_tags(tags, audio) + make_ogg_cover(tags['cover'], audio) make_ogg_chapters(tags['chapters'], audio) else: print("Skipping unsupported file: %s" % path) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e78043e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +mutagen==1.36 +PyYAML==3.12