viewbox drag now visible on playback annotator.

This commit is contained in:
Ruben van de Ven 2022-03-29 15:07:18 +02:00
parent 949a16b01f
commit 6d7a223a69
4 changed files with 82 additions and 14 deletions

View file

@ -171,6 +171,7 @@ class AnimationSlice:
dwg = svgwrite.Drawing(fn, size=(box.width, box.height)) dwg = svgwrite.Drawing(fn, size=(box.width, box.height))
dwg.viewbox(box.x, box.y, box.width, box.height) dwg.viewbox(box.x, box.y, box.width, box.height)
self.add_to_dwg(dwg) self.add_to_dwg(dwg)
dwg.defs.add(dwg.style("path{stroke-width:1mm;stroke-linecap: round;}"))
return dwg return dwg
def get_as_svg(self) -> str: def get_as_svg(self) -> str:
@ -325,6 +326,7 @@ class AnnotationIndex:
self.shelve = {} self.shelve = {}
def refresh(self): def refresh(self):
logger.info("refreshing")
# reset the index # reset the index
for key in list(self.shelve.keys()): for key in list(self.shelve.keys()):
print(key) print(key)

View file

@ -40,7 +40,7 @@
</ul> --> </ul> -->
<hr> <hr>
<a href="?refresh">Reload index</a> <a href="?refresh=1">Reload index</a>
</body> </body>
<script> <script>
let images = document.querySelectorAll('[data-audio]'); let images = document.querySelectorAll('[data-audio]');

View file

@ -279,7 +279,7 @@ class AnimationHandler(tornado.web.RequestHandler):
self.config.storage, os.path.basename( self.config.storage, os.path.basename(
filename) + ".json_appendable" filename) + ".json_appendable"
) )
drawing = {"file": filename, "shape": []} drawing = {"file": filename, "shape": [], "viewboxes": []}
with open(path, "r") as fp: with open(path, "r") as fp:
events = json.loads("[" + fp.read() + "]") events = json.loads("[" + fp.read() + "]")
for i, event in enumerate(events): for i, event in enumerate(events):
@ -292,7 +292,7 @@ class AnimationHandler(tornado.web.RequestHandler):
# ignore double metadatas, which appear when continuaing an existing drawing # ignore double metadatas, which appear when continuaing an existing drawing
continue continue
if event["event"] == "viewbox": if event["event"] == "viewbox":
pass drawing["viewboxes"].extend(event['viewboxes'])
if event["event"] == "stroke": if event["event"] == "stroke":
# points = [] # points = []
# for i in range(int(len(stroke) / 4)): # for i in range(int(len(stroke) / 4)):
@ -496,9 +496,9 @@ class IndexHandler(tornado.web.RequestHandler):
def get(self): def get(self):
do_refresh = bool(self.get_query_argument('refresh', False)) do_refresh = bool(self.get_query_argument('refresh', False))
if do_refresh: if do_refresh:
self.logger.info("Reloading Annotation Index") logger.info("Reloading Annotation Index")
self.index.refresh() self.index.refresh()
self.logger.info("\treloaded annotation index") logger.info("\treloaded annotation index")
self.render("templates/index.html", index=self.index) self.render("templates/index.html", index=self.index)

View file

@ -476,23 +476,26 @@ class Annotator extends EventTarget {
} }
this.filename = drawing.file; this.filename = drawing.file;
this.strokes = drawing.shape.map(s => new Stroke(s['color'], s['points'])); this.strokes = drawing.shape.map(s => new Stroke(s['color'], s['points']));
this.viewboxes = drawing.viewboxes;
this.currentPathI = null; this.currentPathI = null;
this.currentPointI = null; this.currentPointI = null;
this.currentViewboxI = null;
this.dimensions = drawing.dimensions; this.dimensions = drawing.dimensions;
this.svgEl.setAttribute('viewBox', `0 0 ${this.dimensions[0]} ${this.dimensions[1]}`) this.svgEl.setAttribute('viewBox', `0 0 ${this.dimensions[0]} ${this.dimensions[1]}`)
let bgEl = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); // let bgEl = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
bgEl.setAttribute("x", 0); // bgEl.setAttribute("x", 0);
bgEl.setAttribute("y", 0); // bgEl.setAttribute("y", 0);
bgEl.setAttribute("width", this.dimensions[0]); // bgEl.setAttribute("width", this.dimensions[0]);
bgEl.setAttribute("height", this.dimensions[1]); // bgEl.setAttribute("height", this.dimensions[1]);
bgEl.classList.add('background'); // bgEl.classList.add('background');
this.svgEl.prepend(bgEl); // this.svgEl.prepend(bgEl);
this.firstFrameTime = this.strokes[0].points[0][3]; this.firstFrameTime = this.strokes[0].points[0][3];
this.lastFrameTime = this.getFinalFrameTime(); this.lastFrameTime = this.getFinalFrameTime();
this.playheadEl.max = this.lastFrameTime; this.playheadEl.max = this.lastFrameTime;
this.nextFrameTimeout = null; this.nextFrameTimeout = null;
this.nextViewboxTimeout = null;
this._setPausedFlag(true); this._setPausedFlag(true);
this.formatter = wNumb({ this.formatter = wNumb({
@ -740,6 +743,15 @@ class Annotator extends EventTarget {
// this.currentTime = path.points[point_i][3]; // this.currentTime = path.points[point_i][3];
} }
setViewboxPosition(box_i) {
if(this.currentViewboxI == box_i){
return;
}
this.currentViewboxI = box_i
const b = this.viewboxes[box_i];
this.svgEl.setAttribute('viewBox', `${b.x} ${b.y} ${this.dimensions[0]} ${this.dimensions[1]}`)
}
getNextPosition(path_i, point_i) { getNextPosition(path_i, point_i) {
const path = this.strokes[path_i]; const path = this.strokes[path_i];
let next_path, next_point; let next_path, next_point;
@ -790,6 +802,32 @@ class Annotator extends EventTarget {
this.nextFrameTimeout = setTimeout(() => this.playStrokePosition(next_path, next_point, true), dt); this.nextFrameTimeout = setTimeout(() => this.playStrokePosition(next_path, next_point, true), dt);
} }
playViewboxPosition(box_i, allow_interrupt) {
if (allow_interrupt) {
if (!this.videoIsPlaying) {
console.log('not playing because of interrupt');
return;
}
}
// else {
// this.videoIsPlaying = true;
// }
this.setViewboxPosition(box_i);
const next_box_i = box_i+1;
if(this.viewboxes.length <= next_box_i){
console.debug('done playing viewbox');
return;
}
const t = this.viewboxes[next_box_i].t;
// calculate interval based on playback start to avoid drifting of time
const dt = t - (window.performance.now() - this.startTimeMs);
this.nextViewboxTimeout = setTimeout(() => this.playViewboxPosition(next_box_i, true), dt);
}
scrubTo(ms) { scrubTo(ms) {
// const [path_i, point_i] = this.findPositionForTime(ms); // const [path_i, point_i] = this.findPositionForTime(ms);
// console.log(path_i, point_i); // console.log(path_i, point_i);
@ -816,6 +854,7 @@ class Annotator extends EventTarget {
_interruptPlayback() { _interruptPlayback() {
clearTimeout(this.nextFrameTimeout); clearTimeout(this.nextFrameTimeout);
clearTimeout(this.nextViewboxTimeout);
clearTimeout(this.audioEndTimeout); clearTimeout(this.audioEndTimeout);
clearTimeout(this.audioStartTimeout); clearTimeout(this.audioStartTimeout);
clearTimeout(this.startVideoTimeout); clearTimeout(this.startVideoTimeout);
@ -835,12 +874,18 @@ class Annotator extends EventTarget {
this._seekByTimeMs(this._currentTimeMs); // prevent playback issue for initial load this._seekByTimeMs(this._currentTimeMs); // prevent playback issue for initial load
this.startTimeMs = window.performance.now() - this._currentTimeMs; this.startTimeMs = window.performance.now() - this._currentTimeMs;
// strokes
if (this._currentTimeMs < 0) { if (this._currentTimeMs < 0) {
this.startVideoTimeout = setTimeout((e) => this.playStrokePosition(this.currentPathI, this.currentPointI), this._currentTimeMs * -1); this.startVideoTimeout = setTimeout((e) => this.playStrokePosition(this.currentPathI, this.currentPointI), this._currentTimeMs * -1);
} else { } else {
this.playStrokePosition(this.currentPathI, this.currentPointI); this.playStrokePosition(this.currentPathI, this.currentPointI);
} this.playAudioSegment(this._currentTimeMs, this.outPointTimeMs); }
// viewboxes
// const nextViewboxI = Math.max(this.currentViewboxI++, this.viewboxes.length-1);
this.playViewboxPosition(this.currentViewboxI);
// audio
this.playAudioSegment(this._currentTimeMs, this.outPointTimeMs);
// this.playStrokePosition(this.currentPathI, this.currentPointI); // this.playStrokePosition(this.currentPathI, this.currentPointI);
this._setPausedFlag(false); this._setPausedFlag(false);
@ -927,6 +972,7 @@ class Annotator extends EventTarget {
this.dispatchEvent(new CustomEvent('seeking', { detail: time })); this.dispatchEvent(new CustomEvent('seeking', { detail: time }));
this._currentTimeMs = Number.parseFloat(time) * 1000; this._currentTimeMs = Number.parseFloat(time) * 1000;
[this.currentPathI, this.currentPointI] = this.findPositionForTime(this._currentTimeMs); [this.currentPathI, this.currentPointI] = this.findPositionForTime(this._currentTimeMs);
this.playheadEl.value = this._currentTimeMs; this.playheadEl.value = this._currentTimeMs;
this._updateFrame(); this._updateFrame();
this.dispatchEvent(new CustomEvent('seeked', { detail: this.currentTime })); this.dispatchEvent(new CustomEvent('seeked', { detail: this.currentTime }));
@ -934,6 +980,7 @@ class Annotator extends EventTarget {
_updateFrame() { _updateFrame() {
this.drawStrokePosition(this.inPointPosition, [this.currentPathI, this.currentPointI]); this.drawStrokePosition(this.inPointPosition, [this.currentPathI, this.currentPointI]);
this.setViewboxPosition(this.findViewboxForTime(this._currentTimeMs));
} }
/** /**
@ -996,5 +1043,24 @@ class Annotator extends EventTarget {
return [path_i, point_i]; return [path_i, point_i];
} }
findViewboxForTime(ms){
ms = Math.min(Math.max(ms, 0), this.lastFrameTime);
// console.log('scrub to', ms)
let box_i = 0;
this.viewboxes.every((viewbox, index) => {
const startAt = viewbox.t;
if (startAt > ms) {
return false; // too far
} else {
// in case nothings comes after, we store the last best option thus far
box_i = index;
return true;
}
});
return box_i;
}
} }