Coverage for / opt / hostedtoolcache / Python / 3.12.13 / x64 / lib / python3.12 / site-packages / fcp7xml_to_unreal / models / Shot.py: 97%
60 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
1from abc import ABC, abstractmethod
3from fcp7xml_to_unreal import config
6# A TimelineEntity is something that lives in a timeline, could be a shot, a burnin, a scene marker etc.
7class TimelineEntity(ABC):
8 def __init__(self, name, sf, ef, ip, op):
9 self.rawname = name
10 self.sf = int(sf)
11 self.ef = int(ef)
12 self.ip = int(ip)
13 self.op = int(op)
14 self.dur = self.sf - self.ef
15 self.clipdur = self.op - self.ip
16 self.notes = []
17 self.fx = {}
18 self.container = None # e.g. broader entity containing this shot, aka a scene
20 # TODO: needed?
21 # def is_valid(self):
22 # logger.info(f"shot: {self.ip} {self.op} {self.sf} {self.ef}")
23 # return True
25 # The name method needs local implementation and needs to be sortable
26 @abstractmethod
27 def name(self):
28 pass
30 def __lt__(self, other):
31 return self.name() < other.name()
33 def __str__(self):
34 outstr = f"{self.rawname:30s} {self.sf:6d} {self.ef:6d}"
35 return outstr
37 def fx_str(self):
38 outstr = ""
39 for k in self.fx:
40 outstr += f" FX: {k}"
41 for param in self.fx[k]:
42 outstr += f" {param} {self.fx[k][param]}"
43 return outstr
45 def has_fx(self):
46 return len(self.fx) > 0
48 def match(self, s):
49 # Logic is this:
50 # If both start and end frames match, it's "perfect"
51 # If the above isn't true, and both start and end are within 2, it's "close"
52 # Otherwise it's not a match (None)
54 if (self.sf == s.sf) & (self.ef == s.ef):
55 return "perfect"
56 elif (self.ef in range(s.ef - 2, s.ef + 2)) & (
57 self.sf in range(s.sf - 2, s.sf + 2)
58 ):
59 return "close"
60 else:
61 return None
63 # return true if s is inside self
64 def contains(self, s):
65 return (s.sf >= self.sf) & (s.ef <= self.ef)
67 # return true if this TimelineEntity overlaps any of the frame range given
68 def overlaps(self, sf, ef):
69 return not (ef < self.sf) | (sf > self.ef)
71 def add_fx(self, fx_name, fx_val_dict):
72 self.fx[fx_name] = fx_val_dict
75# Whatever your pipeline, you can build in your local specifics to these subclasses.
76# Scenes are made up of StoryShots.
77# When it's time to conform, ConformedShots are matched to StoryShots.
80class UnrealShot(TimelineEntity):
81 def __init__(self, name, sf, ef, ip, op):
82 super().__init__(name, sf, ef, ip, op)
84 def name(self):
85 return self.rawname
87 # def scene_number(self):
88 # return self.rawname.split("_")[1]
91class ConformScene(TimelineEntity):
92 def __init__(self, name, sf, ef, ip, op):
93 super().__init__(name, sf, ef, ip, op)
95 # Our scene burnin images were named as follows:
96 # seq_001,
97 # seq_121a, etc.
98 # So returning the name without the prefix 'seq_' gives us our sortable scene "name"
99 def name(self):
100 # TODO: this is the raw name less the prefix defined in ALL CAPS
102 return self.rawname[len(config["CONFORMSCENE_BURNIN_PREFIX"]) :]
105class ConformShot(TimelineEntity):
106 def __init__(self, name, sf, ef, ip, op):
107 super().__init__(name, sf, ef, ip, op)
109 def name(self):
110 # ConformShots in this implementation are named SC_SHT
111 # SC = Scene code
112 # SHT = Shot code
113 # TODO: this is the raw name less the prefix defined in ALL CAPS
115 return (
116 self.container.name()
117 + "_"
118 + self.rawname[len(config["CONFORMSHOT_BURNIN_PREFIX"]) :]
119 )
121 # TODO: this is very implementation-specific, and exists only to try and auto-resolve issues where
122 # the conform shot burnin overlaps with multiple scene burnins.
124 def is_first_shot(self):
125 return int(self.rawname[3:]) == 1