vtskit 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.
- vtskit/__init__.py +25 -0
- vtskit/errors.py +8 -0
- vtskit/ifo.py +113 -0
- vtskit/lang.py +648 -0
- vtskit/menu.py +171 -0
- vtskit/nav.py +256 -0
- vtskit/ps.py +484 -0
- vtskit/spu.py +262 -0
- vtskit/subtitles.py +126 -0
- vtskit/titleset.py +967 -0
- vtskit/vm.py +482 -0
- vtskit/vmg.py +159 -0
- vtskit-0.1.0.dist-info/METADATA +205 -0
- vtskit-0.1.0.dist-info/RECORD +18 -0
- vtskit-0.1.0.dist-info/WHEEL +5 -0
- vtskit-0.1.0.dist-info/licenses/COPYING +674 -0
- vtskit-0.1.0.dist-info/licenses/COPYING.LESSER +165 -0
- vtskit-0.1.0.dist-info/top_level.txt +1 -0
vtskit/spu.py
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""Subpicture units: the button overlay of a menu, or a subtitle, as a DVD
|
|
2
|
+
subpicture.
|
|
3
|
+
|
|
4
|
+
A subpicture unit (https://dvdnav.mplayerhq.hu/dvdinfo/spu.html) is a
|
|
5
|
+
header, the run-length coded pixels of the top and bottom fields, and
|
|
6
|
+
display control sequences: the first shows the picture (its colours from
|
|
7
|
+
the program chain's palette, a contrast each), a second may take it off
|
|
8
|
+
the screen after a delay. Pixels have four codes: 0 background, 1 pattern,
|
|
9
|
+
2 and 3 emphasis. A menu's overlay here is drawn with code 1 where a button
|
|
10
|
+
lights up; the unit shows nothing by itself (every code transparent) and
|
|
11
|
+
the player colours a button's pixels from the PCI's colour table when it is
|
|
12
|
+
selected or activated (vtskit.menu). A subtitle (vtskit.subtitles) is a
|
|
13
|
+
picture placed at a point of the screen, shown for a while. The unit is
|
|
14
|
+
carried in one or more packs of private stream 1, sub-stream 0x20 + n.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import struct
|
|
18
|
+
|
|
19
|
+
from . import ps
|
|
20
|
+
from .errors import VtsError
|
|
21
|
+
|
|
22
|
+
MAX_UNIT = 53220 # the subpicture buffer
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Overlay:
|
|
26
|
+
"""A picture of pixel codes (bytes, one a pixel, row by row)."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, width, height, pixels):
|
|
29
|
+
if len(pixels) != width * height:
|
|
30
|
+
raise VtsError(f"{len(pixels)} pixels for {width}x{height}")
|
|
31
|
+
if any(p > 3 for p in pixels):
|
|
32
|
+
raise VtsError("a subpicture pixel is 0 to 3")
|
|
33
|
+
self.width, self.height, self.pixels = width, height, bytes(pixels)
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def from_rects(cls, width, height, rects, code=1):
|
|
37
|
+
"""Code `code` inside each (x0, y0, x1, y1) (x1, y1 exclusive), 0 elsewhere."""
|
|
38
|
+
rows = []
|
|
39
|
+
for y in range(height):
|
|
40
|
+
row = bytearray(width)
|
|
41
|
+
for x0, y0, x1, y1 in rects:
|
|
42
|
+
if y0 <= y < y1:
|
|
43
|
+
row[x0:x1] = bytes([code]) * (x1 - x0)
|
|
44
|
+
rows.append(bytes(row))
|
|
45
|
+
return cls(width, height, b"".join(rows))
|
|
46
|
+
|
|
47
|
+
def bounds(self):
|
|
48
|
+
"""(x0, y0, x1, y1) around the non-zero pixels, inclusive; None if blank."""
|
|
49
|
+
xs, ys = [], []
|
|
50
|
+
for y in range(self.height):
|
|
51
|
+
row = self.pixels[y * self.width:(y + 1) * self.width]
|
|
52
|
+
if any(row):
|
|
53
|
+
ys.append(y)
|
|
54
|
+
xs.append(next(x for x, p in enumerate(row) if p))
|
|
55
|
+
xs.append(self.width - 1 - next(x for x, p in enumerate(reversed(row)) if p))
|
|
56
|
+
if not ys:
|
|
57
|
+
return None
|
|
58
|
+
return min(xs), ys[0], max(xs), ys[-1]
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class _Bits:
|
|
62
|
+
def __init__(self):
|
|
63
|
+
self.out = bytearray()
|
|
64
|
+
self.acc, self.n = 0, 0
|
|
65
|
+
|
|
66
|
+
def put(self, value, bits):
|
|
67
|
+
self.acc = (self.acc << bits) | value
|
|
68
|
+
self.n += bits
|
|
69
|
+
while self.n >= 8:
|
|
70
|
+
self.n -= 8
|
|
71
|
+
self.out.append((self.acc >> self.n) & 0xFF)
|
|
72
|
+
self.acc &= (1 << self.n) - 1
|
|
73
|
+
|
|
74
|
+
def align(self):
|
|
75
|
+
if self.n:
|
|
76
|
+
self.put(0, 8 - self.n)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _encode_line(bits, line):
|
|
80
|
+
"""One line of pixel codes as runs: 4, 8, 12 or 16 bits a run, the
|
|
81
|
+
shortest that holds the count; a run to the end of the line that needs
|
|
82
|
+
16 bits (over 63) is count 0 in 16 bits (as spumux chooses)."""
|
|
83
|
+
x = 0
|
|
84
|
+
n = len(line)
|
|
85
|
+
while x < n:
|
|
86
|
+
code = line[x]
|
|
87
|
+
run = 1
|
|
88
|
+
while x + run < n and line[x + run] == code:
|
|
89
|
+
run += 1
|
|
90
|
+
if x + run == n and run > 63:
|
|
91
|
+
bits.put(code, 16) # count 0, to the end of the line
|
|
92
|
+
elif run <= 3:
|
|
93
|
+
bits.put(run << 2 | code, 4)
|
|
94
|
+
elif run <= 15:
|
|
95
|
+
bits.put(run << 2 | code, 8)
|
|
96
|
+
elif run <= 63:
|
|
97
|
+
bits.put(run << 2 | code, 12)
|
|
98
|
+
elif run <= 255:
|
|
99
|
+
bits.put(run << 2 | code, 16)
|
|
100
|
+
else:
|
|
101
|
+
bits.put(255 << 2 | code, 16)
|
|
102
|
+
run = 255
|
|
103
|
+
x += run
|
|
104
|
+
bits.align()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def encode(overlay, forced=True, colours=(0, 0, 0, 0), contrasts=(0, 0, 0, 0), at=None,
|
|
108
|
+
stop=None):
|
|
109
|
+
"""The subpicture unit of an overlay: its pixels inside their bounds
|
|
110
|
+
(or the whole picture with its top left corner `at` (x, y)), shown
|
|
111
|
+
from its PTS on (forced: shown even with subtitles off), the four
|
|
112
|
+
codes drawn with these palette entries and contrasts (0 transparent to
|
|
113
|
+
15 opaque; all transparent by default, for a button overlay), taken off
|
|
114
|
+
the screen `stop` ticks (90 kHz) after the PTS if given."""
|
|
115
|
+
for name, values in (("colours", colours), ("contrasts", contrasts)):
|
|
116
|
+
if len(values) != 4 or not all(0 <= v <= 15 for v in values):
|
|
117
|
+
raise VtsError(f"the four {name} are 0 to 15")
|
|
118
|
+
if at is None:
|
|
119
|
+
box = overlay.bounds()
|
|
120
|
+
if box is None:
|
|
121
|
+
raise VtsError("the overlay is blank")
|
|
122
|
+
lx0, ly0, lx1, ly1 = box # in the picture
|
|
123
|
+
x0, y0, x1, y1 = box # on the screen
|
|
124
|
+
else:
|
|
125
|
+
lx0, ly0, lx1, ly1 = 0, 0, overlay.width - 1, overlay.height - 1
|
|
126
|
+
x0, y0 = at
|
|
127
|
+
x1, y1 = x0 + overlay.width - 1, y0 + overlay.height - 1
|
|
128
|
+
if not (0 <= x0 and x1 < 1 << 10 and 0 <= y0 and y1 < 1 << 10):
|
|
129
|
+
raise VtsError(f"a picture at {at} of {overlay.width}x{overlay.height} is off the screen")
|
|
130
|
+
fields = []
|
|
131
|
+
for first in (ly0, ly0 + 1): # the top field: lines y0, y0 + 2, ...
|
|
132
|
+
bits = _Bits()
|
|
133
|
+
for y in range(first, ly1 + 1, 2):
|
|
134
|
+
_encode_line(bits, overlay.pixels[y * overlay.width + lx0:y * overlay.width + lx1 + 1])
|
|
135
|
+
fields.append(bytes(bits.out))
|
|
136
|
+
top = 4
|
|
137
|
+
bottom = top + len(fields[0])
|
|
138
|
+
dcsqt = bottom + len(fields[1])
|
|
139
|
+
if dcsqt + 24 > MAX_UNIT:
|
|
140
|
+
raise VtsError(f"the subpicture unit is over {dcsqt} bytes (at most {MAX_UNIT})")
|
|
141
|
+
commands = bytes([0x00 if forced else 0x01])
|
|
142
|
+
commands += bytes([0x03, colours[3] << 4 | colours[2], colours[1] << 4 | colours[0]])
|
|
143
|
+
commands += bytes([0x04, contrasts[3] << 4 | contrasts[2], contrasts[1] << 4 | contrasts[0]])
|
|
144
|
+
commands += bytes([0x05, x0 >> 4, (x0 & 0xF) << 4 | x1 >> 8, x1 & 0xFF,
|
|
145
|
+
y0 >> 4, (y0 & 0xF) << 4 | y1 >> 8, y1 & 0xFF])
|
|
146
|
+
commands += struct.pack(">BHH", 0x06, top, bottom)
|
|
147
|
+
commands += b"\xff"
|
|
148
|
+
if stop is None:
|
|
149
|
+
dcsq = struct.pack(">HH", 0, dcsqt) + commands # one sequence, pointing at itself
|
|
150
|
+
else:
|
|
151
|
+
delay = int(round(stop / 1024))
|
|
152
|
+
if not 0 < delay <= 0xFFFF:
|
|
153
|
+
raise VtsError(f"a picture shown for {stop} ticks: up to {0xFFFF * 1024} and over 0")
|
|
154
|
+
second = dcsqt + 4 + len(commands)
|
|
155
|
+
dcsq = struct.pack(">HH", 0, second) + commands + struct.pack(">HH", delay, second) + b"\x02\xff"
|
|
156
|
+
size = dcsqt + len(dcsq)
|
|
157
|
+
if size > MAX_UNIT:
|
|
158
|
+
raise VtsError(f"the subpicture unit is {size} bytes (at most {MAX_UNIT})")
|
|
159
|
+
return struct.pack(">HH", size, dcsqt) + fields[0] + fields[1] + dcsq
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def decode(unit):
|
|
163
|
+
"""The pixel codes of a unit as (x0, y0, {(x, y): code}) and its
|
|
164
|
+
commands, for tests."""
|
|
165
|
+
size, dcsqt = struct.unpack_from(">HH", unit, 0)
|
|
166
|
+
if size != len(unit):
|
|
167
|
+
raise VtsError("the unit's size does not match")
|
|
168
|
+
cmds = unit[dcsqt + 4:]
|
|
169
|
+
area = top = bottom = None
|
|
170
|
+
i = 0
|
|
171
|
+
while i < len(cmds) and cmds[i] != 0xFF:
|
|
172
|
+
c = cmds[i]
|
|
173
|
+
if c in (0x00, 0x01, 0x02):
|
|
174
|
+
i += 1
|
|
175
|
+
elif c in (0x03, 0x04):
|
|
176
|
+
i += 3
|
|
177
|
+
elif c == 0x05:
|
|
178
|
+
b = cmds[i + 1:i + 7]
|
|
179
|
+
area = (b[0] << 4 | b[1] >> 4, (b[1] & 0xF) << 8 | b[2], b[3] << 4 | b[4] >> 4,
|
|
180
|
+
(b[4] & 0xF) << 8 | b[5])
|
|
181
|
+
i += 7
|
|
182
|
+
elif c == 0x06:
|
|
183
|
+
top, bottom = struct.unpack_from(">HH", cmds, i + 1)
|
|
184
|
+
i += 5
|
|
185
|
+
else:
|
|
186
|
+
raise VtsError(f"subpicture command {c:#x} is not decoded here")
|
|
187
|
+
x0, x1, y0, y1 = area
|
|
188
|
+
width = x1 - x0 + 1
|
|
189
|
+
pixels = {}
|
|
190
|
+
|
|
191
|
+
def read_field(offset, first):
|
|
192
|
+
pos = offset * 8
|
|
193
|
+
for y in range(first, y1 + 1, 2):
|
|
194
|
+
x = 0
|
|
195
|
+
while x < width:
|
|
196
|
+
v, bits = 0, 0
|
|
197
|
+
while True:
|
|
198
|
+
v = v << 4 | (unit[pos >> 3] >> (4 - (pos & 7))) & 0xF
|
|
199
|
+
pos += 4
|
|
200
|
+
bits += 4
|
|
201
|
+
if v >= (1 << (bits // 2)) or bits == 16: # 4 bits: n >= 1; 8: n >= 4; 12: n >= 16
|
|
202
|
+
break
|
|
203
|
+
n, code = v >> 2, v & 3
|
|
204
|
+
if n == 0:
|
|
205
|
+
n = width - x
|
|
206
|
+
for k in range(n):
|
|
207
|
+
pixels[(x0 + x + k, y)] = code
|
|
208
|
+
x += n
|
|
209
|
+
pos = (pos + 7) & ~7
|
|
210
|
+
read_field(top, y0)
|
|
211
|
+
read_field(bottom, y0 + 1)
|
|
212
|
+
return area, pixels, cmds
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def insert(data, unit, pts, sub=0x20):
|
|
216
|
+
"""The stream (bytes or bytearray) with the unit added right after its
|
|
217
|
+
first navigation pack, in packs of private stream 1, sub-stream `sub`,
|
|
218
|
+
stamped `pts`. Returns a new bytearray."""
|
|
219
|
+
packs = list(ps.iter_packs(data))
|
|
220
|
+
if not packs or not packs[0].nav:
|
|
221
|
+
raise VtsError("the stream does not start with a navigation pack")
|
|
222
|
+
nav = bytes(data[:ps.SECTOR])
|
|
223
|
+
scr = packs[0].scr
|
|
224
|
+
out = bytearray(nav)
|
|
225
|
+
remaining = unit
|
|
226
|
+
first = True
|
|
227
|
+
while remaining:
|
|
228
|
+
room = ps.SECTOR - 14 - 6 - (3 + 5 if first else 3) - 1 # pack, PES, header, sub id
|
|
229
|
+
chunk, remaining = remaining[:room], remaining[room:]
|
|
230
|
+
header = bytes([0x81, 0x80, 0x05]) + _pts(pts) if first else bytes([0x81, 0x00, 0x00])
|
|
231
|
+
pes = b"\x00\x00\x01\xbd" + struct.pack(">H", len(header) + 1 + len(chunk)) + header \
|
|
232
|
+
+ bytes([sub]) + chunk
|
|
233
|
+
scr += 1
|
|
234
|
+
pack = _pack_header(scr, nav) + pes
|
|
235
|
+
pad = ps.SECTOR - len(pack)
|
|
236
|
+
if pad:
|
|
237
|
+
if pad < 6:
|
|
238
|
+
raise VtsError("no room for the padding packet") # (cannot happen: room leaves 1)
|
|
239
|
+
pack += b"\x00\x00\x01\xbe" + struct.pack(">H", pad - 6) + b"\xff" * (pad - 6)
|
|
240
|
+
out += pack
|
|
241
|
+
first = False
|
|
242
|
+
out += data[ps.SECTOR:]
|
|
243
|
+
return out
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _pts(t):
|
|
247
|
+
return bytes([0x21 | (t >> 29) & 0x0E, (t >> 22) & 0xFF, 0x01 | (t >> 14) & 0xFE,
|
|
248
|
+
(t >> 7) & 0xFF, 0x01 | (t << 1) & 0xFE])
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _pack_header(scr, like):
|
|
252
|
+
"""A pack header with this SCR (90 kHz; extension 0) and the mux rate of
|
|
253
|
+
the pack `like`."""
|
|
254
|
+
b = bytearray(like[:14])
|
|
255
|
+
b[4] = 0x44 | ((scr >> 30) & 7) << 3 | (scr >> 28) & 3
|
|
256
|
+
b[5] = (scr >> 20) & 0xFF
|
|
257
|
+
b[6] = 0x04 | ((scr >> 15) & 0x1F) << 3 | (scr >> 13) & 3
|
|
258
|
+
b[7] = (scr >> 5) & 0xFF
|
|
259
|
+
b[8] = 0x04 | ((scr & 0x1F) << 3)
|
|
260
|
+
b[9] = 0x01
|
|
261
|
+
b[13] = 0xF8 # no stuffing
|
|
262
|
+
return bytes(b)
|
vtskit/subtitles.py
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Subtitles: pictures shown over a title's video for a while, added to its
|
|
2
|
+
program stream as subpicture packs (what spumux does).
|
|
3
|
+
|
|
4
|
+
A cue is a picture of pixel codes (vtskit.spu.Overlay) placed at a point
|
|
5
|
+
of the screen, shown from `start` to `end` (seconds from the title's first
|
|
6
|
+
picture), each code drawn with an entry of the title's palette
|
|
7
|
+
(vtskit.titleset.Title.palette, 16 colours) and a contrast. `mux` turns
|
|
8
|
+
the cues of each subpicture stream into units and places their packs in
|
|
9
|
+
the stream a little before they are shown: the unit's packs go after the
|
|
10
|
+
last pack whose clock (SCR) is `lead` seconds before the cue's time, so the
|
|
11
|
+
player has the picture when it is due (spumux delivers about 0.15 s
|
|
12
|
+
early). The other packs stay as they are; the clock of an added pack is
|
|
13
|
+
the previous pack's plus one. Each stream's cues must not overlap.
|
|
14
|
+
|
|
15
|
+
The DVD-Video program stream from ffmpeg (-f dvd) carries no subpicture
|
|
16
|
+
streams; `mux` adds them before vtskit.titleset.write fills the
|
|
17
|
+
navigation packs, which then point at the units (DSI SP_SYNCA).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import bisect
|
|
21
|
+
import struct
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
|
|
24
|
+
from . import nav, ps, spu
|
|
25
|
+
from .errors import VtsError
|
|
26
|
+
|
|
27
|
+
LEAD = 0.15
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class Cue:
|
|
32
|
+
start: float
|
|
33
|
+
end: float
|
|
34
|
+
picture: spu.Overlay
|
|
35
|
+
x: int = 0
|
|
36
|
+
y: int = 0
|
|
37
|
+
colours: tuple = (0, 1, 2, 3) # the palette entry drawing each pixel code
|
|
38
|
+
contrasts: tuple = (0, 15, 15, 15) # and its contrast, 0 (not drawn) to 15
|
|
39
|
+
forced: bool = False # shown even with subtitles off
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def unit(cue):
|
|
43
|
+
"""The subpicture unit of a cue."""
|
|
44
|
+
if not cue.end > cue.start >= 0:
|
|
45
|
+
raise VtsError(f"a cue from {cue.start} to {cue.end} s")
|
|
46
|
+
return spu.encode(cue.picture, cue.forced, cue.colours, cue.contrasts, (cue.x, cue.y),
|
|
47
|
+
stop=(cue.end - cue.start) * 90000)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _packets(unit_, pts, sub, scr, like):
|
|
51
|
+
"""The packs carrying a unit, the first stamped `pts`, clocks from
|
|
52
|
+
`scr` + 1 on."""
|
|
53
|
+
out = []
|
|
54
|
+
remaining = unit_
|
|
55
|
+
first = True
|
|
56
|
+
while remaining:
|
|
57
|
+
room = ps.SECTOR - 14 - 6 - (3 + 5 if first else 3) - 1
|
|
58
|
+
chunk, remaining = remaining[:room], remaining[room:]
|
|
59
|
+
header = bytes([0x81, 0x80, 0x05]) + spu._pts(pts) if first else bytes([0x81, 0x00, 0x00])
|
|
60
|
+
pes = b"\x00\x00\x01\xbd" + struct.pack(">H", len(header) + 1 + len(chunk)) + header \
|
|
61
|
+
+ bytes([sub]) + chunk
|
|
62
|
+
scr += 1
|
|
63
|
+
pack = spu._pack_header(scr, like) + pes
|
|
64
|
+
pad = ps.SECTOR - len(pack)
|
|
65
|
+
if pad:
|
|
66
|
+
pack += b"\x00\x00\x01\xbe" + struct.pack(">H", pad - 6) + b"\xff" * (pad - 6)
|
|
67
|
+
out.append(pack)
|
|
68
|
+
first = False
|
|
69
|
+
return out, scr
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def mux(data, streams, lead=LEAD, out=None):
|
|
73
|
+
"""The program stream `data` (bytes, or a ps.Mapped file) with the cues
|
|
74
|
+
of each subpicture stream added: `streams` is {stream number 0-31:
|
|
75
|
+
[Cue]}. Returns a bytearray, or writes into the open file `out` and
|
|
76
|
+
returns None (a title of gigabytes passes through in blocks)."""
|
|
77
|
+
packs = ps.read_packs(data)
|
|
78
|
+
if not packs or not packs[0].nav:
|
|
79
|
+
raise VtsError("the stream does not start with a navigation pack")
|
|
80
|
+
stream = ps.scan(data)
|
|
81
|
+
s0 = nav.vobus(stream)[0].s_ptm
|
|
82
|
+
scrs = [p.scr for p in packs]
|
|
83
|
+
if any(b <= a for a, b in zip(scrs, scrs[1:])):
|
|
84
|
+
raise VtsError("the packs' clocks are not in order")
|
|
85
|
+
events = [] # (pack to go before, pts, stream, unit)
|
|
86
|
+
for number, cues in streams.items():
|
|
87
|
+
if not 0 <= number <= 31:
|
|
88
|
+
raise VtsError(f"subpicture stream {number}: 0 to 31")
|
|
89
|
+
if 0x20 + number in stream.subpictures:
|
|
90
|
+
raise VtsError(f"the stream already has subpicture stream {number}")
|
|
91
|
+
last = None
|
|
92
|
+
for cue in sorted(cues, key=lambda c: c.start):
|
|
93
|
+
if last is not None and cue.start < last:
|
|
94
|
+
raise VtsError(f"subpicture stream {number}: a cue at {cue.start} s overlaps the one before")
|
|
95
|
+
last = cue.end
|
|
96
|
+
pts = s0 + int(round(cue.start * 90000))
|
|
97
|
+
k = bisect.bisect_right(scrs, pts - int(lead * 90000)) # after the last pack early enough
|
|
98
|
+
events.append((max(k, 1), pts, 0x20 + number, unit(cue)))
|
|
99
|
+
events.sort(key=lambda e: (e[0], e[1], e[2]))
|
|
100
|
+
result = bytearray() if out is None else None
|
|
101
|
+
e = 0
|
|
102
|
+
last_scr = None
|
|
103
|
+
first = bytes(data[:14])
|
|
104
|
+
k = 0
|
|
105
|
+
while k < len(packs):
|
|
106
|
+
while e < len(events) and events[e][0] == k:
|
|
107
|
+
_, pts, sub, u = events[e]
|
|
108
|
+
added, last_scr = _packets(u, pts, sub, last_scr, first)
|
|
109
|
+
if last_scr >= packs[k].scr:
|
|
110
|
+
raise VtsError(f"no room in the clock before pack {k} for the subpicture packs")
|
|
111
|
+
for p in added:
|
|
112
|
+
_emit(result, out, p)
|
|
113
|
+
e += 1
|
|
114
|
+
# the stretch of packs up to the next insertion goes through whole
|
|
115
|
+
nxt = events[e][0] if e < len(events) else len(packs)
|
|
116
|
+
_emit(result, out, data[k * ps.SECTOR:nxt * ps.SECTOR])
|
|
117
|
+
last_scr = packs[nxt - 1].scr
|
|
118
|
+
k = nxt
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _emit(result, out, data):
|
|
123
|
+
if out is None:
|
|
124
|
+
result += data
|
|
125
|
+
else:
|
|
126
|
+
out.write(data)
|