Added coverart support
Added Readme Added requirements.txt
This commit is contained in:
parent
0b83358e37
commit
57933a1815
|
@ -0,0 +1,5 @@
|
|||
*.ogg
|
||||
*.opus
|
||||
*.mp3
|
||||
*.mp4
|
||||
/virtenv
|
|
@ -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)
|
|
@ -3,7 +3,6 @@ artist: "Tony the tester"
|
|||
date: "2017-01-14"
|
||||
comment: "This is comment"
|
||||
language: "de"
|
||||
|
||||
cover: "cover.jpg"
|
||||
|
||||
chapters:
|
||||
|
|
52
multitag.py
52
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)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
mutagen==1.36
|
||||
PyYAML==3.12
|
Loading…
Reference in New Issue