Coverage for pyguymer3/media/return_dict_of_ISO_tracks.py: 5%
22 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_dict_of_ISO_tracks(
5 fname,
6 /,
7 *,
8 lsdvdPath = None,
9 timeout = 60.0,
10):
11 # Import standard modules ...
12 import html
13 import shutil
14 import subprocess
16 # Import special modules ...
17 try:
18 import lxml
19 import lxml.etree
20 except:
21 raise Exception("\"lxml\" is not installed; run \"pip install --user lxml\"") from None
23 # **************************************************************************
25 # Try to find the paths if the user did not provide them ...
26 if lsdvdPath is None:
27 lsdvdPath = shutil.which("lsdvd")
28 assert lsdvdPath is not None, "\"lsdvd\" is not installed"
30 # Find track info ...
31 # NOTE: "lsdvd" specifies the output encoding in the accompanying XML
32 # header, however, this is a lie. By inspection of "oxml.c" in the
33 # "lsdvd" source code it appears that the XML header is hard-coded and
34 # that "lsdvd" does not perform any checks to make sure that the
35 # output is either valid XML or valid UTF-8. Therefore, I must load it
36 # as a byte sequence and manually convert it to a UTF-8 string whilst
37 # replacing the invalid UTF-8 bytes (and remove the XML header).
38 # NOTE: Don't merge standard out and standard error together as the result
39 # will probably not be valid XML if standard error is not empty.
40 resp = subprocess.run(
41 [
42 lsdvdPath,
43 "-x",
44 "-Ox",
45 fname,
46 ],
47 check = True,
48 encoding = "utf-8",
49 errors = "replace",
50 stderr = subprocess.DEVNULL,
51 stdout = subprocess.PIPE,
52 timeout = timeout,
53 )
54 stdout = resp.stdout.removeprefix("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
56 # Fix the file name itself ...
57 stdout = stdout.replace(f"<device>{fname}</device>", f"<device>{html.escape(fname)}</device>")
59 # Fix common errors ...
60 stdout = stdout.replace("<df>Pan&Scan</df>", "<df>Pan&Scan</df>")
61 stdout = stdout.replace("<df>P&S + Letter</df>", "<df>P&S + Letter</df>")
63 # Initialize dictionary ...
64 ans = {}
66 # Parse the XML ...
67 xml = lxml.etree.XML(stdout)
69 # Loop over all tracks ...
70 for track in xml.findall("track"):
71 # Append information ...
72 ans[track.find("ix").text] = {
73 "length" : float(track.find("length").text) # [s]
74 }
76 # Return dictionary ...
77 return ans