#!/usr/bin/python # -*- coding: utf8 -*- # # Copyright (C) 2018 Niccolo Rigacci # # Convert a portrait-oriented video to landscape mode. Crop a # region from the original video and overlay it over a fake # background. The background is created by blurring the cropped # region and stretching it to the 16:9 aspect ratio. # # This program will generate a shell script, requiring the # ffmpeg program and the convert tool from the ImageMagick. # Tested with ffmpeg 3.4.2, ImageMagick 6.9.7.4 and Debian # GNU/Linux 9 Stretch. import os.path, random, sys if len(sys.argv) < 2: print "Usage: %s video_file [crop_aspect] [offset_y]" % os.path.basename(sys.argv[0]) sys.exit(1) #-------------------------------------------------------------------------- # Input (source) video size. Width and height intended without rotation. # Use the mediainfo tool to inspect the video file. #-------------------------------------------------------------------------- video_in = sys.argv[1] input_w = 1280 input_h = 720 # Rotate 90 deg clockwise (1) or counterclockwise (2) transpose = 1 # Region to crop from the input video: # 0.000 = a square # 0.885 = a rectangle occupying 1/3 of the final width # 1.000 = the full (rotated) rectangle if len(sys.argv) > 2: crop_aspect = float(sys.argv[2]) else: crop_aspect = 0.3 # Y offset of the cropped region (after the rotation): # 0.0 = top # 0.5 = center # 1.0 = bottom if len(sys.argv) > 3: offset_y = float(sys.argv[3]) else: offset_y = 0.5 # Overlay fuzzy border: percent of width (0% = no fuzzy): fuzzy_border = 0.05 #-------------------------------------------------------------------------- # Output (destination) video size and filename. #-------------------------------------------------------------------------- output_w = 1366 output_ratio_w = 16 output_ratio_h = 9 video_out = video_in[0:-4] + '.fixed.mp4' #-------------------------------------------------------------------------- # End of customizable parameters, no changes required beyond this point. #-------------------------------------------------------------------------- #-------------------------------------------------------------------------- # Temporary files: original video without rotation and overlay mask. #-------------------------------------------------------------------------- rnd = random.randint(0, 9999) tmp_norotate = 'tmp_norotate_%04d.mp4' % (rnd,) tmp_mask = 'tmp_mask_%04d.png' % (rnd,) #-------------------------------------------------------------------------- # Calculate the nearest even integer. #-------------------------------------------------------------------------- def even_int(f): return int(round(f / 2.0)) * 2 #-------------------------------------------------------------------------- # Output (destination) video size. #-------------------------------------------------------------------------- output_h = even_int(float(output_w) / float(output_ratio_w) * float(output_ratio_h)) #-------------------------------------------------------------------------- # Region to crop from the input video. #-------------------------------------------------------------------------- crop_w = input_h crop_h = even_int(input_h + float(input_w - input_h) * crop_aspect) # Scale the cropped region to match the final video size. scale_crop_w = even_int(float(crop_w) * float(output_h) / float(crop_h)) scale_crop_h = output_h # Offset of the cropped region. offset_crop_x = int(float(input_h - crop_w) / 2.0) offset_crop_y = int(float(input_w - crop_h) * offset_y) # X offset to overlay cropped region over the background. offset_overlay_x = int(float(output_w - scale_crop_w) / 2.0) offset_overlay_y = int(float(output_h - scale_crop_h) / 2.0) # Calculate the cropping region and overlay offset. crop = 'crop=%d:%d:%d:%d' % (crop_w, crop_h, offset_crop_x, offset_crop_y) overlay = 'overlay=%d:%d' % (offset_overlay_x, offset_overlay_y) # How much to blur the background. avgblur = int(float(output_w) / 25.0) filter_crop = 'transpose=%d,%s,scale=%d:%d,setdar=%d/%d' % (transpose, crop, scale_crop_w, scale_crop_h, scale_crop_w, scale_crop_h) filter_back = 'transpose=%d,%s,scale=%d:%d,setdar=%d/%d,avgblur=%d' % (transpose, crop, output_w, output_h, output_ratio_w, output_ratio_h, avgblur) print "#!/bin/sh" print '# Interpolate video "%s" to fix the aspect ratio.' % (video_in,) print "# Input video size: %dx%d (non rotated)" % (input_w, input_h) print "# Cropping region: %dx%d+%d+%d (rotated)" % (crop_w, crop_h, offset_crop_x, offset_crop_y) print "# Output video size: %dx%d" % (output_w, output_h) # Cannot remove the rotate metatag while applying transpose video filter. # https://trac.ffmpeg.org/ticket/6370 ffmpeg = 'ffmpeg -i "%s" -c copy -metadata:s:v:0 rotate=0 %s' % (video_in, tmp_norotate) print print "# Make a copy of the input file removing rotate metadata." print ffmpeg # Create the mask (PNG image) to overlay with fuzzy borders. fb = int(float(scale_crop_w) * fuzzy_border) convert = 'convert -size %dx%d' % (scale_crop_h, scale_crop_w) convert += ' -define "gradient:bounding-box=%dx%d+0+0"' % (scale_crop_h, fb) convert += ' -define "gradient:vector=0,%d,0,0" gradient:none-black' % (fb - 1) convert += ' -define "gradient:bounding-box=%dx%d+0+%d"' % (scale_crop_h, scale_crop_w, scale_crop_w - fb) convert += ' -define "gradient:vector=0,%d,0,%d" gradient:none-black' % (scale_crop_w - fb, scale_crop_w - 1) convert += ' -composite -channel a -negate -rotate 90 %s' % (tmp_mask,) print print "# Make the overlay mask." print convert cfilter = '[0:v]split [a][b];' cfilter += '[a]%s [crop];' % (filter_crop,) cfilter += '[b]%s [back];' % (filter_back,) cfilter += '[1:v]alphaextract [mask];' cfilter += '[crop][mask]alphamerge [masked];' cfilter += '[back][masked]%s' % (overlay,) ffmpeg = 'ffmpeg -i %s -loop 1 -i %s -filter_complex "%s" "%s"' % (tmp_norotate, tmp_mask, cfilter, video_out) print print "# Overlay the cropped region over the background to make the final video." print ffmpeg print print "# Remove temporaty files." print "rm %s %s" % (tmp_norotate, tmp_mask)