comparison python/ffslice.py @ 741:9681a0bd74d6

add utility for slicing video files
author Jeff Hammel <k0scist@gmail.com>
date Sun, 28 Jun 2015 11:56:13 -0700
parents
children f011ec45b8e8
comparison
equal deleted inserted replaced
739:8a9fe2f20bcb 741:9681a0bd74d6
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 """
5 slice a clip with `ffmpeg`:
6
7 ffmpeg -ss <00:00:00> -t <sec> -i <input_file> -q 1 <output_file> #
8
9 --ss = start time ;
10
11 -t = length of video to create from the start time in seconds.
12 """
13
14 # imports
15 import argparse
16 import os
17 import subprocess
18 import sys
19
20 # module globals
21 __all__ = ['main', 'Parser']
22 here = os.path.dirname(os.path.realpath(__file__))
23 string = (str, unicode)
24
25
26 def ensure_dir(directory):
27 """ensure a directory exists"""
28 if os.path.exists(directory):
29 assert os.path.isdir(directory)
30 return directory
31 os.makedirs(directory)
32 return directory
33
34
35 def convert_seconds_to_hhmmss(time_sec):
36 """converts `time_sec` to (hh,mm,ss)"""
37
38 hh = int(time_sec / 3600)
39 mm = int((time_sec % 3600) / 60)
40 ss = int((time_sec % 3600) % 60)
41 return (hh,mm,ss)
42
43
44 def format_seconds_to_hhmmss(time_sec, separator=':'):
45 """converts `time_sec` to string 'hh:mm:ss'"""
46 return separator.join(['{:0>2d}'.format(i)
47 for i in convert_seconds_to_hhmmss(time_sec)])
48
49
50 def duration(clip, ffprobe='ffprobe'):
51 """get the duration in seconds using `ffprobe` from ffmpeg"""
52
53 command = [ffprobe, clip]
54 assert os.path.exists(clip), "Missing clip, duration not available"
55
56 # probe the file
57 try:
58 output = subprocess.check_output(command, stderr=subprocess.STDOUT)
59 # (for some reason, ffprobe sends output to stderr)
60 except subprocess.CalledProcessError as e:
61 print (e.output)
62 raise
63
64 duration = None
65 for line in output.splitlines():
66 # Parse the output of `ffprobe`
67 # look for a line like:
68 # Duration: 00:00:59.73, start: 0.000000, bitrate: 7783 kb/s
69
70 line = line.strip()
71 if line.startswith("Duration:"):
72 if duration:
73 raise AssertionError("Duplicate duration - already found: {}".format(duration))
74 line = line.split(',')[0]
75 duration = line.split(':', 1)[1].strip()
76
77 if duration:
78 hh, mm, ss = [float(i) for i in duration.split(':')]
79 duration = 3600*hh + 60*mm + ss
80
81 return duration
82
83
84 class FFSliceParser(argparse.ArgumentParser):
85 """fflice CLI option parser"""
86
87 default_slice_time = 300.
88
89 def __init__(self):
90 argparse.ArgumentParser.__init__(self, description=__doc__)
91 self.add_argument('clips', metavar='clip', nargs='+', help="clips to slice")
92 self.add_argument('-d', '--directory', dest='directory',
93 default=os.getcwd(),
94 help="output directory [DEFAULT: %(default)s]")
95 self.add_argument('--durations', '--print-durations', dest='print_durations',
96 action='store_true', default=False,
97 help="print durations and exit")
98 self.add_argument('-n', dest='number', type=int,
99 help="number of slices")
100 self.add_argument('-t', '--time', dest='slice_time',
101 type=float,
102 help="time of each slice [DEFAULT: {}]".format(self.default_slice_time))
103 self.add_argument('--dry-run', dest='dry_run',
104 action='store_true', default=False,
105 help="print out what will be done")
106 self.add_argument('--hhmmss', dest='hhmmss',
107 action='store_true', default=False,
108 help="display times in 'hh:mm:ss' format; thedefault is in seconds")
109
110 self.add_argument('-v', '--verbose', dest='verbose',
111 action='store_true', default=False,
112 help="be verbose")
113 self.options = None
114
115 def parse_args(self, *args, **kw):
116 options = argparse.ArgumentParser.parse_args(self, *args, **kw)
117 self.validate(options)
118 self.options = options
119 return options
120
121 def validate(self, options):
122 """validate options"""
123
124 missing = [clip for clip in options.clips
125 if not os.path.exists(clip)]
126 if missing:
127 self.error("Not found: {}".format(', '.join(missing)))
128
129 if options.slice_time and options.number:
130 self.error("Cannot specify slice time and number of slices")
131 # TODO: allow specification of both
132 elif options.slice_time is None and options.number is None:
133 options.slice_time = self.default_slice_time
134
135 ensure_dir(options.directory)
136
137 def format_seconds(self, seconds):
138 """format seconds to string"""
139 if self.options.hhmmss:
140 return format_seconds_to_hhmmss(seconds)
141 return '{:.2}'.format(seconds)
142
143
144 def main(args=sys.argv[1:]):
145 """CLI"""
146
147 # parse command line options
148 parser = FFSliceParser()
149 options = parser.parse_args(args)
150
151 # compute durations
152 durations = {clip: duration(clip) for clip in options.clips}
153 if options.print_durations:
154 returncode = 0
155 total = 0.
156 for clip in options.clips:
157 _duration = durations[clip]
158 if _duration is None:
159 print ("Duration not found: '{}'".format(clip))
160 returncode = 1
161 else:
162 print ('{} : {}'.format(clip,
163 parser.format_seconds(_duration)))
164 sys.exit(returncode)
165
166 if __name__ == '__main__':
167 main()
168