Beispiel #1
0
    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
Beispiel #2
0
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