vtskit 0.2.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (32) hide show
  1. {vtskit-0.2.0/vtskit.egg-info → vtskit-0.3.1}/PKG-INFO +13 -8
  2. {vtskit-0.2.0 → vtskit-0.3.1}/README.md +12 -7
  3. {vtskit-0.2.0 → vtskit-0.3.1}/pyproject.toml +1 -1
  4. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_lang.py +12 -1
  5. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_title_buttons.py +44 -7
  6. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_vmg.py +17 -0
  7. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/__init__.py +1 -1
  8. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/lang.py +9 -7
  9. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/titleset.py +39 -17
  10. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/vmg.py +10 -4
  11. {vtskit-0.2.0 → vtskit-0.3.1/vtskit.egg-info}/PKG-INFO +13 -8
  12. {vtskit-0.2.0 → vtskit-0.3.1}/COPYING +0 -0
  13. {vtskit-0.2.0 → vtskit-0.3.1}/COPYING.LESSER +0 -0
  14. {vtskit-0.2.0 → vtskit-0.3.1}/setup.cfg +0 -0
  15. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_angles.py +0 -0
  16. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_ifo.py +0 -0
  17. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_menu.py +0 -0
  18. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_nav.py +0 -0
  19. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_subtitles.py +0 -0
  20. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_titleset.py +0 -0
  21. {vtskit-0.2.0 → vtskit-0.3.1}/tests/test_vm.py +0 -0
  22. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/errors.py +0 -0
  23. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/ifo.py +0 -0
  24. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/menu.py +0 -0
  25. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/nav.py +0 -0
  26. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/ps.py +0 -0
  27. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/spu.py +0 -0
  28. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/subtitles.py +0 -0
  29. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit/vm.py +0 -0
  30. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit.egg-info/SOURCES.txt +0 -0
  31. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit.egg-info/dependency_links.txt +0 -0
  32. {vtskit-0.2.0 → vtskit-0.3.1}/vtskit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vtskit
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: DVD-Video titlesets in pure Python: VOBs, navigation packs and IFO files
5
5
  Author: kajisho5
6
6
  License-Expression: LGPL-3.0-or-later
@@ -146,10 +146,13 @@ oncemore writing its discs with vtskit.
146
146
  to an end, the buttons, the palette entries that draw them): their
147
147
  overlay is a forced subpicture added with `vtskit.subtitles`, and the
148
148
  highlight information goes only into the VOBUs that play during the
149
- stretch, with the stretch's start and end as its start time, end time
149
+ stretch (where two stretches meet inside a VOBU, the one covering more
150
+ of it), with the stretch's start and end as its start time, end time
150
151
  and the end of the selection (dvdinfo's hli_s_ptm, hli_e_ptm,
151
152
  btn_sl_e_ptm). dvdauthor gives a title's buttons the whole title (every
152
- VOBU, never ending), which is what buttons without an end write.
153
+ VOBU, never ending), which is what buttons without an end write. As
154
+ dvdauthor does, a title with buttons starts with its first button
155
+ highlighted (SetHL_BTNN 1 before its own pre commands).
153
156
  * `vtskit.subtitles` adds subtitles to a title's program stream, as spumux
154
157
  does: each cue a picture of up to four colours placed on the screen and
155
158
  shown for a while, as subpicture packs a little before their time.
@@ -157,8 +160,8 @@ oncemore writing its discs with vtskit.
157
160
  `if (g5 != 0) { button = g5; }`, `call menu entry root;`) for the domain
158
161
  a text runs in, to the same commands dvdauthor writes for it: a tool
159
162
  that already speaks the language, as oncemore does, keeps its text. A
160
- button of several commands runs them from the menu's post through a
161
- dispatcher, and a jump to a chapter of another titleset goes through pad
163
+ button of several commands, a menu's or a title's, runs them from the
164
+ program chain's post through a dispatcher, and a jump to a chapter of another titleset goes through pad
162
165
  program chains, as dvdauthor's "jumppads" do.
163
166
  * `vtskit.vmg` writes the video manager: VIDEO_TS.IFO and its BUP, with
164
167
  the first-play commands, the table of titles and the titlesets'
@@ -181,7 +184,8 @@ The tests give the same titles to dvdauthor and to vtskit, and the IFO and
181
184
  the VOBs must be the same byte for byte: NTSC and PAL, 4:3 and 16:9,
182
185
  chapters on and between VOBU starts, two titles in a titleset, AC-3, LPCM
