def clip_timeline(self, fi, far, sw=30, sh=50, font_name=None): seq = DATPens() seq += (DATPen() .rect(Rect(0, 0, (self.duration * sw), sh)).f(bw(0.5, 0.35))) for tidx, t in enumerate(self.tracks): for cgidx, cg in enumerate(t.clip_groups): for cidx, c in enumerate(cg.clips): r = Rect(c.start*sw, sh*tidx, (c.duration*sw)-2, sh) current = c.start-25 <= fi <= c.end+25 if current: seq.append(DATPens([ (DATPen() .rect(r) .f(hsl(cgidx*0.4+tidx*0.2, 0.75).lighter(0.2))), (DATText(c.input_text, Style(font_name, 24, load_font=0, fill=bw(0, 1 if tidx == self.workarea_track else 0.5)), r.inset(3, 8)))])) on_cut = fi in self.jumps() seq.translate(far.w/2 - fi*sw, 10) seq.append(DATPen() .rect(far.take(sh*len(self.tracks)+20, "mny").take(6 if on_cut else 2, "mdx")) .f(hsl(0.9, 1) if on_cut else hsl(0.8, 0.5))) return seq
class animation(renderable, Timeable): """ Base class for any frame-wise animation animatable by Coldtype """ def __init__(self, rect=(1080, 1080), duration=10, storyboard=[0], timeline: Timeline = None, audio=None, **kwargs): super().__init__(**kwargs) self.rect = Rect(rect) self.r = self.rect self.audio = audio if self.audio and sf: self.wavfile = Wavfile(audio) else: self.wavfile = None self.start = 0 self.end = duration self.storyboard = storyboard if timeline: self.timeline = timeline self.t = timeline self.start = timeline.start self.end = timeline.end if self.storyboard != [0] and timeline.storyboard == [0]: pass else: self.storyboard = timeline.storyboard.copy() else: self.timeline = Timeline(duration) def __call__(self, func): res = super().__call__(func) self.prefix = self.name + "_" return res def folder(self, filepath): return filepath.stem + "/" + self.name # TODO necessary? def all_frames(self): return list(range(0, self.duration)) def _active_frames(self, renderer_state): frames = [] for f in renderer_state.get_frame_offsets(self.name): frames.append(f % self.duration) return frames def active_frames(self, action, renderer_state, indices): frames = self._active_frames(renderer_state) if action == Action.RenderAll: frames = self.all_frames() elif action in [Action.PreviewIndices, Action.RenderIndices]: frames = indices elif action in [Action.RenderWorkarea]: if self.timeline: try: frames = self.workarea() except: frames = self.all_frames() #if hasattr(self.timeline, "find_workarea"): # frames = self.timeline.find_workarea() return frames def workarea(self): return list(self.timeline.workareas[0]) def jump(self, current, direction): c = current % self.duration js = self.timeline.jumps() if direction < 0: for j in reversed(js): if c > j: return j else: for j in js: if c < j: return j return current def pass_suffix(self, index): return "{:04d}".format(index) def passes(self, action, renderer_state, indices=[]): frames = self.active_frames(action, renderer_state, indices) return [ RenderPass(self, self.pass_suffix(i), [Frame(i, self)]) for i in frames ] def runpost(self, result, render_pass, renderer_state): res = super().runpost(result, render_pass, renderer_state) if Overlay.Info in renderer_state.overlays: t = self.rect.take(50, "mxy") frame: Frame = render_pass.args[0] wave = DATPen() if self.audio and sf: wave = (self.wavfile.frame_waveform( frame.i, self.rect.inset(0, 300), 20).translate(0, self.rect.h / 2).s(1).sw(5)) return DATPens([ wave, res, DATPen().rect(t).f(bw(0, 0.75)), DATText(f"{frame.i} / {self.duration}", Style("Times", 42, load_font=0, fill=bw(1)), t.inset(10)) ]) return res def package(self, filepath, output_folder): pass def contactsheet(self, gx, sl=slice(0, None, None)): try: sliced = True start, stop, step = sl.indices(self.duration) duration = (stop - start) // step except AttributeError: # indices storyboard duration = len(sl) sliced = False ar = self.rect gy = math.ceil(duration / gx) @renderable(rect=(ar.w * gx, ar.h * gy), bg=self.bg, name=self.name + "_contactsheet") def contactsheet(r: Rect): _pngs = list(sorted(self.output_folder.glob("*.png"))) if sliced: pngs = _pngs[sl] else: pngs = [p for i, p in enumerate(_pngs) if i in sl] dps = DATPens() dps += DATPen().rect(r).f(self.bg) for idx, g in enumerate(r.grid(columns=gx, rows=gy)): if idx < len(pngs): dps += DATPen().rect(g).f(None).img(pngs[idx], g, pattern=False) return dps return contactsheet