Coverage for pyguymer3/media/ffprobe.py: 68%

19 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 ffprobe( 

5 fname, 

6 /, 

7 *, 

8 cwd = None, 

9 ensureNFC = True, 

10 ffprobePath = None, 

11 playlist = -1, 

12 timeout = 60.0, 

13): 

14 """ 

15 Run "ffprobe" on a file and return the format and stream information. 

16 

17 Parameters 

18 ---------- 

19 fname : str 

20 the file to be surveyed 

21 cwd : str, optional 

22 the child working directory 

23 ensureNFC : bool, optional 

24 ensure that the Unicode encoding is NFC 

25 ffprobePath : str, optional 

26 the path to the "ffprobe" binary (if not provided then Python will 

27 attempt to find the binary itself) 

28 playlist : int, optional 

29 the playlist within the Blu-ray folder structure to be surveyed 

30 timeout : float, optional 

31 the timeout for any requests/subprocess calls 

32 

33 Returns 

34 ------- 

35 ans : dict 

36 the format and stream information 

37 

38 Notes 

39 ----- 

40 Copyright 2017 Thomas Guymer [1]_ 

41 

42 References 

43 ---------- 

44 .. [1] PyGuymer3, https://github.com/Guymer/PyGuymer3 

45 """ 

46 

47 # Import standard modules ... 

48 import json 

49 import shutil 

50 import subprocess 

51 import unicodedata 

52 

53 # ************************************************************************** 

54 

55 # Try to find the paths if the user did not provide them ... 

56 if ffprobePath is None: 

57 ffprobePath = shutil.which("ffprobe") 

58 assert ffprobePath is not None, "\"ffprobe\" is not installed" 

59 

60 # Check input ... 

61 if fname.startswith("bluray:") and playlist < 0: 

62 raise Exception("a Blu-ray was specified but no playlist was supplied") from None 

63 

64 # Check if it is a Blu-ray ... 

65 if fname.startswith("bluray:"): 

66 # Find stream info ... 

67 # NOTE: Sometimes "ffprobe" appears to work fine but even with 

68 # "-loglevel quiet" it sometimes outputs things like: 

69 # disc.c:424: error opening file CERTIFICATE/id.bdmv 

70 # disc.c:424: error opening file CERTIFICATE/BACKUP/id.bdmv 

71 # bluray.c:255: 00008.m2ts: no timestamp for SPN 0 (got 0). clip 90000-7467995. 

72 # ... to standard error, hence I have to only attempt to parse 

73 # standard out as JSON rather than both standard error and 

74 # standard out together. 

75 resp = subprocess.run( 

76 [ 

77 ffprobePath, 

78 "-hide_banner", 

79 "-loglevel", "quiet", 

80 "-probesize", "1G", 

81 "-analyzeduration", "1800M", 

82 "-print_format", "json", 

83 "-show_format", 

84 "-show_streams", 

85 "-playlist", f"{playlist:d}", 

86 fname, 

87 ], 

88 check = True, 

89 cwd = cwd, 

90 encoding = "utf-8", 

91 stderr = subprocess.DEVNULL, 

92 stdout = subprocess.PIPE, 

93 timeout = timeout, 

94 ) 

95 else: 

96 # Attempt to survey the file ... 

97 try: 

98 # Find stream info ... 

99 # NOTE: Sometimes "ffprobe" appears to work fine but even with 

100 # "-loglevel quiet" it sometimes outputs things like: 

101 # disc.c:424: error opening file CERTIFICATE/id.bdmv 

102 # disc.c:424: error opening file CERTIFICATE/BACKUP/id.bdmv 

103 # bluray.c:255: 00008.m2ts: no timestamp for SPN 0 (got 0). clip 90000-7467995. 

104 # ... to standard error, hence I have to only attempt to parse 

105 # standard out as JSON rather than both standard error and 

106 # standard out together. 

107 resp = subprocess.run( 

108 [ 

109 ffprobePath, 

110 "-hide_banner", 

111 "-loglevel", "quiet", 

112 "-probesize", "1G", 

113 "-analyzeduration", "1800M", 

114 "-print_format", "json", 

115 "-show_format", 

116 "-show_streams", 

117 fname, 

118 ], 

119 check = True, 

120 cwd = cwd, 

121 encoding = "utf-8", 

122 stderr = subprocess.DEVNULL, 

123 stdout = subprocess.PIPE, 

124 timeout = timeout, 

125 ) 

126 except subprocess.CalledProcessError: 

127 # Fallback and attempt to find stream info as a raw M-JPEG stream ... 

128 # NOTE: Sometimes "ffprobe" appears to work fine but even with 

129 # "-loglevel quiet" it sometimes outputs things like: 

130 # disc.c:424: error opening file CERTIFICATE/id.bdmv 

131 # disc.c:424: error opening file CERTIFICATE/BACKUP/id.bdmv 

132 # bluray.c:255: 00008.m2ts: no timestamp for SPN 0 (got 0). clip 90000-7467995. 

133 # ... to standard error, hence I have to only attempt to parse 

134 # standard out as JSON rather than both standard error and 

135 # standard out together. 

136 resp = subprocess.run( 

137 [ 

138 ffprobePath, 

139 "-hide_banner", 

140 "-loglevel", "quiet", 

141 "-probesize", "1G", 

142 "-analyzeduration", "1800M", 

143 "-print_format", "json", 

144 "-show_format", 

145 "-show_streams", 

146 "-f", "mjpeg", 

147 fname, 

148 ], 

149 check = True, 

150 cwd = cwd, 

151 encoding = "utf-8", 

152 stderr = subprocess.DEVNULL, 

153 stdout = subprocess.PIPE, 

154 timeout = timeout, 

155 ) 

156 

157 # Return ffprobe output as dictionary ... 

158 if ensureNFC and not unicodedata.is_normalized("NFC", resp.stdout): 

159 return json.loads(unicodedata.normalize("NFC", resp.stdout)) 

160 return json.loads(resp.stdout)