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

1#!/usr/bin/env python3 

2 

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 

16 

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 

27 

28 # Import sub-functions ... 

29 from .yuv2rgb import yuv2rgb 

30 

31 # ************************************************************************** 

32 

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" 

37 

38 # Check input ... 

39 if usr_track == -1: 

40 raise Exception("no track was requested") from None 

41 

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

67 

68 # Fix the file name itself ... 

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

70 

71 # Fix common errors ... 

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

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

74 

75 # Parse the XML ... 

76 xml = lxml.etree.XML(stdout) 

77 

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 

83 

84 # Create empty list ... 

85 vals = [] 

86 

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

96 

97 # Return answer ... 

98 return ",".join(vals)