Concatinating slideshow files - 2020
Now, we will concatenate the slides we created in previous chapters.
Regarding merging/joining/concatenating video files, we can have detailed info from How to concatenate (join, merge) media files.
We'll start by creating slides from I-frame as we've done in Thumbnails:
The file to make slides from is yosemiteA.mp4.
ffmpeg -i yosemiteA.mp4 -f image2 -vf "select='eq(pict_type,PICT_TYPE_I)'" -vsync vfr yi%03d.png
The command will create one thumbnail image every I-frame, named yi001.png, yi002.jpg, yi003.png...,yi116.png.
Since we have media files with exactly the same codec and codec parameters, we can concatenate them as described in "Concatenation of files with same codecs" from How to concatenate (join, merge) media files.
ffmpeg -f concat -i mylist.txt -c copy out.mp4
This will merge all the slides into one slideshow.
The "mylist.txt" looks like this:
file yos0.mp4 file yos1.mp4 file yos2.mp4 ... file yos113.mp4 file yos114.mp4 file yos115.mp4
The following code will make a slideshow from 116 I-frames we made earlier. It puts fade effect into each frame as we've done in Transitions : python script for fade-in & fade-out with two slides. It extends the feature in two ways:
- It takes any number of slides.
- It makes a slide list, and then use it to concatenate all the slides to create "yosemite_with_fades.mp4" file.
Here is the code, concat.py. By default it doesn't take any parameters, so we can just run the file: "python concat.py" :
import os import sys import subprocess import psutil class mytool: def __init__(self): self.count = 0 self.copied_file = ['./in001.png', './in002.png'] self.slideshow_list_filename = 'mytext.txt' def add_fade_effect(self, infilename, outfilename = 'final'): # Makes two frames : at the beginning and at the end # This is done by copying one I-Frame for a slide # Then, adds fades at both ends for f in self.copied_file: cmd = map(lambda x: '%s' %x, ['cp', infilename, f]) subprocess.call(cmd) # make normal slide # ffmpeg -r 1/5 -i in%03d.png -c:v libx264 -r 30 -y -pix_fmt yuv420p slide.mp4 in_framerate = 1./5 out_framerate = 30 cmd = ['ffmpeg', '-r', in_framerate, '-i','in%03d.png','-c:v','libx264', '-r', out_framerate, '-y','-pix_fmt','yuv420p','slide.mp4'] cmd = map(lambda x: '%s' %x, cmd) subprocess.call(cmd) # add fade-in effect - from 0th to 30th frame #ffmpeg -i slide.mp4 -y -vf fade=in:0:30 slide_fade_in.mp4 cmd = ['ffmpeg', '-i','slide.mp4','-y','-vf','fade=in:0:30','slide_fade_in.mp4'] subprocess.call(cmd) # add fade-out effect to the slide that has fade-in effect already : 30 frames starting from 120th #ffmpeg -i slide_fade_in.mp4 -y -vf fade=out:120:30 slide_fade_in_out.mp4 cmd = ['ffmpeg', '-i','slide_fade_in.mp4','-y','-vf','fade=out:120:30', 'slide_fade_in_out.mp4'] subprocess.call(cmd) # rename the output to 'final#.mp4' slide_name = outfilename+str(self.count)+'.mp4' cmd = map(lambda x: '%s' %x, ['cp', 'slide_fade_in_out.mp4', slide_name]) subprocess.call(cmd) # remove the copied files for f in self.copied_file: cmd = map(lambda x: '%s' %x, ['rm','-f', f]) subprocess.call(cmd) self.count += 1 return slide_name # make concat list def make_slideshow_list(self, slides, fname='mylist.txt'): self.slideshow_list_filename = fname with open(self.slideshow_list_filename, mode='wb') as f: for slide in slides: f.write('file ' + slide +'\n') # concat all slides in the slideshow list def concat_slides(self, slideshow_name = 'my_slideshow.mp4' ): cmd = ['ffmpeg', '-y', '-f','concat','-i', self.slideshow_list_filename, '-c', 'copy', slideshow_name] subprocess.call(cmd) # get the list of file def file_list(self, d): basedir = d subdir = [] slides = [] sorted_slides = [] for item in os.listdir(d): fullpath = os.path.join(basedir, item) if os.path.isdir(fullpath): subdir.append(fullpath) else: if item.startswith('yi') and item.endswith('.png'): slides.append(fullpath) return sorted(slides) if __name__ == '__main__': if len(sys.argv) <= 1: path = '.' else: path = sys.argv[1] m = mytool() slides = m.file_list(path) fade_slides = [] outfile_name = 'yos' for slide in slides: s = m.add_fade_effect(slide, outfile_name) fade_slides.append(s) # make slideshow list file listname = 'slideshow_list.txt' m.make_slideshow_list(fade_slides, listname) # concatenate the slides in the list file slideshow_name = 'yosemite_with_fades.mp4' m.concat_slides(slideshow_name)
To run it (default mode):
python concat.py
The concat2.py file is a modified version of concat.py. Unlike the concat.py, this updated version takes an input video file name and generates i-frames for us. So, we don't need to take any preparation step and we just throw the input video file name.
To run it, we need the path and input video name. In this case, we use current directory for the path and the input file is another mp4 file, yosemiteB.mp4.
python concat2 . yosemiteB.mp4
Then, it will generate "yosemite_with_fades.mp4"
To avoid the name conflict, I just renamed it as "yosemite_with_fades2.mp4".
The concat2.py looks like this:
import os import sys import subprocess import psutil class mytool: def __init__(self): self.count = 0 self.copied_file = ['./in001.png', './in002.png'] self.slideshow_list_filename = 'mytext.txt' def get_iframes(self, video): #ffmpeg -i yosemiteA.mp4 -f image2 -vf "select='eq(pict_type,PICT_TYPE_I)'" -vsync vfr yi%03d.png cmd = ['ffmpeg','-i', video,'-f', 'image2','-vf', "select='eq(pict_type,PICT_TYPE_I)'",'-vsync','vfr','yi%03d.png'] subprocess.call(cmd) def add_fade_effect(self, infilename, outfilename = 'final'): # Makes two frames : at the beginning and at the end # This is done by copying one I-Frame for a slide # Then, adds fades at both ends for f in self.copied_file: cmd = map(lambda x: '%s' %x, ['cp', infilename, f]) subprocess.call(cmd) # make normal slide # ffmpeg -r 1/5 -i in%03d.png -c:v libx264 -r 30 -y -pix_fmt yuv420p slide.mp4 in_framerate = 1./5 out_framerate = 30 cmd = ['ffmpeg', '-r', in_framerate, '-i','in%03d.png','-c:v','libx264', '-r', out_framerate, '-y','-pix_fmt','yuv420p','slide.mp4'] cmd = map(lambda x: '%s' %x, cmd) subprocess.call(cmd) # add fade-in effect - from 0th to 30th frame #ffmpeg -i slide.mp4 -y -vf fade=in:0:30 slide_fade_in.mp4 cmd = ['ffmpeg', '-i','slide.mp4','-y','-vf','fade=in:0:30','slide_fade_in.mp4'] subprocess.call(cmd) # add fade-out effect to the slide that has fade-in effect already : 30 frames starting from 120th #ffmpeg -i slide_fade_in.mp4 -y -vf fade=out:120:30 slide_fade_in_out.mp4 cmd = ['ffmpeg', '-i','slide_fade_in.mp4','-y','-vf','fade=out:120:30', 'slide_fade_in_out.mp4'] subprocess.call(cmd) # rename the output to 'final#.mp4' slide_name = outfilename+str(self.count)+'.mp4' cmd = map(lambda x: '%s' %x, ['cp', 'slide_fade_in_out.mp4', slide_name]) subprocess.call(cmd) # remove the copied files for f in self.copied_file: cmd = map(lambda x: '%s' %x, ['rm','-f', f]) subprocess.call(cmd) self.count += 1 return slide_name # make concat list def make_slideshow_list(self, slides, fname='mylist.txt'): self.slideshow_list_filename = fname with open(self.slideshow_list_filename, mode='wb') as f: for slide in slides: f.write('file ' + slide +'\n') # concat all slides in the slideshow list def concat_slides(self, slideshow_name = 'my_slideshow.mp4' ): cmd = ['ffmpeg', '-y', '-f','concat','-i', self.slideshow_list_filename, '-c', 'copy', slideshow_name] subprocess.call(cmd) # get the list of file def file_list(self, d): basedir = d subdir = [] slides = [] sorted_slides = [] for item in os.listdir(d): fullpath = os.path.join(basedir, item) if os.path.isdir(fullpath): subdir.append(fullpath) else: if item.startswith('yi') and item.endswith('.png'): slides.append(fullpath) return sorted(slides) if __name__ == '__main__': m = mytool() if len(sys.argv) <= 1: path = '.' elif len(sys.argv) == 2: path = sys.argv[1] else: path = sys.argv[1] video_name = sys.argv[2] # extract i-frames m.get_iframes(video_name) # get list of slide files (i-frames) slides = m.file_list(path) fade_slides = [] outfile_name = 'yos' for slide in slides: s = m.add_fade_effect(slide, outfile_name) fade_slides.append(s) # make slideshow list file listname = 'slideshow_list.txt' m.make_slideshow_list(fade_slides, listname) # concatenate the slides in the list file slideshow_name = 'yosemite_with_fades.mp4' m.concat_slides(slideshow_name)
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization