1- import os , glob , math
1+ # Make videos from images that have been sorted into sets.
22
3- dir = os .path .expanduser ('~/Desktop/DCIM/100MSDCF' )
3+ import os , glob , math , tempfile
4+ from constants import *
45
5- quality = 15 # MP4 quality; lower is better
6- fps = 24
7- boomerangLengthSeconds = 15 # final video length, minimum.
8- sets = sorted (glob .glob ('%s/set_*' % dir ))
6+ temp_dir = tempfile .mkdtemp (prefix = 'video_maker_' )
7+
8+ def get_output (cmd : str ) -> str :
9+ # Super lazy way to get the output of a program: output to a file and then
10+ # read that file.
11+ temp_file = os .path .join (temp_dir , 'temp.txt' )
12+ os .system (f'{ cmd } > { temp_file } ' )
13+ with open (temp_file , 'r' ) as f :
14+ return f .read ()
15+
16+ sets = sorted (glob .glob (f'{ input_dir } /set_*' ))
17+ output_dims = None
918for set in sets :
1019 if not os .path .isdir (set ): continue
11- files = sorted (glob .glob ('%s/*.JPG' % set ))
12- if len (files ) < 2 :
13- print 'Set does not contain enough images!'
20+ files = sorted (glob .glob (f' { set } /* { photo_ext } ' ))
21+ if len (files ) < min_images_per_set :
22+ print ( 'Set does not contain enough images!' )
1423 continue
15- setName = os .path .basename (set )
16- print setName
17- # Check the orientation of these images. Currently only support "horizontal" and "90 CW".
18- os .system ('exiftool -Orientation -n %s > /tmp/orient.txt' % files [0 ])
19- with open ('/tmp/orient.txt' , 'r' ) as f :
20- orientation = f .read ()
24+ set_name = os .path .basename (set )
25+ print (set_name )
26+ if output_dims is None :
27+ # We have to compute our own output size, maintaining aspect ratio, and
28+ # making sure the height is divisible by 2.
29+ exiftool = f'{ exiftool_call } -ImageWidth -s -s -s { files [0 ]} ' # -s 3 times gets value only
30+ width = int (get_output (exiftool ))
31+ exiftool = f'{ exiftool_call } -ImageHeight -s -s -s { files [0 ]} ' # -s 3 times gets value only
32+ height = int (get_output (exiftool ))
33+ AR = height / float (width )
34+ new_width = video_width
35+ new_height = int (new_width * AR )
36+ if new_height % 2 :
37+ new_height -= 1
38+ output_dims = f'{ new_width } x{ new_height } '
39+ print (f'{ width } x{ height } -> { output_dims } ' )
40+ # Check the orientation of these images. Currently only support "horizontal"
41+ # and "90 CW".
42+ exiftool = f'{ exiftool_call } -Orientation -n { files [0 ]} '
43+ orientation = get_output (exiftool )
2144 if ': 1' in orientation :
22- vf = 'scale=1920:1280 '
23- print 'Images are horizontal'
45+ vf = f 'scale={ output_dims } '
46+ print ( 'Images are horizontal' )
2447 elif ': 6' in orientation :
25- vf = 'scale=1920:1280 ,transpose=1' # see https://stackoverflow.com/a/9570992
26- print 'Images are vertical'
48+ vf = f 'scale={ output_dims } ,transpose=1' # see https://stackoverflow.com/a/9570992
49+ print ( 'Images are vertical' )
2750 else :
28- raise ValueError ( 'Unsupported image rotation!\n %s' % orientation )
51+ raise RuntimeError ( f 'Unsupported image rotation!\n { orientation } ' )
2952 # Start at the second frame, because the reverse clip will end at the first frame.
30- startNum = int (os .path .basename (files [1 ])[len ('image-' ):- len ('.JPG' )])
31- videoFileForward = os .path .join (dir , '%s_forward.mp4' % setName )
32- ffmpeg = "ffmpeg -y -start_number %d -r %d -i '%s/image-%%05d.JPG' -vf '%s' -vcodec libx264 " \
33- "-crf %d -pix_fmt yuv420p %s > /dev/null 2>&1" % (startNum , fps , set , vf , quality , videoFileForward )
34- print 'Forward'
53+ start_num = int (os .path .basename (files [1 ])[len ('image-' ):- len (photo_ext )])
54+ vid_file_fwd = os .path .join (input_dir , f'{ set_name } _forward.mp4' )
55+ ffmpeg = f"{ ffmpeg_call } -y -start_number { start_num } -r { fps } -i \" { set } /image-%05d.JPG\" " \
56+ f"-vf \" { vf } \" -vcodec libx264 -crf { quality } -pix_fmt yuv420p { vid_file_fwd } " \
57+ f"> { dev_null } 2>&1"
58+ print ('Forward' )
3559 os .system (ffmpeg )
3660
3761 # Reverse clip starts at the second to last frame, because the forward clip ends at the last frame.
38- startNum = int (os .path .basename (files [- 2 ])[len ('image' ):- len ('.JPG' )])
39- videoFileReverse = os .path .join (dir , '%s_reverse.mp4' % setName )
40- ffmpeg = "ffmpeg -y -start_number %d -r %d -i '%s/image%%05d.JPG' -vf '%s' -vcodec libx264 " \
41- "-crf %d -pix_fmt yuv420p %s > /dev/null 2>&1" % (startNum , fps , set , vf , quality , videoFileReverse )
42- print 'Reverse'
62+ start_num = int (os .path .basename (files [- 2 ])[len ('image' ):- len (photo_ext )])
63+ vid_file_rev = os .path .join (input_dir , f'{ set_name } _reverse.mp4' )
64+ ffmpeg = f"{ ffmpeg_call } -y -start_number { start_num } -r { fps } -i \" { set } /image%05d.JPG\" " \
65+ f"-vf \" { vf } \" -vcodec libx264 -crf { quality } -pix_fmt yuv420p { vid_file_rev } " \
66+ f"> { dev_null } 2>&1"
67+ print ('Reverse' )
4368 os .system (ffmpeg )
4469
4570 # How long is our sequence?
46- numFrames = len (files )
47- lengthSeconds = numFrames / float (fps ) * 2 # 2 for forward and reverse
48- numPairs = int (math .ceil (boomerangLengthSeconds / lengthSeconds ))
71+ num_frames = len (files )
72+ len_seconds = num_frames / float (fps ) * 2 # 2 for forward and reverse
73+ num_pairs = int (math .ceil (boomerang_length_seconds / len_seconds ))
74+
75+ print (f'Pair is { len_seconds :.2f} seconds long. Will require { num_pairs } pairs to hit '
76+ f'{ boomerang_length_seconds } seconds.' )
4977
50- print 'Pair is %.2f seconds long. Will require %d pairs to hit %d seconds.' % (
51- lengthSeconds , numPairs , boomerangLengthSeconds
52- )
5378 # Make the concat list.
54- with open ('/tmp/concat.txt' , 'w' ) as f :
55- for i in range (0 , numPairs ):
56- f .write ('file %s\n ' % videoFileForward )
57- f .write ('file %s\n ' % videoFileReverse )
79+ concat_file = os .path .join (temp_dir , 'concat.txt' )
80+ # ffmpeg needs the backslashes escaped when loading a file list from a file.
81+ fwd = vid_file_fwd .replace ("\\ " , "\\ \\ " )
82+ rev = vid_file_rev .replace ("\\ " , "\\ \\ " )
83+ with open (concat_file , 'w' ) as f :
84+ for i in range (num_pairs ):
85+ f .write (f'file { fwd } \n ' )
86+ f .write (f'file { rev } \n ' )
5887
59- videoFile = os .path .join (dir , '%s_boomerang .mp4' % setName )
88+ video_file = os .path .join (input_dir , f' { set_name } _boomerang .mp4' )
6089
61- ffmpeg = "ffmpeg -y -safe 0 -f concat -i '/tmp/concat.txt' -c copy %s > /dev/null 2>&1" % (videoFile )
62- print 'Concat'
63- # print ffmpeg
90+ ffmpeg = f"{ ffmpeg_call } -y -safe 0 -f concat -i \" { concat_file } \" -c copy { video_file } > { dev_null } 2>&1"
91+ print ('Concat' )
6492 os .system (ffmpeg )
65- print ''
93+ print ()
0 commit comments