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

1#!/usr/bin/env python3 

2 

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 

15 

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 

22 

23 # ************************************************************************** 

24 

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" 

29 

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") 

55 

56 # Fix the file name itself ... 

57 stdout = stdout.replace(f"<device>{fname}</device>", f"<device>{html.escape(fname)}</device>") 

58 

59 # Fix common errors ... 

60 stdout = stdout.replace("<df>Pan&Scan</df>", "<df>Pan&amp;Scan</df>") 

61 stdout = stdout.replace("<df>P&S + Letter</df>", "<df>P&amp;S + Letter</df>") 

62 

63 # Initialize dictionary ... 

64 ans = {} 

65 

66 # Parse the XML ... 

67 xml = lxml.etree.XML(stdout) 

68 

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 } 

75 

76 # Return dictionary ... 

77 return ans