183
186
  and MPEG sound with languages, B-pictures, short GOPs, a 40-second title,
184
- a subtitle track, two titlesets; every command form is the bytes dvdauthor
187
+ a subtitle track, two titlesets and three (whose attributes take the VMG
188
+ a second sector); every command form is the bytes dvdauthor
185
189
  compiles for the same statement, and a disc described in its language
186
190
  (menus of commands alone, buttons of several commands, cell commands, two
187
191
  titlesets) comes out with the same IFOs; for menus, the highlight
@@ -190,8 +194,9 @@ dvdauthor's (its buttons made with spumux), and a menu's overlay in a
190
194
  normal colour is spumux's unit byte for byte; subtitles muxed by vtskit are
191
195
  spumux's units, placed where spumux places them, and the title written
192
196
  from that stream (4:3, and 16:9 with and without pan-scan or letterbox) is
193
- dvdauthor's; a title with buttons over the whole of it is dvdauthor's
194
- VOB byte for byte. A VIDEO_TS written by vtskit alone plays
197
+ dvdauthor's; a title with buttons over the whole of it (one of them of
198
+ several commands) is dvdauthor's VOB and IFO byte for byte, but for the
199
+ palette. A VIDEO_TS written by vtskit alone plays
195
200
  its titles and chapters in order under libdvdnav, its commands jumping
196
201
  between titles, its menus' buttons lead where they say, and its jumps into
197
202
  another titleset's chapters land; a title with an angle block plays and
@@ -129,10 +129,13 @@ oncemore writing its discs with vtskit.
129
129
  to an end, the buttons, the palette entries that draw them): their
130
130
  overlay is a forced subpicture added with `vtskit.subtitles`, and the
131
131
  highlight information goes only into the VOBUs that play during the
132
- stretch, with the stretch's start and end as its start time, end time
132
+ stretch (where two stretches meet inside a VOBU, the one covering more
133
+ of it), with the stretch's start and end as its start time, end time
133
134
  and the end of the selection (dvdinfo's hli_s_ptm, hli_e_ptm,
134
135
  btn_sl_e_ptm). dvdauthor gives a title's buttons the whole title (every
135
- VOBU, never ending), which is what buttons without an end write.
136
+ VOBU, never ending), which is what buttons without an end write. As
137
+ dvdauthor does, a title with buttons starts with its first button
138
+ highlighted (SetHL_BTNN 1 before its own pre commands).
136
139
  * `vtskit.subtitles` adds subtitles to a title's program stream, as spumux
137
140
  does: each cue a picture of up to four colours placed on the screen and
138
141
  shown for a while, as subpicture packs a little before their time.
@@ -140,8 +143,8 @@ oncemore writing its discs with vtskit.
140
143
  `if (g5 != 0) { button = g5; }`, `call menu entry root;`) for the domain
141
144
  a text runs in, to the same commands dvdauthor writes for it: a tool
142
145
  that already speaks the language, as oncemore does, keeps its text. A
143
- button of several commands runs them from the menu's post through a
144
- dispatcher, and a jump to a chapter of another titleset goes through pad
146
+ button of several commands, a menu's or a title's, runs them from the
147
+ program chain's post through a dispatcher, and a jump to a chapter of another titleset goes through pad
145
148
  program chains, as dvdauthor's "jumppads" do.
146
149
  * `vtskit.vmg` writes the video manager: VIDEO_TS.IFO and its BUP, with
147
150
  the first-play commands, the table of titles and the titlesets'
@@ -164,7 +167,8 @@ The tests give the same titles to dvdauthor and to vtskit, and the IFO and
164
167
  the VOBs must be the same byte for byte: NTSC and PAL, 4:3 and 16:9,
165
168
  chapters on and between VOBU starts, two titles in a titleset, AC-3, LPCM
166
169
  and MPEG sound with languages, B-pictures, short GOPs, a 40-second title,
167
- a subtitle track, two titlesets; every command form is the bytes dvdauthor
170
+ a subtitle track, two titlesets and three (whose attributes take the VMG
171
+ a second sector); every command form is the bytes dvdauthor
168
172
  compiles for the same statement, and a disc described in its language
169
173
  (menus of commands alone, buttons of several commands, cell commands, two
170
174
  titlesets) comes out with the same IFOs; for menus, the highlight
@@ -173,8 +177,9 @@ dvdauthor's (its buttons made with spumux), and a menu's overlay in a
173
177
  normal colour is spumux's unit byte for byte; subtitles muxed by vtskit are
