├── removeduplicateframes.sh ├── cut_config_sample.md ├── autocut.sh ├── recordscreen.sh ├── README.md ├── cheatsheet-ffmpeg.md ├── combinevideos.sh └── combine.sh /removeduplicateframes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Reduces frame rate by dropping somewhat duplicate frames 4 | # Usage: removeduplicateframes $infile 5 | 6 | IN=$1 7 | 8 | #To get: filename without extenstion 9 | base=$(echo $IN | sed 's/\.[^.]*$//') 10 | #To get: file extension 11 | ext=$(echo $IN | sed 's@.*\.@@') 12 | newfilename=$(echo "decimated_${base}.${ext}") 13 | 14 | ffmpeg -i $IN -vf mpdecimate $newfilename 15 | 16 | # We could have usef following setpts; but in our case, it had put audio out of sync 17 | # ffmpeg -i $IN -vf decimate=cycle=6,setpts=N/25/TB $newfilename 18 | # REFERENCE: https://stackoverflow.com/a/52062421/7360184 19 | -------------------------------------------------------------------------------- /cut_config_sample.md: -------------------------------------------------------------------------------- 1 | > This is a template of how to note important info for reference and quick commands to cut them 2 | 3 | ## Day 1 day1.mkv 4 | 5 | ### Masterclass Branding personal brand for developer by Scott 6 | 7 | 2:27 Presentation by Scott starts 8 | 9 | 19:03-19:20 "Finite number of keystroke left in my hands" 10 | 11 | 27:48 Presentation by Scott ends 12 | 13 | 42:57 Q&A ends 14 | 15 | `ffmpeg -i day1.mkv -ss 02:27 -to 42:57 -c copy cuts/masterclass_scott.mkv` 16 | 17 | `ffmpeg -i day1.mkv -ss 19:03 -to 19:20 -c copy cuts/masterclass_scott_highlight_1.mkv` 18 | 19 | 20 | ### Masterclass : Enabling speed and confidence with Testing by Saarthak 21 | 22 | 59:01 Pradeep introduces talk and Saarthak 23 | 24 | 1:00:17 Saarthak starts the talk 25 | 26 | 1:49:52 Q&A ends 27 | 28 | `ffmpeg -i day1.mkv -ss 1:00:17 -to 1:49:52 -c copy cuts/masterclass_saarthak.mkv` 29 | 30 | 31 | -------------------------------------------------------------------------------- /autocut.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Cut videos using ffmpeg 4 | # Usage: autocut filename timestamp1 timestamp2 timestamp 3 ... (timestamp : hh:mm) 5 | 6 | echo "$# parameters found" 7 | 8 | IN=$1 9 | START="00:00" 10 | #Getting end of video by it's duration 11 | END=$(ffmpeg -i $IN 2>&1 | grep "Duration"| cut -d ' ' -f 4 | sed s/,//) 12 | count=0 13 | 14 | #To get: filename without extenstion 15 | base=$(echo $IN | sed 's/\.[^.]*$//') 16 | #To get: file extension 17 | ext=$(echo $IN | sed 's@.*\.@@') 18 | 19 | echo "base: $base ; ext: $ext" 20 | 21 | 22 | if [ $# -gt 1 ]; then 23 | echo "Going to make some good cuts. Hang on..." 24 | else 25 | echo "Enter in this format : autocut filename hh:mm hh:mm ..." 26 | exit 1 27 | fi 28 | 29 | makeTheCut(){ 30 | newfilename=$(echo "cut_${count}_${base}__${1}_${2}.${ext}") 31 | echo "Making #$count cut : $1-$2" 32 | echo "Creating new file : $newfilename" 33 | ffmpeg -i $IN -ss $1 -to $2 -c copy $newfilename 34 | } 35 | 36 | echo "Going to make some cuts in this $END long video" 37 | 38 | count=2 39 | lastcut=$START 40 | while [ $count -le $# ]; do 41 | makeTheCut $lastcut ${!count} 42 | lastcut=${!count} 43 | count=$[$count+1] 44 | done 45 | 46 | #one more cut for last clip till the end 47 | makeTheCut $lastcut $END 48 | -------------------------------------------------------------------------------- /recordscreen.sh: -------------------------------------------------------------------------------- 1 | #Script to record screen using ffmpeg 2 | 3 | #Get the folder path for videos 4 | videos_folder_path=$(xdg-user-dir VIDEOS) 5 | targetfolder=$(echo $videos_folder_path) 6 | 7 | #Create a consistent naming for new files 8 | newfilename=screenrecord_$(echo $(date '+%Y%m%d_%H%M_%S')) 9 | echo "Going to store the new recording at ${targetfolder}/${newfilename}.mp4" 10 | 11 | #Record full screen using ffmpeg(assuming screen size is 1920x1080) 12 | ffmpeg -video_size 1920x1080 -framerate 30 -f x11grab -i :0.0 -c:v libx264rgb -crf 0 -preset ultrafast ${targetfolder}/${newfilename}.mp4 13 | 14 | #FUTUREWORK- Currently recordWithMic command has some issues 15 | #recordWithMic(){ 16 | #with-sound 17 | # echo "Recording screen with sound..." 18 | # ffmpeg -video_size 1920x1080 -framerate 25 -f x11grab -i :0.0+100,200 -f pulse -ac 2 -i default ${targetfolder}/${newfilename}.mp4 19 | #} 20 | #recordSilent(){ 21 | #without-sound 22 | # echo "Recording screen without sound..." 23 | # ffmpeg -video_size 1920x1080 -framerate 30 -f x11grab -i :0.0 -c:v libx264rgb -crf 0 -preset ultrafast ${targetfolder}/${newfilename}.mp4 24 | #} 25 | 26 | #if [ $# -gt 0 ]; then 27 | # if [ "$1" = "withmic" ]; then 28 | # recordWithMic 29 | # else 30 | # recordSilent 31 | # fi 32 | #else 33 | # recordSilent 34 | #fi 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Programmable video editing 2 | ## Scripts, tools and resources to automate video editing 3 | 4 | ### Scripts 5 | 6 | 1. [autocut: Cut video at specified timestamps](/autocut.sh) `autocut infile timestamp1 timestamp2 ...` 7 | 2. [combine: Combine multiple videos into one - quite slow but it doesn't have audio/video sync issue](/combine.sh) `combine fileList.txt` 8 | 3. [removeduplicateframes: Reduce frame rate and size by removing duplicate frames](/removeduplicateframes.sh) `removeduplicateframes mybigvideofile.mp4` 9 | 10 | Additional scripts for advanced use 11 | 12 | 4. [combinevideos: Combine multiple videos into one (fast but works only when all files format, timescale and fps are same)](/combinevideos.sh) `combinevideos fileList.txt` 13 | 5. [recordscreen: Record screen using ffmpeg](/recordscreen.sh) `recordscreen` 14 | 15 | **How to use scripts on linux/ubuntu** 16 | 17 | 1. Download the scripts and go to your terminal 18 | 2. Open `~/.bashrc` with command `sudo gedit ~/.bashrc` (gedit is my favorite editor on linux but you can just use any editor to do what's coming next) 19 | 3. Append the content of the script (e.g. content of autocut script) to `~/.bashrc` inside a function as follows 20 | ``` 21 | autocut() { 22 | #Content of autocut script goes here 23 | } 24 | ``` 25 | 4. Run `source ~/.bashrc` 26 | 5. Now you will be able to run the command such as `autocut infilepath timestamp1 timestamp2` 27 | 28 | Now, get started with your main source video file and make the first cut by creating a text file such as [this one](./cut_config_sample.md). 29 | Use simple ffmpeg commands to pick one part, use `autocut` to make multiple cuts Use `combine` to combine selected cuts. In the end, use `removeduplicatefreames` to reduce the size of the final video. 30 | 31 | **A note for macOS users** 32 | 33 | Instructions to use it are the same as linux except the file `~/.bashrc` is usually not there, rather it is `~/.bash_profile` on macOS and the default text editor on macOS is TextEdit instead of gedit. Usually I prefer to use my favorite editors to edit the files and make sure everything is formatted well. 34 | -------------------------------------------------------------------------------- /cheatsheet-ffmpeg.md: -------------------------------------------------------------------------------- 1 | ## Useful ffmpeg commands for common video editing tasks 2 | 3 | ### Cutting video 4 | 5 | **From time 00:03 to 00:06 without reencoding(fast)** 6 | 7 | `ffmpeg -i infile.mp4 -ss 00:03 -to 00:06 -c copy outfile.mp4` 8 | 9 | 10 | ### Merging multiple videos in one file 11 | 12 | ``` 13 | ffmpeg -f concat -i filesToJoin.txt combined.mp4 14 | // filesToJoin.txt content 15 | // file infile1.mp4 16 | // file infile2.mp4 17 | 18 | ``` 19 | 20 | 21 | ### Converting file to another format 22 | 23 | **mov to mp4** 24 | 25 | `ffmpeg -i infile.mov outfile.mp4` 26 | 27 | 28 | **Convert video to GIF** 29 | 30 | The dirty way 31 | `ffmpeg -i infile.mp4 -vf "fps=10,scale=320:-2:flags=lanczos" outfile.gif` 32 | 33 | > GIF color issues might be there as gif has only 256 color pallette. To create better gif, [first create color pallette and use that to create gif](https://medium.com/abraia/basic-video-editing-for-social-media-with-ffmpeg-commands-1e873801659). 34 | 35 | 36 | ### Overlay 37 | 38 | **Overlay a logo** 39 | 40 | `ffmpeg -i video_clip.mp4 -i logo.png -filter_complex "overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2" video.mp4` 41 | 42 | 43 | ### Creating video preview 44 | 45 | **Using [ffmpeg-generate-video-preview](https://github.com/transitive-bullshit/ffmpeg-generate-video-preview) to create screenshots collage** 46 | 47 | `generate-video-preview infile.mp4 previewCollage.jpg --width 160 --rows 5 --cols 6 --padding 4 --margin 4` 48 | 49 | 50 | ### References 51 | 52 | 0. [Learn ffmpeg the hard way](https://github.com/leandromoreira/ffmpeg-libav-tutorial) 53 | 1. [ffmpeg for social media](https://medium.com/abraia/basic-video-editing-for-social-media-with-ffmpeg-commands-1e873801659) 54 | 2. [awesome-ffmpeg](https://github.com/transitive-bullshit/awesome-ffmpeg) 55 | 3. [editly : minimal command line video editing](https://github.com/mifi/editly) 56 | 4. [vidgear; video processing with access to multiple libraries e.g. opencv, ffmpeg](https://github.com/abhiTronix/vidgear) 57 | 5. [auto-motion](https://github.com/teamxenox/auto-motion) 58 | 6. [scripts](https://gitlab.com/dak425/scripts) by [donald feury](https://medium.com/@highlordazurai425/how-i-completely-automated-my-youtube-editing-5748c0e08b9d) 59 | 7. [some ffmpeg video editing commands](https://viveksb007.github.io/2017/12/ffmpeg-automate-filtering-and-editing-videos) 60 | 8. [seeking in ffmpeg](http://trac.ffmpeg.org/wiki/Seeking) 61 | 9. [concatenate videos in ffmpeg](https://trac.ffmpeg.org/wiki/Concatenate) 62 | 10. [deinterlacing algo in ffmpeg](https://ffmpeg.org/ffmpeg-filters.html#yadif-1) 63 | 11. [preset ffmpeg](http://ffmpeg.org/ffmpeg.html#Preset-files) 64 | 12. [.ass subtitles ffmpeg](http://ffmpeg.org/ffmpeg.html#ass) 65 | 13. [ffmpeg cheatsheet by steven2358](https://gist.github.com/steven2358/ba153c642fe2bb1e47485962df07c730) 66 | 14. [Adding subtitle to the video](https://video.stackexchange.com/a/14788/30092) 67 | -------------------------------------------------------------------------------- /combinevideos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Purpose of this code: Combine multiple videos to create one single video 4 | # Input : combinevideos file_containing_list_of_files.txt 5 | # Structure of file_containing_list_of_files.txt is as follows \/ 6 | # file myfile1.mp4 7 | # file myfile2.mp4 ... 8 | 9 | # Output: New file will be created with the name combined2files_myfile1_myfile2.mp4 10 | 11 | FILE_LIST=$1 12 | 13 | filescount=$(wc -l $FILE_LIST | awk '{ print $1 }') 14 | finalfilepath="combined${filescount}files" 15 | 16 | echo "Going to combine following videos as mentioned in ${FILE_LIST}..." 17 | 18 | function float_gt() { 19 | perl -e "{if($1>$2){print 1} else {print 0}}" 20 | } 21 | 22 | 23 | count=1 24 | largest_duration=0; 25 | longest_video=""; 26 | timebase_of_largest_video=0; 27 | filearray=() 28 | while IFS= read -r line 29 | do 30 | #process only lines which start with keyword 'file' 31 | keyword=$(echo $line | awk -F'[ ]' '{ print $1 }') 32 | if [ "$keyword" != "file" ] 33 | then 34 | #the line doesn't start with keyord file, ignore 35 | continue 36 | fi 37 | filepath=$(echo $line | awk -F'[ ]' '{ print $2 }') 38 | filearray+=("$filepath") 39 | base=$(echo $filepath | sed 's/\.[^.]*$//') 40 | timebase=$(ffprobe -loglevel warning -show_streams $filepath | awk '/^time_base/' | awk -F\= '{ "basename "$1"" |& getline $1; print $2;exit}'|awk -F\/ '{print $2}') 41 | duration=$(ffprobe -loglevel warning -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $filepath) 42 | echo "${count}: $filepath ; duration: $duration s; time_base: $timebase" 43 | count=$[$count+1] 44 | #Find the longest video and remeber it's timebase 45 | if [ $(float_gt $duration $largest_duration) == 1 ] 46 | then 47 | largest_duration=$duration 48 | timebase_of_largest_video=$timebase 49 | longest_video="$filepath" 50 | fi 51 | #finalfilepath generation: apending all file names except when filename exceeds 30 characters 52 | if [ ${#finalfilepath} -lt 30 ] 53 | then 54 | finalfilepath="${finalfilepath}_$base" 55 | fi 56 | done < "$FILE_LIST" 57 | 58 | finalfilepath="${finalfilepath}.mp4" 59 | 60 | echo "" 61 | echo -e "\e[1mLongest video is $longest_video\e[0m with duration: $largest_duration and \e[1mtimebase: $timebase_of_largest_video\e[0m" 62 | echo "" 63 | 64 | #Step 1 : Fix the time_base of all videos(why? detail in the references section of this script) 65 | newfilearray=() 66 | setTimeBase(){ 67 | timebase=$(ffprobe -loglevel warning -show_streams $1 | awk '/^time_base/' | awk -F\= '{ "basename "$1"" |& getline $1; print $2;exit}'|awk -F\/ '{print $2}') 68 | #if timebase is different than what we want to fix, create a temporary video with new timebase 69 | if [ $timebase -ne $2 ] 70 | then 71 | tmp_filename="tmp_${2}_$1" 72 | echo -e "\e[7mChanging timebase for file: $1 from $timebase to $2 tbn and saving new file at $tmp_filename\e[0m" 73 | ffmpeg -loglevel warning -i $1 -video_track_timescale $2 $tmp_filename 74 | newfilearray+=("$tmp_filename") 75 | else 76 | newfilearray+=("$1") 77 | fi 78 | } 79 | 80 | for i in ${!filearray[@]}; do 81 | setTimeBase ${filearray[$i]} $timebase_of_largest_video 82 | done 83 | 84 | #create a new text file to store all file names that we want to concat 85 | newfilelistwithsyncdtimebase="tmp_combine_file_list" 86 | 87 | for i in ${!newfilearray[@]}; do 88 | filepath="${newfilearray[$i]}" 89 | base=$(echo $filepath | sed 's/\.[^.]*$//') 90 | newfilelistwithsyncdtimebase="${newfilelistwithsyncdtimebase}_$base" 91 | done 92 | 93 | newfilelistwithsyncdtimebase="${newfilelistwithsyncdtimebase}.txt" 94 | 95 | if [ ! -f "$newfilelistwithsyncdtimebase" ] 96 | then 97 | echo "Creating file ${newfilelistwithsyncdtimebase}" 98 | touch $newfilelistwithsyncdtimebase 99 | else 100 | echo "File '${newfilelistwithsyncdtimebase}' already exists. Remove it and run this again to create a new combined video." 101 | exit 102 | fi 103 | 104 | for i in ${!newfilearray[@]}; do 105 | echo "file ${newfilearray[$i]}" >> $newfilelistwithsyncdtimebase 106 | done 107 | 108 | 109 | echo "" 110 | echo "Preparing output $finalfilepath" 111 | echo "" 112 | 113 | #Step 2: concatenate the files using the updated list of files 114 | ffmpeg -f concat -safe 0 -i $newfilelistwithsyncdtimebase -copytb 0 -c copy -loglevel warning $finalfilepath 115 | 116 | 117 | # References 118 | # -loglevel warning for less verbose command print 119 | # -c copy to skip encoding the videos again 120 | 121 | # ISSUE: DTS out of order https://stackoverflow.com/a/56002050/7360184 (unexpected output when encoding does not match) 122 | # To solve this issue here, we set all videos' timebase to be same as the longest video's timebase 123 | -------------------------------------------------------------------------------- /combine.sh: -------------------------------------------------------------------------------- 1 | # Combine multiple videos to create one single video(different than combinevideos - slow but corrects timestamp issues by converting to intermediary format Quicktime) 2 | # Input : combinevideos file_containing_list_of_files.txt 3 | # Structure of file_containing_list_of_files.txt is as follows \/ 4 | # file myfile1.mp4 5 | # file myfile2.mp4 ... 6 | # Output: New file will be created with the name combined2files_myfile1_myfile2.mp4 7 | 8 | ## A summary of how it works under the hood 9 | ## 1. Converts videos to intermediary format(Quicktime container MTS) 10 | ## ffmpeg -i clip.flv -q 0 clip.MTS 11 | ## ffmpeg -i intro.flv -q 0 intro.MTS 12 | ## ffmpeg -i outro.flv -q 0 outro.MTS 13 | ## 2. Combines these videos 14 | ## //filesToJoin.tx : file intro.MTS \n file clip.MTS \n file outro.MTS 15 | ## ffmpeg -f concat -i filesToJoin.txt -c copy output.MTS 16 | ## 3. ffmpeg -f concat -i filesToJoin.txt -c copy output.MTS 17 | ## 4. Convert format of output.MTS to .mp4 18 | ## ffmpeg -i output.MTS -q 0 intro.mp4 19 | 20 | FILE_LIST=$1 21 | filescount=$(wc -l $FILE_LIST | awk '{ print $1 }') 22 | finalfilepath="combined${filescount}files" 23 | echo "Going to combine following videos as mentioned in ${FILE_LIST}..." 24 | function float_gt() { 25 | perl -e "{if($1>$2){print 1} else {print 0}}" 26 | } 27 | count=1 28 | largest_duration=0; 29 | longest_video=""; 30 | timescale_of_largest_video=0; 31 | filearray=() 32 | while IFS= read -r line 33 | do 34 | #process only lines which start with keyword 'file' 35 | keyword=$(echo $line | awk -F'[ ]' '{ print $1 }') 36 | if [ "$keyword" != "file" ] 37 | then 38 | #the line doesn't start with keyord file, ignore 39 | continue 40 | fi 41 | filepath=$(echo $line | awk -F'[ ]' '{ print $2 }') 42 | filearray+=("$filepath") 43 | base=$(echo $filepath | sed 's/\.[^.]*$//') 44 | timescale=$(ffprobe -loglevel warning -show_streams $filepath | awk '/^time_base/' | awk -F\= '{ "basename "$1"" |& getline $1; print $2;exit}'|awk -F\/ '{print $2}') 45 | duration=$(ffprobe -loglevel warning -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $filepath) 46 | echo "${count}: $filepath ; duration: $duration s; time_base: $timescale" 47 | count=$[$count+1] 48 | #Find the longest video and remeber it's timescale 49 | if [ $(float_gt $duration $largest_duration) == 1 ] 50 | then 51 | largest_duration=$duration 52 | timescale_of_largest_video=$timescale 53 | longest_video="$filepath" 54 | fi 55 | #finalfilepath generation: apending all file names except when filename exceeds 30 characters 56 | if [ ${#finalfilepath} -lt 30 ] 57 | then 58 | finalfilepath="${finalfilepath}_$base" 59 | fi 60 | done < "$FILE_LIST" 61 | finalmtsfilepath="${finalfilepath}.MTS" 62 | finalfilepath="${finalfilepath}.mp4" 63 | echo "" 64 | echo -e "\e[1mLongest video is $longest_video\e[0m with duration: $largest_duration and \e[1mtimescale: $timescale_of_largest_video\e[0m" 65 | echo "" 66 | mtsfilearray=() 67 | convertToMTS(){ 68 | base=$(echo $1 | sed 's/\.[^.]*$//') 69 | mtsfilepath="tmp_$base.MTS" 70 | mtsfilearray+=("$mtsfilepath") 71 | ffmpeg -i $1 -q 0 $mtsfilepath 72 | } 73 | for i in ${!filearray[@]}; do 74 | # settimescale ${filearray[$i]} $timescale_of_largest_video 75 | convertToMTS ${filearray[$i]} 76 | done 77 | #create a new text file to store all file names that we want to concat 78 | newfilelistwithsyncdtimescale="tmp_combine_file_list" 79 | for i in ${!mtsfilearray[@]}; do 80 | echo "New MTS File : ${mtsfilearray[$i]}" 81 | filepath="${mtsfilearray[$i]}" 82 | base=$(echo $filepath | sed 's/\.[^.]*$//') 83 | newfilelistwithsyncdtimescale="${newfilelistwithsyncdtimescale}_$base" 84 | done 85 | newfilelistwithsyncdtimescale="${newfilelistwithsyncdtimescale}.txt" 86 | if [ ! -f "$newfilelistwithsyncdtimescale" ] 87 | then 88 | echo "Creating file ${newfilelistwithsyncdtimescale}" 89 | touch $newfilelistwithsyncdtimescale 90 | else 91 | echo "File '${newfilelistwithsyncdtimescale}' already exists" 92 | echo -n "Do you want to proceed further (y/n)? " 93 | read answer 94 | if [ "$answer" != "${answer#[Yy]}" ] ;then 95 | > "$newfilelistwithsyncdtimescale" 96 | else 97 | echo "Quitting..." 98 | exit 99 | fi 100 | fi 101 | #for i in ${!newfilearray[@]}; do 102 | # echo "file ${newfilearray[$i]}" >> $newfilelistwithsyncdtimescale 103 | #done 104 | 105 | for i in ${!mtsfilearray[@]}; do 106 | echo "file ${mtsfilearray[$i]}" >> $newfilelistwithsyncdtimescale 107 | done 108 | while IFS= read -r line 109 | do 110 | echo "$line" 111 | done < "$newfilelistwithsyncdtimescale" 112 | echo "" 113 | echo "Preparing output $finalmtsfilepath" 114 | echo "" 115 | # Now that all the files have commmon encoding 116 | #Step 2: concatenate the files using the updated list of files 117 | ffmpeg -f concat -i $newfilelistwithsyncdtimescale -c copy $finalmtsfilepath 118 | # -copyts can be used to not remove the initial start time offset value 119 | echo "" 120 | echo "Preparing output $finalfilepath" 121 | echo "" 122 | #Step 3: Convert intermediary format to .mp4 format 123 | ffmpeg -i $finalmtsfilepath -q 0 $finalfilepath 124 | #Step 4: Remove all temporary files 125 | for i in ${!mtsfilearray[@]}; do 126 | echo -e "\e[31mX Deleting ${mtsfilearray[$i]}\e[0m" 127 | rm "${mtsfilearray[$i]}" 128 | done 129 | echo -e "\e[31mX Deleting $newfilelistwithsyncdtimescale ...\e[0m" 130 | rm "$newfilelistwithsyncdtimescale" 131 | echo -e "\e[31mX Deleting $finalmtsfilepath ...\e[0m" 132 | rm "$finalmtsfilepath" 133 | --------------------------------------------------------------------------------