diff --git a/sharkclipper/api/handlers.py b/sharkclipper/api/handlers.py index 8eae79f..5823408 100644 --- a/sharkclipper/api/handlers.py +++ b/sharkclipper/api/handlers.py @@ -17,6 +17,7 @@ STATIC_DIR = os.path.join(sharkclipper.util.file.get_root_dir(), 'static') METADATA_FILENAME_SUFFIX = '_metadata.json' +CLIP_FILENAME_SUFFIX = '_clip.mp4' EXIF_DATETIME_FORMAT = '%Y:%m:%d %H:%M:%S' @@ -116,21 +117,32 @@ def save(handler, path, temp_dir = None, data = None, out_dir = None, **kwargs): exif = _set_exif_data(image, metadata, now) image.save(path, exif = exif) - # Save web-encoded video with new metadata. + # Save a web-encoded copy of the original video with the new metadata. video_metadata = data['video'].copy() video_metadata['screenshots'] = [metadata['image'] for metadata in screenshots_metadata] old_path = os.path.join(temp_dir, 'webencode', data['video']['id'] + '.mp4') new_path = os.path.join(out_dir, data['video']['name'] + '.mp4') sharkclipper.util.ffmpeg.copy_with_metadata(old_path, new_path, video_metadata) - # Save all metadata. + # Save an additional copy of the video that is only the clipped portion. + clip_info = data.get('clip', {}) + if ((clip_info is not None) and (clip_info != {})): + video_metadata['clip'] = clip_info, + old_path = os.path.join(temp_dir, 'webencode', data['video']['id'] + '.mp4') + new_path = os.path.join(out_dir, data['video']['name'] + CLIP_FILENAME_SUFFIX) + sharkclipper.util.ffmpeg.copy_with_metadata(old_path, new_path, video_metadata, + start_secs = clip_info.get('start', None), end_secs = clip_info.get('end', None)) + + # Save all the metadata in a single file. metadata = { 'saved_at_unix': int(now.timestamp()), 'video': data['video'], + 'clip': clip_info, 'screenshots': screenshots_metadata, 'key': data.get('key_metadata', {}), 'all': data.get('all_metadata', {}), } + metadata_path = os.path.join(out_dir, data['video']['name'] + METADATA_FILENAME_SUFFIX) with open(metadata_path, 'w') as file: json.dump(metadata, file, indent = 4) diff --git a/sharkclipper/util/ffmpeg.py b/sharkclipper/util/ffmpeg.py index 42c875b..63a732f 100644 --- a/sharkclipper/util/ffmpeg.py +++ b/sharkclipper/util/ffmpeg.py @@ -172,7 +172,7 @@ def _transcode_with_metadata(in_path, out_path, metadata): _run(args, 'ffmpeg') # Copy a file with new metadata, -def copy_with_metadata(in_path, out_path, metadata): +def copy_with_metadata(in_path, out_path, metadata, start_secs = None, end_secs = None, **kwargs): args = [ _get_path('ffmpeg'), # Input file. @@ -193,6 +193,22 @@ def copy_with_metadata(in_path, out_path, metadata): out_path, ] + # Put in the optional end time (in seconds) before '-i'. + if (end_secs is not None): + clip_start = 0.0 + if (start_secs is not None): + clip_start = start_secs + + clip_length = end_secs - clip_start + + args.insert(1, '-t') + args.insert(2, "%0.2f" % (clip_length)) + + # Put in the optional start time (in seconds) before '-i'. + if (start_secs is not None): + args.insert(1, '-ss') + args.insert(2, "%0.2f" % (start_secs)) + _run(args, 'ffmpeg') def _run(args, name): diff --git a/static/js/main.js b/static/js/main.js index fc2d0ac..aa07600 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -66,6 +66,7 @@ function save() { method: 'POST', body: JSON.stringify({ 'video': window.shark.info['video'], + 'clip': window.shark.media.clip, 'screenshots': window.shark.screenshots, 'key_metadata': window.shark.info['key_metadata'], 'all_metadata': window.shark.info['all_metadata'], diff --git a/static/js/media.js b/static/js/media.js index f469a2f..e1ad41c 100644 --- a/static/js/media.js +++ b/static/js/media.js @@ -1,7 +1,11 @@ 'use strict'; -window.shark = window.shark || {}; -window.shark.media = window.shark.media || {}; +window.shark = window.shark ?? {}; +window.shark.media = window.shark.media ?? {}; + +// TODO - Make seek durations constants: short seek, long seek. + +// TODO - Keyboard shortcuts: left, right, shift+left, shift+right, home, end. // Initialize the video controls and block until the video is ready. function initVideoControls() { @@ -47,7 +51,6 @@ function metadataLoaded() { } _set_duration(duration); - setClipAbsolute(duration, false); // Set initial values for times. setCurrentTime(0); @@ -161,8 +164,9 @@ function setClipAbsolute(value, isStart) { return; } - let start = window.shark.media['clip-start'] ?? 0; - let end = window.shark.media['clip-end'] ?? _get_duration(); + let clipInfo = window.shark.media.clip ?? {}; + let start = clipInfo.start ?? 0; + let end = clipInfo.end ?? _get_duration(); if (isStart) { start = value; @@ -174,8 +178,10 @@ function setClipAbsolute(value, isStart) { let newStart = Math.min(start, end); let newEnd = Math.max(start, end); - window.shark.media['clip-start'] = newStart; - window.shark.media['clip-end'] = newEnd; + window.shark.media.clip = { + start: newStart, + end: newEnd, + }; document.querySelector('.video-controls .clip-start').value = formatTimeString(newStart); document.querySelector('.video-controls .clip-end').value = formatTimeString(newEnd); @@ -194,8 +200,10 @@ function _updateClipHighlight() { return; } - let start = window.shark.media['clip-start'] ?? 0; - let end = window.shark.media['clip-end'] ?? _get_duration(); + + let clipInfo = window.shark.media.clip ?? {}; + let start = clipInfo.start ?? 0; + let end = clipInfo.end ?? _get_duration(); let startPercent = 100.0 * (start / duration); let widthPercent = 100.0 * ((end - start) / duration); @@ -281,13 +289,14 @@ function _bound_video_time(secs) { } function _get_duration() { - return window.shark.media['duration']; + return window.shark.media.duration; } function _has_duration() { - return !isNaN(window.shark.media['duration']); + let duration = window.shark.media.duration; + return (duration !== undefined) && !isNaN(duration); } function _set_duration(value) { - window.shark.media['duration'] = value; + window.shark.media.duration = value; }