Created
March 17, 2014 15:10
-
-
Save damian0815/9601029 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import sys | |
import subprocess | |
import re | |
import os | |
import argparse | |
import math | |
class Size: | |
width = 0 | |
height = 0 | |
def __init__(self, width, height): | |
self.width = float(width) | |
self.height = float(height) | |
def __str__(self): | |
return "Size("+str(self.width)+"x"+str(self.height)+")" | |
def __repr__(self): | |
return str(self) | |
@classmethod | |
def aspectFitted(cls, targetSize, aspectRatio): | |
outerAspectRatio = targetSize.getAspectRatio() | |
innerAspectRatio = aspectRatio | |
if innerAspectRatio < outerAspectRatio: | |
return Size(targetSize.height*innerAspectRatio, targetSize.height) | |
else: | |
return Size(targetSize.width,targetSize.width/innerAspectRatio) | |
@classmethod | |
def aspectFilled(cls, targetSize, aspectRatio): | |
outerAspectRatio = targetSize.getAspectRatio() | |
innerAspectRatio = aspectRatio | |
if innerAspectRatio > outerAspectRatio: | |
return Size(targetSize.height*innerAspectRatio, targetSize.height) | |
else: | |
return Size(targetSize.width,targetSize.width/innerAspectRatio) | |
def getAspectRatio(self): | |
return self.width/self.height | |
def makeIntegral(self): | |
self.width = math.floor(abs(self.width)+0.5) | |
self.height = math.floor(abs(self.height)+0.5) | |
return self | |
def makeEven(self): | |
self.width = round(self.width*0.5)*2.0 | |
self.height = round(self.height*0.5)*2.0 | |
return self | |
def getFFMpegSizeString(self): | |
return "w="+str(int(self.width))+":h="+str(int(self.height)) | |
# calculate best aspect ratio for the given aspect ratios | |
def getBestAspectRatioForAspectRatios(aspectRatios): | |
if len(aspectRatios)==0: | |
return 1.0 | |
# calculate the mean aspect ratio | |
meanAspectRatio = 0; | |
for aspectRatio in aspectRatios: | |
meanAspectRatio += aspectRatio | |
meanAspectRatio /= len(aspectRatios) | |
# find the nearest actual aspect ratio to the mean | |
bestActualAspectRatio = -1 | |
conformingAspectRatios = [ 16.0/9.0, 3.0/2.0, 4.0/3.0, 5.0/4.0, 1.0, 4.0/5.0, 3.0/4.0, 2.0/3.0, 9.0/16.0 ] | |
for aspectRatio in conformingAspectRatios: | |
bestDiff = abs(bestActualAspectRatio-meanAspectRatio) | |
diff = abs(aspectRatio-meanAspectRatio) | |
if bestActualAspectRatio<0 or diff < bestDiff: | |
bestActualAspectRatio = aspectRatio | |
return bestActualAspectRatio; | |
# calculate best-fit size for the given sizes | |
def getBestSizeForSizes(sizes, aspectRatioOrZero=0): | |
if len(sizes)==0: | |
return Size(320,320) | |
renderSize = Size(0,0) | |
if len(sizes)==1: | |
# simple case | |
renderSize = sizes[0] | |
else: | |
# calculate best aspect ratio | |
aspectRatios = [] | |
for size in sizes: | |
aspectRatios.append(size.getAspectRatio()) | |
bestAspectRatio = getBestAspectRatioForAspectRatios(aspectRatios) | |
# now that we have the best aspect ratio, conform sizes to this (by aspect fitting) and calculate a biggest size | |
renderWidth = 0 | |
for size in sizes: | |
fitSize = Size.aspectFitted( size, bestAspectRatio ) | |
renderWidth = max(renderWidth,fitSize.width) | |
# calculate height from width and aspect | |
renderHeight = renderWidth/bestAspectRatio | |
renderSize = Size(renderWidth,renderHeight) | |
# conform to requested aspect ratio | |
if aspectRatioOrZero > sys.float_info.epsilon: | |
renderSize = Size.aspectFitted(renderSize, aspectRatioOrZero) | |
# integral | |
renderSize.makeIntegral().makeEven() | |
return renderSize | |
def getMovieSize(path): | |
output = subprocess.check_output(["ffprobe",path], stderr=subprocess.STDOUT) | |
findSizeExpr = re.compile(r"([0-9]{2,}x[0-9]+)") | |
for line in output.split('\n'): | |
match = findSizeExpr.search(line) | |
if ( match is not None ): | |
# found the size | |
sizeStr = match.group(1) | |
# split at x | |
components = sizeStr.split('x') | |
return Size(components[0], components[1]) | |
return None | |
parser = argparse.ArgumentParser() | |
parser.add_argument( 'files', metavar='F', type=str, nargs='+', help = 'an input file' ) | |
parser.add_argument( '-o', '--output', type=str, help='output filename', required=1 ) | |
args = parser.parse_args() | |
# get files from command line | |
files = args.files | |
for f in files: | |
if not os.path.isfile(f): | |
print "file '"+f+"' doesn't exist" | |
sys.exit(1) | |
# collect up source sizes | |
sizes = [] | |
for f in files: | |
try: | |
size = getMovieSize(f) | |
except subprocess.CalledProcessError as e: | |
print "Movie size subprocess returned an error: "+str(e) | |
raise | |
sizes.append(size) | |
# calculate output size | |
outputSize = getBestSizeForSizes(sizes) | |
# build up command line | |
command = "ffmpeg -y -filter_complex '" | |
allInputs = "" | |
for i in range(0,len(files)): | |
command += " \n " | |
audioInputName = "[a"+str(i)+"]" | |
videoInputName = "[v"+str(i)+"]" | |
videoIntermediateName = "[intv"+str(i)+"]" | |
allInputs += videoInputName+" "+audioInputName+" " | |
# command to load the movie and get video and audio streams | |
path = files[i] | |
command += " movie="+path+":s=dv+da "+videoIntermediateName+" "+audioInputName+" ; " | |
command += " \n " | |
# resize to aspect-fill | |
thisSize = sizes[i] | |
cropSize = Size.aspectFitted( thisSize, outputSize.getAspectRatio() ) | |
scaleSize = outputSize | |
# apply resize and crop | |
command += videoIntermediateName+" crop="+cropSize.getFFMpegSizeString()+", scale="+scaleSize.getFFMpegSizeString()+" "+videoInputName+" ; " | |
command += " \n " | |
# build filter command | |
command += " \n " | |
command += allInputs + "concat=n="+str(len(files))+":v=1:a=1 [v] [a]' -map '[v]' -map '[a]' "+args.output | |
print str(sizes)+" -> "+str(outputSize) | |
print command | |
os.system(command) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment