-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathmp3file.py
More file actions
114 lines (91 loc) · 3.56 KB
/
mp3file.py
File metadata and controls
114 lines (91 loc) · 3.56 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
from typing import List
from pathlib import Path
import eyed3
from chapter import Chapter
from overdrive import MediaMarker
from timestamp import Timestamp
class Mp3File(object):
def __init__(self, filepath):
self._filepath = Path(filepath)
self._mp3 = eyed3.load(self._filepath)
if self._mp3 is not None:
self._media_markers = self._read_media_markers()
self._chapters = self._read_id3v2_chapters()
if len(self._chapters) == 0:
self._chapters = self.media_markers_as_chapters
def _read_id3v2_chapters(self):
chapters = []
# Find top-level toc
for toc in self._mp3.tag.table_of_contents:
if toc.toplevel:
for ch_eid in toc.child_ids:
ch = self._mp3.tag.chapters.get(ch_eid)
title = ch.title
start, end = map(Timestamp.from_milliseconds, ch.times)
chapters.append(Chapter(title, start=start, end=end))
return chapters
def _read_media_markers(self):
user_frames = self._mp3.tag.user_text_frames
if 'OverDrive MediaMarkers' in user_frames:
markers_frame = user_frames.get('OverDrive MediaMarkers')
return MediaMarker.from_xml(markers_frame.text)
return []
def clean(self):
"""
Removes existing chapter tags from the mp3 file if present.
"""
# Remove chapters
for chap in self._mp3.tag.chapters:
self._mp3.tag.chapters.remove(chap.element_id)
# Remove TOCs
for toc in self._mp3.tag.table_of_contents:
self._mp3.tag.table_of_contents.remove(toc.element_id)
self._mp3.tag.save()
def save(self) -> None:
"""
Saves the chapters
:return:
"""
self.clean()
if len(self.chapters) == 0:
return
toc = self._mp3.tag.table_of_contents.set(b'toc', toplevel=True, description=u'Table of Contents')
for index, chapter in zip(range(1, len(self.chapters) + 1), self.chapters):
start = chapter.start.total_milliseconds
end = chapter.end.total_milliseconds
ch_eid = 'ch{}'.format(index).encode('ascii')
ch = self._mp3.tag.chapters.set(ch_eid, (start, end))
ch.title = chapter.title
toc.child_ids.append(ch.element_id)
self._mp3.tag.save()
@property
def path(self):
return self._filepath
@property
def duration(self) -> int:
"""
Duration of audio file in milliseconds
:return: number of milliseconds in mp3 file
"""
return int(self._mp3.info.time_secs * 1000)
@property
def chapters(self) -> List[Chapter]:
return self._chapters
@property
def media_markers_as_chapters(self) -> List[Chapter]:
markers = self.media_markers
chapters = []
if len(markers) > 0:
for prev, cur in zip(markers[:-1], markers[1:]):
chapters.append(Chapter(title=prev.name, start=prev.time, end=cur.time))
last_marker = markers[-1]
chapters.append(Chapter(title=last_marker.name,
start=last_marker.time,
end=Timestamp.from_milliseconds(self.duration)))
return chapters
@property
def media_markers(self):
return self._media_markers
def __str__(self):
return "{}:\n{}".format(self.path.name,
'\n'.join([str(ch) for ch in self._chapters]))