174
178
  spumux's units, placed where spumux places them, and the title written
175
179
  from that stream (4:3, and 16:9 with and without pan-scan or letterbox) is
176
- dvdauthor's; a title with buttons over the whole of it is dvdauthor's
177
- VOB byte for byte. A VIDEO_TS written by vtskit alone plays
180
+ dvdauthor's; a title with buttons over the whole of it (one of them of
181
+ several commands) is dvdauthor's VOB and IFO byte for byte, but for the
182
+ palette. A VIDEO_TS written by vtskit alone plays
178
183
  its titles and chapters in order under libdvdnav, its commands jumping
179
184
  between titles, its menus' buttons lead where they say, and its jumps into
180
185
  another titleset's chapters land; a title with an angle block plays and
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "vtskit"
7
- version = "0.2.0"
7
+ version = "0.3.1"
8
8
  description = "DVD-Video titlesets in pure Python: VOBs, navigation packs and IFO files"
9
9
  readme = "README.md"
10
10
  license = "LGPL-3.0-or-later"
@@ -138,6 +138,17 @@ class TestForms(unittest.TestCase):
138
138
  self.assertEqual(hexes(self.d.compile("g1 = 0; exit;", "vts", 1)), ["7100000100000000", "3001000000000000"])
139
139
  self.assertEqual(hexes(self.d.compile("call menu entry root resume 3;", "vts", 1)), ["3008000003830000"])
140
140
 
141
+ def test_title_buttons(self):
142
+ # dvdauthor's bytes for the same buttons in a title's <pgc>, with
143
+ # <post>jump title 2;</post>: the same dispatcher as a menu's
144
+ buttons, post = self.d.menu(["g1 = 2; g2 = 3; jump title 2 chapter 1;", "g3 = 4; g4 = 5; jump title 2;"],
145
+ "jump title 2;", "vts", 1)
146
+ self.assertEqual(hexes(buttons), ["7101000f0001000d", "7101000f0002000d"])
147
+ self.assertEqual(hexes(post), ["6100000e000f0000", "7100000f00000000", "00a1000e00010006",
148
+ "00a1000e00020009", "3003000000020000", "7100000100020000",
149
+ "7100000200030000", "3005000100020000", "7100000300040000",
150
+ "7100000400050000", "3003000000020000"])
151
+
141
152
  def test_dead_code_after_a_jump(self):
142
153
  self.assertEqual(hexes(self.d.compile("g1 = 0; resume; g1 = 0; jump title 1 chapter 2; "
143
154
  "subtitle = 65; audio = 1;", "vtsm", 1)),
@@ -191,7 +202,7 @@ class TestForms(unittest.TestCase):
191
202
  with self.assertRaises(VtsError):
192
203
  self.d.menu(["g1 = 1; g2 = 2;"], "g3 = 3;", "vtsm", 1) # post that does not leave
193
204
  with self.assertRaises(VtsError):
194
- self.d.menu(["exit;"], "exit;", "vts", 1)
205
+ self.d.menu(["exit;"], "exit;", "fp") # no buttons in the first play
195
206
 
196
207
 
197
208
  def author(folder, menu_n, menu_h, a, b):
@@ -13,7 +13,7 @@ import sys
13
13
  import tempfile
14
14
  import unittest
15
15
 
16
- from vtskit import VtsError, menu, ps, spu, subtitles, titleset, vm, vmg
16
+ from vtskit import VtsError, lang, menu, ps, spu, subtitles, titleset, vm, vmg
17
17
  from vtskit.menu import Button, TitleButtons
18
18
  from vtskit.titleset import Audio, Title
19
19
 
@@ -104,7 +104,7 @@ class TestTitleButtons(unittest.TestCase):
104
104
  def test_whole_title_as_dvdauthor(self):
105
105
  # spumux's buttons over the whole title, then dvdauthor; vtskit
106
106
  # given the same stream and buttons without an end writes the same
107
- # VOB (dvdauthor writes the HLI in every VOBU, never ending)
107
+ # VOB (dvdauthor writes the HLI in every VOBU, never ending) and IFO
108
108
  t = self.tmp
109
109
  png(f"{t}/hl.png", 720, 480, RECTS, (255, 255, 0))
110
110
  png(f"{t}/sel.png", 720, 480, RECTS, (255, 0, 0))
@@ -118,17 +118,35 @@ class TestTitleButtons(unittest.TestCase):
118
118
  with open(self.plain, "rb") as src, open(sp, "wb") as dst:
119
119
  subprocess.run(["spumux", "-s", "0", f"{t}/spu.xml"], stdin=src, stdout=dst, check=True,
120
120
  stderr=subprocess.DEVNULL, env=ENV)
121
+ texts = ["g1 = 2; g2 = 3; jump title 2 chapter 1;", "exit;"]
121
122
  with open(f"{t}/dvd.xml", "w") as f:
122
123
  f.write(f'<dvdauthor dest="{t}/dvd"><vmgm/><titleset><titles>'
123
124
  '<video format="ntsc" aspect="4:3"/><audio format="ac3" channels="2" lang="en"/>'
124
- f'<pgc><vob file="{sp}"/><button name="b1">jump title 1 chapter 1;</button>'
125
- '<button name="b2">exit;</button></pgc></titles></titleset></dvdauthor>')
125
+ f'<pgc><vob file="{sp}"/><button name="b1">{texts[0]}</button>'
126
+ f'<button name="b2">{texts[1]}</button><post>jump title 2;</post></pgc>'
127
+ f'<pgc><vob file="{self.short}"/></pgc></titles></titleset></dvdauthor>')
126
128
  subprocess.run(["dvdauthor", "-x", f"{t}/dvd.xml"], check=True, capture_output=True, env=ENV)
