TonieToolbox 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- TonieToolbox/__init__.py +5 -0
- TonieToolbox/__main__.py +145 -0
- TonieToolbox/audio_conversion.py +194 -0
- TonieToolbox/constants.py +14 -0
- TonieToolbox/dependency_manager.py +378 -0
- TonieToolbox/filename_generator.py +94 -0
- TonieToolbox/logger.py +57 -0
- TonieToolbox/ogg_page.py +588 -0
- TonieToolbox/opus_packet.py +219 -0
- TonieToolbox/tonie_analysis.py +522 -0
- TonieToolbox/tonie_file.py +411 -0
- TonieToolbox/tonie_header.proto +11 -0
- TonieToolbox/tonie_header_pb2.py +99 -0
- tonietoolbox-0.1.0.dist-info/METADATA +301 -0
- tonietoolbox-0.1.0.dist-info/RECORD +19 -0
- tonietoolbox-0.1.0.dist-info/WHEEL +5 -0
- tonietoolbox-0.1.0.dist-info/entry_points.txt +2 -0
- tonietoolbox-0.1.0.dist-info/licenses/LICENSE.md +674 -0
- tonietoolbox-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,219 @@
|
|
1
|
+
"""
|
2
|
+
Classes and functions for handling Opus audio packets
|
3
|
+
"""
|
4
|
+
|
5
|
+
import struct
|
6
|
+
from .constants import SAMPLE_RATE_KHZ
|
7
|
+
from .logger import get_logger
|
8
|
+
|
9
|
+
# Setup logging
|
10
|
+
logger = get_logger('opus_packet')
|
11
|
+
|
12
|
+
|
13
|
+
class OpusPacket:
|
14
|
+
"""
|
15
|
+
Represents an Opus audio packet in an Ogg container.
|
16
|
+
|
17
|
+
This class provides methods to parse, modify, and write Opus packets,
|
18
|
+
with particular focus on features needed for Tonie compatibility.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(self, filehandle, size=-1, last_size=-1, dont_parse_info=False):
|
22
|
+
"""
|
23
|
+
Initialize a new OpusPacket.
|
24
|
+
|
25
|
+
Args:
|
26
|
+
filehandle: File handle to read the packet data from, or None to create an empty packet
|
27
|
+
size: Size of the packet in bytes
|
28
|
+
last_size: Size of the previous packet in bytes
|
29
|
+
dont_parse_info: If True, don't parse the packet information even if this is a first packet
|
30
|
+
"""
|
31
|
+
self.config_value = None
|
32
|
+
self.stereo = None
|
33
|
+
self.framepacking = None
|
34
|
+
self.padding = None
|
35
|
+
self.frame_count = None
|
36
|
+
self.frame_size = None
|
37
|
+
self.granule = None
|
38
|
+
|
39
|
+
if filehandle is None:
|
40
|
+
logger.trace("Creating empty opus packet")
|
41
|
+
return
|
42
|
+
|
43
|
+
self.size = size
|
44
|
+
self.data = filehandle.read(self.size)
|
45
|
+
self.spanning_packet = size == 255
|
46
|
+
self.first_packet = last_size != 255
|
47
|
+
|
48
|
+
logger.trace("Created opus packet: size=%d, spanning=%s, first=%s",
|
49
|
+
self.size, self.spanning_packet, self.first_packet)
|
50
|
+
|
51
|
+
if self.first_packet and not dont_parse_info:
|
52
|
+
self.parse_segment_info()
|
53
|
+
|
54
|
+
def get_frame_count(self):
|
55
|
+
"""
|
56
|
+
Get the number of frames in this packet based on its framepacking.
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
int: Number of frames in the packet
|
60
|
+
"""
|
61
|
+
if self.framepacking == 0:
|
62
|
+
return 1
|
63
|
+
elif self.framepacking == 1:
|
64
|
+
return 2
|
65
|
+
elif self.framepacking == 2:
|
66
|
+
return 2
|
67
|
+
elif self.framepacking == 3:
|
68
|
+
unpacked = struct.unpack("<B", self.data[1:2])
|
69
|
+
return unpacked[0] & 63
|
70
|
+
|
71
|
+
def get_padding(self):
|
72
|
+
"""
|
73
|
+
Get the padding count for this packet.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
int: Number of padding bytes
|
77
|
+
"""
|
78
|
+
if self.framepacking != 3:
|
79
|
+
return 0
|
80
|
+
|
81
|
+
unpacked = struct.unpack("<BB", self.data[1:3])
|
82
|
+
is_padded = (unpacked[0] >> 6) & 1
|
83
|
+
if not is_padded:
|
84
|
+
return 0
|
85
|
+
|
86
|
+
padding = unpacked[1]
|
87
|
+
total_padding = padding
|
88
|
+
i = 3
|
89
|
+
while padding == 255:
|
90
|
+
padding = struct.unpack("<B", self.data[i:i + 1])
|
91
|
+
total_padding = total_padding + padding[0] - 1
|
92
|
+
i = i + 1
|
93
|
+
|
94
|
+
logger.trace("Packet has %d bytes of padding", total_padding)
|
95
|
+
return total_padding
|
96
|
+
|
97
|
+
def get_frame_size(self):
|
98
|
+
"""
|
99
|
+
Get the frame size in milliseconds based on the config value.
|
100
|
+
|
101
|
+
Returns:
|
102
|
+
float: Frame size in milliseconds
|
103
|
+
|
104
|
+
Raises:
|
105
|
+
RuntimeError: If the config value indicates a non-CELT encoding
|
106
|
+
"""
|
107
|
+
if self.config_value in [16, 20, 24, 28]:
|
108
|
+
return 2.5
|
109
|
+
elif self.config_value in [17, 21, 25, 29]:
|
110
|
+
return 5
|
111
|
+
elif self.config_value in [18, 22, 26, 30]:
|
112
|
+
return 10
|
113
|
+
elif self.config_value in [19, 23, 27, 31]:
|
114
|
+
return 20
|
115
|
+
else:
|
116
|
+
error_msg = (
|
117
|
+
"Found config value {} in opus packet, but CELT-only encodings (16-31) are required by the box.\n"
|
118
|
+
"Please encode your input files accordingly or fix your encoding pipeline to do so.\n"
|
119
|
+
"Did you built libopus with custom modes support?".format(self.config_value)
|
120
|
+
)
|
121
|
+
logger.error(error_msg)
|
122
|
+
raise RuntimeError(error_msg)
|
123
|
+
|
124
|
+
def calc_granule(self):
|
125
|
+
"""
|
126
|
+
Calculate the granule position for this packet.
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
float: Granule position
|
130
|
+
"""
|
131
|
+
granule = self.frame_size * self.frame_count * SAMPLE_RATE_KHZ
|
132
|
+
logger.trace("Calculated granule: %f (frame_size=%f, frame_count=%d)",
|
133
|
+
granule, self.frame_size, self.frame_count)
|
134
|
+
return granule
|
135
|
+
|
136
|
+
def parse_segment_info(self):
|
137
|
+
"""Parse the segment information from the packet data."""
|
138
|
+
logger.trace("Parsing segment info from packet data")
|
139
|
+
byte = struct.unpack("<B", self.data[0:1])[0]
|
140
|
+
self.config_value = byte >> 3
|
141
|
+
self.stereo = (byte & 4) >> 2
|
142
|
+
self.framepacking = byte & 3
|
143
|
+
self.padding = self.get_padding()
|
144
|
+
self.frame_count = self.get_frame_count()
|
145
|
+
self.frame_size = self.get_frame_size()
|
146
|
+
self.granule = self.calc_granule()
|
147
|
+
|
148
|
+
logger.trace("Packet info: config=%d, stereo=%d, framepacking=%d, frame_count=%d, frame_size=%f",
|
149
|
+
self.config_value, self.stereo, self.framepacking, self.frame_count, self.frame_size)
|
150
|
+
|
151
|
+
def write(self, filehandle):
|
152
|
+
"""
|
153
|
+
Write the packet data to a file.
|
154
|
+
|
155
|
+
Args:
|
156
|
+
filehandle: File handle to write the data to
|
157
|
+
"""
|
158
|
+
if len(self.data):
|
159
|
+
logger.trace("Writing %d bytes of packet data to file", len(self.data))
|
160
|
+
filehandle.write(self.data)
|
161
|
+
|
162
|
+
def convert_to_framepacking_three(self):
|
163
|
+
"""
|
164
|
+
Convert the packet to use framepacking mode 3.
|
165
|
+
|
166
|
+
This is needed for proper padding in Tonie files.
|
167
|
+
"""
|
168
|
+
if self.framepacking == 3:
|
169
|
+
logger.trace("Packet already uses framepacking mode 3, no conversion needed")
|
170
|
+
return
|
171
|
+
|
172
|
+
logger.debug("Converting packet from framepacking mode %d to mode 3", self.framepacking)
|
173
|
+
toc_byte = struct.unpack("<B", self.data[0:1])[0]
|
174
|
+
toc_byte = toc_byte | 0b11
|
175
|
+
|
176
|
+
frame_count_byte = self.frame_count
|
177
|
+
if self.framepacking == 2:
|
178
|
+
frame_count_byte = frame_count_byte | 0b10000000 # vbr
|
179
|
+
logger.trace("Setting VBR flag in frame count byte")
|
180
|
+
|
181
|
+
self.data = struct.pack("<BB", toc_byte, frame_count_byte) + self.data[1:]
|
182
|
+
self.framepacking = 3
|
183
|
+
logger.debug("Packet successfully converted to framepacking mode 3")
|
184
|
+
|
185
|
+
def set_pad_count(self, count):
|
186
|
+
"""
|
187
|
+
Set the padding count for this packet.
|
188
|
+
|
189
|
+
Args:
|
190
|
+
count: Number of padding bytes to add
|
191
|
+
|
192
|
+
Raises:
|
193
|
+
AssertionError: If the packet is not using framepacking mode 3 or is already padded
|
194
|
+
"""
|
195
|
+
logger.debug("Setting padding count to %d bytes", count)
|
196
|
+
|
197
|
+
if self.framepacking != 3:
|
198
|
+
logger.error("Cannot set padding on a packet with framepacking mode %d", self.framepacking)
|
199
|
+
assert self.framepacking == 3, "Only code 3 packets can contain padding!"
|
200
|
+
|
201
|
+
if self.padding != 0:
|
202
|
+
logger.error("Packet already has %d bytes of padding", self.padding)
|
203
|
+
assert self.padding == 0, "Packet already padded. Not supported yet!"
|
204
|
+
|
205
|
+
frame_count_byte = struct.unpack("<B", self.data[1:2])[0]
|
206
|
+
frame_count_byte = frame_count_byte | 0b01000000
|
207
|
+
logger.trace("Setting padding flag in frame count byte")
|
208
|
+
|
209
|
+
pad_count_data = bytes()
|
210
|
+
val = count
|
211
|
+
while val > 254:
|
212
|
+
pad_count_data = pad_count_data + b"\xFF"
|
213
|
+
val = val - 254
|
214
|
+
logger.trace("Added padding byte 0xFF (254 bytes)")
|
215
|
+
pad_count_data = pad_count_data + struct.pack("<B", val)
|
216
|
+
logger.trace("Added final padding byte %d", val)
|
217
|
+
|
218
|
+
self.data = self.data[0:1] + struct.pack("<B", frame_count_byte) + pad_count_data + self.data[2:]
|
219
|
+
logger.debug("Padding count set successfully, new data length: %d bytes", len(self.data))
|