diff --git a/cutter/cutter/main.py b/cutter/cutter/main.py index 373ba07..7a38379 100644 --- a/cutter/cutter/main.py +++ b/cutter/cutter/main.py @@ -16,7 +16,7 @@ from psycopg2 import sql import common from common.database import DBManager, query -from common.segments import get_best_segments, fast_cut_segments, ContainsHoles +from common.segments import get_best_segments, fast_cut_segments, full_cut_segments, ContainsHoles from .upload_backends import Youtube, Local @@ -267,7 +267,13 @@ class Cutter(object): upload_backend = self.upload_locations[job.upload_location] self.logger.info("Cutting and uploading job {} to {}".format(format_job(job), upload_backend)) - cut = fast_cut_segments(job.segments, job.video_start, job.video_end) + + if upload_backend.encoding_settings is None: + self.logger.debug("No encoding settings, using fast cut") + cut = fast_cut_segments(job.segments, job.video_start, job.video_end) + else: + self.logger.debug("Using encoding settings for cut: {}".format(upload_backend.encoding_settings)) + cut = full_cut_segments(job.segments, job.video_start, job.video_end, upload_backend.encoding_settings) # This flag tracks whether we've told requests to finalize the upload, # and serves to detect whether errors from the request call are recoverable. @@ -550,6 +556,9 @@ def main( This is useful when multiple upload locations actually refer to the same place just with different settings, and you only want one of them to actually do the check. + cut_type: + One of 'fast' or 'full'. Default 'fast'. This indicates whether to use + fast_cut_segments() or full_cut_segments() for this location. along with any additional config options defined for that backend type. creds_file should contain any required credentials for the upload backends, as JSON. @@ -608,6 +617,7 @@ def main( for location, backend_config in config.items(): backend_type = backend_config.pop('type') no_transcode_check = backend_config.pop('no_transcode_check', False) + cut_type = backend_config.pop('cut_type', 'fast') if type == 'youtube': backend_type = Youtube elif type == 'local': @@ -615,6 +625,11 @@ def main( else: raise ValueError("Unknown upload backend type: {!r}".format(type)) backend = backend_type(credentials, **backend_config) + if cut_type == 'fast': + # mark for fast cut by clearing encoding settings + backend.encoding_settings = None + elif cut_type != 'full': + raise ValueError("Unknown cut type: {!r}".format(cut_type)) upload_locations[location] = backend if backend.needs_transcode and not no_transcode_check: needs_transcode_check.append(backend) diff --git a/cutter/cutter/upload_backends.py b/cutter/cutter/upload_backends.py index 042d2b5..bd3a056 100644 --- a/cutter/cutter/upload_backends.py +++ b/cutter/cutter/upload_backends.py @@ -31,12 +31,14 @@ class UploadBackend(object): The upload backend also determines the encoding settings for the cutting process, this is given as a list of ffmpeg args under the 'encoding_settings' attribute. + If this is None, instead uses the 'fast cut' strategy where nothing + is transcoded. """ needs_transcode = False # reasonable default if settings don't otherwise matter - encoding_settings = [] # TODO + encoding_settings = ['-f', 'mp4'] def upload_video(self, title, description, tags, data): raise NotImplementedError @@ -59,7 +61,18 @@ class Youtube(UploadBackend): """ needs_transcode = True - encoding_settings = [] # TODO youtube's recommended settings + encoding_settings = [ + # Youtube's recommended settings: + '-codec:v', 'libx264', # Make the video codec x264 + '-crf', '21', # Set the video quality, this produces the bitrate range that YT likes + '-bf', '2', # Have 2 consecutive bframes, as requested + '-flags', '+cgop', # Use closed GOP, as requested + '-pix_fmt', 'yuv420p', # chroma subsampling 4:2:0, as requrested + '-codec:a', 'aac', '-strict', '-2', # audio codec aac, as requested + '-b:a', '384k' # audio bitrate at 348k for 2 channel, use 512k if 5.1 audio + '-r:a', '48000', # set audio sample rate at 48000Hz, as requested + '-movflags', 'faststart', # put MOOV atom at the front of the file, as requested + ] def __init__(self, credentials, hidden=False, category_id=23, language="en"): self.logger = logging.getLogger(type(self).__name__)