129
+ # vtskit: the same buttons compiled with vtskit.lang (a button of
130
+ # several commands runs them from the post, through a dispatcher)
131
+ disc = lang.Disc({(1, 1): 1, (1, 2): 2}, titlesets={1: lang.Menus()})
132
+ commands, post = disc.menu(texts, "jump title 2;", "vts", 1)
133
+ tb = buttons(0, None)
134
+ for b, c in zip(tb.buttons, commands):
135
+ b.command = c
127
136
  mine = f"{t}/mine-whole"
128
- titleset.write(mine, 1, [Title(sp, audio=(Audio("en", 2),), buttons=(buttons(0, None),))])
129
- a, b = read(f"{mine}/VTS_01_1.VOB"), read(f"{t}/dvd/VIDEO_TS/VTS_01_1.VOB")
137
+ titleset.write(mine, 1, [Title(sp, audio=(Audio("en", 2),), post=tuple(post), buttons=(tb,)),
138
+ Title(self.short, audio=(Audio("en", 2),))])
139
+ theirs = f"{t}/dvd/VIDEO_TS"
140
+ a, b = read(f"{mine}/VTS_01_1.VOB"), read(f"{theirs}/VTS_01_1.VOB")
130
141
  self.assertEqual([n[2] for n in navs(a)], [n[2] for n in navs(b)])
131
142
  self.assertEqual(a, b)
143
+ # the IFO: the same, the title's pre (SetHL_BTNN 1) and post
144
+ # included, but for the palette (spumux's colours)
145
+ a, b = read(f"{mine}/VTS_01_0.IFO"), read(f"{theirs}/VTS_01_0.IFO")
146
+ self.assertEqual(len(a), len(b))
147
+ pgc = struct.unpack_from(">I", a, 0xCC)[0] * 2048 + 16
148
+ self.assertEqual(a[:pgc + 0xA4], b[:pgc + 0xA4])
149
+ self.assertEqual(a[pgc + 0xE4:], b[pgc + 0xE4:])
132
150
 
133
151
  def stretch_disc(self, start, end):
134
152
  """Title 1 (10 s) with buttons from `start` to `end` (button 1:
@@ -173,10 +191,29 @@ class TestTitleButtons(unittest.TestCase):
173
191
  with self.assertRaisesRegex(VtsError, "overlap"):
174
192
  write(buttons(1, None), buttons(3, 5))
175
193
  with self.assertRaisesRegex(VtsError, "one set of buttons"):
176
- write(buttons(1, 2.8), buttons(2.9, 5)) # in the VOBU from 2.4 s to 3.0 s
194
+ write(buttons(1, 3.0), buttons(3.05, 3.2), buttons(3.25, 5)) # in the VOBU from 3.0 s
177
195
  with self.assertRaisesRegex(VtsError, "after the title ends"):
178
196
  write(buttons(30, 40))
179
197
 
198
+ def test_stretches_meeting_in_a_vobu(self):
199
+ # the VOBUs start every 0.6006 s; where two stretches meet inside a
200
+ # VOBU, it carries the one covering more of it
201
+ def starts(*tbs):
202
+ """{VOBU start (s, rounded): its HLI's start from the title's start}"""
203
+ out = os.path.join(self.tmp, "meeting")
204
+ titleset.write(out, 1, [Title(self.plain, audio=(Audio("en", 2),), buttons=tbs)])
205
+ packs = navs(read(f"{out}/VTS_01_1.VOB"))
206
+ s0 = packs[0][1]
207
+ return {round((s - s0) / 90000, 1): struct.unpack_from(">I", h, 2)[0] - s0 if h[1] else None
208
+ for _, s, h in packs}
209
+ got = starts(buttons(1, 2.8), buttons(2.9, 5))
210
+ self.assertEqual((got[1.8], got[2.4], got[3.0]), (90000, 90000, 261000))
211
+ # buttons that change a frame before or after a VOBU's start (as a
212
+ # film title's frames fall): each VOBU still carries its own
213
+ for edge in (3.003 - 3003 / 90000, 3.003 + 3003 / 90000):
214
+ got = starts(buttons(1, edge), buttons(edge, 5))
215
+ self.assertEqual((got[2.4], got[3.0]), (90000, int(round(edge * 90000))), edge)
216
+
180
217
  @unittest.skipUnless(LIBDVDNAV, "needs libdvdnav")
181
218
  def test_under_libdvdnav(self):
182
219
  out = self.stretch_disc(3.0, 6.0)
@@ -105,6 +105,23 @@ class TestAgainstDvdauthor(unittest.TestCase):
105
105
  self.assertEqual([c[:3] for c in ifo.chapters(ifo.read_folder(mine))],
106
106
  [(1, 1, 1), (1, 1, 2), (2, 1, 1)])
107
107
 
108
+ def test_three_titlesets(self):
109
+ # VTS_ATRT holds 776 bytes a titleset: three no longer fit in a
110
+ # sector, and the table goes on into the next (as dvdauthor writes it)
111
+ paths = [os.path.join(self.tmp, f"{n}.mpg") for n in "abc"]
112
+ for p in paths:
113
+ encode(p, seconds=2)
114
+ theirs, mine = self.same("ntsc", [[Title(p, audio=(Audio("en", 2),))] for p in paths])
115
+ d = read(os.path.join(mine, "VIDEO_TS.IFO"))
116
+ tt_srpt, atrt = struct.unpack_from(">I", d, 0xC4)[0], struct.unpack_from(">I", d, 0xD0)[0]
117
+ self.assertEqual(struct.unpack_from(">H", d, atrt * 2048)[0], 3)
118
+ self.assertGreater(struct.unpack_from(">I", d, atrt * 2048 + 4)[0], 2048)
119
+ self.assertEqual([c[:3] for c in ifo.chapters(ifo.read_folder(mine))],
120
+ [(1, 1, 1), (2, 1, 1), (3, 1, 1)])
121
+ if LIBDVDNAV:
122
+ with dvdnav_sim.DvdSim(mine, LIBDVDNAV) as nav:
123
+ self.assertEqual(nav.num_titles(), 3)
124
+
108
125
  @unittest.skipUnless(LIBDVDNAV, "needs libdvdnav")
109
126
  def test_libdvdnav_plays_a_whole_disc(self):
110
127
  # a VIDEO_TS written by vtskit alone: two titlesets, three titles;
@@ -21,5 +21,5 @@ subtitles and angles, and dvdauthor's command language:
21
21
 
22
22
  from .errors import VtsError
23
23
 
24
- __version__ = "0.2.0"
24
+ __version__ = "0.3.1"
25
25
  __all__ = ["VtsError"]
@@ -25,10 +25,10 @@ authored):
25
25
  * `audio = 1; subtitle = 65;` next to each other are one SetSTN; `g1 = 0;
26
26
  jump menu 2;` is one command (vtskit.vm folds a Set and a Link), and
27
27
  what follows an unconditional jump in a block is left out;
28
- * a menu's button runs one command: a button whose text compiles to more
29
- sets GPRM 15 to its number and links to the end of the program chain,
30
- whose post commands start with a dispatcher (`g14 = g15; g15 = 0;` and a
31
- Goto per such button) and end with each button's code;
28
+ * a menu's or a title's button runs one command: a button whose text
29
+ compiles to more sets GPRM 15 to its number and links to the end of the
30
+ program chain, whose post commands start with a dispatcher (`g14 = g15;
31
+ g15 = 0;` and a Goto per such button) and end with each button's code;
32
32
  * a jump into another titleset (dvdauthor's "jumppad") cannot be one
33
33
  command: from the video manager a jump to a chapter sets GPRM 15 to a
34
34
  code naming the title and chapter and jumps to the titleset's chapter
@@ -572,10 +572,12 @@ class Disc:
572
572
  def menu(self, buttons, post, domain="vtsm", vts=0):
573
573
  """A menu's buttons and post commands: ([one command a button],
