Skip to content

Instantly share code, notes, and snippets.

@damian0815
Created March 17, 2014 15:10
Show Gist options
  • Save damian0815/9601029 to your computer and use it in GitHub Desktop.
Save damian0815/9601029 to your computer and use it in GitHub Desktop.
#!/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
print command
print
os.system(command)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment