summaryrefslogtreecommitdiff
path: root/formats/mp3.py
blob: 67019cc90c94495b39b7668be02f9eddaae23d6d (plain)
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
115
116
from formats.__base__ import *
from coding import to_bytes

import subprocess
import struct
import array

def synchsafe(num):
	if num <= 0x7f:
		return num

	return synchsafe(num >> 7) << 8 | num & 0x7f

class ID3Tagger:
	# id3v2 frame mapping
	__mapping = {
		"album":	"TALB",
		"artist":	"TPE1",
		"composer":	"TCOM",
		"date":		"TDRC",
		"title":	"TIT2",
		"tracknumber":	"TRCK",
	}

	# id3v1 offsets
	__offset = {
		"title": 3,
		"artist": 33,
		"album": 63,
	}

	@staticmethod
	def header(size):
		return struct.pack(">3s3BI", b"ID3", 4, 0, 0, synchsafe(size))

	@staticmethod
	def frame(name, data):
		size = len(data) + 1
		hdr = struct.pack(">4sIHB", name, size, 0, 3)
		return hdr + data

	def __init__(self):
		self.frames = []

		self.v1 = array.array("B", b"\x00" * 128)
		struct.pack_into("3s", self.v1, 0, b"TAG")
		struct.pack_into("B", self.v1, 127, 0xff)

	def frame_size(self):
		return sum(map(len, self.frames))

	def add(self, tag, value):
		value = to_bytes(value)

		if tag in self.__mapping:
			key = to_bytes(self.__mapping[tag])
			self.frames.append(self.frame(key, value))

		off = self.__offset.get(tag)
		if off:
			struct.pack_into("30s", self.v1, off, value)
		elif tag == "date":
			struct.pack_into("4s", self.v1, 93, value)
		elif tag == "tracknumber":
			number = int(value.partition(b"/")[0])
			struct.pack_into("B", self.v1, 126, number)

	def write(self, path):
		fp = open(path, "r+b")
		data = fp.read()

		fp.seek(0)
		fp.truncate(0)

		# save id3v2
		fp.write(self.header(self.frame_size()))
		for frame in self.frames:
			fp.write(frame)

		fp.write(data)

		# save id3v1
		self.v1.tofile(fp)

		fp.close()

class Mp3Handler(BaseHandler):
	name = "mp3"
	ext = "mp3"

	def encode(self, opt, info):
		self.add("cust ext=%s sox -" % self.ext)

		if opt.bitrate is not None:
			self.add("-C %d" % opt.bitrate)

		self.add_sox_args(opt, info)
		self.add("%f")

		return self.build()

	def tag(self, path, tags):
		tagger = ID3Tagger()

		for k, v in tags.items():
			if v and k not in ("tracknumber", "tracktotal"):
				tagger.add(k, v)

		number = "%d/%d" % (tags["tracknumber"], tags["tracktotal"])
		tagger.add("tracknumber", number)

		tagger.write(path)
		return True

def init():
	return Mp3Handler