574
574
  post commands). A button whose text takes several commands runs
575
- them from the post, through a dispatcher, as dvdauthor arranges."""
575
+ them from the post, through a dispatcher, as dvdauthor arranges.
576
+ A title's buttons (domain "vts") are compiled the same way, as
577
+ dvdauthor compiles them."""
576
578
  self._check_domain(domain)
577
- if domain not in ("vtsm", "vmgm"):
578
- raise VtsError("buttons belong to menus")
579
+ if domain not in ("vtsm", "vmgm", "vts"):
580
+ raise VtsError("buttons belong to menus and titles")
579
581
  commands, deferred = [], []
580
582
  for n, text in enumerate(buttons, 1):
581
583
  items = self.instructions(text, domain, vts)
@@ -63,7 +63,7 @@ import os
63
63
  import struct
64
64
  from dataclasses import dataclass, field
65
65
 
66
- from . import menu as menus_, nav, ps, spu
66
+ from . import menu as menus_, nav, ps, spu, vm
67
67
  from .errors import VtsError
68
68
 
69
69
  SECTOR = ps.SECTOR
@@ -748,12 +748,25 @@ def _lay_title(number, title, offset, vob_id, loaded):
748
748
  return w, sector, vob_id
749
749
 
750
750
 
751
+ def _pre(title):
752
+ """A title's pre commands: with buttons, first the first button
753
+ highlighted (SetHL_BTNN 1), as dvdauthor writes it for a title with
754
+ buttons (tests/test_title_buttons.py)."""
755
+ if not title.buttons:
756
+ return tuple(title.pre)
757
+ return (vm.set_hl_btnn(1 << 10).encode(),) + tuple(title.pre)
758
+
759
+
751
760
  def _title_buttons(number, title, loaded, part_start):
