Skip to content

Instantly share code, notes, and snippets.

@dwf
Created November 29, 2011 19:41
Show Gist options
  • Save dwf/1406134 to your computer and use it in GitHub Desktop.
Save dwf/1406134 to your computer and use it in GitHub Desktop.
A batch video-resizing script.
#!/usr/bin/env python
# Copyright (c) 2009, David Warde-Farley
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""Wrappers and a command line tool for batch-resizing with ffmpeg."""
import argparse
import os
import re
import subprocess
__author__ = "David Warde-Farley"
__copyright__ = "Copyright 2011, David Warde-Farley / Universite de Montreal"
__license__ = "BSD"
__maintainer__ = "David Warde-Farley"
__email__ = "wardefar@iro"
_size_regex = re.compile(r'(\d+)x(\d+)')
def get_video_size(src_file, verbose=False):
"""
Determine the frame size of a video file using the ffmpeg
command line tool.
Parameters
----------
src_file : str
The path to the source file
verbose : boolean, optional
If `True`, all ffmpeg output is seen on the command line.
Default is `False`.
Returns
-------
width : int
The width of the video stream.
height : int
The height of the video stream.
Notes
-----
Technically only looks for the first video stream in the
file, but most files should only have one.
"""
if not os.path.exists(src_file):
raise IOError("No such file '%s'" % src_file)
elif os.path.isdir(src_file):
raise IOError("'%s' is a directory" % src_file)
proc = subprocess.Popen(['ffmpeg', '-i', src_file],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
sizes = []
for line in proc.stdout:
if verbose:
print line.rstrip()
tokens = line.split()
if "Stream" in tokens and "Video:" in tokens:
for token in tokens:
match = _size_regex.match(token)
if match:
width = int(match.group(1))
height = int(match.group(2))
return width, height
raise ValueError("No video stream found in file '%s'" % src_file)
def resize_video(src_file, dest_file, overwrite=True,
scale_factor=0.5, verbose=False):
"""
Resizes a video file with the ffmpeg command line tool.
Parameters
----------
src_file : str
The path to the source video file.
dest_file : str
The destination path for the file. If the directory
portion does not exist, it will be created.
overwrite : boolean, optional
Overwrite `dest_file` if it exists. Defaults to `True`.
scale_factor : float, optional
The amount by which to scale each spatial dimension.
Defaults to 1/2, i.e. make the video approximately
a quarter of the resolution of the original.
verbose : boolean, optional
If `True`, all ffmpeg output is seen on the command line.
Default is `False`.
"""
dirname = os.path.dirname(dest_file)
if len(dirname) > 0 and not os.path.exists(os.path.dirname(dest_file)):
os.makedirs(os.path.dirname(dest_file))
width, height = get_video_size(src_file, verbose)
if os.path.exists(dest_file):
if os.path.isdir(dest_file):
raise IOError("'%s' is a directory" % dest_file)
elif overwrite:
os.remove(dest_file)
else:
raise IOError("file exists: '%s'" % dest_file)
new_size = [int(scale_factor * width), int(scale_factor * height)]
# ffmpeg wants even dimensions.
if new_size[0] % 2 == 1:
new_size[0] += 1
if new_size[1] % 2 == 1:
new_size[1] += 1
cmd = ['ffmpeg', '-i', src_file, '-s', 'x'.join(map(str, new_size)),
dest_file]
if verbose:
stdout = None
else:
stdout = subprocess.PIPE
ret = subprocess.call(cmd, stdout=stdout,
stderr=subprocess.STDOUT)
if ret != 0:
raise OSError('Command %s failed with return code %d' %
(str(cmd), ret))
def resize_dir(src_dir, dest_dir, extensions=('mpg', 'mpeg', 'avi'),
recurse=True, overwrite=True, scale_factor=0.5, verbose=False):
"""
src_dir : str
The path to the source directory.
dest_dir : str
The path to destination directory. If it does not
exist, it will be created.
extensions : sequence of strings, optional
The filename extensions to try to resize with ffmpeg. Defaults to
`('mpg', 'mpeg', 'avi')`.
recurse : boolean, optional
Recursively convert videos in subdirectories. Defaults to
`True`.
overwrite : boolean, optional
Overwrite destination files if they exist. Defaults to `True`.
scale_factor : float, optional
The amount by which to scale each spatial dimension.
Defaults to 1/2, i.e. make the video approximately
a quarter of the resolution of the original.
verbose : boolean, optional
If `True`, all ffmpeg output is seen on the command line.
Default is `False`.
"""
files = os.listdir(src_dir)
for fn in files:
if recurse and os.path.isdir(fn):
resize_dir(fn, os.path.join(dest_dir, os.path.basename(fn)),
extensions, overwrite, scale_factor)
else:
basename = fn
fn = os.path.join(src_dir, fn)
if basename.split('.')[-1].lower() in extensions:
dest_fn = os.path.join(dest_dir, basename)
print "Resizing %s to %s..." % (fn, dest_fn)
resize_video(fn, dest_fn, overwrite, scale_factor, verbose)
def make_parser():
parser = argparse.ArgumentParser(description="batch resize videos with "
"ffmpeg")
parser.add_argument('src_dir', action='store', type=str,
help='Path to source directory')
parser.add_argument('dest_dir', action='store', type=str,
help='Path to destination directory (created if '
'non-existent)')
parser.add_argument('-v', '--verbose', action='store_true',
help='Display all ffmpeg output')
parser.add_argument('-s', '--scale-factor', action='store',
type=float, default=0.5,
help='Amount by which to scale the video')
parser.add_argument('-N', '--no-overwrite', action='store_true',
help='Don\'t overwrite existing files')
parser.add_argument('-n', '--no-recurse', action='store_false',
help='Don\'t recurse on sub-directories')
return parser
def main():
parser = make_parser()
args = parser.parse_args()
if not os.path.exists(args.src_dir):
parser.error("specified source '%s' does not exist")
try:
resize_dir(args.src_dir, args.dest_dir,
recurse=not args.no_recurse,
overwrite=not args.no_overwrite,
scale_factor=args.scale_factor,
verbose=args.verbose)
except IOError, e:
parser.error(e.message)
except OSError, e:
parser.error(e.message)
if __name__ == "__main__":
main()
@rizkysyazuli
Copy link

do you have an instruction to use this script?

@rudolphos
Copy link

I would like to know this too...

I want to batch resize videos from smaller dimensions to larger dimensions, I think ffmpeg is better for these kind of thinks, usually I used handbrake and vidcoder, but it encoded video to lower quality...

@skroo
Copy link

skroo commented Aug 17, 2017

I want to batch resize videos from smaller dimensions to larger dimensions

Don't ever do this. You will make the file bigger without improving quality in the slightest. You are better off playing the original file and scaling it with your player.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment