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 ADDED
@@ -0,0 +1,25 @@
1
+ """vtskit: DVD-Video titlesets in pure Python.
2
+
3
+ Steps 1 to 3 of the plan in the README, a disc with titles, menus,
4
+ subtitles and angles, and dvdauthor's command language:
5
+
6
+ * `vtskit.titleset`: a titleset written from its titles' program streams
7
+ (several parts in a row, with angle blocks) and its menus: the VOBs with
8
+ their navigation packs filled, the IFO and its BUP (`vtskit.menu`
9
+ describes a menu and its buttons, `vtskit.spu` draws the button overlay).
10
+ * `vtskit.vmg`: the video manager (VIDEO_TS.IFO, its BUP, the title
11
+ menu's VOB).
12
+ * `vtskit.subtitles`: subtitles added to a title's program stream, as
13
+ spumux does.
14
+ * `vtskit.vm`: the navigation commands a program chain carries, encoded;
15
+ `vtskit.lang` compiles dvdauthor's command language to them.
16
+ * `vtskit.nav`: the navigation packs of a VOB filled in (`vtskit.ps` reads
17
+ the program stream for both).
18
+ * `vtskit.ifo`: the chapters of a disc and where they start, read from its
19
+ IFO files.
20
+ """
21
+
22
+ from .errors import VtsError
23
+
24
+ __version__ = "0.1.0"
25
+ __all__ = ["VtsError"]
vtskit/errors.py ADDED
@@ -0,0 +1,8 @@
1
+ """The one exception vtskit raises."""
2
+
3
+
4
+ class VtsError(Exception):
5
+ """A disc cannot be read or written as asked."""
6
+
7
+ def __init__(self, message):
8
+ super().__init__(message)
vtskit/ifo.py ADDED
@@ -0,0 +1,113 @@
1
+ """Read the chapters of a DVD-Video disc from its IFO files.
2
+
3
+ Where each chapter of each title starts, as a sector of the disc: what a
4
+ titleset writer is checked against, and what a layer break is chosen from.
5
+ Read-only; nothing here writes an IFO.
6
+
7
+ Sector numbers in the IFO files are relative: the VMG's title table gives
8
+ where each titleset starts (counted from VIDEO_TS.IFO), a titleset IFO
9
+ gives where its title VOBs start (VTS_NN_0.IFO at 0xC4, counted from the
10
+ titleset), and a cell's first sector, in the program chain's cell playback
11
+ table, is counted from the title VOBs.
12
+
13
+ (From oncemore's layerbreak.py, by the same author, relicensed under
14
+ LGPL-3.0-or-later for vtskit.)
15
+ """
16
+
17
+ import os
18
+ import struct
19
+
20
+ from .errors import VtsError
21
+
22
+ SECTOR = 2048
23
+
24
+
25
+ def _u16(b, o):
26
+ return struct.unpack_from(">H", b, o)[0]
27
+
28
+
29
+ def _u32(b, o):
30
+ return struct.unpack_from(">I", b, o)[0]
31
+
32
+
33
+ def _check(b, o, size, what):
34
+ if o < 0 or o + size > len(b):
35
+ raise VtsError(f"{what} is outside the IFO ({o} of {len(b)} bytes)")
36
+
37
+
38
+ def chapter_cells(ifo):
39
+ """The chapters of a VTS_NN_0.IFO (its bytes): [(title number in the
40
+ titleset, chapter, first sector)], the sector relative to the start of
41
+ the title VOBs."""
42
+ if ifo[:12] != b"DVDVIDEO-VTS":
43
+ raise VtsError("not a titleset IFO")
44
+ try:
45
+ ptt = _u32(ifo, 0xC8) * SECTOR
46
+ pgcit = _u32(ifo, 0xCC) * SECTOR
47
+ _check(ifo, ptt, 8, "the chapter table")
48
+ _check(ifo, pgcit, 8, "the program chain table")
49
+ ntitles, end = _u16(ifo, ptt), ptt + _u32(ifo, ptt + 4) + 1
50
+ offsets = [ptt + _u32(ifo, ptt + 8 + 4 * k) for k in range(ntitles)] + [end]
51
+ out = []
52
+ for ttn in range(1, ntitles + 1):
53
+ a, b = offsets[ttn - 1], offsets[ttn]
54
+ for ch, o in enumerate(range(a, b - 3, 4), 1):
55
+ pgcn, pgn = _u16(ifo, o), _u16(ifo, o + 2)
56
+ if pgcn < 1 or pgn < 1:
57
+ raise VtsError(f"title {ttn} chapter {ch} points at no program")
58
+ pgc = pgcit + _u32(ifo, pgcit + 8 + 8 * (pgcn - 1) + 4)
59
+ _check(ifo, pgc, 0xEC, f"program chain {pgcn}")
60
+ entry = pgc + _u16(ifo, pgc + 0xE6) + pgn - 1
61
+ _check(ifo, entry, 1, f"program {pgn} of program chain {pgcn}")
62
+ cell = ifo[entry] # the program's entry cell
63
+ cpb = pgc + _u16(ifo, pgc + 0xE8) + 24 * (cell - 1)
64
+ _check(ifo, cpb, 24, f"cell {cell} of program chain {pgcn}")
65
+ out.append((ttn, ch, _u32(ifo, cpb + 8)))
66
+ return out
67
+ except struct.error as e:
68
+ raise VtsError(f"damaged titleset IFO: {e}") from None
69
+
70
+
71
+ def vts_bases(ifo_bytes):
72
+ """{titleset: sector of its title VOBs, relative to VIDEO_TS.IFO} from
73
+ the VMG and VTS IFOs ({"VIDEO_TS.IFO": bytes, "VTS_01_0.IFO": bytes, ...})."""
74
+ vmg = ifo_bytes.get("VIDEO_TS.IFO", b"")
75
+ if vmg[:12] != b"DVDVIDEO-VMG":
76
+ raise VtsError("VIDEO_TS.IFO is not a DVD-Video IFO")
77
+ try:
78
+ tt = _u32(vmg, 0xC4) * SECTOR
79
+ _check(vmg, tt, 8, "the title table")
80
+ starts = {}
81
+ for k in range(_u16(vmg, tt)):
82
+ o = tt + 8 + 12 * k
83
+ _check(vmg, o, 12, f"title {k + 1} of the title table")
84
+ starts[vmg[o + 6]] = _u32(vmg, o + 8)
85
+ except struct.error as e:
86
+ raise VtsError(f"damaged VIDEO_TS.IFO: {e}") from None
87
+ out = {}
88
+ for vts, start in starts.items():
89
+ name = f"VTS_{vts:02d}_0.IFO"
90
+ if name not in ifo_bytes:
91
+ raise VtsError(f"the title table names titleset {vts}, and {name} is missing")
92
+ out[vts] = start + _u32(ifo_bytes[name], 0xC4)
93
+ return out
94
+
95
+
96
+ def chapters(ifo_bytes):
97
+ """Every chapter on the disc: [(titleset, title number, chapter, sector
98
+ relative to VIDEO_TS.IFO)], in disc order."""
99
+ out = []
100
+ for vts, base in sorted(vts_bases(ifo_bytes).items()):
101
+ for ttn, ch, s in chapter_cells(ifo_bytes[f"VTS_{vts:02d}_0.IFO"]):
102
+ out.append((vts, ttn, ch, base + s))
103
+ return sorted(out, key=lambda c: c[3])
104
+
105
+
106
+ def read_folder(video_ts):
107
+ """The IFO files of a VIDEO_TS folder: {name: bytes}."""
108
+ out = {}
109
+ for n in os.listdir(video_ts):
110
+ if n == "VIDEO_TS.IFO" or (n.startswith("VTS_") and n.endswith("_0.IFO")):
111
+ with open(os.path.join(video_ts, n), "rb") as f:
112
+ out[n] = f.read()
113
+ return out