Skip to content

Commit 0a44bdc

Browse files
caitlinwheelesscaitlinwheeless
and
caitlinwheeless
authored
docs: Add video offset and sync custom script example (#6901)
Co-authored-by: caitlinwheeless <[email protected]>
1 parent 3be1a99 commit 0a44bdc

File tree

2 files changed

+128
-1
lines changed

2 files changed

+128
-1
lines changed

docs/source/guide/script_examples.md

+128-1
Original file line numberDiff line numberDiff line change
@@ -470,4 +470,131 @@ This is an example of a "hard" block, meaning that the user must resolve the iss
470470
* [View](/tags/view.html)
471471
* [RectangleLabels](/tags/rectanglelabels.html)
472472
* [TextArea](/tags/textarea.html)
473-
* [Labels](/tags/labels.html)
473+
* [Labels](/tags/labels.html)
474+
475+
## Sync videos with frame offset
476+
477+
This labeling configuration arranges three video players vertically, making it easier to view and annotate each video frame.
478+
479+
The script ensures the videos are synced, with one player showing one frame forward, and another player the previous frame.
480+
481+
![Screenshot of JSON error message](/images/project/video_sync.png)
482+
483+
#### Script
484+
485+
```javascript
486+
// Wait for the Label Studio Interface to be ready
487+
await LSI;
488+
489+
// Get references to the video objects by their names
490+
var videoMinus1 = LSI.annotation.names.get('videoMinus1');
491+
var video0 = LSI.annotation.names.get('video0');
492+
var videoPlus1 = LSI.annotation.names.get('videoPlus1');
493+
494+
if (!videoMinus1 || !video0 || !videoPlus1) return;
495+
496+
// Convert frameRate to a number and ensure it's valid
497+
var frameRate = Number.parseFloat(video0.framerate) || 24;
498+
var frameDuration = 1 / frameRate;
499+
500+
// Function to adjust video sync with offset and guard against endless loops
501+
function adjustVideoSync(video, offsetFrames) {
502+
video.isSyncing = false;
503+
504+
["seek", "play", "pause"].forEach(event => {
505+
video.syncHandlers.set(event, function(data) {
506+
if (video.isSyncing) return;
507+
508+
video.isSyncing = true;
509+
510+
if (!video.ref.current || video === video0) {
511+
video.isSyncing = false;
512+
return;
513+
}
514+
515+
const videoElem = video.ref.current;
516+
517+
adjustedTime = (video0.ref.current.currentFrame + offsetFrames) * frameDuration;
518+
adjustedTime = Math.max(0, Math.min(adjustedTime, video.ref.current.duration));
519+
520+
if (data.playing) {
521+
if (!videoElem.playing) videoElem.play();
522+
} else {
523+
if (videoElem.playing) videoElem.pause();
524+
}
525+
526+
if (data.speed) {
527+
video.speed = data.speed;
528+
}
529+
530+
videoElem.currentTime = adjustedTime;
531+
if (Math.abs(videoElem.currentTime - adjustedTime) > frameDuration/2) {
532+
videoElem.currentTime = adjustedTime;
533+
}
534+
535+
video.isSyncing = false;
536+
});
537+
});
538+
}
539+
540+
// Adjust offsets for each video
541+
adjustVideoSync(videoMinus1, -1);
542+
adjustVideoSync(videoPlus1, 1);
543+
adjustVideoSync(video0, 0);
544+
```
545+
546+
**Related LSI instance methods:**
547+
548+
* [annotation](scripts#LSI-annotation)
549+
550+
#### Labeling config
551+
552+
Each video is wrapped in a `<View>` tag with a width of 100% to ensure they stack on top of each other. The `Header` tag provides a title for
553+
each video, indicating which frame is being displayed.
554+
555+
The `Video` tags are used to load the video content, with the `name` attribute uniquely identifying each video player.
556+
557+
The `TimelineLabels` tag is connected to the second video (`video0`), allowing annotators to label specific segments of that video. The labels `class1` and `class2` can be used to categorize the content of the video, enhancing the annotation process.
558+
559+
```xml
560+
<View>
561+
<View style="display: flex">
562+
<View style="width: 100%">
563+
<Header value="Video -1 Frame"/>
564+
<Video name="videoMinus1" value="$video_url"
565+
height="200" sync="lag" frameRate="29.97"/>
566+
</View>
567+
<View style="width: 100%">
568+
<Header value="Video +1 Frame"/>
569+
<Video name="videoPlus1" value="$video_url"
570+
height="200" sync="lag" frameRate="29.97"/>
571+
</View>
572+
</View>
573+
<View style="width: 100%; margin-bottom: 1em;">
574+
<Header value="Video 0 Frame"/>
575+
<Video name="video0" value="$video_url"
576+
height="400" sync="lag" frameRate="29.97"/>
577+
</View>
578+
<TimelineLabels name="timelinelabels" toName="video0">
579+
<Label value="class1"/>
580+
<Label value="class2"/>
581+
</TimelineLabels>
582+
</View>
583+
```
584+
585+
**Related tags:**
586+
587+
* [View](/tags/view.html)
588+
* [Video](/tags/video.html)
589+
* [TimelineLabels](/tags/timelinelabels.html)
590+
* [Label](/tags/label.html)
591+
592+
#### Data
593+
594+
```json
595+
{
596+
"data": {
597+
"video_url": "https://example.com/path/to/video.mp4"
598+
}
599+
}
600+
```
Loading

0 commit comments

Comments
 (0)