From 50d4656ad7079d08fe518a1bb9c82f828fa767b8 Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Tue, 15 Mar 2022 13:41:12 +0100 Subject: [PATCH] Python script now correctly handles in and outpoint differently + formatting --- svganim/strokes.py | 117 +++++++++++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 37 deletions(-) diff --git a/svganim/strokes.py b/svganim/strokes.py index 27d9147..2f6435d 100644 --- a/svganim/strokes.py +++ b/svganim/strokes.py @@ -12,20 +12,21 @@ import logging logger = logging.getLogger('svganim.strokes') + class Annotation: def __init__(self, tag: str, drawing: Drawing, t_in: float, t_out: float) -> None: self.tag = tag self.t_in = t_in self.t_out = t_out self.drawing = drawing - + @property def id(self) -> str: return f'{self.drawing.id}:{self.tag}:{self.t_in}:{self.t_out}' def getAnimationSlice(self) -> AnimationSlice: return self.drawing.get_animation().getSlice(self.t_in, self.t_out) - + def get_as_svg(self) -> str: return self.getAnimationSlice().get_as_svg() @@ -68,8 +69,8 @@ class Drawing: return None if 'file' not in md['audio']: return None - - return AudioSlice(filename=os.path.join(self.basedir,md['audio']['file'][1:]), offset=md['audio']['offset']*1000) + + return AudioSlice(filename=os.path.join(self.basedir, md['audio']['file'][1:]), offset=md['audio']['offset']*1000) def get_animation(self) -> AnimationSlice: # with open(self.eventfile, "r") as fp: @@ -95,10 +96,11 @@ class Drawing: strokes.append( Stroke( event["color"], - [Point.fromTuple(tuple(p)) for p in event["points"]], + [Point.fromTuple(tuple(p)) + for p in event["points"]], ) ) - return AnimationSlice(strokes, audioslice=self.get_audio() ) + return AnimationSlice(strokes, audioslice=self.get_audio()) def get_metadata(self): canvas = self.get_canvas_metadata() @@ -157,12 +159,11 @@ class AnimationSlice: return Viewbox(min_x, min_y, max_x - min_x, max_y - min_y) def getSlice(self, t_in: float, t_out: float) -> AnimationSlice: - frame_in = self.getIndexForTime(t_in) - frame_out = self.getIndexForTime(t_out) + frame_in = self.getIndexForInPoint(t_in) + frame_out = self.getIndexForOutPoint(t_out) strokes = self.getStrokeSlices(frame_in, frame_out) audio = self.audio.getSlice(t_in, t_out) if self.audio else None return AnimationSlice(strokes, t_in, t_out, audio) - def get_as_svg_dwg(self) -> svgwrite.Drawing: box = self.get_bounding_box() @@ -177,7 +178,7 @@ class AnimationSlice: fp = io.StringIO() dwg.write(fp, pretty=True) return fp.getvalue() - + def add_to_dwg(self, dwg: SvgDrawing): group = svgwrite.container.Group() for stroke in self.strokes: @@ -199,11 +200,47 @@ class AnimationSlice: break in_i = index_in[1] if index_in[0] == i else 0 - out_i = index_out[1] if index_out[0] == i else len(stroke.points) - 1 + out_i = index_out[1] if index_out[0] == i else len( + stroke.points) - 1 slices.append(StrokeSlice(stroke, in_i, out_i)) return slices + def getIndexForInPoint(self, ms) -> FrameIndex: + """Get the frame index (path, point) based on the given time + The In point version (so the first index after ms) + Equal to annotations.js findPositionForTime(ms) + """ + path_i = 0 + point_i = 0 + for i, stroke in enumerate(self.strokes): + start_at = stroke.points[0].t + end_at = stroke.points[-1].t + if end_at < ms: + # certainly not the right point yet + continue + if start_at > ms: + path_i = i + point_i = 0 + break # too far, so this is the first point after in point + else: + # our in-point is inbetween first and last of the stroke + # we are getting close, find the right point_i + path_i = i + for pi, point in enumerate(stroke.points): + point_i = pi + if point.t > ms: + break # stop when finding the next point after in point + break # done :-) + return (path_i, point_i) + + def getIndexForOutPoint(self, ms) -> FrameIndex: + """Get the frame index (path, point) based on the given time + The Out point version (so the last index before ms) + Equal to annotations.js findPositionForTime(ms) + """ + return self.getIndexForTime( ms) + def getIndexForTime(self, ms) -> FrameIndex: """Get the frame index (path, point) based on the given time Equal to annotations.js findPositionForTime(ms) @@ -220,7 +257,7 @@ class AnimationSlice: # we are getting close, find the right point_i path_i = i for pi, point in enumerate(stroke.points): - if point.t: + if point.t > ms: break # too far point_i = pi break # done :-) @@ -233,12 +270,12 @@ class AnimationSlice: class AudioSlice: - def __init__(self, filename: Filename, t_in: float=None, t_out: float=None, offset:float = None): + def __init__(self, filename: Filename, t_in: float = None, t_out: float = None, offset: float = None): self.filename = filename self.t_in = t_in # in ms self.t_out = t_out # in ms - self.offset = offset # in ms - + self.offset = offset # in ms + def getSlice(self, t_in: float, t_out: float) -> AnimationSlice: return AudioSlice(self.filename, t_in, t_out, self.offset) @@ -250,19 +287,22 @@ class AudioSlice: end = self.t_out - self.offset if start < 0 and end < 0: - extract = AudioSegment.silent(duration=end-start, frame_rate=song.frame_rate) + extract = AudioSegment.silent( + duration=end-start, frame_rate=song.frame_rate) else: if start < 0: - preroll = AudioSegment.silent(duration=start * -1, frame_rate=song.frame_rate) + preroll = AudioSegment.silent( + duration=start * -1, frame_rate=song.frame_rate) start = 0 else: preroll = None if end > len(song): - postroll = AudioSegment.silent(duration=end - len(song), frame_rate=song.frame_rate) + postroll = AudioSegment.silent( + duration=end - len(song), frame_rate=song.frame_rate) end = len(song) - 1 else: postroll = None - + extract = song[start: end] if preroll: extract = preroll + extract @@ -281,7 +321,8 @@ class AnnotationIndex: self.drawing_dir = drawing_dir self.metadata_dir = metadata_dir - self.shelve = {} #disable disk cache because of glitches shelve.open(filename, writeback=True) + # disable disk cache because of glitches shelve.open(filename, writeback=True) + self.shelve = {} def refresh(self): # reset the index @@ -304,7 +345,8 @@ class AnnotationIndex: if 'annotations' not in meta: continue for ann in meta['annotations']: - annotation = Annotation(ann['tag'], drawing, ann['t_in'], ann['t_out']) + annotation = Annotation( + ann['tag'], drawing, ann['t_in'], ann['t_out']) self.shelve['_annotations'][annotation.id] = annotation if annotation.tag not in self.shelve['_tags']: self.shelve['_tags'][annotation.tag] = [annotation] @@ -312,11 +354,10 @@ class AnnotationIndex: self.shelve['_tags'][annotation.tag].append( annotation ) - - + @property def drawings(self) -> dict[str, Drawing]: - return self.shelve["_drawings"] + return self.shelve["_drawings"] @property def tags(self) -> dict[str, list[Annotation]]: @@ -354,14 +395,14 @@ class AnnotationIndex: class Point: def __init__(self, x: float, y: float, last: bool, t: float): self.x = float(x) - self.y = float(y) # if y == 0 it can still be integer.... odd python + self.y = float(y) # if y == 0 it can still be integer.... odd python self.last = last self.t = t @classmethod def fromTuple(cls, p: tuple[float, float, int, float]): return cls(p[0], p[1], bool(p[2]), p[3]) - + def scaledToFit(self, dimensions: dict[str, float]) -> Point: # TODO: change so that it actually scales to FIT dimensions return Point(self.x, self.y, self.last, self.t) @@ -370,16 +411,17 @@ class Point: Points = list[Point] SvgDrawing = Union[svgwrite.container.SVG, svgwrite.container.Group] + class Stroke: def __init__(self, color: str, points: Points) -> None: self.color = color self.points = points - + def add_to_dwg(self, dwg: SvgDrawing): - path = svgwrite.path.Path(d=self.get_as_d()).stroke(self.color,1).fill("none") + path = svgwrite.path.Path(d=self.get_as_d()).stroke( + self.color, 1).fill("none") dwg.add(path) - def get_bounding_box(self) -> Viewbox: min_x, max_x = float("inf"), float("-inf") min_y, max_y = float("inf"), float("-inf") @@ -432,7 +474,7 @@ class StrokeSlice(Stroke): @property def points(self) -> Points: - return self.stroke.points[self.i_in : self.i_out + 1] + return self.stroke.points[self.i_in: self.i_out + 1] @property def color(self) -> str: @@ -441,22 +483,23 @@ class StrokeSlice(Stroke): def strokes2D(strokes): # strokes to a d attribute for a path - d = ""; - last_stroke = None; - cmd = ""; + d = "" + last_stroke = None + cmd = "" for stroke in strokes: if not last_stroke: d += f"M{stroke[0]},{stroke[1]} " cmd = 'M' else: - if last_stroke[2] == 1: + if last_stroke[2] == 1: d += " m" cmd = 'm' elif cmd != 'l': - d+=' l ' + d += ' l ' cmd = 'l' - rel_stroke = [stroke[0] - last_stroke[0], stroke[1] - last_stroke[1]]; + rel_stroke = [stroke[0] - last_stroke[0], + stroke[1] - last_stroke[1]] d += f"{rel_stroke[0]},{rel_stroke[1]} " last_stroke = stroke - return d \ No newline at end of file + return d