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
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-17 22:47 +0000
1import csv
2import os
3import subprocess
4import tempfile
6from fcp7xml_to_unreal.models.Audio import AudioFile
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
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]
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())
30 # now write out
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
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
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.
63 # unmatched_shots = 0
64 boarded_shots = 0
65 cg_shots = 0
66 output = ""
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
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"
90 output += "\n"
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
105 output += "\n"
107 # now check each sequence for consecutive shots (looking for skipped burnins)
108 # how to do this? Is this still a #TODO?
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.
113 for scene in episode.scenes:
114 shotlist = []
115 for cshot in episode.cshots:
116 if cshot.container == scene:
117 shotlist.append(cshot.name())
119 # report the count, that's useful, and name the first and last shots too
120 shotlist.sort()
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"
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
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
158 # TODO: do we need/want the CSV aspect of this?
160 # want to sort this list by starting frame
161 episode.sshots.sort(key=lambda x: x.sf)
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
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