Created
November 5, 2015 05:15
-
-
Save bmatherly/e39fb68c876161d5b17b to your computer and use it in GitHub Desktop.
Normalize media files using Python and FFMpeg
This file contains 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/python | |
import getopt, sys, os, time, subprocess, shutil, argparse | |
WIDTH = 1280 | |
HEIGHT = 720 | |
RATE = "60000/1001" | |
EXT = ".mov" | |
FFMPEG = "ffmpeg.exe" | |
description = ( | |
"Normalize files\n" | |
" Input and output may be specified as either a file or directory. \n" | |
" * If input is a file, it will be normalized to the output file. \n" | |
" * If input is a directory, all files inside the directory will be \n" | |
" normalized and stored in the output directory. \n" | |
" In all cases, an appropriate file extension is added. \n" | |
) | |
parser = argparse.ArgumentParser(description=description) | |
parser.add_argument("-t", help="Rename output files with modification [t]ime", action='store_true') | |
parser.add_argument("src") | |
parser.add_argument("dst") | |
args = parser.parse_args() | |
def get_destination_filename(srcname): | |
dstname = "" | |
if args.t: | |
dstname = time.strftime("%Y-%m-%d-%H%M%S", time.gmtime(os.path.getmtime(srcname)) ) | |
else: | |
# Strip the path and extension | |
dstname = os.path.basename( os.path.splitext(srcname) ) | |
# Clean the name of spaces and strange characters | |
dstname.replace(" ", "") | |
''.join(e for e in dstname if e not in "{}()![],") | |
return dstname + EXT | |
def normalize_file(src_file, dst_file): | |
print("Normalize", srcfile, "to", dstfile) | |
# Get information about the video | |
infocmd = [ FFMPEG, | |
'-i', | |
src_file, | |
'-vframes', '1', | |
"-vf", 'showinfo', | |
'-an', | |
'-f', 'rawvideo', | |
'-y', | |
os.devnull | |
] | |
procinfo = subprocess.run(infocmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) | |
vidinfo = "" | |
for line in procinfo.stdout.split("\n"): | |
if "showinfo" in line: | |
vidinfo = line | |
# Structure is printed as i:T or i:B or i:P | |
structure = vidinfo.split("i:")[1].split()[0] | |
# Size is printed as s:1920x1080 | |
iw = vidinfo.split(" s:")[1].split()[0].split("x")[0] | |
ih = vidinfo.split(" s:")[1].split()[0].split("x")[1] | |
filters = "" | |
# Deinterlace if necessary | |
if structure != "P": | |
print("Source is Interlaced") | |
# Duplicate frames | |
filters = filters + "yadif=1:-1:0" | |
else: | |
print("Source is Progressive") | |
# Scale if necessary | |
if iw != WIDTH or ih != HEIGHT: | |
print( "Scale", iw, "x", ih, "->", WIDTH, "x", HEIGHT) | |
if filters != "": | |
filters += "," | |
filters += "scale=(iw*sar)*min({WIDTH}/(iw*sar)\,{HEIGHT}/ih):ih*min({WIDTH}/(iw*sar)\,{HEIGHT}/ih)".format(WIDTH=WIDTH, HEIGHT=HEIGHT) | |
filters += "," | |
# Add bars if AR does not match. Has no effect when they do match. | |
filters += "pad={WIDTH}:{HEIGHT}:({WIDTH}-iw*min({WIDTH}/iw\,{HEIGHT}/ih))/2:({HEIGHT}-ih*min({WIDTH}/iw\,{HEIGHT}/ih))/2".format(WIDTH=WIDTH, HEIGHT=HEIGHT) | |
cmd = [ FFMPEG, | |
'-i', | |
src_file, | |
'-sn', | |
] | |
if filters: | |
cmd += ["-vf", filters] | |
cmd += [ | |
'-r', RATE, | |
'-vcodec', 'dnxhd', | |
'-vb', '220000k', | |
'-pix_fmt', 'yuv422p', | |
'-threads', '3', | |
'-acodec', 'pcm_s16le', | |
'-ac', '2', | |
'-ar', '48000', | |
dst_file | |
] | |
print( "About to run command:" ) | |
print(cmd) | |
subprocess.run(cmd) | |
# Make sure the normalize succeeded. | |
if os.path.isfile(dst_file): | |
shutil.copystat( src_file, dst_file ) | |
else: | |
print("Normalize Failed") | |
exit( 7 ) | |
# Sanity checking, to make sure everything is in order. | |
if os.path.isfile(args.src): | |
# Normalize a single file | |
srcfile = args.src | |
dstfile = get_destination_filename(srcfile) | |
normalize_file( srcfile, dstfile ) | |
elif os.path.isdir(args.src): | |
if not os.path.isdir(args.dst): | |
print(args.src, "is a directory but", args.dst, "is not a directory") | |
exit(1) | |
for file in os.listdir(args.src): | |
srcfile = os.path.join(args.src, file) | |
dstfile = os.path.join(args.dst, get_destination_filename(srcfile)) | |
normalize_file( srcfile, dstfile ) | |
else: | |
print( args.src, "is not a file or directory" ) | |
exit(2) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment