Coverage for  / opt / hostedtoolcache / Python / 3.12.13 / x64 / lib / python3.12 / site-packages / fcp7xml_to_unreal / xml_helpers / reports.py: 81%

101 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-17 22:47 +0000

1import csv 

2import os 

3import subprocess 

4import tempfile 

5 

6from fcp7xml_to_unreal.models.Audio import AudioFile 

7 

8 

9def audio_report(episode, to_csv=False): 

10 # create a dictionary of master clip id to name so we can get the path of any audiofile 

11 master_clip_dict = {} 

12 for af in episode.audio_files: 

13 mcid = af.masterclipid 

14 mcpath = af.path 

15 if (mcid not in master_clip_dict) & (mcpath is not None): 

16 master_clip_dict[mcid] = mcpath 

17 

18 # set the master clip paths 

19 for af in episode.audio_files: 

20 if af.masterclipid in master_clip_dict: 

21 af.path = master_clip_dict[af.masterclipid] 

22 

23 # go through dialogue files, try and identify which scenes they're in. 

24 for af in episode.audio_files: 

25 if af.is_dialogue(): 

26 for cshot in episode.cshots: 

27 if cshot.overlaps(af.sf, af.ef): 

28 af.shotlist.append(cshot.name()) 

29 

30 # now write out 

31 

32 if to_csv: 

33 with tempfile.NamedTemporaryFile( 

34 mode="w", suffix=".csv", newline="", delete=False 

35 ) as tmpfile: 

36 csvwriter = csv.writer(tmpfile, dialect="excel", quoting=csv.QUOTE_MINIMAL) 

37 csvwriter.writerow(episode.audio_files[0].dump_header()) 

38 for tn in episode.track_names: 

39 for af in episode.audio_files: 

40 if (af.trackname == tn) & (not af.printed): 

41 csvwriter.writerow(af.to_list()) 

42 af.printed = True 

43 tmpfile.close() 

44 try: 

45 os.startfile(tmpfile.name) # Windows only 

46 except BaseException: 

47 subprocess.call(("open", tmpfile.name)) # macOS 

48 return None 

49 

50 else: 

51 output = "" 

52 output += ",".join(AudioFile.OUTPUT_VALS) + "\n" 

53 for af in episode.audio_files: 

54 output += str(af) + "\n" 

55 af.printed = True 

56 return output 

57 

58 

59def conform_report(episode): 

60 # see if we can match every conformed shot to a story shot. 

61 # if we can't let the user know about it. 

62 

63 # unmatched_shots = 0 

64 boarded_shots = 0 

65 cg_shots = 0 

66 output = "" 

67 

68 for cshot in episode.cshots: 

69 matched = [] 

70 for sshot in episode.sshots: 

71 result = cshot.match(sshot) 

72 if result == "perfect": 

73 matched.append(sshot) 

74 cshot.matched_shot = sshot 

75 continue 

76 elif result == "close": 

77 matched.append(sshot) 

78 cshot.matched_shot = sshot 

79 

80 if len(matched) == 0: 

81 boarded_shots += 1 

82 output += f"Warning: Conformed shot {cshot.name()} doesn't have a matching unreal shot.\n" 

83 else: 

84 cg_shots += 1 

85 for m in matched: 

86 mtype = cshot.match(m) 

87 if mtype != "perfect": 

88 output += f"Possible conform mismatch detected:\n\t{cshot}\n\t{m}\n" 

89 

90 output += "\n" 

91 

92 # check to make sure we have consecutive scenes. 

93 sorted_scenes = [s.name() for s in episode.scenes] 

94 sorted_scenes.sort() 

95 if len(sorted_scenes) > 0: 

96 last_scene = sorted_scenes[0] 

97 for scene in sorted_scenes[1:]: 

98 if last_scene == scene: 

99 output += f"SCENE BURNIN WARNING: scene {last_scene} burnin exists multiple times\n" 

100 # TODO: Can't do this one because we're losing the idea that scene names must be numbers. 

101 # elif last_scene + 1 < scene: 

102 # output += f"SCENE BURNIN WARNING: scene burnin may be missing between {last_scene} and {scene}\n" 

103 last_scene = scene 

104 

105 output += "\n" 

106 

107 # now check each sequence for consecutive shots (looking for skipped burnins) 

108 # how to do this? Is this still a #TODO? 

109 

110 # check each scene for consecutive shots (aka look for skipped burnins) 

111 # TODO: either figure out a way to include the A, B, C shot logic or remove this entirely. 

112 

113 for scene in episode.scenes: 

114 shotlist = [] 

115 for cshot in episode.cshots: 

116 if cshot.container == scene: 

117 shotlist.append(cshot.name()) 

118 

119 # report the count, that's useful, and name the first and last shots too 

120 shotlist.sort() 

121 

122 output += ( 

123 f"Conform scene {scene.name()} has {len(shotlist)} shot" 

124 + ("s" if len(shotlist) != 1 else "") 

125 + ":" 

126 ) 

127 output += "\n" + "\t".join([f"{shot}" for shot in shotlist]) + "\n" 

128 

129 """ 

130 # we have a sorted list of shot names. Let's go through them in order, see what their CG 

131 # counterpart is, and flag consecutive duplicates. 

132 # This code is ugly, I'm sorry. If you're reading this, forgive me. It's production code. 

133 last_cg_shot = "boarded" 

134 for cshotname in shotlist: 

135 this_shot = None 

136 for cshot in episode.cshots: 

137 if cshotname == cshot.name: 

138 this_shot = cshot 

139 break 

140 if this_shot is None: 

141 output += f"Error: couldn't find shot {cshotname} in cshot list. Shouldn't be possible \n" 

142 continue 

143 if this_shot.matched_shot is None: 

144 last_cg_shot = "boarded" 

145 continue 

146 # if we're here we have a real CG shot, let's compare names. 

147 if last_cg_shot == this_shot.matched_shot.name: 

148 output += f"WARNING: shot {cshot.name} references same CG shot ({this_shot.matched_shot.name}) as the previous shot.\n" 

149 last_cg_shot = this_shot.matched_shot.name 

150 """ 

151 return output 

152 

153 

154def cgfixes_report(episode): 

155 # this report is to loop over all shots, identify those with editorial fx and print them 

156 # ideally in an easy-to-use CSV style 

157 

158 # TODO: do we need/want the CSV aspect of this? 

159 

160 # want to sort this list by starting frame 

161 episode.sshots.sort(key=lambda x: x.sf) 

162 

163 # for CSV-type reporting: 

164 # initial_output = "Shot,Fix Type,Report Text\n" 

165 initial_output = "" 

166 output = initial_output 

167 lastshotname = None 

168 last_ef = None 

169 for shot in episode.sshots: 

170 if shot.name() == lastshotname and last_ef == shot.sf: # check for frame parity 

171 output += f"Warning: {lastshotname} appears back to back in the edit. This may be fine if it's a cut, but worth checking.\n" 

172 # output += f"{lastshotname},CG Conform,appears back to back\n" 

173 lastshotname = shot.name() 

174 last_ef = shot.ef 

175 

176 for shot in episode.sshots: 

177 if shot.has_fx(): 

178 output += ( 

179 # f"{shot.name()},CG Conform, {shot.fx_str()}\n" 

180 f"Warning: {shot.name()} has editorial FX applied that may need CG fixes: {shot.fx_str()}\n" 

181 ) 

182 if output == initial_output: 

183 return "No CG fixes found!" 

184 return output