752
761
  """The HLI of the title's buttons: {(part, angle): {VOBU: HLI}}, in the
753
762
  VOBUs that play during each stretch (every angle's), shown from the
754
763
  stretch's start to its end in the VOB's time (a stretch that began in
755
764
  an earlier part from the VOB's start; one that goes on into a later
756
- part to the VOB's end; one without an end never ending)."""
765
+ part to the VOB's end; one without an end never ending). Where two
766
+ stretches meet inside a VOBU, it carries the one that covers more of
767
+ it: buttons that change between VOBUs (oncemore starts a VOBU there)
768
+ are not lost to a picture's difference between a time and the VOBU's
769
+ start, as in a film title with pulldown."""
757
770
  if not title.buttons:
758
771
  return {}
759
772
  width, height = loaded[0][0][0].sequence[:2]
@@ -768,26 +781,35 @@ def _title_buttons(number, title, loaded, part_start):
768
781
  if e1 is None or e1 > s2:
769
782
  raise VtsError(f"{w1} and {w2} overlap in time")
770
783
  out = {}
771
- used = set()
784
+ touched, used = set(), set()
772
785
  for k, part in enumerate(loaded):
773
786
  for a, (_, vs, _) in enumerate(part):
774
787
  base, vob_end = vs[0].s_ptm, vs[-1].e_ptm
775
788
  table = out[(k, a)] = {}
776
- for s, e, tb, what in stretches:
777
- for i, v in enumerate(vs):
778
- t0 = part_start[k] + v.s_ptm - base
779
- t1 = part_start[k] + v.e_ptm - base
780
- if t1 <= s or e is not None and t0 >= e:
781
- continue
782
- if i in table:
783
- raise VtsError(f"{what} start in the VOBU (part {k + 1}, VOBU {i + 1}) where "
784
- "the buttons before end: a VOBU carries one set of buttons")
785
- he = 0xFFFFFFFF if e is None else min(base + e - part_start[k], vob_end)
786
- table[i] = tb.hli(base + max(s - part_start[k], 0), he)
787
- used.add(what)
789
+ for i, v in enumerate(vs):
790
+ t0 = part_start[k] + v.s_ptm - base
791
+ t1 = part_start[k] + v.e_ptm - base
792
+ # (two stretches in a VOBU: the one covering more of it, the
793
+ # later on a tie; a VOBU carries one set of buttons)
794
+ best = None
795
+ for s, e, tb, what in stretches:
796
+ cover = min(t1, e if e is not None else t1) - max(t0, s)
797
+ if cover > 0:
798
+ touched.add(what)
799
+ if best is None or cover >= best[0]:
800
+ best = (cover, s, e, tb, what)
801
+ if best is None:
802
+ continue
803
+ _, s, e, tb, what = best
804
+ he = 0xFFFFFFFF if e is None else min(base + e - part_start[k], vob_end)
805
+ table[i] = tb.hli(base + max(s - part_start[k], 0), he)
806
+ used.add(what)
788
807
  for _, _, _, what in stretches:
789
- if what not in used:
808
+ if what not in touched:
790
809
  raise VtsError(f"{what} start after the title ends")
810
+ if what not in used:
811
+ raise VtsError(f"{what} are shown for less of each VOBU they fall in than the buttons "
812
+ "beside them: a VOBU carries one set of buttons")
791
813
  return out
792
814
 
793
815
 
@@ -955,7 +977,7 @@ def _write(folder, vts, titles, written, menus, language, pan_scan, letterbox, c
955
977
  for name in ("next_pgc", "prev_pgc", "up_pgc"):
956
978
  if not 0 <= getattr(t, name) <= len(titles):
957
979
  raise VtsError(f"title {n}: {name} {getattr(t, name)}: the titleset has {len(titles)} program chains")
958
- pgcs = [_pgc((t.pre, t.post, t.cell_commands), w, frame, sorted(w.stream.subpictures), t.palette,
980
+ pgcs = [_pgc((_pre(t), t.post, t.cell_commands), w, frame, sorted(w.stream.subpictures), t.palette,
959
981
  stills=t.stills, uops=t.uops, video=video, links=(t.next_pgc, t.prev_pgc, t.up_pgc))
960
982
  for t, w in zip(titles, written)]
961
983
  pgci = struct.pack(">HHI", len(pgcs), 0, 0)
@@ -7,8 +7,9 @@ it, TT_SRPT (every title of the disc: its titleset, number there, chapters,
7
7
  angles, and where the titleset starts on the disc), VTS_ATRT (a copy of
8
8
  each titleset's stream attributes) and, with menus, VMGM_PGCI_UT (one
9
9
  language unit), VMGM_C_ADT and VMGM_VOBU_ADMAP. The parental masks and
10
- the text data are absent. Each table starts at a sector; the BUP is the
11
- IFO again.
10
+ the text data are absent. Each table starts at a sector and takes as many
11
+ as it needs (VTS_ATRT two from three titlesets on); the BUP is the IFO
12
+ again.
12
13
 
13
14
  From dvdauthor's output: the VMG takes its IFO, its menu VOB and its BUP
14
15
  on the disc, 32 sectors at least, and the first titleset starts right
@@ -92,7 +93,12 @@ def write(folder, titlesets, menus=(), first_play=None, language="en", provider=
92
93
  for name, t in order:
93
94
  if t is not None:
94
95
  ifo_sectors += len(_pad(t)) // SECTOR
95
- ifo_sectors += 2 # TT_SRPT and VTS_ATRT below, a sector each (checked)
96
+ # TT_SRPT and VTS_ATRT, written below once the titlesets' places are
97
+ # known: 12 bytes a title, and 4 + 8 + 0x300 bytes a titleset (from
98
+ # three titlesets on, the attributes take a second sector)
99
+ titles = sum(len(ts.titles) for ts in titlesets)
100
+ ifo_sectors += -(-(8 + 12 * titles) // SECTOR)
101
+ ifo_sectors += -(-(8 + (4 + 8 + 0x300) * len(titlesets)) // SECTOR)
96
102
  vmg_sectors = max(2 * ifo_sectors + menu_sectors, VMG_SECTORS)
97
103
 
98
104
  # TT_SRPT: the titles of the disc
@@ -131,7 +137,7 @@ def write(folder, titlesets, menus=(), first_play=None, language="en", provider=
131
137
  pointers[name] = sectors
132
138
  sectors += len(tables[name]) // SECTOR
133
139
  if sectors != ifo_sectors:
134
- raise VtsError("the title table or the attributes outgrew a sector") # (99 titles fit)
140
+ raise VtsError("the title table or the attributes are not the size counted")
135
141
  mat = bytearray(head)
136
142
  mat[:12] = b"DVDVIDEO-VMG"
137
143
  struct.pack_into(">I", mat, 0x0C, vmg_sectors - 1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vtskit
3
- Version: 0.2.0
3
+ Version: 0.3.1
4
4
  Summary: DVD-Video titlesets in pure Python: VOBs, navigation packs and IFO files
5
5
  Author: kajisho5
6
6
  License-Expression: LGPL-3.0-or-later
@@ -146,10 +146,13 @@ oncemore writing its discs with vtskit.
146
146
  to an end, the buttons, the palette entries that draw them): their
147
147
  overlay is a forced subpicture added with `vtskit.subtitles`, and the
148
148
  highlight information goes only into the VOBUs that play during the
149
- stretch, with the stretch's start and end as its start time, end time
149
+ stretch (where two stretches meet inside a VOBU, the one covering more
150
+ of it), with the stretch's start and end as its start time, end time
150
151
  and the end of the selection (dvdinfo's hli_s_ptm, hli_e_ptm,
151
152
  btn_sl_e_ptm). dvdauthor gives a title's buttons the whole title (every
152
- VOBU, never ending), which is what buttons without an end write.
153
+ VOBU, never ending), which is what buttons without an end write. As
154
+ dvdauthor does, a title with buttons starts with its first button
155
+ highlighted (SetHL_BTNN 1 before its own pre commands).
153
156
  * `vtskit.subtitles` adds subtitles to a title's program stream, as spumux
154
157
  does: each cue a picture of up to four colours placed on the screen and
155
158
  shown for a while, as subpicture packs a little before their time.
@@ -157,8 +160,8 @@ oncemore writing its discs with vtskit.
157
160
  `if (g5 != 0) { button = g5; }`, `call menu entry root;`) for the domain
158
161
  a text runs in, to the same commands dvdauthor writes for it: a tool
159
162
  that already speaks the language, as oncemore does, keeps its text. A
160
- button of several commands runs them from the menu's post through a
161
- dispatcher, and a jump to a chapter of another titleset goes through pad
163
+ button of several commands, a menu's or a title's, runs them from the
164
+ program chain's post through a dispatcher, and a jump to a chapter of another titleset goes through pad
162
165
  program chains, as dvdauthor's "jumppads" do.
163
166
  * `vtskit.vmg` writes the video manager: VIDEO_TS.IFO and its BUP, with
164
167
  the first-play commands, the table of titles and the titlesets'
@@ -181,7 +184,8 @@ The tests give the same titles to dvdauthor and to vtskit, and the IFO and
181
184
  the VOBs must be the same byte for byte: NTSC and PAL, 4:3 and 16:9,
182
185
  chapters on and between VOBU starts, two titles in a titleset, AC-3, LPCM
183
186
  and MPEG sound with languages, B-pictures, short GOPs, a 40-second title,
184
- a subtitle track, two titlesets; every command form is the bytes dvdauthor
187
+ a subtitle track, two titlesets and three (whose attributes take the VMG
188
+ a second sector); every command form is the bytes dvdauthor
185
189
  compiles for the same statement, and a disc described in its language
186
190
  (menus of commands alone, buttons of several commands, cell commands, two
187
191
  titlesets) comes out with the same IFOs; for menus, the highlight
@@ -190,8 +194,9 @@ dvdauthor's (its buttons made with spumux), and a menu's overlay in a
190
194
  normal colour is spumux's unit byte for byte; subtitles muxed by vtskit are
191
195
  spumux's units, placed where spumux places them, and the title written
192
196
  from that stream (4:3, and 16:9 with and without pan-scan or letterbox) is
193
- dvdauthor's; a title with buttons over the whole of it is dvdauthor's
194
- VOB byte for byte. A VIDEO_TS written by vtskit alone plays
197
+ dvdauthor's; a title with buttons over the whole of it (one of them of
198
+ several commands) is dvdauthor's VOB and IFO byte for byte, but for the
199
+ palette. A VIDEO_TS written by vtskit alone plays
195
200
  its titles and chapters in order under libdvdnav, its commands jumping
196
201
  between titles, its menus' buttons lead where they say, and its jumps into
197
202
  another titleset's chapters land; a title with an angle block plays and
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes