Add ability to crop videos

Full cuts only. Uploads just the cropped area. This can be used to make youtube "shorts".
mike/cropping
Mike Lang 2 years ago
parent 8e314eea94
commit d91e85ea47

@ -181,6 +181,7 @@ columns | type | role
`public` | `BOOLEAN NOT NULL DEFAULT TRUE` | edit input | Whether the uploaded video should be public or not, if the upload location supports that distinction. For example, on youtube, non-public videos are "unlisted". It also controls whether the video will be added to playlists, only public videos are added to playlists. `public` | `BOOLEAN NOT NULL DEFAULT TRUE` | edit input | Whether the uploaded video should be public or not, if the upload location supports that distinction. For example, on youtube, non-public videos are "unlisted". It also controls whether the video will be added to playlists, only public videos are added to playlists.
`video_ranges` | `{start TIMESTAMP, end TIMESTAMP}[]` | edit input | A non-zero number of start and end times, describing the ranges of video to cut. They will be cut back-to-back in the given order, with the transitions between as per `video_transitions`. If already set, used as the default range settings when editing. `video_ranges` | `{start TIMESTAMP, end TIMESTAMP}[]` | edit input | A non-zero number of start and end times, describing the ranges of video to cut. They will be cut back-to-back in the given order, with the transitions between as per `video_transitions`. If already set, used as the default range settings when editing.
`video_transitions` | `{type TEXT, duration INTERVAL}[]` | edit input | Defines how to transition between each range defined in `video_ranges`, and must be exactly the length of `video_ranges` minus 1. Each index in `video_transitions` defines the transition between the range with the same index in `video_ranges` and the next one. Transitions either specify a transition type as understood by `ffmpeg`'s `xfade` filter and a duration (amount of overlap), or can be NULL to indicate a hard cut. `video_transitions` | `{type TEXT, duration INTERVAL}[]` | edit input | Defines how to transition between each range defined in `video_ranges`, and must be exactly the length of `video_ranges` minus 1. Each index in `video_transitions` defines the transition between the range with the same index in `video_ranges` and the next one. Transitions either specify a transition type as understood by `ffmpeg`'s `xfade` filter and a duration (amount of overlap), or can be NULL to indicate a hard cut.
`video_crop` | `{x, y, w, h INTEGER}` | edit input | If given, defines how the video should be cropped when editing.
`video_title` | `TEXT` | edit input | The title of the video. If already set, used as the default title when editing instead of `description`. `video_title` | `TEXT` | edit input | The title of the video. If already set, used as the default title when editing instead of `description`.
`video_description` | `TEXT` | edit input | The description field of the video. If already set, used as the default description when editing instead of `description`. `video_description` | `TEXT` | edit input | The description field of the video. If already set, used as the default description when editing instead of `description`.
`video_tags` | `TEXT[]` | edit input | Custom tags to annotate the video with. If already set, used as the default when editing instead of `tags`. `video_tags` | `TEXT[]` | edit input | Custom tags to annotate the video with. If already set, used as the default when editing instead of `tags`.

@ -63,6 +63,7 @@ CUT_JOB_PARAMS = [
"public", "public",
"video_ranges", "video_ranges",
"video_transitions", "video_transitions",
"video_crop",
"video_title", "video_title",
"video_description", "video_description",
"video_tags", "video_tags",
@ -400,12 +401,20 @@ class Cutter(object):
self.logger.debug("No encoding settings, using fast cut") self.logger.debug("No encoding settings, using fast cut")
if any(transition is not None for transition in job.video_transitions): if any(transition is not None for transition in job.video_transitions):
raise ValueError("Fast cuts do not support complex transitions") raise ValueError("Fast cuts do not support complex transitions")
if job.video_crop is not None:
raise ValueError("Fast cuts do not support cropping")
cut = fast_cut_segments(job.segment_ranges, job.video_ranges) cut = fast_cut_segments(job.segment_ranges, job.video_ranges)
else: else:
self.logger.debug("Using encoding settings for {} cut: {}".format( self.logger.debug("Using encoding settings for {} cut: {}".format(
"streamable" if upload_backend.encoding_streamable else "non-streamable", "streamable" if upload_backend.encoding_streamable else "non-streamable",
upload_backend.encoding_settings, upload_backend.encoding_settings,
)) ))
encode_args = upload_backend.encoding_settings
if job.video_crop:
x, y, w, h = job.video_crop
encode_args = [
"-vf", "crop={}:{}:{}:{}".format(w, h, x, y)
] + encode_args
if len(job.video_ranges) > 1: if len(job.video_ranges) > 1:
raise ValueError("Full cuts do not support multiple ranges") raise ValueError("Full cuts do not support multiple ranges")
range = job.video_ranges[0] range = job.video_ranges[0]

@ -64,6 +64,13 @@ CREATE TYPE thumbnail_mode as ENUM (
'CUSTOM' 'CUSTOM'
); );
CREATE TYPE box as (
x INTEGER,
y INTEGER,
w INTEGER,
h INTEGER
)
CREATE TABLE events ( CREATE TABLE events (
id UUID PRIMARY KEY, id UUID PRIMARY KEY,
@ -88,6 +95,7 @@ CREATE TABLE events (
(video_ranges IS NULL AND video_transitions IS NULL) (video_ranges IS NULL AND video_transitions IS NULL)
OR CARDINALITY(video_ranges) = CARDINALITY(video_transitions) + 1 OR CARDINALITY(video_ranges) = CARDINALITY(video_transitions) + 1
), ),
video_crop box,
video_title TEXT CHECK (state IN ('UNEDITED', 'DONE') OR video_title IS NOT NULL), video_title TEXT CHECK (state IN ('UNEDITED', 'DONE') OR video_title IS NOT NULL),
video_description TEXT CHECK (state IN ('UNEDITED', 'DONE') OR video_description IS NOT NULL), video_description TEXT CHECK (state IN ('UNEDITED', 'DONE') OR video_description IS NOT NULL),
video_tags TEXT[] CHECK (state IN ('UNEDITED', 'DONE') OR video_tags IS NOT NULL), video_tags TEXT[] CHECK (state IN ('UNEDITED', 'DONE') OR video_tags IS NOT NULL),

@ -242,6 +242,7 @@ def update_row(ident, editor=None):
] ]
edit_columns = non_null_columns + [ edit_columns = non_null_columns + [
'allow_holes', 'uploader_whitelist', 'thumbnail_time', 'thumbnail_template', 'thumbnail_image' 'allow_holes', 'uploader_whitelist', 'thumbnail_time', 'thumbnail_template', 'thumbnail_image'
'video_crop'
] ]
sheet_columns = [ sheet_columns = [
'sheet_name', 'event_start', 'event_end', 'sheet_name', 'event_start', 'event_end',
@ -337,6 +338,8 @@ def update_row(ident, editor=None):
None if transition is None else tuple(transition) None if transition is None else tuple(transition)
for transition in new_row['video_transitions'] for transition in new_row['video_transitions']
] ]
if new_row.get('video_crop') is not None:
new_row['video_crop'] = tuple(new_row['video_crop'])
# Convert binary fields from base64 and do basic validation of contents # Convert binary fields from base64 and do basic validation of contents
if new_row.get('thumbnail_image') is not None: if new_row.get('thumbnail_image') is not None:

Loading…
Cancel
Save