diff --git a/cvat/apps/dashboard/static/dashboard/js/dashboard.js b/cvat/apps/dashboard/static/dashboard/js/dashboard.js
index d12668c0d03c..3cac38716c0b 100644
--- a/cvat/apps/dashboard/static/dashboard/js/dashboard.js
+++ b/cvat/apps/dashboard/static/dashboard/js/dashboard.js
@@ -331,6 +331,8 @@ function setupTaskCreator() {
let customOverlapSize = $("#dashboardCustomOverlap");
let imageQualityInput = $("#dashboardImageQuality");
let customCompressQuality = $("#dashboardCustomQuality");
+ let frameRateInput = $("#dashboardFrameRate");
+ let customFrameRate = $("#dashboardCustomFrameRate");
let taskMessage = $("#dashboardCreateTaskMessage");
let submitCreate = $("#dashboardSubmitTask");
@@ -345,6 +347,7 @@ function setupTaskCreator() {
let segmentSize = 5000;
let overlapSize = 0;
let compressQuality = 50;
+ let frameRate = 0;
let files = [];
dashboardCreateTaskButton.on("click", function() {
@@ -417,6 +420,7 @@ function setupTaskCreator() {
customSegmentSize.on("change", (e) => segmentSizeInput.prop("disabled", !e.target.checked));
customOverlapSize.on("change", (e) => overlapSizeInput.prop("disabled", !e.target.checked));
customCompressQuality.on("change", (e) => imageQualityInput.prop("disabled", !e.target.checked));
+ customFrameRate.on("change", (e) => frameRateInput.prop("disabled", !e.target.checked));
segmentSizeInput.on("change", function() {
let value = Math.clamp(
@@ -451,6 +455,17 @@ function setupTaskCreator() {
compressQuality = value;
});
+ frameRateInput.on("change", function() {
+ let value = Math.clamp(
+ +frameRateInput.prop("value"),
+ +frameRateInput.prop("min"),
+ +frameRateInput.prop("max")
+ );
+
+ frameRateInput.prop("value", value);
+ frameRate = value;
+ });
+
submitCreate.on("click", function() {
if (!validateName(name)) {
taskMessage.css("color", "red");
@@ -515,6 +530,9 @@ function setupTaskCreator() {
if (customCompressQuality.prop("checked")) {
taskData.append("compress_quality", compressQuality);
}
+ if (customFrameRate.prop("checked")) {
+ taskData.append("frame_rate", frameRate);
+ }
for (let file of files) {
taskData.append("data", file);
diff --git a/cvat/apps/dashboard/templates/dashboard/dashboard.html b/cvat/apps/dashboard/templates/dashboard/dashboard.html
index 18ddd0882f93..b2c082804c16 100644
--- a/cvat/apps/dashboard/templates/dashboard/dashboard.html
+++ b/cvat/apps/dashboard/templates/dashboard/dashboard.html
@@ -156,6 +156,15 @@
+
+
+
+ |
+
+
+
+ |
+
diff --git a/cvat/apps/engine/task.py b/cvat/apps/engine/task.py
index 4d2a75f2679d..ebb39005c8c1 100644
--- a/cvat/apps/engine/task.py
+++ b/cvat/apps/engine/task.py
@@ -272,13 +272,17 @@ def rq_handler(job, exc_type, exc_value, traceback):
############################# Internal implementation for server API
class _FrameExtractor:
- def __init__(self, source_path, compress_quality, flip_flag=False):
+ def __init__(self, source_path, compress_quality, flip_flag=False, frame_rate=0):
# translate inversed range 1:95 to 2:32
translated_quality = 96 - compress_quality
translated_quality = round((((translated_quality - 1) * (31 - 2)) / (95 - 1)) + 2)
self.output = tempfile.mkdtemp(prefix='cvat-', suffix='.data')
target_path = os.path.join(self.output, '%d.jpg')
- output_opts = '-start_number 0 -b:v 10000k -vsync 0 -an -y -q:v ' + str(translated_quality)
+ output_opts = '-start_number 0 -b:v 10000k -an -y -q:v ' + str(translated_quality)
+ if frame_rate > 0:
+ output_opts += ' -r ' + str(frame_rate)
+ else:
+ output_opts += ' -vsync 0'
if flip_flag:
output_opts += ' -vf "transpose=2,transpose=2"'
ff = FFmpeg(
@@ -536,7 +540,7 @@ def _find_and_unpack_archive(upload_dir):
'''
Search a video in upload dir and split it by frames. Copy frames to target dirs
'''
-def _find_and_extract_video(upload_dir, output_dir, db_task, compress_quality, flip_flag, job):
+def _find_and_extract_video(upload_dir, output_dir, db_task, compress_quality, flip_flag, frame_rate, job):
video = None
for root, _, files in os.walk(upload_dir):
fullnames = map(lambda f: os.path.join(root, f), files)
@@ -548,7 +552,7 @@ def _find_and_extract_video(upload_dir, output_dir, db_task, compress_quality, f
if video:
job.meta['status'] = 'Video is being extracted..'
job.save_meta()
- extractor = _FrameExtractor(video, compress_quality, flip_flag)
+ extractor = _FrameExtractor(video, compress_quality, flip_flag, frame_rate)
for frame, image_orig_path in enumerate(extractor):
image_dest_path = _get_frame_path(frame, output_dir)
db_task.size += 1
@@ -696,6 +700,7 @@ def _create_thread(tid, params):
'compress': int(params.get('compress_quality', 50)),
'segment': int(params.get('segment_size', sys.maxsize)),
'labels': params['labels'],
+ 'frame_rate': int(params.get('frame_rate', 0)),
}
task_params['overlap'] = int(params.get('overlap_size', 5 if task_params['mode'] == 'interpolation' else 0))
task_params['overlap'] = min(task_params['overlap'], task_params['segment'] - 1)
@@ -703,7 +708,7 @@ def _create_thread(tid, params):
if task_params['mode'] == 'interpolation':
video = _find_and_extract_video(upload_dir, output_dir, db_task,
- task_params['compress'], task_params['flip'], job)
+ task_params['compress'], task_params['flip'], task_params['frame_rate'], job)
task_params['data'] = os.path.relpath(video, upload_dir)
else:
files =_find_and_compress_images(upload_dir, output_dir, db_task,