Coverage for pyguymer3/media/return_ISO_palette.py: 3%
37 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-08 18:47 +0000
1#!/usr/bin/env python3
3# Define function ...
4def return_ISO_palette(
5 fname,
6 /,
7 *,
8 lsdvdPath = None,
9 timeout = 60.0,
10 usr_track = -1,
11):
12 # Import standard modules ...
13 import html
14 import shutil
15 import subprocess
17 # Import special modules ...
18 try:
19 import lxml
20 import lxml.etree
21 except:
22 raise Exception("\"lxml\" is not installed; run \"pip install --user lxml\"") from None
23 try:
24 import numpy
25 except:
26 raise Exception("\"numpy\" is not installed; run \"pip install --user numpy\"") from None
28 # Import sub-functions ...
29 from .yuv2rgb import yuv2rgb
31 # **************************************************************************
33 # Try to find the paths if the user did not provide them ...
34 if lsdvdPath is None:
35 lsdvdPath = shutil.which("lsdvd")
36 assert lsdvdPath is not None, "\"lsdvd\" is not installed"
38 # Check input ...
39 if usr_track == -1:
40 raise Exception("no track was requested") from None
42 # Find track info ...
43 # NOTE: "lsdvd" specifies the output encoding in the accompanying XML
44 # header, however, this is a lie. By inspection of "oxml.c" in the
45 # "lsdvd" source code it appears that the XML header is hard-coded and
46 # that "lsdvd" does not perform any checks to make sure that the
47 # output is either valid XML or valid UTF-8. Therefore, I must load it
48 # as a byte sequence and manually convert it to a UTF-8 string whilst
49 # replacing the invalid UTF-8 bytes (and remove the XML header).
50 # NOTE: Don't merge standard out and standard error together as the result
51 # will probably not be valid XML if standard error is not empty.
52 resp = subprocess.run(
53 [
54 lsdvdPath,
55 "-x",
56 "-Ox",
57 fname,
58 ],
59 check = True,
60 encoding = "utf-8",
61 errors = "replace",
62 stderr = subprocess.DEVNULL,
63 stdout = subprocess.PIPE,
64 timeout = timeout,
65 )
66 stdout = resp.stdout.removeprefix("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
68 # Fix the file name itself ...
69 stdout = stdout.replace(f"<device>{fname}</device>", f"<device>{html.escape(fname)}</device>")
71 # Fix common errors ...
72 stdout = stdout.replace("<df>Pan&Scan</df>", "<df>Pan&Scan</df>")
73 stdout = stdout.replace("<df>P&S + Letter</df>", "<df>P&S + Letter</df>")
75 # Parse the XML ...
76 xml = lxml.etree.XML(stdout)
78 # Loop over all tracks ...
79 for track in xml.findall("track"):
80 # Skip if this track is not the chosen one ...
81 if int(track.find("ix").text) != int(usr_track):
82 continue
84 # Create empty list ...
85 vals = []
87 # Loop over all colours in the palette ...
88 for color in track.find("palette").findall("color"):
89 # Convert YUV to RGB ...
90 yuv = numpy.zeros((1, 1, 3), dtype = numpy.uint8)
91 yuv[0, 0, 0] = int(color.text[0:2], 16)
92 yuv[0, 0, 1] = int(color.text[2:4], 16)
93 yuv[0, 0, 2] = int(color.text[4:6], 16)
94 rgb = yuv2rgb(yuv, version = "SDTV")
95 vals.append(format(rgb[0, 0, 0], "x").rjust(2, '0') + format(rgb[0, 0, 1], "x").rjust(2, '0') + format(rgb[0, 0, 2], "x").rjust(2, '0'))
97 # Return answer ...
98 return ",".join(vals)