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

1from abc import ABC, abstractmethod 

2 

3from fcp7xml_to_unreal import config 

4 

5 

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 

19 

20 # TODO: needed? 

21 # def is_valid(self): 

22 # logger.info(f"shot: {self.ip} {self.op} {self.sf} {self.ef}") 

23 # return True 

24 

25 # The name method needs local implementation and needs to be sortable 

26 @abstractmethod 

27 def name(self): 

28 pass 

29 

30 def __lt__(self, other): 

31 return self.name() < other.name() 

32 

33 def __str__(self): 

34 outstr = f"{self.rawname:30s} {self.sf:6d} {self.ef:6d}" 

35 return outstr 

36 

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 

44 

45 def has_fx(self): 

46 return len(self.fx) > 0 

47 

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) 

53 

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 

62 

63 # return true if s is inside self 

64 def contains(self, s): 

65 return (s.sf >= self.sf) & (s.ef <= self.ef) 

66 

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) 

70 

71 def add_fx(self, fx_name, fx_val_dict): 

72 self.fx[fx_name] = fx_val_dict 

73 

74 

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. 

78 

79 

80class UnrealShot(TimelineEntity): 

81 def __init__(self, name, sf, ef, ip, op): 

82 super().__init__(name, sf, ef, ip, op) 

83 

84 def name(self): 

85 return self.rawname 

86 

87 # def scene_number(self): 

88 # return self.rawname.split("_")[1] 

89 

90 

91class ConformScene(TimelineEntity): 

92 def __init__(self, name, sf, ef, ip, op): 

93 super().__init__(name, sf, ef, ip, op) 

94 

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 

101 

102 return self.rawname[len(config["CONFORMSCENE_BURNIN_PREFIX"]) :] 

103 

104 

105class ConformShot(TimelineEntity): 

106 def __init__(self, name, sf, ef, ip, op): 

107 super().__init__(name, sf, ef, ip, op) 

108 

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 

114 

115 return ( 

116 self.container.name() 

117 + "_" 

118 + self.rawname[len(config["CONFORMSHOT_BURNIN_PREFIX"]) :] 

119 ) 

120 

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. 

123 

124 def is_first_shot(self): 

125 return int(self.rawname[3:]) == 1