├── Makefiles └── scene-detection │ └── Makefile ├── LICENSE.txt ├── loudnorm ├── ebu-meter ├── img2video ├── chapter-add ├── sexagesimal-time ├── waveform ├── chapter-extract ├── scene-cut-to ├── subtitle-add ├── chapter-csv ├── vid2gif ├── overlay-clip ├── webp ├── scene-images ├── scene-cut ├── extract-frame ├── pan-scan ├── scene-time ├── clip-time ├── scopes ├── audio-silence ├── xfade ├── fade-clip ├── fade-normalize ├── correct-clip ├── normalize ├── scene-detect ├── overlay-pip ├── crossfade-clips ├── tile-thumbnails ├── zoompan ├── combine-clips ├── ffmpeg-tips.org ├── trim-clip-to ├── trim-clip ├── fade-title └── README.org /Makefiles/scene-detection/Makefile: -------------------------------------------------------------------------------- 1 | # ffmpeg scene detection makefile 2 | 3 | # Move this Makefile into the same directory as the video to process 4 | 5 | # you need to tell the Makefile the name of the video to process 6 | # either by editing the Makefile and changing input.mp4 to the name of the video to process 7 | # and then running: make 8 | 9 | # or by running: make input=filename.mp4 10 | # where filename.mp4 is the name of the file to process 11 | 12 | # make clean: will prompt you to remove text files and video clips, except the input file 13 | 14 | 15 | # change input.mp4 to the file name to process 16 | input := input.mp4 17 | 18 | # all - run the cut recipe which runs scene-detect, scene-time and scene-cut in order 19 | all: cut 20 | 21 | # scene-detect - input is the prerequisite 22 | detect: $(input) 23 | @scene-detect -i $(input) -o detection.txt 24 | 25 | 26 | # scene-time - detect is the prerequisite 27 | time: detect 28 | @scene-time -i detection.txt -o cutlist.txt 29 | 30 | 31 | # scene-cut - time is the prerequisite 32 | cut: time 33 | @scene-cut -i $(input) -c cutlist.txt 34 | 35 | 36 | # make clean - prompt to remove text files and video clips, except the input file 37 | .PHONY: clean 38 | clean: 39 | rm -fI cutlist.txt detection.txt $(wildcard *-[0-9]*.mp4) 40 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) [year], [fullname] 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /loudnorm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # loudnorm 5 | # ffmpeg loudnorm 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # ffmpeg loudnorm 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3)" 24 | exit 2 25 | } 26 | 27 | 28 | #=============================================================================== 29 | # error messages 30 | #=============================================================================== 31 | 32 | INVALID_OPT_ERR='Invalid option:' 33 | REQ_ARG_ERR='requires an argument' 34 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 35 | 36 | 37 | #=============================================================================== 38 | # check the number of arguments passed to the script 39 | #=============================================================================== 40 | 41 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 42 | 43 | 44 | #=============================================================================== 45 | # getopts check the options passed to the script 46 | #=============================================================================== 47 | 48 | while getopts ':i:h' opt 49 | do 50 | case ${opt} in 51 | i) infile="${OPTARG}";; 52 | h) usage;; 53 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 54 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 55 | esac 56 | done 57 | shift $((OPTIND-1)) 58 | 59 | 60 | #=============================================================================== 61 | # functions 62 | #=============================================================================== 63 | 64 | # ffmpeg loudnorm get stats from file 65 | normalize () { 66 | ffmpeg \ 67 | -hide_banner \ 68 | -i "${infile}" \ 69 | -af "loudnorm=I=-16:dual_mono=true:TP=-1.5:LRA=11:print_format=summary" \ 70 | -f null - 71 | } 72 | 73 | # run the normalize function 74 | normalize "${infile}" 75 | -------------------------------------------------------------------------------- /ebu-meter: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # ebu-meter 5 | # ffplay ebu meter 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffplay 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | [ -z "${1}" ] || echo "! ${1}" 18 | echo "\ 19 | # ffplay ebu meter 20 | 21 | $(basename "$0") -i input.(mp4|mkv|mov|m4v|webm|aac|m4a|wav|mp3) -t (00)" 22 | exit 2 23 | } 24 | 25 | 26 | #=============================================================================== 27 | # error messages 28 | #=============================================================================== 29 | 30 | INVALID_OPT_ERR='Invalid option:' 31 | REQ_ARG_ERR='requires an argument' 32 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 33 | 34 | 35 | #=============================================================================== 36 | # check the number of arguments passed to the script 37 | #=============================================================================== 38 | 39 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 40 | 41 | 42 | #=============================================================================== 43 | # getopts check the options passed to the script 44 | #=============================================================================== 45 | 46 | # getopts check and validate options 47 | while getopts ':i:t:h' opt 48 | do 49 | case ${opt} in 50 | i) infile="${OPTARG}";; 51 | t) target="${OPTARG}";; 52 | h) usage;; 53 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 54 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 55 | esac 56 | done 57 | shift $((OPTIND-1)) 58 | 59 | 60 | #=============================================================================== 61 | # variables 62 | #=============================================================================== 63 | 64 | # default target level 65 | target_default='16' 66 | 67 | 68 | #=============================================================================== 69 | # functions 70 | #=============================================================================== 71 | 72 | # ebu function 73 | ebu () { 74 | ffplay -hide_banner \ 75 | -f lavfi -i \ 76 | "amovie=${infile}, 77 | ebur128=video=1: 78 | meter=18: 79 | dualmono=true: 80 | target=-${target:=${target_default}}: 81 | size=1280x720 [out0][out1]" 82 | } 83 | 84 | # run the ebu function 85 | ebu "${infile}" 86 | -------------------------------------------------------------------------------- /img2video: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # img2video 5 | # image to video 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | echo "\ 18 | # image to video 19 | 20 | $(basename "$0") -i input.(png|jpg|jpeg) -d (000) -o output.mp4 21 | -i input.(mp4|mkv|mov|m4v) 22 | -d (000) : duration 23 | -o output.mp4 : optional argument # if option not provided defaults to input-name-video-date-time" 24 | exit 2 25 | } 26 | 27 | 28 | #=============================================================================== 29 | # error messages 30 | #=============================================================================== 31 | 32 | INVALID_OPT_ERR='Invalid option:' 33 | REQ_ARG_ERR='requires an argument' 34 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 35 | 36 | 37 | #=============================================================================== 38 | # check the number of arguments passed to the script 39 | #=============================================================================== 40 | 41 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 42 | 43 | 44 | #=============================================================================== 45 | # getopts check the options passed to the script 46 | #=============================================================================== 47 | 48 | while getopts ':i:d:o:h' opt 49 | do 50 | case ${opt} in 51 | i) infile="${OPTARG}";; 52 | d) dur="${OPTARG}";; 53 | o) outfile="${OPTARG}";; 54 | h) usage;; 55 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 56 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 57 | esac 58 | done 59 | shift $((OPTIND-1)) 60 | 61 | 62 | #=============================================================================== 63 | # variables 64 | #=============================================================================== 65 | 66 | # input, input name and extension 67 | infile_nopath="${infile##*/}" 68 | infile_name="${infile_nopath%.*}" 69 | 70 | # output file recording destination 71 | outfile_default="${infile_name}-video-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 72 | 73 | 74 | #=============================================================================== 75 | # functions 76 | #=============================================================================== 77 | 78 | # image to video function 79 | imgtovid () { 80 | ffmpeg \ 81 | -hide_banner \ 82 | -stats -v panic \ 83 | -framerate 1/"${dur}" \ 84 | -i "${infile}" \ 85 | -c:v libx264 -crf 18 -profile:v high \ 86 | -r 30 -pix_fmt yuv420p \ 87 | -movflags +faststart -f mp4 \ 88 | "${outfile:=${outfile_default}}" 89 | } 90 | 91 | # run the imgtovid function 92 | imgtovid "${infile}" 93 | -------------------------------------------------------------------------------- /chapter-add: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # chapter-add 5 | # add chapters to a video or audio file with ffmpeg 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | add chapters to a video or audio file with ffmpeg 21 | $(basename "$0") -i input -c metadata.txt -o output" 22 | exit 2 23 | } 24 | 25 | 26 | #=============================================================================== 27 | # error messages 28 | #=============================================================================== 29 | 30 | INVALID_OPT_ERR='Invalid option:' 31 | REQ_ARG_ERR='requires an argument' 32 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 33 | 34 | 35 | #=============================================================================== 36 | # check the number of arguments passed to the script 37 | #=============================================================================== 38 | 39 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 40 | 41 | 42 | #=============================================================================== 43 | # getopts check the options passed to the script 44 | #=============================================================================== 45 | 46 | while getopts ':i:c:o:h' opt 47 | do 48 | case ${opt} in 49 | i) input="${OPTARG}";; 50 | c) metadata="${OPTARG}";; 51 | h) usage;; 52 | o) output="${OPTARG}";; 53 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 54 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 55 | esac 56 | done 57 | shift $((OPTIND-1)) 58 | 59 | 60 | #=============================================================================== 61 | # variables 62 | #=============================================================================== 63 | 64 | # get the input file name 65 | input_nopath="${input##*/}" 66 | input_name="${input_nopath%.*}" 67 | 68 | # input file extension 69 | input_ext="${input##*.}" 70 | 71 | # output file name 72 | output_default="${input_name}-metadata.${input_ext}" 73 | 74 | 75 | #=============================================================================== 76 | # sexagesimal function 77 | #=============================================================================== 78 | 79 | meta () { 80 | ffmpeg \ 81 | -hide_banner \ 82 | -stats -v panic \ 83 | -i "${input}" \ 84 | -i "${metadata}" -map_metadata 1 -c copy \ 85 | "${output:=${output_default}}" 86 | } 87 | 88 | 89 | #=============================================================================== 90 | # run function 91 | #=============================================================================== 92 | 93 | meta 94 | -------------------------------------------------------------------------------- /sexagesimal-time: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # sexagesimal-time 5 | # calculate sexagesimal duration by subtacting end time from start time 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # awk 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | calculate sexagesimal duration by subtacting end time from start time 21 | $(basename "$0") -s 00:00:00 -e 00:00:00" 22 | exit 2 23 | } 24 | 25 | 26 | #=============================================================================== 27 | # error messages 28 | #=============================================================================== 29 | 30 | INVALID_OPT_ERR='Invalid option:' 31 | REQ_ARG_ERR='requires an argument' 32 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 33 | 34 | 35 | #=============================================================================== 36 | # check the number of arguments passed to the script 37 | #=============================================================================== 38 | 39 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 40 | 41 | 42 | #=============================================================================== 43 | # getopts check the options passed to the script 44 | #=============================================================================== 45 | 46 | while getopts ':s:e:h' opt 47 | do 48 | case ${opt} in 49 | s) start="${OPTARG}";; 50 | h) usage;; 51 | e) end="${OPTARG}";; 52 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 53 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 54 | esac 55 | done 56 | shift $((OPTIND-1)) 57 | 58 | 59 | #=============================================================================== 60 | # function 61 | #=============================================================================== 62 | 63 | sexagesimal () { 64 | printf "%s %s\n" "${start}" "${end}" \ 65 | | awk ' 66 | { 67 | start = $1 68 | end = $2 69 | if (start ~ /:/) { 70 | split(start, t, ":") 71 | sseconds = (t[1] * 3600) + (t[2] * 60) + t[3] 72 | } 73 | if (end ~ /:/) { 74 | split(end, t, ":") 75 | eseconds = (t[1] * 3600) + (t[2] * 60) + t[3] 76 | } 77 | duration = eseconds - sseconds 78 | printf("%s\n"), duration 79 | }' \ 80 | | awk -F. 'NF==1 { printf("%02d:%02d:%02d\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60) }\ 81 | NF==2 { printf("%02d:%02d:%02d.%s\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60), ($2) }' 82 | } 83 | 84 | 85 | #=============================================================================== 86 | # run function 87 | #=============================================================================== 88 | 89 | sexagesimal 90 | -------------------------------------------------------------------------------- /waveform: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # waveform 5 | # create a waveform from an audio or video file and save as a png 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # create a waveform from an audio or video file and save as a png 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v|webm|aac|m4a|wav|mp3) -o output.png 24 | -i output.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) 25 | -o output.png : optional argument # if option not provided defaults to input-name-waveform-date-time" 26 | exit 2 27 | } 28 | 29 | 30 | #=============================================================================== 31 | # error messages 32 | #=============================================================================== 33 | 34 | INVALID_OPT_ERR='Invalid option:' 35 | REQ_ARG_ERR='requires an argument' 36 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 37 | 38 | 39 | #=============================================================================== 40 | # check the number of arguments passed to the script 41 | #=============================================================================== 42 | 43 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 44 | 45 | 46 | #=============================================================================== 47 | # getopts check the options passed to the script 48 | #=============================================================================== 49 | 50 | while getopts ':i:o:h' opt 51 | do 52 | case ${opt} in 53 | i) infile="${OPTARG}";; 54 | o) outfile="${OPTARG}";; 55 | h) usage;; 56 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 57 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 58 | esac 59 | done 60 | shift $((OPTIND-1)) 61 | 62 | 63 | #=============================================================================== 64 | # variables 65 | #=============================================================================== 66 | 67 | # variables 68 | infile_nopath="${infile##*/}" 69 | infile_name="${infile_nopath%.*}" 70 | 71 | # defaults for variables if not defined 72 | outfile_default="${infile_name}-waveform-$(date +"%Y-%m-%d-%H-%M-%S").png" 73 | 74 | 75 | #=============================================================================== 76 | # functions 77 | #=============================================================================== 78 | 79 | # waveform function 80 | wform () { 81 | ffmpeg \ 82 | -hide_banner \ 83 | -stats -v panic \ 84 | -i "${infile}" \ 85 | -filter_complex "aformat=channel_layouts=mono,showwavespic=s=1000x200" \ 86 | -frames:v 1 \ 87 | -f apng \ 88 | "${outfile:=${outfile_default}}" 89 | } 90 | 91 | # run the wform function 92 | wform "${infile}" 93 | -------------------------------------------------------------------------------- /chapter-extract: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # chapter-extract 5 | # extract chapters from a video or audo file and save as a csv file 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffprobe awk 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | extract chapters from a video or audo file and save as a csv file 21 | $(basename "$0") -i input -o output" 22 | exit 2 23 | } 24 | 25 | 26 | #=============================================================================== 27 | # error messages 28 | #=============================================================================== 29 | 30 | INVALID_OPT_ERR='Invalid option:' 31 | REQ_ARG_ERR='requires an argument' 32 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 33 | 34 | 35 | #=============================================================================== 36 | # check the number of arguments passed to the script 37 | #=============================================================================== 38 | 39 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 40 | 41 | 42 | #=============================================================================== 43 | # getopts check the options passed to the script 44 | #=============================================================================== 45 | 46 | while getopts ':i:o:h' opt 47 | do 48 | case ${opt} in 49 | i) input="${OPTARG}";; 50 | h) usage;; 51 | o) output="${OPTARG}";; 52 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 53 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 54 | esac 55 | done 56 | shift $((OPTIND-1)) 57 | 58 | 59 | #=============================================================================== 60 | # variables 61 | #=============================================================================== 62 | 63 | # get the input file name 64 | input_nopath="${input##*/}" 65 | input_name="${input_nopath%.*}" 66 | 67 | # output file name 68 | output_default="${input_name}-chapters.txt" 69 | 70 | 71 | #=============================================================================== 72 | # sexagesimal function 73 | #=============================================================================== 74 | 75 | extract () { 76 | ffprobe -v panic -hide_banner \ 77 | -show_chapters -sexagesimal -of csv \ 78 | -i "${input}" \ 79 | | awk -F "," '{ 80 | time = $5 81 | chapter = $NF 82 | if (time ~ /:/) { 83 | split(time, t, ".") 84 | sexagesimal = t[1] 85 | } 86 | printf("%s,%s\n"), sexagesimal, chapter 87 | }' > "${output:=${output_default}}" 88 | } 89 | 90 | 91 | #=============================================================================== 92 | # run function 93 | #=============================================================================== 94 | 95 | extract 96 | -------------------------------------------------------------------------------- /scene-cut-to: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # scene-cut-to 5 | # ffmpeg scene cut to position 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | $(basename "$0") -i input -c cutfile 21 | 22 | -i input.(mp4|mov|mkv|m4v) 23 | -c cutfile" 24 | exit 2 25 | } 26 | 27 | 28 | #=============================================================================== 29 | # error messages 30 | #=============================================================================== 31 | 32 | INVALID_OPT_ERR='Invalid option:' 33 | REQ_ARG_ERR='requires an argument' 34 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 35 | 36 | #=============================================================================== 37 | # check the number of arguments passed to the script 38 | #=============================================================================== 39 | 40 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 41 | 42 | 43 | #=============================================================================== 44 | # getopts check the options passed to the script 45 | #=============================================================================== 46 | 47 | while getopts ':i:c:h' opt 48 | do 49 | case ${opt} in 50 | i) input="${OPTARG}";; 51 | c) cutfile="${OPTARG}";; 52 | h) usage;; 53 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 54 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 55 | esac 56 | done 57 | shift $((OPTIND-1)) 58 | 59 | 60 | #=============================================================================== 61 | # variables 62 | #=============================================================================== 63 | 64 | # get the input file name 65 | input_nopath="${input##*/}" 66 | input_name="${input_nopath%.*}" 67 | 68 | #=============================================================================== 69 | # ffmpeg create clips - nostdin needed to avoid clash with read command 70 | #=============================================================================== 71 | 72 | trim_video () { 73 | output="${input_name}-[${start}-${end}].mp4" 74 | ffmpeg \ 75 | -nostdin \ 76 | -hide_banner \ 77 | -stats -v panic \ 78 | -ss "${start}" \ 79 | -to "${end}" \ 80 | -i "${input}" \ 81 | -c:a aac \ 82 | -c:v libx264 -profile:v high \ 83 | -pix_fmt yuv420p -movflags +faststart \ 84 | -f mp4 \ 85 | "${output}" 86 | } 87 | 88 | 89 | #=============================================================================== 90 | # read file and set IFS=, read = input before , end = input after , 91 | #=============================================================================== 92 | 93 | count=1 94 | while IFS=, read -r start end; do 95 | trim_video 96 | done < "${cutfile}" 97 | -------------------------------------------------------------------------------- /subtitle-add: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # subtitle-add 5 | # add subtitles to a video 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # add subtitles to a video 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v) -s subtitle.(srt|vtt) -o output.mp4 24 | -i input.(mp4|mkv|mov|m4v) 25 | -s subtitle.(srt|vtt) 26 | -o output.mp4 : optional argument # if option not provided defaults to input-name-subs-date-time" 27 | exit 2 28 | } 29 | 30 | 31 | #=============================================================================== 32 | # error messages 33 | #=============================================================================== 34 | 35 | INVALID_OPT_ERR='Invalid option:' 36 | REQ_ARG_ERR='requires an argument' 37 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 38 | 39 | 40 | #=============================================================================== 41 | # check the number of arguments passed to the script 42 | #=============================================================================== 43 | 44 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 45 | 46 | 47 | #=============================================================================== 48 | # getopts check the options passed to the script 49 | #=============================================================================== 50 | 51 | while getopts ':i:s:o:h' opt 52 | do 53 | case ${opt} in 54 | i) video="${OPTARG}";; 55 | s) subs="${OPTARG}";; 56 | o) outfile="${OPTARG}";; 57 | h) usage;; 58 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 59 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 60 | esac 61 | done 62 | shift $((OPTIND-1)) 63 | 64 | 65 | #=============================================================================== 66 | # variables 67 | #=============================================================================== 68 | 69 | # input, input name and extension 70 | video_nopath="${video##*/}" 71 | video_name="${video_nopath%.*}" 72 | 73 | # defaults for variables if not defined 74 | outfile_default="${video_name}-subs-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 75 | 76 | 77 | #=============================================================================== 78 | # functions 79 | #=============================================================================== 80 | 81 | # audio is aac, copy audio stream 82 | addsubs () { 83 | ffmpeg \ 84 | -hide_banner \ 85 | -stats -v panic \ 86 | -i "${video}" \ 87 | -f srt \ 88 | -i "${subs}" \ 89 | -c:a copy \ 90 | -c:v copy \ 91 | -c:s mov_text -metadata:s:s:0 \ 92 | language=eng \ 93 | -movflags +faststart \ 94 | -f mp4 \ 95 | "${outfile:=${outfile_default}}" 96 | } 97 | 98 | # run the addsubs function 99 | addsubs "$video" "$subs" 100 | -------------------------------------------------------------------------------- /chapter-csv: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # chapter-csv 5 | # convert a csv file into a chapter metadata file for ffmpeg 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # awk 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | convert a csv file into a chapter metadata file for ffmpeg 21 | $(basename "$0") -i input -o output" 22 | exit 2 23 | } 24 | 25 | 26 | #=============================================================================== 27 | # error messages 28 | #=============================================================================== 29 | 30 | INVALID_OPT_ERR='Invalid option:' 31 | REQ_ARG_ERR='requires an argument' 32 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 33 | 34 | 35 | #=============================================================================== 36 | # check the number of arguments passed to the script 37 | #=============================================================================== 38 | 39 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 40 | 41 | 42 | #=============================================================================== 43 | # getopts check the options passed to the script 44 | #=============================================================================== 45 | 46 | while getopts ':i:o:h' opt 47 | do 48 | case ${opt} in 49 | i) input="${OPTARG}";; 50 | h) usage;; 51 | o) output="${OPTARG}";; 52 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 53 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 54 | esac 55 | done 56 | shift $((OPTIND-1)) 57 | 58 | 59 | #=============================================================================== 60 | # variables 61 | #=============================================================================== 62 | 63 | # get the input file name 64 | input_nopath="${input##*/}" 65 | input_name="${input_nopath%.*}" 66 | 67 | # output file name 68 | output_default="${input_name}-metadata.txt" 69 | 70 | 71 | #=============================================================================== 72 | # sexagesimal function 73 | #=============================================================================== 74 | 75 | sexagesimal () { 76 | total="$(awk 'END { print NR }' < "${input}")" 77 | awk -F "," ' 78 | { 79 | time = $1 80 | chapter = $2 81 | if (time ~ /:/) { 82 | split(time, t, ":") 83 | combined = (t[1] * 3600) + (t[2] * 60) + t[3] 84 | milliseconds = (combined * 1000) 85 | } 86 | printf("%d,%s\n"), milliseconds, chapter 87 | }' < "${input}" \ 88 | | awk -v records="$total" -F "," ' \ 89 | { if (FNR < records) end = $1-1; else end = $1 } 90 | NR == 1 {print ";FFMETADATA1"} NR > 1 {printf("%s\n%s\n%s%s\n%s%s\n%s%s\n"), "[CHAPTER]", "TIMEBASE=1/1000", "START=", prev, "END=", end, "title=", chapter}; {prev = $1; chapter = $2}' > "${output:=${output_default}}" 91 | } 92 | 93 | 94 | #=============================================================================== 95 | # run function 96 | #=============================================================================== 97 | 98 | sexagesimal 99 | -------------------------------------------------------------------------------- /vid2gif: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # vid2gif 5 | # convert a video into a gif animation 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe cut 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # convert a video into a gif animation 22 | 23 | $(basename "$0") -s 00:00:00.000 -i input.(mp4|mov|mkv|m4v) -t 00:00:00.000 -f [00] -w [0000] -o output.gif 24 | -s 00:00:00.000 : start time 25 | -i input.(mp4|mov|mkv|m4v) 26 | -t 00:00:00.000 : number of seconds after start time 27 | -f [00] : framerate 28 | -w [0000] : width 29 | -o output.gif : optional argument 30 | # if option not provided defaults input-name-gif-date-time.gif" 31 | exit 2 32 | } 33 | 34 | 35 | #=============================================================================== 36 | # error messages 37 | #=============================================================================== 38 | 39 | INVALID_OPT_ERR='Invalid option:' 40 | REQ_ARG_ERR='requires an argument' 41 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 42 | 43 | 44 | #=============================================================================== 45 | # check the number of arguments passed to the script 46 | #=============================================================================== 47 | 48 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 49 | 50 | 51 | #=============================================================================== 52 | # getopts check the options passed to the script 53 | #=============================================================================== 54 | 55 | while getopts ':s:i:t:f:w:o:h' opt 56 | do 57 | case ${opt} in 58 | s) start="${OPTARG}";; 59 | i) infile="${OPTARG}";; 60 | t) end="${OPTARG}";; 61 | f) framerate="${OPTARG}";; 62 | w) width="${OPTARG}";; 63 | h) usage;; 64 | o) outfile="${OPTARG}";; 65 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 66 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 67 | esac 68 | done 69 | shift $((OPTIND-1)) 70 | 71 | 72 | #=============================================================================== 73 | # variables 74 | #=============================================================================== 75 | 76 | # input name 77 | infile_nopath="${infile##*/}" 78 | infile_name="${infile_nopath%.*}" 79 | 80 | # defaults for variables if not defined 81 | start_default='00:00:00' 82 | end_default=$(ffprobe -v error -sexagesimal -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$infile" | cut -d\. -f1) 83 | framerate_default='15' 84 | width_default='320' 85 | outfile_default="${infile_name}-gif-$(date +"%Y-%m-%d-%H-%M-%S").gif" 86 | 87 | 88 | #=============================================================================== 89 | # functions 90 | #=============================================================================== 91 | 92 | # create gif function 93 | create_gif () { 94 | ffmpeg \ 95 | -hide_banner \ 96 | -stats -v panic \ 97 | -ss "${start:=${start_default}}" \ 98 | -i "${infile}" \ 99 | -t "${end:=${end_default}}" \ 100 | -filter_complex "[0:v] fps=${framerate:=${framerate_default}},scale=${width:=${width_default}}:-1:flags=lanczos,split [a][b];[a] palettegen [p];[b][p] paletteuse" \ 101 | "${outfile:=${outfile_default}}" 102 | } 103 | 104 | # run the create_gif function 105 | create_gif "${infile}" 106 | -------------------------------------------------------------------------------- /overlay-clip: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # overlay-clip 5 | # overlay one video clip on top of another video clip 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe cut 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # overlay one video clip on top of another video clip 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v) -v input.(mp4|mkv|mov|m4v) -p [0-999] -o output.mp4 24 | -i input.(mp4|mkv|mov|m4v) : bottom video 25 | -v input.(mp4|mkv|mov|m4v) : overlay video 26 | -p [0-999] : time to overlay the video 27 | -o output.mp4 : optional argument # if option not provided defaults to input-name-overlay-date-time" 28 | exit 2 29 | } 30 | 31 | 32 | #=============================================================================== 33 | # error messages 34 | #=============================================================================== 35 | 36 | INVALID_OPT_ERR='Invalid option:' 37 | REQ_ARG_ERR='requires an argument' 38 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 39 | 40 | 41 | #=============================================================================== 42 | # check number of arguments passed to the script 43 | #=============================================================================== 44 | 45 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 46 | 47 | 48 | #=============================================================================== 49 | # getopts check the options passed to the script 50 | #=============================================================================== 51 | 52 | while getopts ':i:v:p:o:h' opt 53 | do 54 | case ${opt} in 55 | i) video="${OPTARG}";; 56 | v) overlay="${OPTARG}";; 57 | p) position="${OPTARG}";; 58 | o) outfile="${OPTARG}";; 59 | h) usage;; 60 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 61 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 62 | esac 63 | done 64 | shift $((OPTIND-1)) 65 | 66 | 67 | #=============================================================================== 68 | # variables 69 | #=============================================================================== 70 | 71 | # video name extension and overlay video extensions 72 | video_nopath="${video##*/}" 73 | video_name="${video_nopath%.*}" 74 | 75 | # defaults for variables if not defined 76 | outfile_default="${video_name}-overlay-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 77 | 78 | 79 | #=============================================================================== 80 | # functions 81 | #=============================================================================== 82 | 83 | # overlay video function 84 | overlay_video () { 85 | ffmpeg \ 86 | -hide_banner \ 87 | -stats -v panic \ 88 | -i "${video}" \ 89 | -i "${overlay}" \ 90 | -filter_complex \ 91 | "[0:0]setpts=PTS-STARTPTS[firstclip]; 92 | [1:0]setpts=PTS+${position}/TB[secondclip]; 93 | [firstclip][secondclip]overlay=enable='between(t\,${position},${dp})'[ov]" \ 94 | -map "[ov]" -map 0:1 \ 95 | -pix_fmt yuv420p \ 96 | -c:a copy -c:v libx264 -crf 18 \ 97 | -movflags +faststart \ 98 | -f mp4 \ 99 | "${outfile:=${outfile_default}}" 100 | } 101 | 102 | # get overlay videos duration with ffprobe 103 | duration="$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${overlay}" | cut -d\. -f1)" 104 | 105 | # position + duration 106 | dp="$((position+duration))" 107 | 108 | # run the overlay_video function 109 | overlay_video "${video}" "${overlay}" 110 | -------------------------------------------------------------------------------- /webp: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # webp 5 | # ffmpeg libwebp_anim 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | # webp animated image 21 | 22 | $(basename "$0") -i input -c 0-6 -q 0-100 -f 15 -w 600 -p none -o output.webp 23 | -i input 24 | -c compression level: 0 - 6 : default 4 25 | -q quality: 0 - 100 : default 80 26 | -f framerate: default 15 27 | -w width: default 600px 28 | -p preset: none|default|picture|photo|drawing|icon|text : default none 29 | -o output.webp : optional agument 30 | # if option not provided defaults input-name.webp" 31 | exit 2 32 | } 33 | 34 | 35 | #=============================================================================== 36 | # error messages 37 | #=============================================================================== 38 | 39 | INVALID_OPT_ERR='Invalid option:' 40 | REQ_ARG_ERR='requires an argument' 41 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 42 | 43 | 44 | #=============================================================================== 45 | # check the number of arguments passed to the script 46 | #=============================================================================== 47 | 48 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 49 | 50 | 51 | #=============================================================================== 52 | # getopts check the options passed to the script 53 | #=============================================================================== 54 | 55 | while getopts ':i:c:q:f:w:p:o:h' opt 56 | do 57 | case ${opt} in 58 | i) input="${OPTARG}";; 59 | c) compression="${OPTARG}";; 60 | q) quality="${OPTARG}";; 61 | f) framerate="${OPTARG}";; 62 | w) width="${OPTARG}";; 63 | p) preset="${OPTARG}";; 64 | o) output="${OPTARG}";; 65 | h) usage;; 66 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 67 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 68 | esac 69 | done 70 | shift $((OPTIND-1)) 71 | 72 | 73 | #=============================================================================== 74 | # variables 75 | #=============================================================================== 76 | 77 | # input name 78 | input_nopath="${input##*/}" 79 | input_name="${input_nopath%.*}" 80 | 81 | # defaults for variables if not defined 82 | compression_default='4' 83 | quality_default='80' 84 | preset_default='none' 85 | framerate_default='15' 86 | width_default='600' 87 | output_default="${input_name}.webp" 88 | 89 | 90 | #=============================================================================== 91 | # ffmpeg webp animation function 92 | #=============================================================================== 93 | 94 | animation () { 95 | ffmpeg \ 96 | -hide_banner \ 97 | -stats -v panic \ 98 | -i "${input}" \ 99 | -c:v libwebp_anim \ 100 | -lossless 0 \ 101 | -compression_level "${compression:=${compression_default}}" \ 102 | -quality "${quality:=${quality_default}}" \ 103 | -cr_threshold 0 \ 104 | -cr_size 16 \ 105 | -preset "${preset:=${preset_default}}" \ 106 | -loop 1 \ 107 | -an -vsync 0 \ 108 | -vf "fps=fps=${framerate:=${framerate_default}},scale=${width:=${width_default}}:-1:flags=lanczos" \ 109 | "${output:=${output_default}}" 110 | } 111 | 112 | 113 | #=============================================================================== 114 | # run ffmpeg webp animation function 115 | #=============================================================================== 116 | 117 | animation 118 | -------------------------------------------------------------------------------- /scene-images: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # scene-images 5 | # ffmpeg scene thumbnails 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | $(basename "$0") -i input -c cutfile -t (png|jpg) -x width -y height 21 | 22 | -i input.(mp4|mov|mkv|m4v) 23 | -c cutfile 24 | -t (png|jpg) : optional argument # if option not provided defaults to png 25 | -x width : optional argument # 26 | -y height : optional argument #" 27 | exit 2 28 | } 29 | 30 | 31 | #=============================================================================== 32 | # error messages 33 | #=============================================================================== 34 | 35 | INVALID_OPT_ERR='Invalid option:' 36 | REQ_ARG_ERR='requires an argument' 37 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 38 | 39 | 40 | #=============================================================================== 41 | # check the number of arguments passed to the script 42 | #=============================================================================== 43 | 44 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 45 | 46 | 47 | #=============================================================================== 48 | # getopts check the options passed to the script 49 | #=============================================================================== 50 | 51 | while getopts ':i:c:t:x:y:h' opt 52 | do 53 | case ${opt} in 54 | i) input="${OPTARG}";; 55 | c) cutfile="${OPTARG}";; 56 | t) image="${OPTARG}";; 57 | x) width="${OPTARG}";; 58 | y) height="${OPTARG}";; 59 | h) usage;; 60 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 61 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 62 | esac 63 | done 64 | shift $((OPTIND-1)) 65 | 66 | 67 | #=============================================================================== 68 | # variables 69 | #=============================================================================== 70 | 71 | # get the input file name 72 | input_nopath="${input##*/}" 73 | input_name="${input_nopath%.*}" 74 | 75 | # image default 76 | image_default="png" 77 | 78 | 79 | #=============================================================================== 80 | # ffmpeg extract image 81 | #=============================================================================== 82 | 83 | 84 | # image function 85 | extract () { 86 | output="${input_name}-[${seconds}].${image:=${image_default}}" 87 | ffmpeg \ 88 | -nostdin \ 89 | -hide_banner \ 90 | -stats -v panic \ 91 | -ss "${seconds}" \ 92 | -i "${input}" \ 93 | -q:v 2 -f image2 \ 94 | -vframes 1 \ 95 | "${output}" 96 | } 97 | 98 | 99 | # image and scale function 100 | extract_scale () { 101 | output="${input_name}-[${seconds}].${image:=${image_default}}" 102 | ffmpeg \ 103 | -nostdin \ 104 | -hide_banner \ 105 | -stats -v panic \ 106 | -ss "${seconds}" \ 107 | -i "${input}" \ 108 | -q:v 2 -f image2 \ 109 | -vframes 1 \ 110 | -vf scale="${width:=-1}:${height:=-1}" \ 111 | "${output}" 112 | } 113 | 114 | 115 | #=============================================================================== 116 | # read file and run ffmpeg 117 | #=============================================================================== 118 | 119 | 120 | if [ -n "${width}" ] || [ -n "${height}" ]; then 121 | count=1 122 | while IFS= read -r seconds; do 123 | extract_scale 124 | done < "${cutfile}" 125 | else 126 | count=1 127 | while IFS= read -r seconds; do 128 | extract 129 | done < "${cutfile}" 130 | fi 131 | 132 | -------------------------------------------------------------------------------- /scene-cut: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # scene-cut 5 | # ffmpeg scene cut 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | $(basename "$0") -i input -c cutfile 21 | 22 | -i input.(mp4|mov|mkv|m4v) 23 | -c cutfile" 24 | exit 2 25 | } 26 | 27 | 28 | #=============================================================================== 29 | # error messages 30 | #=============================================================================== 31 | 32 | INVALID_OPT_ERR='Invalid option:' 33 | REQ_ARG_ERR='requires an argument' 34 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 35 | 36 | #=============================================================================== 37 | # check the number of arguments passed to the script 38 | #=============================================================================== 39 | 40 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 41 | 42 | 43 | #=============================================================================== 44 | # getopts check the options passed to the script 45 | #=============================================================================== 46 | 47 | while getopts ':i:c:h' opt 48 | do 49 | case ${opt} in 50 | i) input="${OPTARG}";; 51 | c) cutfile="${OPTARG}";; 52 | h) usage;; 53 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 54 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 55 | esac 56 | done 57 | shift $((OPTIND-1)) 58 | 59 | 60 | #=============================================================================== 61 | # variables 62 | #=============================================================================== 63 | 64 | # get the input file name 65 | input_nopath="${input##*/}" 66 | input_name="${input_nopath%.*}" 67 | 68 | #=============================================================================== 69 | # ffmpeg create clips - nostdin needed to avoid clash with read command 70 | #=============================================================================== 71 | 72 | trim_video () { 73 | # awk convert start and end time from sexagesimal time to seconds 74 | # add the start and end time to get the endpoint and convert from seconds back to sexagesimal time 75 | endtime=$(printf "%s %s\n" "${start}" "${duration}" \ 76 | | awk ' 77 | { 78 | start = $1 79 | end = $2 80 | if (start ~ /:/) { 81 | split(start, t, ":") 82 | sseconds = (t[1] * 3600) + (t[2] * 60) + t[3] 83 | } 84 | if (end ~ /:/) { 85 | split(end, t, ":") 86 | eseconds = (t[1] * 3600) + (t[2] * 60) + t[3] 87 | } 88 | duration = eseconds + sseconds 89 | printf("%s\n"), duration 90 | }' \ 91 | | awk -F. 'NF==1 { printf("%02d:%02d:%02d\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60) }\ 92 | NF==2 { printf("%02d:%02d:%02d.%s\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60), ($2) }' 93 | ) 94 | output="${input_name}-[${start}-${endtime}].mp4" 95 | ffmpeg \ 96 | -nostdin \ 97 | -hide_banner \ 98 | -stats -v panic \ 99 | -ss "${start}" \ 100 | -i "${input}" \ 101 | -t "${duration}" \ 102 | -c:a aac \ 103 | -c:v libx264 -profile:v high \ 104 | -pix_fmt yuv420p -movflags +faststart \ 105 | -f mp4 \ 106 | "${output}" 107 | } 108 | 109 | 110 | #=============================================================================== 111 | # read file and set IFS=, read = input before , duration = input after , 112 | #=============================================================================== 113 | 114 | count=1 115 | while IFS=, read -r start duration; do 116 | trim_video 117 | done < "${cutfile}" 118 | -------------------------------------------------------------------------------- /extract-frame: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # extract-frame 5 | # extract a frame from a video as a png or jpg 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # extract a frame from a video as a png or jpg 22 | https://trac.ffmpeg.org/wiki/Seeking 23 | 24 | $(basename "$0") -i input.(mp4|mov|mkv|m4v|webm) -s 00:00:00.000 -t (png|jpg) -x width -y height -o output.(png|jpg) 25 | -i input.(mp4|mov|mkv|m4v) 26 | -s 00:00:00.000 : optional argument # if option not provided defaults to 00:00:00 27 | -t (png|jpg) : optional argument # if option not provided defaults to png 28 | -x width : optional argument # 29 | -y height : optional argument # 30 | -o output.(png|jpg) : optional argument # if option not provided defaults to input-name-timecode" 31 | exit 2 32 | } 33 | 34 | 35 | #=============================================================================== 36 | # error messages 37 | #=============================================================================== 38 | 39 | INVALID_OPT_ERR='Invalid option:' 40 | REQ_ARG_ERR='requires an argument' 41 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 42 | 43 | 44 | #=============================================================================== 45 | # check the number of arguments passed to the script 46 | #=============================================================================== 47 | 48 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 49 | 50 | 51 | #=============================================================================== 52 | # getopts check the options passed to the script 53 | #=============================================================================== 54 | 55 | while getopts ':i:s:t:x:y:o:h' opt 56 | do 57 | case ${opt} in 58 | i) infile="${OPTARG}";; 59 | s) seconds="${OPTARG}";; 60 | t) image="${OPTARG}";; 61 | x) width="${OPTARG}";; 62 | y) height="${OPTARG}";; 63 | o) outfile="${OPTARG}";; 64 | h) usage;; 65 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 66 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 67 | esac 68 | done 69 | shift $((OPTIND-1)) 70 | 71 | 72 | #=============================================================================== 73 | # variables 74 | #=============================================================================== 75 | 76 | # input, input name 77 | infile_nopath="${infile##*/}" 78 | infile_name="${infile_nopath%.*}" 79 | 80 | # seconds default 81 | seconds_default='00:00:00' 82 | 83 | # image default 84 | image_default="png" 85 | 86 | # output file recording destination 87 | outfile_default="${infile_name}-[${seconds:=${seconds_default}}].${image:=${image_default}}" 88 | 89 | 90 | #=============================================================================== 91 | # functions 92 | #=============================================================================== 93 | 94 | # image to video function 95 | extract () { 96 | ffmpeg \ 97 | -hide_banner \ 98 | -stats -v panic \ 99 | -ss "${seconds:=${seconds_default}}" \ 100 | -i "${infile}" \ 101 | -q:v 2 -f image2 \ 102 | -vframes 1 \ 103 | "${outfile:=${outfile_default}}" 104 | } 105 | 106 | # image to video with scale function 107 | extract_scale () { 108 | ffmpeg \ 109 | -hide_banner \ 110 | -stats -v panic \ 111 | -ss "${seconds:=${seconds_default}}" \ 112 | -i "${infile}" \ 113 | -q:v 2 -f image2 \ 114 | -vframes 1 \ 115 | -vf scale="${width:=-1}:${height:=-1}" \ 116 | "${outfile:=${outfile_default}}" 117 | } 118 | 119 | 120 | #=============================================================================== 121 | # run function 122 | #=============================================================================== 123 | 124 | if [ -n "${width}" ] || [ -n "${height}" ]; then 125 | extract_scale "${infile}" 126 | else 127 | extract "${infile}" 128 | fi 129 | -------------------------------------------------------------------------------- /pan-scan: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # pan-scan 5 | # pan scan over an image 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # pan scan image 22 | 23 | $(basename "$0") -i input.(png|jpg|jpeg) -d (000) -p (l|r|u|d) -o output.mp4 24 | -i = input.(png|jpg|jpeg) 25 | -d = duration : from 1-999 26 | -p = position : left, right, up, down 27 | -o = output.mp4 : optional argument # default is input-name-pan-date-time" 28 | exit 2 29 | } 30 | 31 | 32 | #=============================================================================== 33 | # error messages 34 | #=============================================================================== 35 | 36 | INVALID_OPT_ERR='Invalid option:' 37 | REQ_ARG_ERR='requires an argument' 38 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 39 | NOT_MEDIA_FILE_ERR='is not a media file' 40 | 41 | 42 | #=============================================================================== 43 | # check the number of arguments passed to the script 44 | #=============================================================================== 45 | 46 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 47 | 48 | 49 | #=============================================================================== 50 | # getopts check the options passed to the script 51 | #=============================================================================== 52 | 53 | while getopts ':i:d:p:o:h' opt 54 | do 55 | case ${opt} in 56 | i) infile="${OPTARG}";; 57 | d) dur="${OPTARG}";; 58 | p) position="${OPTARG}";; 59 | o) outfile="${OPTARG}";; 60 | h) usage;; 61 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 62 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 63 | esac 64 | done 65 | shift $((OPTIND-1)) 66 | 67 | 68 | #=============================================================================== 69 | # variables 70 | #=============================================================================== 71 | 72 | # input, input name and extension 73 | infile_nopath="${infile##*/}" 74 | infile_name="${infile_nopath%.*}" 75 | 76 | # check if tile is null 77 | if [ -z "${infile}" ]; then 78 | : # tile variable not set : = pass 79 | else 80 | # ffprobe get image height 81 | imgsize="$(ffprobe -v error -select_streams v:0 -show_entries stream=height,width -of csv=s=x:p=0 "${infile}")" 82 | img_w=$(echo "${imgsize}" | awk -F'x' '{print $1}') 83 | img_h=$(echo "${imgsize}" | awk -F'x' '{print $2}') 84 | fi 85 | 86 | # pan 87 | left="scale=w=-2:h=3*${img_h},crop=w=3*${img_w}/1.05:h=3*${img_h}/1.05:x=t*(in_w-out_w)/${dur},scale=w=${img_w}:h=${img_h},setsar=1" \ 88 | right="scale=w=-2:h=3*${img_h},crop=w=3*${img_w}/1.05:h=3*${img_h}/1.05:x=(in_w-out_w)-t*(in_w-out_w)/${dur},scale=w=${img_w}:h=${img_h},setsar=1" \ 89 | up="scale=w=-2:h=3*${img_h},crop=w=3*${img_w}/1.2:h=3*${img_h}/1.2:y=t*(in_h-out_h)/${dur},scale=w=${img_w}:h=${img_h},setsar=1" \ 90 | down="scale=w=-2:h=3*${img_h},crop=w=3*${img_w}/1.2:h=3*${img_h}/1.2:y=(in_h-out_h)-t*(in_h-out_h)/${dur},scale=w=${img_w}:h=${img_h},setsar=1" \ 91 | 92 | # output file recording destination 93 | outfile_default="${infile_name}-pan-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 94 | 95 | 96 | #=============================================================================== 97 | # functions 98 | #=============================================================================== 99 | 100 | # zoom function 101 | pan_func () { 102 | ffmpeg \ 103 | -hide_banner \ 104 | -stats -v panic \ 105 | -r 30 \ 106 | -y -loop 1 \ 107 | -i "${infile}" -ss 0 -t "${dur}" \ 108 | -filter_complex \ 109 | "${1}" \ 110 | -y -shortest \ 111 | -c:v libx264 -crf 18 -profile:v high \ 112 | -r 30 -pix_fmt yuv420p \ 113 | -movflags +faststart -f mp4 \ 114 | "${outfile:=${outfile_default}}" 115 | } 116 | 117 | # check zoom and position 118 | case "${position}" in 119 | l) pan_func "${left}";; 120 | r) pan_func "${right}" "${infile}";; 121 | u) pan_func "${up}" "${infile}";; 122 | d) pan_func "${down}" "${infile}";; 123 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 124 | esac 125 | -------------------------------------------------------------------------------- /scene-time: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # scene-time 5 | # create ffmpeg cutlist 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg awk head grep 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | $(basename "$0") -i input -o output 21 | 22 | -i input 23 | -o output" 24 | exit 2 25 | } 26 | 27 | 28 | #=============================================================================== 29 | # error messages 30 | #=============================================================================== 31 | 32 | INVALID_OPT_ERR='Invalid option:' 33 | REQ_ARG_ERR='requires an argument' 34 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 35 | 36 | 37 | #=============================================================================== 38 | # check the number of arguments passed to the script 39 | #=============================================================================== 40 | 41 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 42 | 43 | 44 | #=============================================================================== 45 | # getopts check the options passed to the script 46 | #=============================================================================== 47 | 48 | while getopts ':i:o:h' opt 49 | do 50 | case ${opt} in 51 | i) input="${OPTARG}";; 52 | h) usage;; 53 | o) output="${OPTARG}";; 54 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 55 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 56 | esac 57 | done 58 | shift $((OPTIND-1)) 59 | 60 | 61 | #=============================================================================== 62 | # variables 63 | #=============================================================================== 64 | 65 | # get the input file name 66 | input_nopath="${input##*/}" 67 | input_name="${input_nopath%.*}" 68 | 69 | # output file name 70 | output_default="${input_name}-cutlist-$(date +"%Y-%m-%d-%H-%M-%S").txt" 71 | 72 | 73 | #=============================================================================== 74 | # awk subtract 2nd line from first line - print start and duration 75 | #=============================================================================== 76 | 77 | seconds () { 78 | awk -F. 'NR > 1 {printf("%s%s%s\n"), prev, ",", $0-prev}; {prev = $0}' < "${input}" > "${output:=${output_default}}" 79 | } 80 | 81 | #=============================================================================== 82 | # convert sexagesimal to seconds - and subtract 2nd line from first line 83 | # convert from seconds back to sexagesimal 84 | #=============================================================================== 85 | 86 | minutes () { 87 | awk -F: 'NF==3 { printf("%s\n"), ($1 * 3600) + ($2 * 60) + $3 } NF==2 { print ($1 * 60) + $2 } NF==1 { printf("$s\n"), 0 + $1 }' < "${input}" \ 88 | | awk 'NR > 1 {printf("%s%s%s\n"), prev, "\n", $0-prev}; {prev = $0}' \ 89 | | awk -F. 'NF==1 { printf("%02d:%02d:%02d\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60) }\ 90 | NF==2 { printf("%02d:%02d:%02d.%s\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60), ($2) }' \ 91 | | awk '{ORS = NR%2 ? "," : "\n"} 1' > "${output:=${output_default}}" 92 | } 93 | 94 | 95 | #=============================================================================== 96 | # timecode regex 97 | #=============================================================================== 98 | 99 | # grab the first line of the file 100 | check=$(head -n1 "${input}") 101 | 102 | # minutes - match 00:00:00.000 103 | minutes_regex='^[0-9]{1,2}:[0-9]{2}:[0-9]{2}([.]{1}[0-9]{1,3})?$' 104 | 105 | # seconds - match 00:00:00.000 106 | seconds_regex='^[0-9]{1,8}([.]{1}[0-9]{1,3})?$' 107 | 108 | # grep for the minutes 109 | minutes=$(echo "${check}" | grep -E "${minutes_regex}") 110 | 111 | # grep for the seconds 112 | seconds=$(echo "${check}" | grep -E "${seconds_regex}") 113 | 114 | 115 | #=============================================================================== 116 | # check timecode and run function 117 | #=============================================================================== 118 | 119 | if [ -n "${minutes}" ]; then 120 | minutes 121 | elif [ -n "${seconds}" ]; then 122 | seconds 123 | else 124 | usage 125 | fi 126 | -------------------------------------------------------------------------------- /clip-time: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # clip-time 5 | # create ffmpeg cutlist 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | $(basename "$0") -i input -o output 21 | 22 | -i input 23 | -o output" 24 | exit 2 25 | } 26 | 27 | 28 | #=============================================================================== 29 | # error messages 30 | #=============================================================================== 31 | 32 | INVALID_OPT_ERR='Invalid option:' 33 | REQ_ARG_ERR='requires an argument' 34 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 35 | 36 | #=============================================================================== 37 | # check the number of arguments passed to the script 38 | #=============================================================================== 39 | 40 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 41 | 42 | 43 | #=============================================================================== 44 | # getopts check the options passed to the script 45 | #=============================================================================== 46 | 47 | while getopts ':i:o:h' opt 48 | do 49 | case ${opt} in 50 | i) input="${OPTARG}";; 51 | h) usage;; 52 | o) output="${OPTARG}";; 53 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 54 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 55 | esac 56 | done 57 | shift $((OPTIND-1)) 58 | 59 | 60 | #=============================================================================== 61 | # variables 62 | #=============================================================================== 63 | 64 | # get the input file name 65 | input_nopath="${input##*/}" 66 | input_name="${input_nopath%.*}" 67 | 68 | # output file name 69 | output_default="${input_name}-cutlist-$(date +"%Y-%m-%d-%H-%M-%S").txt" 70 | 71 | 72 | #=============================================================================== 73 | # awk subtract 2nd line from first line - print start and duration 74 | #=============================================================================== 75 | 76 | seconds () { 77 | awk -F. 'NR > 1 && NR%2 == 0 {printf("%s%s%s\n"), prev, ",", $0-prev}; {prev = $0}' < "${input}" > "${output:=${output_default}}" 78 | } 79 | 80 | #=============================================================================== 81 | # convert sexagesimal to seconds - and subtract 2nd line from first line 82 | # convert from seconds back to sexagesimal 83 | #=============================================================================== 84 | 85 | minutes () { 86 | awk -F: 'NF==3 { printf("%s\n"), ($1 * 3600) + ($2 * 60) + $3 } NF==2 { print ($1 * 60) + $2 } NF==1 { printf("$s\n"), 0 + $1 }' < "${input}" \ 87 | | awk 'NR > 1 && NR%2 == 0 {printf("%s%s%s\n"), prev, "\n", $0-prev}; {prev = $0}' \ 88 | | awk -F. 'NF==1 { printf("%02d:%02d:%02d\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60) }\ 89 | NF==2 { printf("%02d:%02d:%02d.%s\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60), ($2) }' \ 90 | | awk '{ORS = NR%2 ? "," : "\n"} 1' > "${output:=${output_default}}" 91 | } 92 | 93 | 94 | #=============================================================================== 95 | # timecode regex 96 | #=============================================================================== 97 | 98 | # grab the first line of the file 99 | check=$(head -n1 "${input}") 100 | 101 | # minutes - match 00:00:00.000 102 | minutes_regex='^[0-9]{1,2}:[0-9]{2}:[0-9]{2}([.]{1}[0-9]{1,6})?$' 103 | 104 | # seconds - match 00:00:00.000 105 | seconds_regex='^[0-9]{1,8}([.]{1}[0-9]{1,6})?$' 106 | 107 | # grep for the minutes 108 | minutes=$(echo "${check}" | grep -E "${minutes_regex}") 109 | 110 | # grep for the seconds 111 | seconds=$(echo "${check}" | grep -E "${seconds_regex}") 112 | 113 | 114 | #=============================================================================== 115 | # check timecode and run function 116 | #=============================================================================== 117 | 118 | if [ -n "${minutes}" ]; then 119 | minutes 120 | elif [ -n "${seconds}" ]; then 121 | seconds 122 | else 123 | usage 124 | fi 125 | -------------------------------------------------------------------------------- /scopes: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # scopes 5 | # ffplay video scopes 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffplay 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | [ -z "${1}" ] || echo "! ${1}" 18 | echo "\ 19 | # ffplay video scopes 20 | 21 | $(basename "$0") -i input = histogram 22 | $(basename "$0") -o input = rgb overlay 23 | $(basename "$0") -p input = rgb parade 24 | $(basename "$0") -s input = rgb overlay and parade 25 | $(basename "$0") -w input = waveform 26 | $(basename "$0") -v input = vector scope 27 | $(basename "$0") -h = help 28 | " 29 | exit 2 30 | } 31 | 32 | 33 | #=============================================================================== 34 | # error messages 35 | #=============================================================================== 36 | 37 | INVALID_OPT_ERR='Invalid option:' 38 | REQ_ARG_ERR='requires an argument' 39 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 40 | 41 | 42 | #=============================================================================== 43 | # check the number of arguments passed to the script 44 | #=============================================================================== 45 | 46 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 47 | 48 | 49 | #=============================================================================== 50 | # functions 51 | #=============================================================================== 52 | 53 | # histogram overlay 54 | histogram_overlay () { 55 | ffplay "${infile}" \ 56 | -hide_banner \ 57 | -stats -v panic \ 58 | -vf \ 59 | "split=2[a][b], 60 | [b]histogram, 61 | format=yuva444p[hh], 62 | [a][hh]overlay=x=W-w:y=H-h" 63 | } 64 | 65 | 66 | # rgb overlay 67 | rgb_overlay () { 68 | ffplay "${infile}" \ 69 | -hide_banner \ 70 | -stats -v panic \ 71 | -vf \ 72 | "format=gbrp, 73 | split=2[a][b], 74 | [b]waveform=g=green: 75 | s=ire: 76 | fl=numbers: 77 | filter=lowpass: 78 | components=7: 79 | display=overlay[bb], 80 | [a][bb]vstack" 81 | } 82 | 83 | 84 | # rgb parade 85 | rgb_parade () { 86 | ffplay "${infile}" \ 87 | -hide_banner \ 88 | -stats -v panic \ 89 | -vf \ 90 | "format=gbrp, 91 | split=2[a][b], 92 | [b]waveform=g=green: 93 | s=ire: 94 | fl=numbers: 95 | filter=lowpass: 96 | components=7[bb], 97 | [a][bb]vstack" 98 | } 99 | 100 | 101 | # rgb overlay and parade stacked 102 | rgb_stacked () { 103 | ffplay "${infile}" \ 104 | -hide_banner \ 105 | -stats -v panic \ 106 | -vf \ 107 | "format=gbrp, 108 | split=3[a][b][c], 109 | [b]waveform=g=green: 110 | s=ire: 111 | fl=numbers: 112 | filter=lowpass: 113 | components=7: 114 | display=overlay[bb], 115 | [c]waveform=g=green: 116 | s=ire: 117 | fl=numbers: 118 | filter=lowpass: 119 | components=7[cc], 120 | [bb][cc]vstack[d], 121 | [a][d]vstack" 122 | } 123 | 124 | 125 | # waveform lowpass 126 | waveform_lowpass () { 127 | ffplay "${infile}" \ 128 | -hide_banner \ 129 | -stats -v panic \ 130 | -vf \ 131 | "split=2[a][b], 132 | [b]waveform=f=lowpass: 133 | s=ire: 134 | g=green: 135 | e=instant[bb], 136 | [a][bb]vstack" 137 | } 138 | 139 | 140 | # vectorscope 141 | vectorscope () { 142 | ffplay "${infile}" \ 143 | -hide_banner \ 144 | -stats -v panic \ 145 | -vf \ 146 | "format=yuva444p9, 147 | split=2[m][v], 148 | [v]vectorscope=b=0.7: 149 | m=color4: 150 | e=peak+instant: 151 | f=name: 152 | g=color[v], 153 | [m][v]overlay=x=W-w:y=H-h" 154 | } 155 | 156 | 157 | #=============================================================================== 158 | # getopts check the options passed to the script 159 | #=============================================================================== 160 | 161 | while getopts ':i:o:p:s:w:v:h' opt 162 | do 163 | case ${opt} in 164 | i) infile="${OPTARG}" 165 | histogram_overlay;; 166 | o) infile="${OPTARG}" 167 | rgb_overlay;; 168 | p) infile="${OPTARG}" 169 | rgb_parade;; 170 | s) infile="${OPTARG}" 171 | rgb_stacked;; 172 | w) infile="${OPTARG}" 173 | waveform_lowpass;; 174 | v) infile="${OPTARG}" 175 | vectorscope;; 176 | h) usage;; 177 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 178 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 179 | esac 180 | done 181 | shift $((OPTIND-1)) 182 | -------------------------------------------------------------------------------- /audio-silence: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # audio-silence 5 | # add silent audio to a video clip 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # audio-silence add silent audio to a video clip 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v) -c (mono|stereo) -r (44100|48000) -o output.mp4 24 | -i input.(mp4|mkv|mov|m4v) 25 | -c (mono|stereo) : optional argument # if option not provided defaults to mono 26 | -r (44100|48000) : optional argument # if option not provided defaults to 44100 27 | -o output.mp4 : optional argument # if option not provided defaults to input-name-silence-date-time" 28 | exit 2 29 | } 30 | 31 | 32 | #=============================================================================== 33 | # error messages 34 | #=============================================================================== 35 | 36 | INVALID_OPT_ERR='Invalid option:' 37 | REQ_ARG_ERR='requires an argument' 38 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 39 | 40 | 41 | #=============================================================================== 42 | # check the number of arguments passed to the script 43 | #=============================================================================== 44 | 45 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 46 | 47 | 48 | #=============================================================================== 49 | # getopts check the options passed to the script 50 | #=============================================================================== 51 | 52 | while getopts ':i:c:r:o:h' opt 53 | do 54 | case ${opt} in 55 | i) infile="${OPTARG}";; 56 | c) channel="${OPTARG}" 57 | { [ "${channel}" = 'mono' ] || [ "${channel}" = 'stereo' ]; } || usage;; 58 | r) rate="${OPTARG}" 59 | { [ "${rate}" = '44100' ] || [ "${rate}" = '48000' ]; } || usage;; 60 | o) outfile="${OPTARG}";; 61 | h) usage;; 62 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 63 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 64 | esac 65 | done 66 | shift $((OPTIND-1)) 67 | 68 | 69 | #=============================================================================== 70 | # variables 71 | #=============================================================================== 72 | 73 | # input 74 | infile_nopath="${infile##*/}" 75 | infile_name="${infile_nopath%.*}" 76 | 77 | # defaults for variables if not defined 78 | channel_default='mono' 79 | rate_default='44100' 80 | outfile_default="${infile_name}-silence-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 81 | 82 | # check if the libfdk_aac codec is installed, if not fall back to the aac codec 83 | aac_codec="$(ffmpeg -hide_banner -stats -v panic -h encoder=libfdk_aac)" 84 | aac_error="Codec 'libfdk_aac' is not recognized by FFmpeg." 85 | aac_check="$(echo "${aac_codec}" | grep "${aac_error}")" 86 | 87 | # check ffmpeg aac codecs 88 | if [ -z "${aac_check}" ]; then 89 | aac='libfdk_aac' # libfdk_aac codec is installed 90 | else 91 | aac='aac' # libfdk_aac codec isnt installed, fall back to aac codec 92 | fi 93 | 94 | 95 | #=============================================================================== 96 | # functions 97 | #=============================================================================== 98 | 99 | # video function 100 | video_silence () { 101 | ffmpeg \ 102 | -hide_banner \ 103 | -stats -v panic \ 104 | -f lavfi \ 105 | -i anullsrc=channel_layout="${channel:=${channel_default}}":sample_rate="${rate:=${rate_default}}" \ 106 | -i "${infile}" \ 107 | -shortest -c:v copy -c:a "${aac}" \ 108 | -movflags +faststart -f mp4 \ 109 | "${outfile:=${outfile_default}}" 110 | } 111 | 112 | # video and audio function 113 | video_audio_silence () { 114 | ffmpeg \ 115 | -hide_banner \ 116 | -stats -v panic \ 117 | -f lavfi \ 118 | -i anullsrc=channel_layout="${channel:=${channel_default}}":sample_rate="${rate:=${rate_default}}" \ 119 | -i "${infile}" \ 120 | -shortest -c:v copy -c:a "${aac}" \ 121 | -map 0:0 -map 1:0 \ 122 | -movflags +faststart -f mp4 \ 123 | "${outfile:=${outfile_default}}" 124 | } 125 | 126 | # check if the video has an audio track 127 | audio_check="$(ffprobe -i "${infile}" -show_streams -select_streams a -loglevel error)" 128 | 129 | # check if audio_check is null which means the video doesnt have an audio track 130 | if [ -z "${audio_check}" ]; then 131 | video_silence "${infile}" # null value 132 | else 133 | video_audio_silence "${infile}" # non null value 134 | fi 135 | -------------------------------------------------------------------------------- /xfade: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # xfade 5 | # ffmpeg xfade transitions 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe bc 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # ffmpeg xfade transitions 22 | 23 | $(basename "$0") -a clip1.(mp4|mkv|mov|m4v) -b clip2.(mp4|mkv|mov|m4v) -d duration -t transition -f offset -o output.mp4 24 | -a clip1.(mp4|mkv|mov|m4v) : first clip 25 | -b clip2.(mp4|mkv|mov|m4v) : second clip 26 | -d duration : transition duration 27 | -t transition : transition 28 | -f offset : offset 29 | -o output.mp4 : optional argument # if option not provided defaults to input-name-xfade-date-time 30 | 31 | + transitions 32 | 33 | circleclose, circlecrop, circleopen, diagbl, diagbr, diagtl, diagtr, dissolve, distance 34 | fade, fadeblack, fadegrays, fadewhite, hblur, hlslice, horzclose, horzopen, hrslice 35 | pixelize, radial, rectcrop, slidedown, slideleft, slideright, slideup, smoothdown 36 | smoothleft, smoothright, smoothup, squeezeh, squeezev, vdslice, vertclose, vertopen, vuslice 37 | wipebl, wipebr, wipedown, wipeleft, wiperight, wipetl, wipetr, wipeup" 38 | exit 2 39 | } 40 | 41 | 42 | #=============================================================================== 43 | # error messages 44 | #=============================================================================== 45 | 46 | INVALID_OPT_ERR='Invalid option:' 47 | REQ_ARG_ERR='requires an argument' 48 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 49 | 50 | 51 | #=============================================================================== 52 | # check the number of arguments passed to the script 53 | #=============================================================================== 54 | 55 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 56 | 57 | 58 | #=============================================================================== 59 | # getopts check the options passed to the script 60 | #=============================================================================== 61 | 62 | while getopts ':a:b:d:t:f:o:h' opt 63 | do 64 | case ${opt} in 65 | a) clip1="${OPTARG}";; 66 | b) clip2="${OPTARG}";; 67 | d) dur="${OPTARG}";; 68 | t) transition="${OPTARG}";; 69 | f) offset="${OPTARG}";; 70 | o) outfile="${OPTARG}";; 71 | h) usage;; 72 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 73 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 74 | esac 75 | done 76 | shift $((OPTIND-1)) 77 | 78 | 79 | #=============================================================================== 80 | # variables 81 | #=============================================================================== 82 | 83 | # input, input name and extension 84 | clip1_nopath="${clip1##*/}" 85 | clip1_name="${clip1_nopath%.*}" 86 | 87 | # defaults 88 | duration_default='0.5' 89 | transition_default='fade' 90 | outfile_default="${clip1_name}-xfade-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 91 | 92 | # offset and duration options not set 93 | if [ -z "${offset}" ];then 94 | # clip 1 duration to calculate default transition offset 95 | clip1_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${clip1}") 96 | 97 | # round down decimal point 98 | clip1_time=$(printf "%.1f\n" "${clip1_dur}") 99 | 100 | # clip1 duration minus duration default and 0.1 seconds which is needed for the transition 101 | offset_default=$(echo "${clip1_time}" - "${dur:=${duration_default}}" - 0.1 | bc -l) 102 | 103 | # audio trim default match offset default 104 | audio_trim_default=$(echo "${clip1_time}" - 0.1 | bc -l) 105 | else 106 | # offset and duration options set 107 | # trim audio to match length of the duration and offset 108 | audio_trim=$(echo "${dur} + ${offset}" | bc -l) 109 | fi 110 | 111 | 112 | #=============================================================================== 113 | # function 114 | #=============================================================================== 115 | 116 | # cross fade video and audio 117 | xfade_va () { 118 | ffmpeg \ 119 | -hide_banner \ 120 | -stats -v panic \ 121 | -i "${clip1}" -i "${clip2}" \ 122 | -filter_complex \ 123 | "[0:v][1:v]xfade=transition='${transition:=${transition_default}}':duration='${dur:=${duration_default}}':offset='${offset:=${offset_default}}'[xfade]; 124 | [0:a]atrim=0:'${audio_trim:=${audio_trim_default}}'[atrim]; 125 | [atrim][1:a]acrossfade=d='${dur:=${duration_default}}'[afade] 126 | " \ 127 | -map "[xfade]" -map "[afade]" \ 128 | -pix_fmt yuv420p \ 129 | -movflags +faststart -f mp4 \ 130 | "${outfile:=${outfile_default}}" 131 | } 132 | 133 | 134 | # run the xfade_va function 135 | xfade_va "${clip1}" "${clip2}" 136 | -------------------------------------------------------------------------------- /fade-clip: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # fade-clip 5 | # fade video and audio in and out 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg bc 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | echo "\ 18 | # fade video and audio in and out 19 | 20 | $(basename "$0") -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -o output.mp4 21 | -i infile.(mp4|mkv|mov|m4v) 22 | -d (0.[0-9]|1) : optional argument # if option not provided defaults to 0.5 23 | -o output.mp4 : optional argument # if option not provided defaults to input-name-fade-date-time" 24 | exit 2 25 | } 26 | 27 | 28 | #=============================================================================== 29 | # error messages 30 | #=============================================================================== 31 | 32 | INVALID_OPT_ERR='Invalid option:' 33 | REQ_ARG_ERR='requires an argument' 34 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 35 | 36 | 37 | #=============================================================================== 38 | # check the number of arguments passed to the script 39 | #=============================================================================== 40 | 41 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 42 | 43 | 44 | #=============================================================================== 45 | # getopts check the options passed to the script 46 | #=============================================================================== 47 | 48 | while getopts ':i:d:o:h' opt 49 | do 50 | case ${opt} in 51 | i) infile="${OPTARG}";; 52 | d) dur="${OPTARG}";; 53 | o) outfile="${OPTARG}";; 54 | h) usage;; 55 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 56 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 57 | esac 58 | done 59 | shift $((OPTIND-1)) 60 | 61 | 62 | #=============================================================================== 63 | # variables 64 | #=============================================================================== 65 | 66 | # input, input name and extension 67 | infile_nopath="${infile##*/}" 68 | infile_name="${infile_nopath%.*}" 69 | 70 | # defaults for variables if not defined 71 | outfile_default="${infile_name}-fade-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 72 | duration_default="0.5" 73 | 74 | # check if the libfdk_aac codec is installed, if not fall back to the aac codec 75 | aac_codec="$(ffmpeg -hide_banner -stats -v panic -h encoder=libfdk_aac)" 76 | aac_error="Codec 'libfdk_aac' is not recognized by FFmpeg." 77 | aac_check="$(echo "${aac_codec}" | grep "${aac_error}")" 78 | 79 | # check ffmpeg aac codecs 80 | if [ -z "${aac_check}" ]; then 81 | aac='libfdk_aac' # libfdk_aac codec is installed 82 | else 83 | aac='aac' # libfdk_aac codec isnt installed, fall back to aac codec 84 | fi 85 | 86 | 87 | #=============================================================================== 88 | # functions 89 | #=============================================================================== 90 | 91 | # ffmpeg fade video track 92 | fade_v () { 93 | echo '+ Getting video duration' && \ 94 | video_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${infile}" | cut -d\. -f1) 95 | vid_offset=$(echo "${video_dur}-${dur:=${duration_default}}" | bc -l) 96 | ffmpeg \ 97 | -hide_banner \ 98 | -stats -v panic \ 99 | -i "${infile}" \ 100 | -filter_complex \ 101 | " [0:v] fade=t=in:st=0:d=${dur:=${duration_default}},fade=t=out:st='${vid_offset}':d=${dur:=${duration_default}}[fv] " \ 102 | -map "[fv]" \ 103 | -c:v libx264 -preset fast \ 104 | -profile:v high \ 105 | -crf 18 -coder 1 \ 106 | -pix_fmt yuv420p \ 107 | -movflags +faststart \ 108 | -f mp4 \ 109 | "${outfile:=${outfile_default}}" 110 | } 111 | 112 | # fade video and audio tracks 113 | fade_va () { 114 | echo '+ Getting video duration' && \ 115 | video_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${infile}" | cut -d\. -f1) 116 | vid_offset=$(echo "${video_dur}-${dur:=${duration_default}}" | bc -l) 117 | ffmpeg \ 118 | -hide_banner \ 119 | -stats -v panic \ 120 | -i "${infile}" \ 121 | -filter_complex \ 122 | " [0:a] afade=t=in:st=0:d=${dur:=${duration_default}},afade=t=out:st='${vid_offset}':d=${dur:=${duration_default}}[fa]; 123 | [0:v] fade=t=in:st=0:d=${dur:=${duration_default}},fade=t=out:st='${vid_offset}':d=${dur:=${duration_default}}[fv] 124 | " \ 125 | -map "[fv]" -map "[fa]" \ 126 | -c:a "${aac}" \ 127 | -c:v libx264 -preset fast \ 128 | -profile:v high \ 129 | -crf 18 -coder 1 \ 130 | -pix_fmt yuv420p \ 131 | -movflags +faststart \ 132 | -f mp4 \ 133 | "${outfile:=${outfile_default}}" 134 | } 135 | 136 | # check if video has an audio track 137 | audio_check="$(ffprobe -i "${infile}" -show_streams -select_streams a -loglevel error)" 138 | 139 | # check if audio_check is null which means the video doesnt have an audio track 140 | if [ -z "${audio_check}" ]; then 141 | fade_v "${infile}" # null value 142 | else 143 | fade_va "${infile}" # non null value 144 | fi 145 | -------------------------------------------------------------------------------- /fade-normalize: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # fade-normalize 5 | # fade video and normalize audio levels 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg awk grep 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # fade video and normalize audio levels 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -o output.mp4 24 | 25 | -d (0.[0-9]|1) : optional argument # if option not provided defaults to 0.5 26 | -o output.mp4 : optional argument # if option not provided defaults to input-name-normalized-date-time" 27 | exit 2 28 | } 29 | 30 | 31 | #=============================================================================== 32 | # error messages 33 | #=============================================================================== 34 | 35 | INVALID_OPT_ERR='Invalid option:' 36 | REQ_ARG_ERR='requires an argument' 37 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 38 | 39 | 40 | #=============================================================================== 41 | # check the number of arguments passed to the script 42 | #=============================================================================== 43 | 44 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 45 | 46 | 47 | #=============================================================================== 48 | # getopts check the options passed to the script 49 | #=============================================================================== 50 | 51 | while getopts ':i:d:o:h' opt 52 | do 53 | case ${opt} in 54 | i) infile="${OPTARG}";; 55 | d) dur="${OPTARG}";; 56 | o) outfile="${OPTARG}";; 57 | h) usage;; 58 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 59 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 60 | esac 61 | done 62 | shift $((OPTIND-1)) 63 | 64 | 65 | #=============================================================================== 66 | # variables 67 | #=============================================================================== 68 | 69 | # input, input name and file extension 70 | infile_nopath="${infile##*/}" 71 | infile_name="${infile_nopath%.*}" 72 | 73 | # defaults for variables if not defined 74 | outfile_default="${infile_name}-normalized-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 75 | duration_default="0.5" 76 | 77 | # print analyzing file 78 | echo '+ Analyzing file with ffmpeg' 79 | 80 | # ffmpeg loudnorm get stats from file 81 | normalize=$(ffmpeg \ 82 | -hide_banner \ 83 | -i "${infile}" \ 84 | -af "loudnorm=I=-16:dual_mono=true:TP=-1.5:LRA=11:print_format=summary" \ 85 | -f null - 2>&1 | tail -n 12) 86 | 87 | # read the output of normalize line by line and store in variables 88 | for line in "${normalize}"; do 89 | measured_I=$(echo "${line}" | awk -F' ' '/Input Integrated:/ {print $3}') 90 | measured_TP=$(echo "${line}" | awk -F' ' '/Input True Peak:/ {print $4}') 91 | measured_LRA=$(echo "${line}" | awk -F' ' '/Input LRA:/ {print $3}') 92 | measured_thresh=$(echo "${line}" | awk -F' ' '/Input Threshold:/ {print $3}') 93 | offset=$(echo "${line}" | awk -F' ' '/Target Offset:/ {print $3}') 94 | done 95 | 96 | # check if the libfdk_aac codec is installed, if not fall back to the aac codec 97 | aac_codec="$(ffmpeg -hide_banner -stats -v panic -h encoder=libfdk_aac)" 98 | aac_error="Codec 'libfdk_aac' is not recognized by FFmpeg." 99 | aac_check="$(echo "${aac_codec}" | grep "${aac_error}")" 100 | 101 | # check ffmpeg aac codecs 102 | if [ -z "${aac_check}" ]; then 103 | aac='libfdk_aac' # libfdk_aac codec is installed 104 | else 105 | aac='aac' # libfdk_aac codec isnt installed, fall back to aac codec 106 | fi 107 | 108 | 109 | #=============================================================================== 110 | # functions 111 | #=============================================================================== 112 | 113 | # video function 114 | video () { 115 | video_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${infile}" | cut -d\. -f1) 116 | vid_offset=$(echo "${video_dur}-${dur:=${duration_default}}" | bc -l) 117 | ffmpeg \ 118 | -hide_banner \ 119 | -stats -v panic \ 120 | -i "${infile}" \ 121 | -filter_complex \ 122 | "[0:a] afade=t=in:st=0:d=${dur:=${duration_default}},afade=t=out:st='${vid_offset}':d=${dur:=${duration_default}}, 123 | compand=attacks=0:points=-70/-90|-24/-12|0/-6|20/-6, 124 | highpass=f=60, 125 | lowpass=f=13700, 126 | afftdn=nt=w, 127 | adeclick, 128 | deesser, 129 | loudnorm=I=-16: 130 | dual_mono=true: 131 | TP=-1.5: 132 | LRA=11: 133 | measured_I=${measured_I}: 134 | measured_LRA=${measured_LRA}: 135 | measured_TP=${measured_TP}: 136 | measured_thresh=${measured_thresh}: 137 | offset=${offset}: 138 | linear=true: 139 | print_format=summary [audio]; 140 | [0:v] fade=t=in:st=0:d=${dur:=${duration_default}},fade=t=out:st='${vid_offset}':d=${dur:=${duration_default}}[video]" \ 141 | -map "[video]" -map "[audio]" \ 142 | -c:a "${aac}" -ar 44100 \ 143 | -c:v libx264 -preset fast \ 144 | -profile:v high \ 145 | -crf 18 -coder 1 \ 146 | -pix_fmt yuv420p \ 147 | -movflags +faststart \ 148 | -f mp4 \ 149 | "${outfile:=${outfile_default}}" 150 | } 151 | 152 | # run the video function 153 | video "${infile}" 154 | -------------------------------------------------------------------------------- /correct-clip: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # correct-clip 5 | # correct a video clip by using a gimp curve 6 | #=============================================================================== 7 | 8 | # code based on: 9 | # https://video.stackexchange.com/questions/16352/converting-gimp-curves-files-to-photoshop-acv-for-ffmpeg/20005#20005 10 | 11 | # converted into a ffmpeg curves filter command 12 | # to adjust the levels and white balance 13 | 14 | # requires a curve file created with the following script 15 | # https://github.com/NapoleonWils0n/curve2ffmpeg 16 | 17 | # dependencies: 18 | # ffmpeg file grep 19 | 20 | #=============================================================================== 21 | # script usage 22 | #=============================================================================== 23 | 24 | usage() 25 | { 26 | # if argument passed to function echo it 27 | [ -z "${1}" ] || echo "! ${1}" 28 | echo "\ 29 | # correct a video clip by using a gimp curve 30 | 31 | # requires a curve file created with the following script 32 | # https://github.com/NapoleonWils0n/curve2ffmpeg 33 | 34 | $(basename "$0") -i input.(mp4|mkv|mov|m4v) -c curve.txt -o output.mp4 35 | -i input.(mp4|mkv|mov|m4v) 36 | -c curve.txt 37 | -o output.mp4 : optional argument # if option not provided defaults to input-name-corrected-date-time" 38 | exit 2 39 | } 40 | 41 | 42 | #=============================================================================== 43 | # error messages 44 | #=============================================================================== 45 | 46 | INVALID_OPT_ERR='Invalid option:' 47 | REQ_ARG_ERR='requires an argument' 48 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 49 | NOT_MEDIA_FILE_ERR='is not a media file' 50 | NOT_TEXT_FILE_ERR='is not a text file' 51 | 52 | 53 | #=============================================================================== 54 | # check the number of arguments passed to the script 55 | #=============================================================================== 56 | 57 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 58 | 59 | 60 | #=============================================================================== 61 | # getopts check the options passed to the script 62 | #=============================================================================== 63 | 64 | while getopts ':i:c:o:h' opt 65 | do 66 | case ${opt} in 67 | i) infile="${OPTARG}";; 68 | c) text="${OPTARG}";; 69 | o) outfile="${OPTARG}";; 70 | h) usage;; 71 | \?) echo "${INVALID_OPT_ERR} ${OPTARG}" 1>&2 && usage;; 72 | :) echo "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2 && usage;; 73 | esac 74 | done 75 | shift $((OPTIND-1)) 76 | 77 | 78 | #=============================================================================== 79 | # variables 80 | #=============================================================================== 81 | 82 | # input, input name 83 | infile_nopath="${infile##*/}" 84 | infile_name="${infile_nopath%.*}" 85 | 86 | # defaults for variables 87 | outfile_default="${infile_name}-corrected-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 88 | # check if the libfdk_aac codec is installed, if not fall back to the aac codec 89 | aac_codec="$(ffmpeg -hide_banner -stats -v panic -h encoder=libfdk_aac)" 90 | aac_error="Codec 'libfdk_aac' is not recognized by FFmpeg." 91 | aac_check="$(echo "${aac_codec}" | grep "${aac_error}")" 92 | 93 | # check ffmpeg aac codecs 94 | if [ -z "${aac_check}" ]; then 95 | aac='libfdk_aac' # libfdk_aac codec is installed 96 | else 97 | aac='aac' # libfdk_aac codec isnt installed, fall back to aac codec 98 | fi 99 | 100 | 101 | #=============================================================================== 102 | # functions 103 | #=============================================================================== 104 | 105 | correct_v () { 106 | ffmpeg \ 107 | -hide_banner \ 108 | -stats -v panic \ 109 | -i "${infile}" \ 110 | -filter_complex \ 111 | "[0:v]${text_contents}[video]" \ 112 | -map "[video]" \ 113 | -c:v libx264 -preset fast \ 114 | -profile:v high \ 115 | -crf 18 -coder 1 \ 116 | -pix_fmt yuv420p \ 117 | -movflags +faststart \ 118 | -f mp4 \ 119 | "${outfile:=${outfile_default}}" 120 | } 121 | 122 | # correct video and audio tracks 123 | correct_va () { 124 | ffmpeg \ 125 | -hide_banner \ 126 | -stats -v panic \ 127 | -i "${infile}" \ 128 | -filter_complex \ 129 | "[0:v]${text_contents}[video]" \ 130 | -map "[video]" -map 0:a \ 131 | -c:a "${aac}" \ 132 | -c:v libx264 -preset fast \ 133 | -profile:v high \ 134 | -crf 18 -coder 1 \ 135 | -pix_fmt yuv420p \ 136 | -movflags +faststart \ 137 | -f mp4 \ 138 | "${outfile:=${outfile_default}}" 139 | } 140 | 141 | # file command check input file mime type 142 | filetype="$(file --mime-type -b "${infile}")" 143 | textfile="$(file --mime-type -b "${text}")" 144 | 145 | # video mimetypes 146 | mov_mime='video/quicktime' 147 | mkv_mime='video/x-matroska' 148 | mp4_mime='video/mp4' 149 | m4v_mime='video/x-m4v' 150 | 151 | # text mimetype 152 | txt_mime='text/plain' 153 | 154 | # check the files mime type is a video 155 | case "${filetype}" in 156 | "${mov_mime}"|"${mkv_mime}"|"${mp4_mime}"|"${m4v_mime}");; 157 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 158 | esac 159 | 160 | # check the text file mime type is a text 161 | case "${textfile}" in 162 | "${txt_mime}");; 163 | *) usage "${textfile} ${NOT_TEXT_FILE_ERR}";; 164 | esac 165 | 166 | # read the contents of the curve text file and store in a variable 167 | text_contents="$(while IFS= read -r line; do echo "${line}"; done < "${text}")" 168 | 169 | # check if video has an audio track 170 | audio_check="$(ffprobe -i "${infile}" -show_streams -select_streams a -loglevel error)" 171 | 172 | # check if audio_check is null which means the video doesnt have an audio track 173 | if [ -z "${audio_check}" ]; then 174 | correct_v "${infile}" # null value 175 | else 176 | correct_va "${infile}" # non null value 177 | fi 178 | -------------------------------------------------------------------------------- /normalize: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # nomalize 5 | # normalize audio levels 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg file awk tail 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | echo "\ 18 | # normalize audio levels 19 | 20 | $(basename "$0") -i input.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) -o output.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) 21 | -i input.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) 22 | -o output.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) : optional argument 23 | # if option not provided defaults to input-name-normalized-date-time-extension" 24 | exit 2 25 | } 26 | 27 | #=============================================================================== 28 | # error messages 29 | #=============================================================================== 30 | 31 | INVALID_OPT_ERR='Invalid option:' 32 | REQ_ARG_ERR='requires an argument' 33 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 34 | NOT_MEDIA_FILE_ERR='is not a media file' 35 | 36 | 37 | #=============================================================================== 38 | # check the number of arguments passed to the script 39 | #=============================================================================== 40 | 41 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 42 | 43 | 44 | #=============================================================================== 45 | # getopts check the options passed to the script 46 | #=============================================================================== 47 | 48 | while getopts ':i:o:h' opt 49 | do 50 | case ${opt} in 51 | i) infile="${OPTARG}";; 52 | o) outfile="${OPTARG}";; 53 | h) usage;; 54 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 55 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 56 | esac 57 | done 58 | shift $((OPTIND-1)) 59 | 60 | 61 | #=============================================================================== 62 | # variables 63 | #=============================================================================== 64 | 65 | # input, input name 66 | infile_nopath="${infile##*/}" 67 | infile_name="${infile_nopath%.*}" 68 | infile_ext="${infile##*.}" 69 | 70 | # file command check input file mime type 71 | filetype="$(file --mime-type -b "${infile}")" 72 | 73 | # audio and video mimetypes 74 | mov_mime='video/quicktime' 75 | mkv_mime='video/x-matroska' 76 | mp4_mime='video/mp4' 77 | m4v_mime='video/x-m4v' 78 | wav_mime='audio/x-wav' 79 | audio_mime='audio/mpeg' 80 | aac_mime='audio/x-hx-aac-adts' 81 | m4a_mime='audio/mp4' 82 | 83 | # the file command wrongly identifies .m4a audio as a video file 84 | # so we check if the file extension is .m4a and set the mime type to audio/mp4 85 | if [ "${infile_ext}" = 'm4a' ]; then 86 | filetype="${m4a_mime}" 87 | fi 88 | 89 | # check the files mime type 90 | case "${filetype}" in 91 | "${mov_mime}"|"${mkv_mime}"|"${mp4_mime}"|"${m4v_mime}"| \ 92 | "${wav_mime}"|"${audio_mime}"|"${aac_mime}"|"${m4a_mime}");; 93 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 94 | esac 95 | 96 | # print analyzing file 97 | echo '+ Analyzing file with ffmpeg' 98 | 99 | # ffmpeg loudnorm get stats from file 100 | normalize=$(ffmpeg \ 101 | -hide_banner \ 102 | -i "${infile}" \ 103 | -af "loudnorm=I=-16:dual_mono=true:TP=-1.5:LRA=11:print_format=summary" \ 104 | -f null - 2>&1 | tail -n 12) 105 | 106 | # read the output of normalize line by line and store in variables 107 | for line in "${normalize}"; do 108 | measured_I=$(echo "${line}" | awk -F' ' '/Input Integrated:/ {print $3}') 109 | measured_TP=$(echo "${line}" | awk -F' ' '/Input True Peak:/ {print $4}') 110 | measured_LRA=$(echo "${line}" | awk -F' ' '/Input LRA:/ {print $3}') 111 | measured_thresh=$(echo "${line}" | awk -F' ' '/Input Threshold:/ {print $3}') 112 | offset=$(echo "${line}" | awk -F' ' '/Target Offset:/ {print $3}') 113 | done 114 | 115 | # defaults for variables if not defined 116 | audio_default="${infile_name}-normalized-$(date +"%Y-%m-%d-%H-%M-%S").${infile_ext}" 117 | video_default="${infile_name}-normalized-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 118 | 119 | 120 | #=============================================================================== 121 | # functions 122 | #=============================================================================== 123 | 124 | # audio function 125 | audio () { 126 | ffmpeg \ 127 | -hide_banner \ 128 | -stats -v panic \ 129 | -i "${infile}" \ 130 | -filter_complex \ 131 | "loudnorm=I=-16: 132 | dual_mono=true: 133 | TP=-1.5: 134 | LRA=11: 135 | measured_I=${measured_I}: 136 | measured_LRA=${measured_LRA}: 137 | measured_TP=${measured_TP}: 138 | measured_thresh=${measured_thresh}: 139 | offset=${offset}: 140 | linear=true: 141 | print_format=summary [audio]" \ 142 | -map "[audio]" \ 143 | -ar 44100 \ 144 | "${outfile:=${audio_default}}" 145 | } 146 | 147 | # video function 148 | video () { 149 | ffmpeg \ 150 | -hide_banner \ 151 | -stats -v panic \ 152 | -i "${infile}" \ 153 | -c:v copy \ 154 | -filter_complex \ 155 | "loudnorm=I=-16: 156 | dual_mono=true: 157 | TP=-1.5: 158 | LRA=11: 159 | measured_I=${measured_I}: 160 | measured_LRA=${measured_LRA}: 161 | measured_TP=${measured_TP}: 162 | measured_thresh=${measured_thresh}: 163 | offset=${offset}: 164 | linear=true: 165 | print_format=summary [audio]" \ 166 | -map 0:v -map "[audio]" \ 167 | -ar 44100 \ 168 | -pix_fmt yuv420p \ 169 | -movflags +faststart \ 170 | -f mp4 \ 171 | "${outfile:=${video_default}}" 172 | } 173 | 174 | # check if the mime type is audio or video then run the correct function 175 | case "${filetype}" in 176 | "${mov_mime}"|"${mkv_mime}"|"${mp4_mime}"|"${m4v_mime}") video "${infile}";; 177 | "${wav_mime}"|"${audio_mime}"|"${aac_mime}"|"${m4a_mime}") audio "${infile}";; 178 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 179 | esac 180 | -------------------------------------------------------------------------------- /scene-detect: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # scene-detect 5 | # ffmpeg scene detection 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe awk 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | $(basename "$0") -s 00:00:00 -i input -e 00:00:00 -t (0.1 - 0.9) -f sec -o output 21 | 22 | -s 00:00:00 : start time 23 | -i input.(mp4|mov|mkv|m4v) 24 | -e 00:00:00 : end time 25 | -t (0.1 - 0.9) # threshold 26 | -f sec # output in seconds 27 | -o output.txt" 28 | exit 2 29 | } 30 | 31 | 32 | #=============================================================================== 33 | # error messages 34 | #=============================================================================== 35 | 36 | INVALID_OPT_ERR='Invalid option:' 37 | REQ_ARG_ERR='requires an argument' 38 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 39 | NOT_MEDIA_FILE_ERR='is not a media file' 40 | 41 | 42 | #=============================================================================== 43 | # check the number of arguments passed to the script 44 | #=============================================================================== 45 | 46 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 47 | 48 | 49 | #=============================================================================== 50 | # getopts check the options passed to the script 51 | #=============================================================================== 52 | 53 | while getopts ':s:i:e:t:o:f:h' opt 54 | do 55 | case ${opt} in 56 | s) start="${OPTARG}";; 57 | i) input="${OPTARG}";; 58 | e) end="${OPTARG}";; 59 | t) threshold="${OPTARG}";; 60 | o) output="${OPTARG}";; 61 | f) format="${OPTARG}";; 62 | h) usage;; 63 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 64 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 65 | esac 66 | done 67 | shift $((OPTIND-1)) 68 | 69 | 70 | #=============================================================================== 71 | # variables 72 | #=============================================================================== 73 | 74 | # get the input file name 75 | input_nopath="${input##*/}" 76 | input_name="${input_nopath%.*}" 77 | 78 | # output file name 79 | output_default="${input_name}-detection-$(date +"%Y-%m-%d-%H-%M-%S").txt" 80 | 81 | # threshold default 82 | threshold_default='0.3' 83 | 84 | # start default 85 | start_default='0.0' 86 | 87 | 88 | #=============================================================================== 89 | # ffprobe video duration 90 | #=============================================================================== 91 | 92 | ffduration () { 93 | # video duration to append to cutfile 94 | duration_default=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${input}") 95 | 96 | # if video duration is empty exit 97 | [ -n "${duration_default}" ] || usage "${input} ${NOT_MEDIA_FILE_ERR}" 98 | } 99 | 100 | 101 | #=============================================================================== 102 | # convert time input to seconds 103 | #=============================================================================== 104 | 105 | checktime () { 106 | start=$(echo "${start}" | awk -F: 'NF==3 { print ($1 * 3600) + ($2 * 60) + $3 } NF==2 { print ($1 * 60) + $2 } NF==1 { print 0 + $1 }') 107 | end=$(echo "${end}" | awk -F: 'NF==3 { print ($1 * 3600) + ($2 * 60) + $3 } NF==2 { print ($1 * 60) + $2 } NF==1 { print 0 + $1 }') 108 | } 109 | 110 | 111 | #=============================================================================== 112 | # ffmpeg scene detection 113 | #=============================================================================== 114 | 115 | # scene detection 116 | ffdetection () { 117 | detection="$(ffmpeg -hide_banner -i "${input}" -filter_complex "select='gt(scene,"${threshold:=${threshold_default}}")',metadata=print:file=-" -f null -)" 118 | } 119 | 120 | # scene detection range 121 | ffdetection_range () { 122 | detection="$(ffmpeg -hide_banner -i "${input}" \ 123 | -filter_complex "[0:0]select='between(t\,"${start}"\,"${end}")'[time];\ 124 | [time]select='gt(scene,"${threshold:=${threshold_default}}")',metadata=print:file=-[out]" -map "[out]" -f null -)" 125 | } 126 | 127 | 128 | #=============================================================================== 129 | # create cutfile - prepend start and append end or duration 130 | #=============================================================================== 131 | 132 | cutfile_seconds () { 133 | echo "${detection}" \ 134 | | awk -F':' 'BEGIN { printf("%s\n", '"${start:=${start_default}}"') }/pts_time/ { printf("%s\n", $4) } END { printf("%s\n", '"${end:=${duration_default}}"') }' > "${output:=${output_default}}" 135 | } 136 | 137 | cutfile_minutes () { 138 | echo "${detection}" \ 139 | | awk -F':' 'BEGIN { printf("%s\n", '"${start:=${start_default}}"') }/pts_time/ { printf("%s\n", $4) } END { printf("%s\n", '"${end:=${duration_default}}"') }' | awk -F. 'NF==1 { printf("%02d:%02d:%02d\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60) }\ 140 | NF==2 { printf("%02d:%02d:%02d.%s\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60), ($2) }' \ 141 | > "${output:=${output_default}}" 142 | } 143 | 144 | 145 | #=============================================================================== 146 | # run function 147 | #=============================================================================== 148 | 149 | if [ -n "${start}" ] && [ -n "${end}" ]; then 150 | # scene detect range in video 151 | checktime 152 | ffdetection_range 153 | if [ -n "${format}" ]; then 154 | cutfile_seconds 155 | else 156 | cutfile_minutes 157 | fi 158 | elif [ -n "${start}" ]; then 159 | usage "${start} ${WRONG_ARGS_ERR}" 160 | elif [ -n "${end}" ]; then 161 | usage "${end} ${WRONG_ARGS_ERR}" 162 | else 163 | # scene detect entire video 164 | ffduration 165 | ffdetection 166 | if [ -n "${format}" ]; then 167 | cutfile_seconds 168 | else 169 | cutfile_minutes 170 | fi 171 | fi 172 | -------------------------------------------------------------------------------- /overlay-pip: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # overlay-pip 5 | # create a picture in picture video 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe cut bc 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # create a picture in picture 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v) -v input.(mp4|mkv|mov|m4v) -p [0-999] 24 | -m [00] -x (tl|tr|bl|br) -w [000] -f (0.1-9|1) -b [00] -c colour -o output.mp4 25 | 26 | -i input.(mp4|mkv|mov|m4v) : bottom video 27 | -v input.(mp4|mkv|mov|m4v) : overlay video 28 | -p [0-999] : time to overlay the video 29 | -m [00] : margin defaults to 0 30 | -x (tl|tr|bl|br) : pip position - defaults to tr 31 | -w [000] : width - defaults to 1/4 of video size 32 | -f (0.1-9|1) : fade from 0.1 to 1 - defaults to 0.2 33 | -b [00] : border 34 | -c colour : colour 35 | -o output.mp4 : optional argument # if option not provided defaults to input-name-pip-date-time" 36 | exit 2 37 | } 38 | 39 | 40 | #=============================================================================== 41 | # error messages 42 | #=============================================================================== 43 | 44 | INVALID_OPT_ERR='Invalid option:' 45 | REQ_ARG_ERR='requires an argument' 46 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 47 | 48 | 49 | #=============================================================================== 50 | # check the number of arguments passed to the script 51 | #=============================================================================== 52 | 53 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 54 | 55 | 56 | #=============================================================================== 57 | # getopts check the options passed to the script 58 | #=============================================================================== 59 | 60 | while getopts ':i:v:p:m:x:w:f:c:b:o:h' opt 61 | do 62 | case ${opt} in 63 | i) video="${OPTARG}";; 64 | v) overlay="${OPTARG}";; 65 | p) position="${OPTARG}";; 66 | m) margin="${OPTARG}";; 67 | x) pip="${OPTARG}" 68 | case "${pip}" in 69 | tl|tr|bl|br);; 70 | *) usage "${pip} ${INVALID_OPT_ERR}";; 71 | esac;; 72 | w) width="${OPTARG}";; 73 | f) fade="${OPTARG}";; 74 | c) colour="${OPTARG}";; 75 | b) border="${OPTARG}";; 76 | o) outfile="${OPTARG}";; 77 | h) usage;; 78 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 79 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 80 | esac 81 | done 82 | shift $((OPTIND-1)) 83 | 84 | 85 | #=============================================================================== 86 | # variables 87 | #=============================================================================== 88 | 89 | # video name extension and overlay video extensions 90 | video_nopath="${video##*/}" 91 | video_name="${video_nopath%.*}" 92 | 93 | # defaults for variables if not defined 94 | outfile_default="${video_name}-pip-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 95 | 96 | 97 | #=============================================================================== 98 | # functions 99 | #=============================================================================== 100 | 101 | # overlay video function 102 | overlay_video () { 103 | ffmpeg \ 104 | -hide_banner \ 105 | -stats -v panic \ 106 | -i "${video}" \ 107 | -i "${overlay}" \ 108 | -filter_complex \ 109 | "[0:0]setpts=PTS-STARTPTS[firstclip]; 110 | [1:0]setpts=PTS+${position}/TB[secondclip]; 111 | [firstclip][secondclip]overlay=enable='between(t\,${position},${dp})'[ov]; 112 | [0:0]scale=${width_scale:=${width_default}}${draw_border}[pip]; 113 | [pip]format=pix_fmts=yuva420p,fade=t=in:st=${position}:d=${fade:=${fade_default}}:alpha=1,fade=t=out:st=${fade_end}:d=${fade:=${fade_default}}:alpha=1[pipfade]; 114 | [ov][pipfade]overlay=${pip:=${pip_default}}:enable='between(t\,${position},${dp})'[pv]" \ 115 | -map "[pv]" -map 0:1 \ 116 | -pix_fmt yuv420p \ 117 | -c:a copy -c:v libx264 -crf 18 \ 118 | -movflags +faststart \ 119 | -f mp4 \ 120 | "${outfile:=${outfile_default}}" 121 | } 122 | 123 | # get overlay videos duration with ffprobe 124 | duration="$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${overlay}" | cut -d\. -f1)" 125 | 126 | # position + duration 127 | dp="$((position+duration))" 128 | 129 | # fade default 130 | fade_default='0.2' 131 | fade_end="$(echo "${dp}" - "${fade:=${fade_default}}" | bc)" 132 | 133 | # pip padding 134 | margin_default='20' 135 | 136 | # pip default position 137 | pip_default="${tr}" 138 | 139 | # pip position 140 | tl="${margin:=${margin_default}}:${margin:=${margin_default}}" 141 | tr="main_w-overlay_w-${margin:=${margin_default}}:${margin:=${margin_default}}" 142 | bl="${margin:=${margin_default}}:main_h-overlay_h-${margin:=${margin_default}}" 143 | br="main_w-overlay_w-${margin:=${margin_default}}:main_h-overlay_h-${margin:=${margin_default}}" 144 | 145 | # pip window size 146 | width_default='iw/4:ih/4' 147 | 148 | # check if width is null and unset 149 | if [ -z "${width}" ]; then 150 | : # width not set pass 151 | else 152 | width_scale="${width}:-1" 153 | fi 154 | 155 | # border 156 | # divide border by 2 for the offset 157 | border_default='4' 158 | offset_default='2' 159 | colour_default='#2f2f2f' 160 | offset="$((${border:=${border_default}}/2))" 161 | draw_border=",pad=w=${border:=${border_default}}+iw:h=${border:=${border_default}}+ih:x=${offset:=${offset_default}}:y=${offset:=${offset_default}}:color=${colour:=${colour_default}}" 162 | 163 | # dont show border if border set to 0 164 | if [ "${border}" = 0 ]; then 165 | draw_border='' 166 | fi 167 | 168 | # pip position case 169 | case "${pip}" in 170 | tl)pip="${tl}";; 171 | tr)pip="${tr}";; 172 | bl)pip="${bl}";; 173 | br)pip="${br}";; 174 | *) pip="${tr}";; 175 | esac 176 | 177 | # run the overlay_video function 178 | overlay_video "${video}" "${overlay}" 179 | -------------------------------------------------------------------------------- /crossfade-clips: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # crossfade-clips 5 | # cross fade video clips 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg file grep 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # ffmpeg cross fade clips 22 | 23 | $(basename "$0") -a clip1.(mp4|mkv|mov|m4v) -b clip2.(mp4|mkv|mov|m4v) -d (1|2) -o output.mp4 24 | -a clip1.(mp4|mkv|mov|m4v) : first clip 25 | -b clip2.(mp4|mkv|mov|m4v) : second clip 26 | -d (1|2) : cross fade duration :optional argument # if option not provided defaults to 1 second 27 | -o output.mp4 : optional argument # if option not provided defaults to input-name-xfade-date-time" 28 | exit 2 29 | } 30 | 31 | 32 | #=============================================================================== 33 | # error messages 34 | #=============================================================================== 35 | 36 | INVALID_OPT_ERR='Invalid option:' 37 | REQ_ARG_ERR='requires an argument' 38 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 39 | 40 | 41 | #=============================================================================== 42 | # check the number of arguments passed to the script 43 | #=============================================================================== 44 | 45 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 46 | 47 | 48 | #=============================================================================== 49 | # getopts check the options passed to the script 50 | #=============================================================================== 51 | 52 | while getopts ':a:b:d:o:h' opt 53 | do 54 | case ${opt} in 55 | a) clip1="${OPTARG}";; 56 | b) clip2="${OPTARG}";; 57 | d) dur="${OPTARG}";; 58 | o) outfile="${OPTARG}";; 59 | h) usage;; 60 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 61 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 62 | esac 63 | done 64 | shift $((OPTIND-1)) 65 | 66 | #=============================================================================== 67 | # variables 68 | #=============================================================================== 69 | 70 | # input, input name and extension 71 | clip1_nopath="${clip1##*/}" 72 | clip1_name="${clip1_nopath%.*}" 73 | 74 | # clip durations for fades 75 | clip1_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$clip1" | cut -d\. -f1) 76 | clip2_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "$clip2" | cut -d\. -f1) 77 | 78 | # clip1 use the bc command to remove 1 second from length of clip for cross fade 79 | clip1_offset=$(echo "${clip1_dur}-1" | bc -l) 80 | clip2_offset=$(echo "${clip2_dur}-1" | bc -l) 81 | 82 | # variables 83 | outfile_default="${clip1_name}-xfade-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 84 | duration_default='1' 85 | 86 | 87 | #=============================================================================== 88 | # functions 89 | #=============================================================================== 90 | 91 | # cross fade video and audio 92 | xfade_va () { 93 | ffmpeg \ 94 | -hide_banner \ 95 | -stats -v panic \ 96 | -i "${clip1}" -i "${clip2}" \ 97 | -an -filter_complex \ 98 | " [0:v]trim=start=0:end='${clip1_offset}',setpts=PTS-STARTPTS[firstclip]; 99 | [1:v]trim=start=${dur:=${duration_default}},setpts=PTS-STARTPTS[secondclip]; 100 | [0:v]trim=start='${clip1_offset}':end='${clip1_dur}',setpts=PTS-STARTPTS[fadeoutsrc]; 101 | [1:v]trim=start=0:end=${dur:=${duration_default}},setpts=PTS-STARTPTS[fadeinsrc]; 102 | [fadeinsrc]format=pix_fmts=yuva420p, 103 | fade=t=in:st=0:d=${dur:=${duration_default}}:alpha=1[fadein]; 104 | [fadeoutsrc]format=pix_fmts=yuva420p, 105 | fade=t=out:st=0:d=${dur:=${duration_default}}:alpha=1[fadeout]; 106 | [fadein]fifo[fadeinfifo]; 107 | [fadeout]fifo[fadeoutfifo]; 108 | [fadeoutfifo][fadeinfifo]overlay[crossfade]; 109 | [firstclip][crossfade][secondclip]concat=n=3[output]; 110 | [0:a] afade=t=in:st=0:d=${dur:=${duration_default}} [audiofadein]; 111 | [1:a] afade=t=out:st='${clip2_offset}':d=${dur:=${duration_default}} [audiofadeout]; 112 | [audiofadein][audiofadeout] acrossfade=d=${dur:=${duration_default}} [audio] 113 | " \ 114 | -map "[output]" -map "[audio]" "${outfile:=${outfile_default}}" 115 | } 116 | 117 | # cross fade video 118 | xfade_v () { 119 | ffmpeg \ 120 | -hide_banner \ 121 | -stats -v panic \ 122 | -i "${clip1}" -i "${clip2}" \ 123 | -an -filter_complex \ 124 | " [0:v]trim=start=0:end='${clip1_offset}',setpts=PTS-STARTPTS[firstclip]; 125 | [1:v]trim=start=${dur:=${duration_default}},setpts=PTS-STARTPTS[secondclip]; 126 | [0:v]trim=start='${clip1_offset}':end='${clip1_dur}',setpts=PTS-STARTPTS[fadeoutsrc]; 127 | [1:v]trim=start=0:end=${dur:=${duration_default}},setpts=PTS-STARTPTS[fadeinsrc]; 128 | [fadeinsrc]format=pix_fmts=yuva420p, 129 | fade=t=in:st=0:d=${dur:=${duration_default}}:alpha=1[fadein]; 130 | [fadeoutsrc]format=pix_fmts=yuva420p, 131 | fade=t=out:st=0:d=${dur:=${duration_default}}:alpha=1[fadeout]; 132 | [fadein]fifo[fadeinfifo]; 133 | [fadeout]fifo[fadeoutfifo]; 134 | [fadeoutfifo][fadeinfifo]overlay[crossfade]; 135 | [firstclip][crossfade][secondclip]concat=n=3[output] 136 | " \ 137 | -map "[output]" "${outfile:=${outfile_default}}" 138 | } 139 | 140 | # check if video has an audio track 141 | clip1_check="$(ffprobe -i "${clip1}" -show_streams -select_streams a -loglevel error)" 142 | clip2_check="$(ffprobe -i "${clip2}" -show_streams -select_streams a -loglevel error)" 143 | 144 | # check if audio_check is null which means the video doesnt have an audio track 145 | if [ -z "${clip1_check}" ] || [ -z "${clip2_check}" ]; then 146 | xfade_v "${clip1}" "${clip2}" # fade video track 147 | else 148 | xfade_va "${clip1}" "${clip2}" # fade video and audio track 149 | fi 150 | -------------------------------------------------------------------------------- /tile-thumbnails: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # tile-thumbnails 5 | # create an image with thumbnails from a video 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe awk bc 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage() 16 | { 17 | echo "\ 18 | # create an image with thumbnails from a video 19 | 20 | $(basename "$0") -i input -s 00:00:00.000 -w 000 -t 0x0 -p 00 -m 00 -c color -f fontcolor -b boxcolor -x on -o output.png 21 | 22 | -i input.(mp4|mkv|mov|m4v|webm) 23 | -s seek into the video file : default 00:00:05 24 | -w thumbnail width : 160 25 | -t tile layout format width x height : 4x3 : default 4x3 26 | -p padding between images : default 7 27 | -m margin : default 2 28 | -c color = https://ffmpeg.org/ffmpeg-utils.html#color-syntax : default black 29 | -f fontcolor : default white 30 | -b boxcolor : default black 31 | -x on : default off, display timestamps 32 | -o output.png : optional argument 33 | # if option not provided defaults to input-name-tile-date-time.png" 34 | exit 2 35 | } 36 | 37 | 38 | #=============================================================================== 39 | # error messages 40 | #=============================================================================== 41 | 42 | INVALID_OPT_ERR='Invalid option:' 43 | REQ_ARG_ERR='requires an argument' 44 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 45 | 46 | 47 | #=============================================================================== 48 | # check the number of arguments passed to the script 49 | #=============================================================================== 50 | 51 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 52 | 53 | 54 | #=============================================================================== 55 | # getopts check the options passed to the script 56 | #=============================================================================== 57 | 58 | while getopts ':i:s:w:t:p:m:c:b:f:x:o:h' opt 59 | do 60 | case ${opt} in 61 | i) infile="${OPTARG}";; 62 | s) seek="${OPTARG}";; 63 | w) scale="${OPTARG}";; 64 | t) tile="${OPTARG}";; 65 | p) padding="${OPTARG}";; 66 | m) margin="${OPTARG}";; 67 | c) color="${OPTARG}";; 68 | f) fontcolor="${OPTARG}";; 69 | b) boxcolor="${OPTARG}";; 70 | x) timestamp="${OPTARG}";; 71 | o) outfile="${OPTARG}";; 72 | h) usage;; 73 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 74 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 75 | esac 76 | done 77 | shift $((OPTIND-1)) 78 | 79 | 80 | #=============================================================================== 81 | # variables 82 | #=============================================================================== 83 | 84 | # input, input name 85 | infile_nopath="${infile##*/}" 86 | infile_name="${infile_nopath%.*}" 87 | 88 | # ffprobe get fps and duration 89 | videostats=$(ffprobe \ 90 | -v error \ 91 | -select_streams v:0 \ 92 | -show_entries stream=r_frame_rate:format=duration \ 93 | -of default=noprint_wrappers=1 \ 94 | "${infile}") 95 | 96 | # fps 97 | fps=$(echo "${videostats}" | awk -F'[=//]' '/r_frame_rate/{print $2/$3}') 98 | 99 | # duration 100 | duration=$(echo "${videostats}" | awk -F'[=/.]' '/duration/{print $2}') 101 | 102 | # check if tile is null 103 | if [ -z "${tile}" ]; then 104 | : # tile variable not set : = pass 105 | else 106 | # tile variable set 107 | # tile layout 108 | tile_w=$(echo "${tile}" | awk -F'x' '{print $1}') 109 | tile_h=$(echo "${tile}" | awk -F'x' '{print $2}') 110 | # title sum 111 | tile_sum=$(echo "${tile_w} * ${tile_h}" | bc) 112 | fi 113 | 114 | # defaults 115 | seek_default='00:00:05' 116 | scale_default='160' 117 | tile_layout_default='4x3' 118 | tile_default='12' 119 | padding_default='7' 120 | margin_default='2' 121 | color_default='black' 122 | fontcolor_default='white' 123 | boxcolor_default='black' 124 | timestamp_default='off' 125 | pts_default='5' 126 | pts=$(printf "%s %s\n" "${seek}" | awk '{ 127 | start = $1 128 | if (start ~ /:/) { 129 | split(start, t, ":") 130 | seconds = (t[1] * 3600) + (t[2] * 60) + t[3] 131 | } 132 | printf("%s\n"), seconds 133 | }') 134 | 135 | outfile_default="${infile_name}-tile-$(date +"%Y-%m-%d-%H-%M-%S").png" 136 | 137 | # duration * fps / number of tiles 138 | frames=$(echo "${duration} * ${fps} / ${tile_sum:=${tile_default}}" | bc) 139 | 140 | 141 | #=============================================================================== 142 | # functions 143 | #=============================================================================== 144 | 145 | # contact sheet - no timestamps 146 | tilevideo () { 147 | ffmpeg \ 148 | -hide_banner \ 149 | -stats -v panic \ 150 | -ss "${seek:=${seek_default}}" \ 151 | -i "${infile}" \ 152 | -frames 1 -vf "select=not(mod(n\,${frames})),scale=${scale:=${scale_default}}:-1,tile=${tile:=${tile_layout_default}}:padding=${padding:=${padding_default}}:margin=${margin:=${margin_default}}:color=${color:=${color_default}}" \ 153 | "${outfile:=${outfile_default}}" 154 | } 155 | 156 | 157 | # contact sheet - timestamps 158 | timestamp () { 159 | ffmpeg \ 160 | -hide_banner \ 161 | -stats -v panic \ 162 | -ss "${seek:=${seek_default}}" \ 163 | -i "${infile}" \ 164 | -frames 1 -vf "drawtext=text='%{pts\:hms\:${pts:=${pts_default}}}':x='(main_w-text_w)/2':y='(main_h-text_h)':fontcolor=${fontcolor:=${fontcolor_default}}:fontsize='(main_h/8)':boxcolor=${boxcolor:=${boxcolor_default}}:box=1,select=not(mod(n\,${frames})),scale=${scale:=${scale_default}}:-1,tile=${tile:=${tile_layout_default}}:padding=${padding:=${padding_default}}:margin=${margin:=${margin_default}}:color=${color:=${color_default}}" \ 165 | "${outfile:=${outfile_default}}" 166 | } 167 | 168 | 169 | #=============================================================================== 170 | # check option passed to script 171 | #=============================================================================== 172 | 173 | if [ "${timestamp}" == on ]; then 174 | timestamp "${infile}" # -x on 175 | elif [ ! -z "${fontcolor}" ]; then 176 | timestamp "${infile}" # -f 177 | elif [ ! -z "${boxcolor}" ]; then 178 | timestamp "${infile}" # -b 179 | elif [ -z "${timestamp}" ]; then 180 | tilevideo "${infile}" # no timestamp 181 | else 182 | tilevideo "${infile}" # no timestamp 183 | fi 184 | -------------------------------------------------------------------------------- /zoompan: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # zoompan 5 | # ken burns effect 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg ffprobe 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | # script usage 16 | usage () 17 | { 18 | # if argument passed to function echo it 19 | [ -z "${1}" ] || echo "! ${1}" 20 | # display help 21 | echo "\ 22 | # zoompan, ken burns effect 23 | 24 | $(basename "$0") -i input.(png|jpg|jpeg) -d (000) -z (in|out) -p (tl|c|tc|tr|bl|br) -o output.mp4 25 | -i = input.(png|jpg|jpeg) 26 | -d = duration : from 1-999 27 | -z = zoom : in or out 28 | -p = position : zoom to location listed below 29 | -o = outfile.mp4 : optional argument # default is input-name-zoompan-date-time 30 | 31 | +------------------------------+ 32 | +tl tc tr+ 33 | + + 34 | + c + 35 | + + 36 | +bl br+ 37 | +------------------------------+" 38 | exit 2 39 | } 40 | 41 | 42 | #=============================================================================== 43 | # error messages 44 | #=============================================================================== 45 | 46 | INVALID_OPT_ERR='Invalid option:' 47 | REQ_ARG_ERR='requires an argument' 48 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 49 | NOT_MEDIA_FILE_ERR='is not a media file' 50 | 51 | 52 | #=============================================================================== 53 | # check the number of arguments passed to the script 54 | #=============================================================================== 55 | 56 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 57 | 58 | 59 | #=============================================================================== 60 | # getopts check the options passed to the script 61 | #=============================================================================== 62 | 63 | while getopts ':i:d:z:p:o:h' opt 64 | do 65 | case ${opt} in 66 | i) infile="${OPTARG}";; 67 | d) dur="${OPTARG}";; 68 | z) zoom="${OPTARG}" 69 | [ "${zoom}" = 'in' ] || [ "${zoom}" = 'out' ];; 70 | p) position="${OPTARG}";; 71 | o) outfile="${OPTARG}";; 72 | h) usage;; 73 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 74 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 75 | esac 76 | done 77 | shift $((OPTIND-1)) 78 | 79 | 80 | #=============================================================================== 81 | # variables 82 | #=============================================================================== 83 | 84 | # input, input name and extension 85 | infile_nopath="${infile##*/}" 86 | infile_name="${infile_nopath%.*}" 87 | 88 | # ffprobe get image height 89 | imgheight="$(ffprobe -v error -select_streams v:0 -show_entries stream=height -of default=nw=1:nk=1 "${infile}")" 90 | 91 | # frames per seconds 92 | infps='30' 93 | 94 | # zoom in positions 95 | 96 | # zoom in top left 97 | zi_top_left="scale=-2:10*ih,\ 98 | zoompan=z='min(zoom+0.0015,1.5)':\ 99 | fps=${infps}:d=${infps}*${dur},\ 100 | scale=-2:${imgheight}" \ 101 | 102 | # zoom in center 103 | zi_center="scale=-2:10*ih,\ 104 | zoompan=z='min(zoom+0.0015,1.5)':\ 105 | fps=${infps}:d=${infps}*${dur}:\ 106 | x='iw/2-(iw/zoom/2)':\ 107 | y='ih/2-(ih/zoom/2)',\ 108 | scale=-2:${imgheight}" \ 109 | 110 | # zoom in top center 111 | zi_top_center="scale=-2:10*ih,\ 112 | zoompan=z='min(zoom+0.0015,1.5)':\ 113 | fps=${infps}:d=${infps}*${dur}:\ 114 | x='iw/2-(iw/zoom/2)',\ 115 | scale=-2:${imgheight}" \ 116 | 117 | # zoom in top right 118 | zi_top_right="scale=-2:10*ih,\ 119 | zoompan=z='min(zoom+0.0015,1.5)':\ 120 | fps=${infps}:d=${infps}*${dur}:\ 121 | x='iw/zoom-(iw/zoom/2)',\ 122 | scale=-2:${imgheight}" \ 123 | 124 | # zoom out positions 125 | 126 | # zoom out top left 127 | zo_top_left="scale=-2:2*ih,\ 128 | zoompan=z='if(lte(zoom,1.0),1.5,max(1.001,zoom-0.0015))':\ 129 | fps=${infps}:d=${infps}*${dur},\ 130 | scale=-2:${imgheight}" \ 131 | 132 | zo_center="scale=-2:2*ih,\ 133 | zoompan=z='if(lte(zoom,1.0),1.5,max(1.001,zoom-0.0015))':\ 134 | fps=${infps}:d=${infps}*${dur}:\ 135 | x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)',\ 136 | scale=-2:${imgheight}" \ 137 | 138 | # zoom out top center 139 | zo_top_center="scale=-2:2*ih,\ 140 | zoompan=z='if(lte(zoom,1.0),1.5,max(1.001,zoom-0.0015))':\ 141 | fps=${infps}:d=${infps}*${dur}:\ 142 | x='iw/2-(iw/zoom/2)',\ 143 | scale=-2:${imgheight}" \ 144 | 145 | # zoom out top right 146 | zo_top_right="scale=-2:2*ih,\ 147 | zoompan=z='if(lte(zoom,1.0),1.5,max(1.001,zoom-0.0015))':\ 148 | fps=${infps}:d=${infps}*${dur}:\ 149 | x='iw/zoom-(iw/zoom/2)',\ 150 | scale=-2:${imgheight}" \ 151 | 152 | # zoom out bottom left 153 | zo_bottom_left="scale=-2:2*ih,\ 154 | zoompan=z='if(lte(zoom,1.0),1.5,max(1.001,zoom-0.0015))':\ 155 | fps=${infps}:d=${infps}*${dur}:\ 156 | y='ih-ih/zoom',\ 157 | scale=-2:${imgheight}" \ 158 | 159 | # zoom out bottom right 160 | zo_bottom_right="scale=-2:2*ih,\ 161 | zoompan=z='if(lte(zoom,1.0),1.5,max(1.001,zoom-0.0015))':\ 162 | fps=${infps}:d=${infps}*${dur}:\ 163 | x='iw-iw/zoom':\ 164 | y='ih-ih/zoom',\ 165 | scale=-2:${imgheight}" \ 166 | 167 | # outfile file recording destination 168 | outfile_default="${infile_name}-zoompan-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 169 | 170 | 171 | #=============================================================================== 172 | # function 173 | #=============================================================================== 174 | 175 | # zoom function 176 | zoom_func () { 177 | ffmpeg \ 178 | -hide_banner \ 179 | -stats -v panic \ 180 | -r 30 \ 181 | -i "${infile}" \ 182 | -filter_complex \ 183 | "${1}" \ 184 | -y -shortest \ 185 | -c:v libx264 -crf 18 -profile:v high \ 186 | -r 30 -pix_fmt yuv420p \ 187 | -movflags +faststart -f mp4 \ 188 | "${outfile:=${outfile_default}}" 189 | } 190 | 191 | # check zoom and position 192 | case "${zoom}" in 193 | in) 194 | case "${position}" in 195 | tl) zoom_func "${zi_top_left}" "${infile}";; 196 | tc) zoom_func "${zi_top_center}" "${infile}";; 197 | c) zoom_func "${zi_center}" "${infile}";; 198 | tr) zoom_func "${zi_top_right}" "${infile}";; 199 | *) help;; 200 | esac 201 | ;; 202 | out) 203 | case "${position}" in 204 | tl) zoom_func "${zo_top_left}" "${infile}";; 205 | tc) zoom_func "${zo_top_center}" "${infile}";; 206 | c) zoom_func "${zo_center}" "${infile}";; 207 | tr) zoom_func "${zo_top_right}" "${infile}";; 208 | bl) zoom_func "${zo_bottom_left}" "${infile}";; 209 | br) zoom_func "${zo_bottom_right}" "${infile}";; 210 | *) help;; 211 | esac 212 | ;; 213 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 214 | esac 215 | -------------------------------------------------------------------------------- /combine-clips: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # combine-clips 5 | # combine an image or video file with an audio clip 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg file grep 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # combine an image or video file with an audio clip 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v|png|jpg) -a audio.(m4a|aac|wav|mp3) -o output.mp4 24 | -i input.(mp4|mkv|mov|m4v|png|jpg) 25 | -a audio.(m4a|aac|wav|mp3) 26 | -o output.mp4 : optional argument # if option not provided defaults to input-name-combined-date-time" 27 | exit 2 28 | } 29 | 30 | 31 | #=============================================================================== 32 | # error messages 33 | #=============================================================================== 34 | 35 | INVALID_OPT_ERR='Invalid option:' 36 | REQ_ARG_ERR='requires an argument' 37 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 38 | NOT_MEDIA_FILE_ERR='is not a media file' 39 | 40 | 41 | #=============================================================================== 42 | # check the number of arguments passed to the script 43 | #=============================================================================== 44 | 45 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 46 | 47 | 48 | #=============================================================================== 49 | # getopts check the options passed to the script 50 | #=============================================================================== 51 | 52 | while getopts ':i:a:o:h' opt 53 | do 54 | case ${opt} in 55 | i) infile="${OPTARG}";; 56 | a) audio="${OPTARG}";; 57 | o) outfile="${OPTARG}";; 58 | h) usage;; 59 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 60 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 61 | esac 62 | done 63 | shift $((OPTIND-1)) 64 | 65 | 66 | #=============================================================================== 67 | # variables 68 | #=============================================================================== 69 | 70 | # input, input name 71 | infile_nopath="${infile##*/}" 72 | infile_name="${infile_nopath%.*}" 73 | 74 | # audio file extension 75 | audio_ext="${audio##*.}" 76 | 77 | # file command check input file mime type 78 | infile_filetype="$(file --mime-type -b "${infile}")" 79 | audio_filetype="$(file --mime-type -b "${audio}")" 80 | 81 | # audio and video mimetypes 82 | mov_mime='video/quicktime' 83 | mkv_mime='video/x-matroska' 84 | mp4_mime='video/mp4' 85 | m4v_mime='video/x-m4v' 86 | aac_mime='audio/x-hx-aac-adts' 87 | m4a_mime='audio/mp4' 88 | 89 | # the file command wrongly identifies .m4a audio as a video file 90 | # so we check if the file extension is .m4a and set the mime type to audio/mp4 91 | if [ "${audio_ext}" = 'm4a' ]; then 92 | audio_filetype="${m4a_mime}" 93 | fi 94 | 95 | # image mimetypes 96 | png_mime='image/png' 97 | jpg_mime='image/jpeg' 98 | 99 | # defaults for variables 100 | outfile_default="${infile_name}-combined-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 101 | 102 | # check if the libfdk_aac codec is installed, if not fall back to the aac codec 103 | aac_codec="$(ffmpeg -hide_banner -stats -v panic -h encoder=libfdk_aac)" 104 | aac_error="Codec 'libfdk_aac' is not recognized by FFmpeg." 105 | aac_check="$(echo "${aac_codec}" | grep "${aac_error}")" 106 | 107 | # check ffmpeg aac codecs 108 | if [ -z "${aac_check}" ]; then 109 | aac='libfdk_aac' # libfdk_aac codec is installed 110 | else 111 | aac='aac' # libfdk_aac codec isnt installed, fall back to aac codec 112 | fi 113 | 114 | #=============================================================================== 115 | # functions 116 | #=============================================================================== 117 | 118 | # video - audio is aac, copy audio stream 119 | record_copy () { 120 | ffmpeg \ 121 | -hide_banner \ 122 | -stats -v panic \ 123 | -i "${infile}" \ 124 | -i "${audio}" \ 125 | -shortest -fflags shortest -max_interleave_delta 100M \ 126 | -c:a copy \ 127 | -c:v copy \ 128 | -map 0:0 -map 1:0 \ 129 | -pix_fmt yuv420p \ 130 | -movflags +faststart \ 131 | -f mp4 \ 132 | "${outfile:=${outfile_default}}" 133 | } 134 | 135 | # video - audio isnt aac, encode audio as aac 136 | record_aac () { 137 | ffmpeg \ 138 | -hide_banner \ 139 | -stats -v panic \ 140 | -i "${infile}" \ 141 | -i "${audio}" \ 142 | -shortest -fflags shortest -max_interleave_delta 100M \ 143 | -c:a "${aac}" \ 144 | -c:v copy \ 145 | -map 0:0 -map 1:0 \ 146 | -pix_fmt yuv420p \ 147 | -movflags +faststart \ 148 | -f mp4 \ 149 | "${outfile:=${outfile_default}}" 150 | } 151 | 152 | # image - audio is aac, copy audio stream 153 | record_img_copy () { 154 | dur="$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${audio}")" 155 | ffmpeg \ 156 | -hide_banner \ 157 | -stats -v panic \ 158 | -framerate 1/"${dur}" \ 159 | -i "${infile}" \ 160 | -i "${audio}" \ 161 | -c:a copy \ 162 | -c:v libx264 -crf 18 -profile:v high \ 163 | -r 30 -pix_fmt yuv420p \ 164 | -movflags +faststart -f mp4 \ 165 | "${outfile:=${outfile_default}}" 166 | } 167 | 168 | # image - audio isnt aac, encode audio as aac 169 | record_img_aac () { 170 | dur="$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${audio}")" 171 | ffmpeg \ 172 | -hide_banner \ 173 | -stats -v panic \ 174 | -framerate 1/"${dur}" \ 175 | -i "${infile}" \ 176 | -i "${audio}" \ 177 | -c:a "${aac}" \ 178 | -c:v libx264 -crf 18 -profile:v high \ 179 | -r 30 -pix_fmt yuv420p \ 180 | -movflags +faststart -f mp4 \ 181 | "${outfile:=${outfile_default}}" 182 | } 183 | 184 | 185 | #=============================================================================== 186 | # case statement 187 | #=============================================================================== 188 | 189 | # run the ffmpeg function based on the audio mime type 190 | case "${infile_filetype}" in 191 | "${mov_mime}"|"${mkv_mime}"|"${mp4_mime}"|"${m4v_mime}") 192 | if [ "${audio_filetype}" = 'audio/x-hx-aac-adts' ]; then 193 | # video - audio is aac, copy audio stream 194 | record_copy 195 | else 196 | # video - audio isnt aac, encode audio as aac 197 | record_aac 198 | fi;; 199 | "${png_mime}"|"${jpg_mime}") 200 | if [ "${audio_filetype}" = "${aac_mime}" ]; then 201 | # image - audio is aac, copy audio stream 202 | record_img_copy 203 | else 204 | # image - audio isnt aac, encode audio as aac 205 | record_img_aac 206 | fi;; 207 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 208 | esac 209 | -------------------------------------------------------------------------------- /ffmpeg-tips.org: -------------------------------------------------------------------------------- 1 | #+STARTUP: content 2 | #+OPTIONS: num:nil author:nil 3 | 4 | * ffmpeg tips 5 | ** Extracting audio 6 | *** Extracting audio from video 7 | 8 | Extracting audio from a video file 9 | 10 | #+BEGIN_SRC sh 11 | ffmpeg -i infile.mp4 -vn -c:a copy outfile.m4a 12 | #+END_SRC 13 | 14 | *** Batch extracting audio from video 15 | 16 | Batch extract audio from video with find 17 | 18 | #+BEGIN_SRC sh 19 | find . -type f -name "*.mp4" -exec sh -c \ 20 | 'ffmpeg -i "${0}" -vn -c:a copy "${0%.*}.m4a"' \ 21 | "{}" \; 22 | #+END_SRC 23 | 24 | ** Extract video tracks 25 | *** extract video with ffmpeg 26 | 27 | ffmpeg extract video 28 | 29 | #+BEGIN_SRC sh 30 | ffmpeg -i infile.mp4 -an -c:v copy outfile.mp4 31 | #+END_SRC 32 | 33 | *** batch extract video with ffmpeg 34 | 35 | ffmpeg batch extract video 36 | 37 | #+BEGIN_SRC sh 38 | find . -type f -name "*.mp4" -exec sh -c \ 39 | 'ffmpeg -i "${0}" -an -c:v copy "${0%.*}-extracted.mp4"' \ 40 | "{}" \; 41 | #+END_SRC 42 | 43 | ** change framerate with ffmpeg 44 | 45 | change a videos framerate to 30fps 46 | 47 | #+BEGIN_SRC sh 48 | ffmpeg -i infile.mp4 -filter:v fps=fps=30 outfile.mp4 49 | #+END_SRC 50 | 51 | ** change framereate batch process 52 | 53 | change frame batch process to 30fps 54 | 55 | #+BEGIN_SRC sh 56 | find . -type f -name "*.mp4" -exec sh -c \ 57 | 'ffmpeg -i "${0}" -filter:v fps=fps=30 "${0%.*}-framerate.mp4"' \ 58 | "{}" \; 59 | #+END_SRC 60 | 61 | ** convert stereo to mono 62 | 63 | #+BEGIN_SRC sh 64 | ffmpeg -i infile.mp4 -ac 1 outfile.mp4 65 | #+END_SRC 66 | 67 | ** convert stereo to mono batch process 68 | 69 | change frame batch process to 30fps 70 | 71 | #+BEGIN_SRC sh 72 | find . -type f -name "*.mp4" -exec sh -c \ 73 | 'ffmpeg -i "${0}" -ac 1 "${0%.*}-mono.mp4"' \ 74 | "{}" \; 75 | #+END_SRC 76 | 77 | ** change audio rate 78 | 79 | change audio rate to 44100 80 | 81 | #+BEGIN_SRC sh 82 | ffmpeg -i infile.mp4 -ar 44100 outfile.mp4 83 | #+END_SRC 84 | ** change audio rate batch process 85 | 86 | change audio rate to 44100 batch process 87 | 88 | #+BEGIN_SRC sh 89 | find . -type f -name "*.mp4" -exec sh -c \ 90 | 'ffmpeg -i "${0}" -ar 44100 "${0%.*}-audiorate.mp4"' \ 91 | "{}" \; 92 | #+END_SRC 93 | 94 | ** scale and pad video to 1080 95 | 96 | scale and pad a video to 1080 97 | 98 | #+BEGIN_SRC sh 99 | ffmpeg -i fps.mp4 -vf "scale=-1:1080,pad=1920:ih:(ow-iw)/2" outfile.mp4 100 | #+END_SRC 101 | 102 | ** scale and pad video to 1080 batch process 103 | 104 | scale and pad a video to 1080 batch process 105 | 106 | #+BEGIN_SRC sh 107 | find . -type f -name "*.mp4" -exec sh -c \ 108 | 'ffmpeg -i "${0}" -vf "scale=-1:1080,pad=1920:ih:(ow-iw)/2" "${0%.*}-1080.mp4"' \ 109 | "{}" \; 110 | #+END_SRC 111 | ** Trim video 112 | 113 | Trim a video clip with a new start and end point 114 | 115 | #+BEGIN_SRC sh 116 | ffmpeg \ 117 | -ss 00:00:30 \ 118 | -i infile.mp4 \ 119 | -t 00:01:30 \ 120 | -c:a copy \ 121 | -c:v libx264 -profile:v high -pix_fmt yuv420p \ 122 | -movflags +faststart -f mp4 outfile.mp4 123 | #+END_SRC 124 | 125 | ** Extract frame as image 126 | 127 | Find all mp4 or mkv files and extract a frame as a png image 128 | 129 | #+BEGIN_SRC sh 130 | find . -type f -name "*.[mp4kv]*" -exec sh -c \ 131 | 'ffmpeg -ss 00:00:05 -i "$1" -q:v 2 -f image2 -vframes 1 "${1%.*}.png" -hide_banner' sh {} \; 132 | #+END_SRC 133 | 134 | ** Convert mkv to mp4 135 | 136 | Convert a mkv video to a mp4 file 137 | to import into your video editor like Final Cut Pro 138 | 139 | #+BEGIN_SRC sh 140 | ffmpeg -i infile.mkv \ 141 | -c:v libx264 -crf 18 -profile:v high \ 142 | -pix_fmt yuv420p -movflags +faststart -f mp4 \ 143 | outfile.mp4 144 | #+END_SRC 145 | 146 | ** Convert audio 147 | *** Convert wav to m4a 148 | 149 | Find wav files and convert to m4a 150 | 151 | #+BEGIN_SRC sh 152 | find . -type f -name "*.wav" -exec sh -c \ 153 | 'ffmpeg -i "$0" -map 0:0 -c:a aac -b:a 320k "${0%.*}.m4a"' "{}" \; 154 | #+END_SRC 155 | 156 | *** Convert wav to mp3 157 | 158 | Find wav files and convert to mp3 159 | 160 | #+BEGIN_SRC sh 161 | find . -type f -name "*.wav" -exec sh -c \ 162 | 'ffmpeg -i "$0" -map 0:0 -c:a libmp3lame -b:a 320k "${0%.*}.mp3"' "{}" \; 163 | #+END_SRC 164 | 165 | ** ffmpeg concat clips 166 | 167 | create a list of all the mp4s in the current directory 168 | 169 | #+BEGIN_SRC sh 170 | printf "file '%s'\n" *.mp4 > list.txt 171 | #+END_SRC 172 | 173 | use ffplay to play the list of videos in the text file 174 | 175 | #+BEGIN_SRC sh 176 | ffmpeg -f concat -i list.txt -c copy outfile.mp4 177 | #+END_SRC 178 | 179 | use ffmpeg to concat the video file in the text file 180 | 181 | #+BEGIN_SRC sh 182 | ffmpeg -f concat -i list.txt -c copy outfile.mp4 183 | #+END_SRC 184 | 185 | use subshell to generate a list of the mp4s in the current directory 186 | 187 | #+BEGIN_SRC sh 188 | ffmpeg -f concat -i <( for f in *.mp4; do echo "file '$(pwd)/$f'"; done ) outfile.mp4 189 | #+END_SRC 190 | 191 | ** Closed captions 192 | 193 | Extracting, adding and deleting closed captions from videos 194 | 195 | *** youtube_dl download subtitles 196 | 197 | youtube_dl download subtitles from video 198 | 199 | #+BEGIN_SRC sh 200 | youtube-dl --write-sub --sub-lang en --skip-download 'youtube-url' 201 | #+END_SRC 202 | 203 | youtube-dl batch download subtitles from a text file with youtube urls 204 | 205 | #+BEGIN_SRC sh 206 | youtube-dl --write-sub --sub-lang en --skip-download -a links.txt 207 | #+END_SRC 208 | 209 | **** convert closed captions to srt 210 | 211 | convert scc closed captions to srt subtitles, 212 | and remove text formatting and font tags 213 | for youtube 214 | 215 | #+BEGIN_SRC sh 216 | ffmpeg -i infile.scc -c:s text outfile.srt 217 | #+END_SRC 218 | 219 | convert the vtt subtitles from youtube to srt format 220 | 221 | #+BEGIN_SRC sh 222 | ffmpeg -i infile.vtt -c:s text outfile.srt 223 | #+END_SRC 224 | 225 | batch convert vtt subtitles to srt format 226 | 227 | #+BEGIN_SRC sh 228 | find . -type f -name "*.vtt" -exec sh -c 'ffmpeg -i "$0" \ 229 | -c:s text "${0%.*}.srt"' "{}" \; 230 | #+END_SRC 231 | 232 | **** ffmpeg add subtitles to video 233 | 234 | #+BEGIN_SRC sh 235 | ffmpeg -i infile.mp4 \ 236 | -f srt -i infile.srt \ 237 | -c:a copy -c:v copy -c:s \ 238 | mov_text -metadata:s:s:0 \ 239 | language=eng \ 240 | -movflags +faststart \ 241 | outfile.mp4 242 | #+END_SRC 243 | 244 | **** remove close captions 245 | 246 | remove close captions from video without re encode 247 | 248 | #+BEGIN_SRC sh 249 | ffmpeg -i infile.mp4 \ 250 | -c copy \ 251 | -bsf:v "filter_units=remove_type=6" \ 252 | -movflags +faststart \ 253 | outfile.mp4 254 | #+END_SRC 255 | 256 | *** ccextractor 257 | 258 | Closed caption extractor for MPEG and H264 files 259 | 260 | Extract closed captions from video and save as a srt file 261 | 262 | #+BEGIN_SRC sh 263 | ccextractor infile.mp4 264 | #+END_SRC 265 | 266 | **** Linux ccextractor install 267 | 268 | #+BEGIN_SRC sh 269 | sudo apt install ccextractor 270 | #+END_SRC 271 | 272 | **** Freebsd ccextractor install 273 | 274 | Freebsd ccextractor install 275 | 276 | #+BEGIN_SRC sh 277 | pkg install ccextractor 278 | #+END_SRC 279 | 280 | ** convert ipad video to 1080 281 | 282 | convert to 1080 30fps 283 | 284 | #+BEGIN_SRC sh 285 | ffmpeg \ 286 | -i infile.mp4 \ 287 | -vf "scale=-1:1080,pad=1920:ih:(ow-iw)/2,fps=fps=30" \ 288 | outfile.mp4 289 | #+END_SRC 290 | 291 | convert to 1080 30fps mono 292 | 293 | #+BEGIN_SRC sh 294 | ffmpeg \ 295 | -i infile.mp4 \ 296 | -vf "scale=-1:1080,pad=1920:ih:(ow-iw)/2,fps=fps=30" \ 297 | -ac 1 outfile.mp4 298 | #+END_SRC 299 | -------------------------------------------------------------------------------- /trim-clip-to: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # trim-clip-to 5 | # trim video or audio clips with millisecond accuracy 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg file 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | # trim video or audio clips with millisecond accuracy 21 | https://trac.ffmpeg.org/wiki/Seeking 22 | 23 | $(basename "$0") -s 00:00:00.000 -i input -t 00:00:00.000 -o output 24 | -s 00:00:00.000 : start time 25 | -i input.(mp4|mov|mkv|m4v|webm|aac|m4a|wav|mp3|ogg) 26 | -t 00:00:00.000 : end time 27 | -o output.(mp4|webm|aac|mp3|wav|ogg) : optional argument 28 | # if option not provided defaults input-name-[start end].(mp4|webm|aac|mp3|wav|ogg)" 29 | exit 2 30 | } 31 | 32 | 33 | #=============================================================================== 34 | # error messages 35 | #=============================================================================== 36 | 37 | INVALID_OPT_ERR='Invalid option:' 38 | REQ_ARG_ERR='requires an argument' 39 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 40 | NOT_MEDIA_FILE_ERR='is not a media file' 41 | 42 | 43 | #=============================================================================== 44 | # check the number of arguments passed to the script 45 | #=============================================================================== 46 | 47 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 48 | 49 | 50 | #=============================================================================== 51 | # getopts check the options passed to the script 52 | #=============================================================================== 53 | 54 | while getopts ':s:i:t:o:h' opt 55 | do 56 | case ${opt} in 57 | s) start="${OPTARG}";; 58 | i) infile="${OPTARG}";; 59 | t) end="${OPTARG}";; 60 | h) usage;; 61 | o) outfile="${OPTARG}";; 62 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 63 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 64 | esac 65 | done 66 | shift $((OPTIND-1)) 67 | 68 | 69 | #=============================================================================== 70 | # variables 71 | #=============================================================================== 72 | 73 | # input name 74 | infile_nopath="${infile##*/}" 75 | infile_name="${infile_nopath%.*}" 76 | 77 | # input file extension 78 | infile_ext="${infile##*.}" 79 | 80 | # file command check input file mime type 81 | filetype="$(file --mime-type -b "${infile}")" 82 | 83 | # video mimetypes 84 | mov_mime='video/quicktime' 85 | mkv_mime='video/x-matroska' 86 | mp4_mime='video/mp4' 87 | webm_mime='video/webm' 88 | m4v_mime='video/x-m4v' 89 | wav_mime='audio/x-wav' 90 | audio_mime='audio/mpeg' 91 | aac_mime='audio/x-hx-aac-adts' 92 | m4a_mime='audio/mp4' 93 | ogg_mime='audio/ogg' 94 | 95 | # the file command wrongly identifies .m4a audio as a video file 96 | # so we check if the file extension is .m4a and set the mime type to audio/mp4 97 | if [ "${infile_ext}" = 'm4a' ]; then 98 | filetype="${m4a_mime}" 99 | fi 100 | 101 | # defaults for variables if not defined 102 | videofile_default="${infile_name}-[${start}-${end}].mp4" 103 | webm_default="${infile_name}-[${start}-${end}].webm" 104 | aac_default="${infile_name}-[${start}-${end}].aac" 105 | mp3_default="${infile_name}-[${start}-${end}].mp3" 106 | wav_default="${infile_name}-[${start}-${end}].wav" 107 | m4a_default="${infile_name}-[${start}-${end}].m4a" 108 | ogg_default="${infile_name}-[${start}-${end}].ogg" 109 | 110 | 111 | #=============================================================================== 112 | # check if the libfdk_aac codec is installed, if not fall back to the aac codec 113 | #=============================================================================== 114 | 115 | aac_codec="$(ffmpeg -hide_banner -stats -v panic -h encoder=libfdk_aac)" 116 | aac_error="Codec 'libfdk_aac' is not recognized by FFmpeg." 117 | aac_check="$(echo "${aac_codec}" | grep "${aac_error}")" 118 | 119 | # check ffmpeg aac codecs 120 | if [ -z "${aac_check}" ]; then 121 | aac='libfdk_aac' # libfdk_aac codec is installed 122 | else 123 | aac='aac' # libfdk_aac codec isnt installed, fall back to aac codec 124 | fi 125 | 126 | 127 | #=============================================================================== 128 | # audio and video functions 129 | #=============================================================================== 130 | 131 | # trim video clip 132 | trim_video () { 133 | ffmpeg \ 134 | -hide_banner \ 135 | -stats -v panic \ 136 | -ss "${start}" \ 137 | -to "${end}" \ 138 | -i "${infile}" \ 139 | -c:a "${aac}" \ 140 | -c:v libx264 -profile:v high \ 141 | -pix_fmt yuv420p -movflags +faststart \ 142 | -f mp4 \ 143 | "${outfile:=${videofile_default}}" 144 | } 145 | 146 | 147 | # trim webm video clip 148 | trim_webm () { 149 | ffmpeg \ 150 | -hide_banner \ 151 | -stats -v panic \ 152 | -ss "${start}" \ 153 | -to "${end}" \ 154 | -i "${infile}" \ 155 | -c:a libopus \ 156 | -c:v vp9 \ 157 | -f webm \ 158 | "${outfile:=${webm_default}}" 159 | } 160 | 161 | # trim aac audio clip 162 | trim_aac () { 163 | ffmpeg \ 164 | -hide_banner \ 165 | -stats -v panic \ 166 | -ss "${start}" \ 167 | -to "${end}" \ 168 | -i "${infile}" \ 169 | -c:a "${aac}" \ 170 | -f adts \ 171 | "${outfile:=${aac_default}}" 172 | } 173 | 174 | # trim m4a audio clip 175 | trim_m4a () { 176 | ffmpeg \ 177 | -hide_banner \ 178 | -stats -v panic \ 179 | -ss "${start}" \ 180 | -to "${end}" \ 181 | -i "${infile}" \ 182 | -c:a "${aac}" \ 183 | -f mp4 \ 184 | "${outfile:=${m4a_default}}" 185 | } 186 | 187 | # trim mp3 audio clip 188 | trim_mp3 () { 189 | ffmpeg \ 190 | -hide_banner \ 191 | -stats -v panic \ 192 | -ss "${start}" \ 193 | -to "${end}" \ 194 | -i "${infile}" \ 195 | -c:a libmp3lame \ 196 | -f mp3 \ 197 | "${outfile:=${mp3_default}}" 198 | } 199 | 200 | # trim wav audio clip 201 | trim_wav () { 202 | ffmpeg \ 203 | -hide_banner \ 204 | -stats -v panic \ 205 | -ss "${start}" \ 206 | -to "${end}" \ 207 | -i "${infile}" \ 208 | -c:a pcm_s16le \ 209 | -f wav \ 210 | "${outfile:=${wav_default}}" 211 | } 212 | 213 | 214 | # trim ogg audio clip 215 | trim_ogg () { 216 | ffmpeg \ 217 | -hide_banner \ 218 | -stats -v panic \ 219 | -ss "${start}" \ 220 | -to "${end}" \ 221 | -i "${infile}" \ 222 | -c:a libopus \ 223 | -f ogg \ 224 | "${outfile:=${ogg_default}}" 225 | } 226 | 227 | #=============================================================================== 228 | # check the files mime type 229 | #=============================================================================== 230 | 231 | case "${filetype}" in 232 | "${mov_mime}"|"${mkv_mime}"|"${mp4_mime}"|"${m4v_mime}") trim_video "${infile}";; 233 | "${webm_mime}") trim_webm "${infile}";; 234 | "${aac_mime}") trim_aac "${infile}";; 235 | "${m4a_mime}") trim_m4a "${infile}";; 236 | "${audio_mime}") trim_mp3 "${infile}";; 237 | "${wav_mime}") trim_wav "${infile}";; 238 | "${ogg_mime}") trim_ogg "${infile}";; 239 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 240 | esac 241 | -------------------------------------------------------------------------------- /trim-clip: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # trim-clip 5 | # trim video or audio clips with millisecond accuracy 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg file 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () { 16 | # if argument passed to function echo it 17 | [ -z "${1}" ] || echo "! ${1}" 18 | # display help 19 | echo "\ 20 | # trim video or audio clips with millisecond accuracy 21 | https://trac.ffmpeg.org/wiki/Seeking 22 | 23 | $(basename "$0") -s 00:00:00.000 -i input -t 00:00:00.000 -o output) 24 | -s 00:00:00.000 : start time 25 | -i input.(mp4|mov|mkv|m4v|webm|aac|m4a|wav|mp3|ogg) 26 | -t 00:00:00.000 : number of seconds after start time 27 | -o output.(mp4|webm|aac|mp3|wav|ogg) : optional argument 28 | # if option not provided defaults input-name-[start end].(mp4|webm|aac|mp3|wav|ogg)" 29 | exit 2 30 | } 31 | 32 | 33 | #=============================================================================== 34 | # error messages 35 | #=============================================================================== 36 | 37 | INVALID_OPT_ERR='Invalid option:' 38 | REQ_ARG_ERR='requires an argument' 39 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 40 | NOT_MEDIA_FILE_ERR='is not a media file' 41 | 42 | 43 | #=============================================================================== 44 | # check the number of arguments passed to the script 45 | #=============================================================================== 46 | 47 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 48 | 49 | 50 | #=============================================================================== 51 | # getopts check the options passed to the script 52 | #=============================================================================== 53 | 54 | while getopts ':s:i:t:o:h' opt 55 | do 56 | case ${opt} in 57 | s) start="${OPTARG}";; 58 | i) infile="${OPTARG}";; 59 | t) end="${OPTARG}";; 60 | h) usage;; 61 | o) outfile="${OPTARG}";; 62 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 63 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 64 | esac 65 | done 66 | shift $((OPTIND-1)) 67 | 68 | 69 | #=============================================================================== 70 | # variables 71 | #=============================================================================== 72 | 73 | # input name 74 | infile_nopath="${infile##*/}" 75 | infile_name="${infile_nopath%.*}" 76 | 77 | # input file extension 78 | infile_ext="${infile##*.}" 79 | 80 | # file command check input file mime type 81 | filetype="$(file --mime-type -b "${infile}")" 82 | 83 | # video mimetypes 84 | mov_mime='video/quicktime' 85 | mkv_mime='video/x-matroska' 86 | mp4_mime='video/mp4' 87 | webm_mime='video/webm' 88 | m4v_mime='video/x-m4v' 89 | wav_mime='audio/x-wav' 90 | audio_mime='audio/mpeg' 91 | aac_mime='audio/x-hx-aac-adts' 92 | m4a_mime='audio/mp4' 93 | ogg_mime='audio/ogg' 94 | 95 | # the file command wrongly identifies .m4a audio as a video file 96 | # so we check if the file extension is .m4a and set the mime type to audio/mp4 97 | if [ "${infile_ext}" = 'm4a' ]; then 98 | filetype="${m4a_mime}" 99 | fi 100 | 101 | # awk convert start and end time from sexagesimal time to seconds 102 | # add the start and end time to get the endpoint and convert from seconds back to sexagesimal time 103 | endtime=$(printf "%s %s\n" "${start}" "${end}" \ 104 | | awk ' 105 | { 106 | start = $1 107 | end = $2 108 | if (start ~ /:/) { 109 | split(start, t, ":") 110 | sseconds = (t[1] * 3600) + (t[2] * 60) + t[3] 111 | } 112 | if (end ~ /:/) { 113 | split(end, t, ":") 114 | eseconds = (t[1] * 3600) + (t[2] * 60) + t[3] 115 | } 116 | duration = eseconds + sseconds 117 | printf("%s\n"), duration 118 | }' \ 119 | | awk -F. 'NF==1 { printf("%02d:%02d:%02d\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60) }\ 120 | NF==2 { printf("%02d:%02d:%02d.%s\n"), ($1 / 3600), ($1 % 3600 / 60), ($1 % 60), ($2) }' 121 | ) 122 | 123 | # defaults for variables if not defined 124 | videofile_default="${infile_name}-[${start}-${endtime}].mp4" 125 | webm_default="${infile_name}-[${start}-${endtime}].webm" 126 | aac_default="${infile_name}-[${start}-${endtime}].aac" 127 | mp3_default="${infile_name}-[${start}-${endtime}].mp3" 128 | wav_default="${infile_name}-[${start}-${endtime}].wav" 129 | m4a_default="${infile_name}-[${start}-${endtime}].m4a" 130 | ogg_default="${infile_name}-[${start}-${endtime}].ogg" 131 | 132 | 133 | #=============================================================================== 134 | # check if the libfdk_aac codec is installed, if not fall back to the aac codec 135 | #=============================================================================== 136 | 137 | aac_codec="$(ffmpeg -hide_banner -stats -v panic -h encoder=libfdk_aac)" 138 | aac_error="Codec 'libfdk_aac' is not recognized by FFmpeg." 139 | aac_check="$(echo "${aac_codec}" | grep "${aac_error}")" 140 | 141 | # check ffmpeg aac codecs 142 | if [ -z "${aac_check}" ]; then 143 | aac='libfdk_aac' # libfdk_aac codec is installed 144 | else 145 | aac='aac' # libfdk_aac codec isnt installed, fall back to aac codec 146 | fi 147 | 148 | 149 | #=============================================================================== 150 | # audio and video functions 151 | #=============================================================================== 152 | 153 | # trim video clip 154 | trim_video () { 155 | ffmpeg \ 156 | -hide_banner \ 157 | -stats -v panic \ 158 | -ss "${start}" \ 159 | -i "${infile}" \ 160 | -t "${end}" \ 161 | -c:a "${aac}" \ 162 | -c:v libx264 -profile:v high \ 163 | -pix_fmt yuv420p -movflags +faststart \ 164 | -f mp4 \ 165 | "${outfile:=${videofile_default}}" 166 | } 167 | 168 | 169 | # trim webm video clip 170 | trim_webm () { 171 | ffmpeg \ 172 | -hide_banner \ 173 | -stats -v panic \ 174 | -ss "${start}" \ 175 | -i "${infile}" \ 176 | -t "${end}" \ 177 | -c:a libopus \ 178 | -c:v vp9 \ 179 | -f webm \ 180 | "${outfile:=${webm_default}}" 181 | } 182 | 183 | # trim aac audio clip 184 | trim_aac () { 185 | ffmpeg \ 186 | -hide_banner \ 187 | -stats -v panic \ 188 | -ss "${start}" \ 189 | -i "${infile}" \ 190 | -t "${end}" \ 191 | -c:a "${aac}" \ 192 | -f adts \ 193 | "${outfile:=${aac_default}}" 194 | } 195 | 196 | # trim m4a audio clip 197 | trim_m4a () { 198 | ffmpeg \ 199 | -hide_banner \ 200 | -stats -v panic \ 201 | -ss "${start}" \ 202 | -i "${infile}" \ 203 | -t "${end}" \ 204 | -c:a "${aac}" \ 205 | -f mp4 \ 206 | "${outfile:=${m4a_default}}" 207 | } 208 | 209 | # trim mp3 audio clip 210 | trim_mp3 () { 211 | ffmpeg \ 212 | -hide_banner \ 213 | -stats -v panic \ 214 | -ss "${start}" \ 215 | -i "${infile}" \ 216 | -t "${end}" \ 217 | -c:a libmp3lame \ 218 | -f mp3 \ 219 | "${outfile:=${mp3_default}}" 220 | } 221 | 222 | # trim wav audio clip 223 | trim_wav () { 224 | ffmpeg \ 225 | -hide_banner \ 226 | -stats -v panic \ 227 | -ss "${start}" \ 228 | -i "${infile}" \ 229 | -t "${end}" \ 230 | -c:a pcm_s16le \ 231 | -f wav \ 232 | "${outfile:=${wav_default}}" 233 | } 234 | 235 | 236 | # trim ogg audio clip 237 | trim_ogg () { 238 | ffmpeg \ 239 | -hide_banner \ 240 | -stats -v panic \ 241 | -ss "${start}" \ 242 | -i "${infile}" \ 243 | -t "${end}" \ 244 | -c:a libopus \ 245 | -f ogg \ 246 | "${outfile:=${ogg_default}}" 247 | } 248 | 249 | #=============================================================================== 250 | # check the files mime type 251 | #=============================================================================== 252 | 253 | case "${filetype}" in 254 | "${mov_mime}"|"${mkv_mime}"|"${mp4_mime}"|"${m4v_mime}") trim_video "${infile}";; 255 | "${webm_mime}") trim_webm "${infile}";; 256 | "${aac_mime}") trim_aac "${infile}";; 257 | "${m4a_mime}") trim_m4a "${infile}";; 258 | "${audio_mime}") trim_mp3 "${infile}";; 259 | "${wav_mime}") trim_wav "${infile}";; 260 | "${ogg_mime}") trim_ogg "${infile}";; 261 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 262 | esac 263 | -------------------------------------------------------------------------------- /fade-title: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | #=============================================================================== 4 | # fade-title 5 | # fade video, audio add title from video filename 6 | #=============================================================================== 7 | 8 | # dependencies: 9 | # ffmpeg file awk grep bc cut tail 10 | 11 | #=============================================================================== 12 | # script usage 13 | #=============================================================================== 14 | 15 | usage () 16 | { 17 | # if argument passed to function echo it 18 | [ -z "${1}" ] || echo "! ${1}" 19 | # display help 20 | echo "\ 21 | # fade video, audio add title from video filename 22 | 23 | $(basename "$0") -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -s 000 -e 000 -o output.mp4 24 | 25 | -i input.(mp4|mkv|mov|m4v) 26 | -d (0.[0-9]|1) : from 0.1 to 0.9 or 1 :optional argument # if option not provided defaults to 0.5 27 | -s 000 : from 000 to 999 28 | -e 000 : from 000 to 999 29 | -o output.mp4 : optional argument # if option not provided defaults to input-name-title-date-time" 30 | exit 2 31 | } 32 | 33 | 34 | #=============================================================================== 35 | # error messages 36 | #=============================================================================== 37 | 38 | INVALID_OPT_ERR='Invalid option:' 39 | REQ_ARG_ERR='requires an argument' 40 | WRONG_ARGS_ERR='wrong number of arguments passed to script' 41 | NOT_MEDIA_FILE_ERR='is not a media file' 42 | TITLE_FADE_ERR='title end must be after title start' 43 | 44 | 45 | #=============================================================================== 46 | # check the number of arguments passed to the script 47 | #=============================================================================== 48 | 49 | [ $# -gt 0 ] || usage "${WRONG_ARGS_ERR}" 50 | 51 | 52 | #=============================================================================== 53 | # getopts check the options passed to the script 54 | #=============================================================================== 55 | 56 | while getopts ':i:d:s:e:o:h' opt 57 | do 58 | case ${opt} in 59 | i) infile="${OPTARG}";; 60 | d) dur="${OPTARG}";; 61 | s) title_start="${OPTARG}";; 62 | e) title_end="${OPTARG}";; 63 | o) outfile="${OPTARG}";; 64 | h) usage;; 65 | \?) usage "${INVALID_OPT_ERR} ${OPTARG}" 1>&2;; 66 | :) usage "${INVALID_OPT_ERR} ${OPTARG} ${REQ_ARG_ERR}" 1>&2;; 67 | esac 68 | done 69 | shift $((OPTIND-1)) 70 | 71 | 72 | #=============================================================================== 73 | # variables 74 | #=============================================================================== 75 | 76 | # input, input name and file extension 77 | infile_nopath="${infile##*/}" 78 | infile_name="${infile_nopath%.*}" 79 | 80 | # file command check input file mime type 81 | filetype="$(file --mime-type -b "${infile}")" 82 | 83 | # video mimetypes 84 | mov_mime='video/quicktime' 85 | mkv_mime='video/x-matroska' 86 | mp4_mime='video/mp4' 87 | m4v_mime='video/x-m4v' 88 | 89 | # check the files mime type 90 | case "${filetype}" in 91 | "${mov_mime}"|"${mkv_mime}"|"${mp4_mime}"|"${m4v_mime}");; 92 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 93 | esac 94 | 95 | # defaults for variables if not defined 96 | outfile_default="${infile_name}-title-$(date +"%Y-%m-%d-%H-%M-%S").mp4" 97 | duration_default="0.5" 98 | 99 | # print analyzing file 100 | echo '+ Analyzing file with ffmpeg' 101 | 102 | # ffmpeg loudnorm get stats from file 103 | normalize=$(ffmpeg \ 104 | -hide_banner \ 105 | -i "${infile}" \ 106 | -af "loudnorm=I=-16:dual_mono=true:TP=-1.5:LRA=11:print_format=summary" \ 107 | -f null - 2>&1 | tail -n 12) 108 | 109 | # read the output of normalize line by line and store in variables 110 | for line in "${normalize}"; do 111 | measured_I=$(echo "${line}" | awk -F' ' '/Input Integrated:/ {print $3}') 112 | measured_TP=$(echo "${line}" | awk -F' ' '/Input True Peak:/ {print $4}') 113 | measured_LRA=$(echo "${line}" | awk -F' ' '/Input LRA:/ {print $3}') 114 | measured_thresh=$(echo "${line}" | awk -F' ' '/Input Threshold:/ {print $3}') 115 | offset=$(echo "${line}" | awk -F' ' '/Target Offset:/ {print $3}') 116 | done 117 | 118 | # video duration and video offset minus 1 second for fade out 119 | video_dur=$(ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "${infile}" | cut -d\. -f1) 120 | vid_offset=$(echo "${video_dur}-${dur:=${duration_default}}" | bc -l) 121 | 122 | # video height 123 | video_size=$(ffprobe -v error -show_entries stream=height -of default=noprint_wrappers=1:nokey=1 "${infile}") 124 | 125 | # video title from filename 126 | title="${infile_name}" 127 | 128 | # video title variables 129 | font="OpenSans-Regular.ttf" 130 | font_color="white" 131 | boxcolor="black@0.4" 132 | 133 | # video title fade 134 | DS="${title_start}" # display start 135 | DE="${title_end}" # display end, number of seconds after start 136 | FID="${dur:=${duration_default}}" # fade in duration 137 | FOD="${dur:=${duration_default}}" # fade out duration 138 | 139 | # check title end is a number larger than title start 140 | if [ "${DE}" -le "${DS}" ]; then 141 | echo "${TITLE_FADE_ERR}" && usage 142 | fi 143 | 144 | # calculate drawbox and drawtext size based on video height 145 | case "${video_size}" in 146 | 1080) # 1080 height 147 | drawbox_height=$(echo "${video_size}/13.4" | bc) 148 | drawtext_size=$(echo "${drawbox_height}/2" | bc) 149 | ;; 150 | 720) # 720 height 151 | drawbox_height=$(echo "${video_size}/9" | bc) 152 | drawtext_size=$(echo "${drawbox_height}/2" | bc) 153 | ;; 154 | *) # all other heights 155 | drawbox_height=$(echo "${video_size}/9" | bc) 156 | drawtext_size=$(echo "${drawbox_height}/2" | bc) 157 | ;; 158 | esac 159 | 160 | # drawbox, drawtext size 161 | boxheight="${drawbox_height}" 162 | font_size="${drawtext_size}" 163 | 164 | # check if the libfdk_aac codec is installed, if not fall back to the aac codec 165 | aac_codec="$(ffmpeg -hide_banner -stats -v panic -h encoder=libfdk_aac)" 166 | aac_error="Codec 'libfdk_aac' is not recognized by FFmpeg." 167 | aac_check="$(echo "${aac_codec}" | grep "${aac_error}")" 168 | 169 | # check ffmpeg aac codecs 170 | if [ -z "${aac_check}" ]; then 171 | aac='libfdk_aac' # libfdk_aac codec is installed 172 | else 173 | aac='aac' # libfdk_aac codec isnt installed, fall back to aac codec 174 | fi 175 | 176 | 177 | #=============================================================================== 178 | # functions 179 | #=============================================================================== 180 | 181 | # video function 182 | video () { 183 | ffmpeg \ 184 | -hide_banner \ 185 | -stats -v panic \ 186 | -i "${infile}" \ 187 | -filter_complex \ 188 | "[0:a] afade=t=in:st=0:d=${dur:=${duration_default}},afade=t=out:st='${vid_offset}':d=${dur:=${duration_default}}, 189 | compand=attacks=0:points=-70/-90|-24/-12|0/-6|20/-6, 190 | highpass=f=60, 191 | lowpass=f=13700, 192 | afftdn=nt=w, 193 | adeclick, 194 | deesser, 195 | loudnorm=I=-16: 196 | dual_mono=true: 197 | TP=-1.5: 198 | LRA=11: 199 | measured_I=${measured_I}: 200 | measured_LRA=${measured_LRA}: 201 | measured_TP=${measured_TP}: 202 | measured_thresh=${measured_thresh}: 203 | offset=${offset}: 204 | linear=true: 205 | print_format=summary [audio]; 206 | [0:v] fade=t=in:st=0:d=${dur:=${duration_default}},fade=t=out:st='${vid_offset}':d=${dur:=${duration_default}}, \ 207 | format=yuv444p, 208 | drawbox=enable='between(t,${DS},${DE})': 209 | y=(ih-h/PHI)-(${boxheight}): 210 | color=${boxcolor}: 211 | width=iw:height=${boxheight}:t=fill, 212 | drawtext=fontfile=${font}: 213 | text=${title}: 214 | fontcolor=${font_color}:fontsize=${font_size}: 215 | x=20: 216 | y=h-(${boxheight})-(${boxheight}/2)+th/4: 217 | :fontcolor_expr=fdfdfd%{eif\\\\: clip(255*(1*between(t\\, $DS + $FID\\, $DE - $FOD) + ((t - $DS)/$FID)*between(t\\, $DS\\, $DS + $FID) + (-(t - $DE)/$FOD)*between(t\\, $DE - $FOD\\, $DE) )\\, 0\\, 255) \\\\: x\\\\: 2 }, \ 218 | format=yuv420p[video]" \ 219 | -map "[video]" -map "[audio]" \ 220 | -c:a "${aac}" -ar 44100 \ 221 | -c:v libx264 -preset fast \ 222 | -profile:v high \ 223 | -crf 18 -coder 1 \ 224 | -pix_fmt yuv420p \ 225 | -movflags +faststart \ 226 | -f mp4 \ 227 | "${outfile:=${outfile_default}}" 228 | } 229 | 230 | # check the files mime type 231 | case "${filetype}" in 232 | "${mov_mime}"|"${mkv_mime}"|"${mp4_mime}"|"${m4v_mime}") video "${infile}";; 233 | *) usage "${infile} ${NOT_MEDIA_FILE_ERR}";; 234 | esac 235 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | #+STARTUP: content 2 | #+OPTIONS: num:nil author:nil 3 | * ffmpeg scripts 4 | 5 | A collection of ffmpeg shell scripts for basic editing tasks 6 | 7 | [[https://github.com/NapoleonWils0n/ffmpeg-scripts][ffmpeg scripts English version]] 8 | 9 | [[https://github.com/NapoleonWils0n/ffmpeg-Skripte][ffmpeg-Skripte Deutsche Ausgabe]] 10 | 11 | + [[#audio-silence][audio-silence]] 12 | + [[#combine-clips][combine-clips]] 13 | + [[#chapter-add][chapter-add]] 14 | + [[#chapter-csv][chapter-csv]] 15 | + [[#clip-extract][clip-extract]] 16 | + [[#clip-time][clip-time]] 17 | + [[#correct-clip][correct-clip]] 18 | + [[#crossfade-clips][crossfade-clips]] 19 | + [[#ebu-meter][ebu-meter]] 20 | + [[#extract-frame][extract-frame]] 21 | + [[#fade-clip][fade-clip]] 22 | + [[#fade-normalize][fade-normalize]] 23 | + [[#fade-title][fade-title]] 24 | + [[#img2video][img2video]] 25 | + [[#loudnorm][loudnorm]] 26 | + [[#normalize][normalize]] 27 | + [[#overlay-clip][overlay-clip]] 28 | + [[#overlay-pip][overlay-pip]] 29 | + [[#pan-scan][pan-scan]] 30 | + [[#scene-cut][scene-cut]] 31 | + [[#scene-cut-to][scene-cut-to]] 32 | + [[#scene-detect][scene-detect]] 33 | + [[#scene-images][scene-images]] 34 | + [[#scene-time][scene-time]] 35 | + [[#sexagesimal-time][sexagesimal-time]] 36 | + [[#sub2transcript][subs2transcript]] 37 | + [[#subtitle-add][subtitle-add]] 38 | + [[#scopes][scopes]] 39 | + [[#tile-thumbnails][tile-thumbnails]] 40 | + [[#trim-clip][trim-clip]] 41 | + [[#trim-clip-to][trim-clip-to]] 42 | + [[#vid2gif][vid2gif]] 43 | + [[#waveform][waveform]] 44 | + [[#webp][webp]] 45 | + [[#xfade][xfade]] 46 | + [[#zoompan][zoompan]] 47 | 48 | ** scripts install 49 | 50 | [[https://youtu.be/UHshlQvdwcQ][ffmpeg scripts install youtube]] 51 | 52 | *** create a bin directory 53 | 54 | create a bin directory in your home to add the scripts to 55 | 56 | #+BEGIN_SRC sh 57 | mkdir -p ~/bin 58 | #+END_SRC 59 | 60 | if you are using bash add the following code to your ~/.bashrc 61 | 62 | #+BEGIN_SRC sh 63 | if [ -d "$HOME/bin" ]; then 64 | PATH="$HOME/bin:$PATH" 65 | fi 66 | #+END_SRC 67 | 68 | if you are using zsh add the following code to your ~/.zshenv file 69 | 70 | #+begin_src sh 71 | typeset -U PATH path 72 | path=("$HOME/bin" "$path[@]") 73 | export PATH 74 | #+end_src 75 | 76 | + source your ~/.bashrc if you are using the bash shell 77 | 78 | #+BEGIN_SRC sh 79 | source ~/.bashrc 80 | #+END_SRC 81 | 82 | + source your ~/.zshenv if you are using the zsh shell 83 | 84 | #+BEGIN_SRC sh 85 | source ~/.zshenv 86 | #+END_SRC 87 | 88 | *** clone the git repository 89 | 90 | create a git directory in you home folder to download the scripts into, 91 | or use any other location in your file system 92 | 93 | #+BEGIN_SRC sh 94 | mkdir -p ~/git 95 | #+END_SRC 96 | 97 | change directory in the git directory 98 | 99 | #+BEGIN_SRC sh 100 | cd ~/git 101 | #+END_SRC 102 | 103 | clone the git repository 104 | 105 | #+BEGIN_SRC sh 106 | git clone https://github.com/NapoleonWils0n/ffmpeg-scripts.git 107 | #+END_SRC 108 | 109 | update the scripts using git pull 110 | 111 | *** copy or symlink scripts into the bin directory 112 | 113 | you can now either copy the scripts into the ~/bin directory in your home, 114 | or create symbolic links from the scripts in the ~/git/ffmpeg-scripts directory to the ~/bin directory 115 | 116 | creating a symbolic link 117 | 118 | #+BEGIN_SRC sh 119 | ln -s path/to/source path/to/destination 120 | #+END_SRC 121 | 122 | example 123 | 124 | #+BEGIN_SRC sh 125 | ln -s ~/git/ffmpeg-scripts/trim-clip ~/bin 126 | #+END_SRC 127 | 128 | *** ffmpeg install 129 | 130 | **** linux ffmpeg install 131 | 132 | install ffmpeg on debian or ubuntu, 133 | for other linux distros see the documentation for your package manager 134 | 135 | #+BEGIN_SRC sh 136 | sudo apt install ffmpeg 137 | #+END_SRC 138 | 139 | **** mac ffmpeg install 140 | 141 | open a terminal and run the following commands to install the xcode command line tools, homebrew and ffmpeg 142 | 143 | + xcode command line tools install 144 | 145 | #+BEGIN_SRC sh 146 | xcode-select --install 147 | #+END_SRC 148 | 149 | + homebrew install 150 | 151 | #+BEGIN_SRC sh 152 | ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" 153 | #+END_SRC 154 | 155 | + ffmpeg install with libfdk_aac 156 | 157 | #+BEGIN_SRC sh 158 | brew tap homebrew-ffmpeg/ffmpeg 159 | brew install homebrew-ffmpeg/ffmpeg/ffmpeg --with-fdk-aac --HEAD 160 | #+END_SRC 161 | 162 | + ffmpeg upgrade 163 | 164 | #+BEGIN_SRC sh 165 | brew update && brew upgrade homebrew-ffmpeg/ffmpeg/ffmpeg --fetch-HEAD 166 | #+END_SRC 167 | 168 | **** freebsd ffmpeg install 169 | 170 | switch to root and install the ffmpeg package 171 | 172 | #+BEGIN_SRC sh 173 | pkg install ffmpeg 174 | #+END_SRC 175 | 176 | you can also install ffmpeg from ports, 177 | or use poudriere to build the ffmpeg package 178 | 179 | note the ebumeter script uses ffplay which isnt installed with the ffmpeg package, 180 | so you need to build ffmpeg with the sdl option enable from ports or with poudriere 181 | 182 | if you want to use the libfdk_aac audio you should also enable that option when building 183 | the ffmpeg port, and build the lame package for mp3 support 184 | 185 | **** windows ffmpeg install 186 | 187 | install the windows subsystem for linux and then install a linux distro like ubuntu, 188 | then follow the linux install instructions 189 | 190 | ** audio-silence 191 | :PROPERTIES: 192 | :CUSTOM_ID: audio-silence 193 | :END: 194 | 195 | audio-silence add silent audio to a video clip 196 | 197 | If the video doesnt have an audio track the script copies the video track, 198 | and adds a silent audio track to match the duration of the video and creates a new video clip 199 | 200 | If the video has a video and audio track the script only copies the video track, 201 | and adds a silent audio track to match the duration of the video and creates a new video clip. 202 | 203 | [[https://youtu.be/OB8RvyenCLY][audio-silence youtube]] 204 | 205 | + script usage 206 | 207 | #+BEGIN_SRC sh 208 | audio-silence -i input.(mp4|mkv|mov|m4v) -c (mono|stereo) -r (44100|48000) -o output.mp4 209 | #+END_SRC 210 | 211 | + script help 212 | 213 | #+begin_src sh 214 | audio-silence -h 215 | #+end_src 216 | 217 | #+BEGIN_EXAMPLE 218 | # audio-silence add silent audio to a video clip 219 | 220 | audio-silence -i input.(mp4|mkv|mov|m4v) -c (mono|stereo) -r (44100|48000) -o output.mp4 221 | -i input.(mp4|mkv|mov|m4v) 222 | -c (mono|stereo) : optional argument # if option not provided defaults to mono 223 | -r (44100|48000) : optional argument # if option not provided defaults to 44100 224 | -o output.mp4 : optional argument # if option not provided defaults to input-name-silence-date-time 225 | #+END_EXAMPLE 226 | 227 | *** audio-silence batch process 228 | 229 | Batch process files in the current working directory 230 | 231 | Note we omit the -o option to use the default outfile name, 232 | which is infile-name-silence-date-time 233 | 234 | audio-silence batch process without specifying the -c and -r options 235 | using the defaults of -c mono and -r 44100 236 | 237 | #+BEGIN_SRC sh 238 | find . -type f -name "*.mp4" -exec sh -c \ 239 | 'audio-silence -i "${0}"' 240 | "{}" \; 241 | #+END_SRC 242 | 243 | audio-silence batch process and override the defaults 244 | with the -c and -r options 245 | 246 | #+BEGIN_SRC sh 247 | find . -type f -name "*.mp4" -exec sh -c \ 248 | 'audio-silence -i "${0}" -c stereo -r 48000' 249 | "{}" \; 250 | #+END_SRC 251 | 252 | ** chapter-add 253 | :PROPERTIES: 254 | :CUSTOM_ID: chapter-add 255 | :END: 256 | 257 | add chapters to a video or audio file with ffmpeg using a metadata file, 258 | use the chapter-csv script to create the metadata file from a csv files 259 | 260 | + script usage 261 | 262 | #+BEGIN_SRC sh 263 | chapter-add -i input -c metadata.txt -o output 264 | #+END_SRC 265 | 266 | + script help 267 | 268 | #+begin_src sh 269 | chapter-add -h 270 | #+end_src 271 | 272 | ** chapter-csv 273 | :PROPERTIES: 274 | :CUSTOM_ID: chapter-csv 275 | :END: 276 | 277 | convert a csv file into a chapter metadata file for ffmpeg 278 | 279 | + script usage 280 | 281 | #+BEGIN_SRC sh 282 | chapter-csv -i input -o output 283 | #+END_SRC 284 | 285 | + script help 286 | 287 | #+begin_src sh 288 | chapter-add -h 289 | #+end_src 290 | 291 | + csv file example 292 | 293 | The last record is the duration of the video and is used as the end time for the previous chapter,and End isnt used as a chapter 294 | 295 | #+begin_example 296 | 00:00:00,Intro 297 | 00:02:30,Scene 1 298 | 00:05:00,Scene 2 299 | 00:07:00,Scene 3 300 | 00:10:00,End 301 | #+end_example 302 | 303 | ** chapter-extract 304 | :PROPERTIES: 305 | :CUSTOM_ID: chapter-extract 306 | :END: 307 | 308 | extract chapters from a video or audo file and save as a csv file 309 | 310 | + script usage 311 | 312 | #+BEGIN_SRC sh 313 | chapter-extract -i input -o output 314 | #+END_SRC 315 | 316 | + script help 317 | 318 | #+begin_src sh 319 | chapter-extract -h 320 | #+end_src 321 | 322 | + convert the csv file to youtube timestamps 323 | 324 | #+begin_src sh 325 | tr ',' ' ' < input.txt > output.txt 326 | #+end_src 327 | 328 | ** clip-time 329 | :PROPERTIES: 330 | :CUSTOM_ID: clip-time 331 | :END: 332 | 333 | convert timestamps into start and duration 334 | 335 | + script usage 336 | 337 | #+BEGIN_SRC sh 338 | clip-time -i input -o output 339 | #+END_SRC 340 | 341 | + script help 342 | 343 | #+begin_src sh 344 | clip-time -h 345 | #+end_src 346 | 347 | #+begin_example 348 | clip-time -i input -o output 349 | 350 | -i input 351 | -o output 352 | #+end_example 353 | 354 | ** combine-clips 355 | :PROPERTIES: 356 | :CUSTOM_ID: combine-clips 357 | :END: 358 | 359 | combine an image or video file with an audio clip 360 | 361 | [[https://youtu.be/BUrmbakPQY8][combine-clips youtube]] 362 | 363 | + script usage 364 | 365 | #+BEGIN_SRC sh 366 | combine-clips -i input.(mp4|mkv|mov|m4v|png|jpg) -a audio.(m4a|aac|wav|mp3) -o output.mp4 367 | #+END_SRC 368 | 369 | + script help 370 | 371 | #+begin_src sh 372 | combine-clips -h 373 | #+end_src 374 | 375 | #+BEGIN_EXAMPLE 376 | # combine an image or video file with an audio clip 377 | 378 | combine-clips -i input.(mp4|mkv|mov|m4v|png|jpg) -a audio.(m4a|aac|wav|mp3) -o output.mp4 379 | -i input.(mp4|mkv|mov|m4v|png|jpg) 380 | -a audio.(m4a|aac|wav|mp3) 381 | -o output.mp4 : optional argument # if option not provided defaults to input-name-combined-date-time 382 | #+END_EXAMPLE 383 | 384 | *** combine-clips batch process 385 | 386 | Batch process files in the current working directory 387 | 388 | Note we omit the -o option to use the default outfile name, 389 | infile-name-combined-date-time 390 | 391 | + batch combine video and audio files into video clips 392 | 393 | The video and audio files you want to combine must have the same name 394 | 395 | for example 396 | 397 | #+BEGIN_EXAMPLE 398 | file1.mp4 399 | file1.wav 400 | file2.mp4 401 | file2.wav 402 | #+END_EXAMPLE 403 | 404 | running the following code will combine 405 | file1.mp4 with file1.wav and 406 | file2.mp4 with file2.wav 407 | 408 | #+BEGIN_SRC sh 409 | find . -type f -name "*.mp4" -exec sh -c \ 410 | 'combine-clip -i "${0}" -a "${0%.*}.wav"' \ 411 | "{}" \; 412 | #+END_SRC 413 | 414 | + batch combine images and audio files into video clips 415 | 416 | The images and audio files you want to combine must have the same name 417 | 418 | for example 419 | 420 | #+BEGIN_EXAMPLE 421 | file1.png 422 | file1.wav 423 | file2.png 424 | file2.wav 425 | #+END_EXAMPLE 426 | 427 | running the following code will combine 428 | file1.png with file1.wav and 429 | file2.png with file2.wav 430 | 431 | #+BEGIN_SRC sh 432 | find -s . -type f -name "*.png" -exec sh -c \ 433 | 'combine-clip -i "${0}" -a "${0%.*}.wav"' \ 434 | "{}" \; 435 | #+END_SRC 436 | 437 | ** correct-clip 438 | :PROPERTIES: 439 | :CUSTOM_ID: correct-clip 440 | :END: 441 | 442 | + curves code based on: 443 | [[https://video.stackexchange.com/questions/16352/converting-gimp-curves-files-to-photoshop-acv-for-ffmpeg/20005#20005][converting gimp curves files for ffmpeg]] 444 | 445 | correct a video clip by using a gimp curve converted into a ffmpeg curves filter command, 446 | to adjust the levels and white balance 447 | 448 | + requires a curve file created with the following script 449 | [[https://github.com/NapoleonWils0n/curve2ffmpeg][curve2ffmpeg]] 450 | 451 | [[https://youtu.be/wQi3Y-6vWYc][correct-clip youtube]] 452 | 453 | + script usage 454 | 455 | #+BEGIN_SRC sh 456 | correct-clip -i input.(mp4|mkv|mov|m4v) -c curve.txt -o output.mp4 457 | #+END_SRC 458 | 459 | + script help 460 | 461 | #+begin_src sh 462 | correct-clip -h 463 | #+end_src 464 | 465 | #+BEGIN_EXAMPLE 466 | # correct a video clip by using a gimp curve 467 | 468 | # requires a curve file created with the following script 469 | # https://github.com/NapoleonWils0n/curve2ffmpeg 470 | 471 | correct-clip -i input.(mp4|mkv|mov|m4v) -c curve.txt -o output.mp4 472 | -i input.(mp4|mkv|mov|m4v) 473 | -c curve.txt 474 | -o output.mp4 : optional argument # if option not provided defaults to input-name-corrected-date-time 475 | #+END_EXAMPLE 476 | 477 | *** correct-clip batch process 478 | 479 | Batch process files in the current working directory 480 | 481 | Note we omit the -o option to use the default outfile name, 482 | infile-name-corrected-date-time 483 | 484 | The video and gimp curve text files you want to combine must have the same name 485 | 486 | for example 487 | 488 | #+BEGIN_EXAMPLE 489 | file1.mp4 490 | file1.txt 491 | file2.mp4 492 | file2.txt 493 | #+END_EXAMPLE 494 | 495 | running the following code will correct 496 | file1.mp4 with file1.txt gimp curve file and 497 | file2.mp4 with file2.txt gimp curve file 498 | 499 | #+BEGIN_SRC sh 500 | find . -type f -name "*.mp4" -exec sh -c \ 501 | 'correct-clip -i "${0}" -c "${0%.*}.txt"' \ 502 | "{}" \; 503 | #+END_SRC 504 | 505 | ** crossfade-clips 506 | :PROPERTIES: 507 | :CUSTOM_ID: xfade-clips 508 | :END: 509 | 510 | cross fade 2 video clips with either a 1 or 2 second cross fade 511 | the videos must have the same codecs, size and frame rate 512 | 513 | [[https://youtu.be/0HnUNVreMVk][crossfade-clips youtube]] 514 | 515 | + script usage 516 | 517 | #+BEGIN_SRC sh 518 | crossfade-clips -a clip1.(mp4|mkv|mov|m4v) -b clip2.(mp4|mkv|mov|m4v) -d (1|2) -o output.mp4 519 | #+END_SRC 520 | 521 | + script help 522 | 523 | #+begin_src sh 524 | crossfade-clips -h 525 | #+end_src 526 | 527 | #+BEGIN_EXAMPLE 528 | # ffmpeg cross fade clips 529 | 530 | crossfade-clips -a clip1.(mp4|mkv|mov|m4v) -b clip2.(mp4|mkv|mov|m4v) -d (1|2) -o output.mp4 531 | -a clip1.(mp4|mkv|mov|m4v) : first clip 532 | -b clip2.(mp4|mkv|mov|m4v) : second clip 533 | -d (1|2) : cross fade duration :optional argument # if option not provided defaults to 1 second 534 | -o output.mp4 : optional argument # if option not provided defaults to input-name-xfade-date-time 535 | #+END_EXAMPLE 536 | 537 | ** ebu-meter 538 | :PROPERTIES: 539 | :CUSTOM_ID: ebu-meter 540 | :END: 541 | 542 | ffplay ebu meter 543 | 544 | [[https://youtu.be/8qrT9TfKwUI][ebu-meter youtube]] 545 | 546 | + script usage 547 | 548 | #+BEGIN_SRC sh 549 | ebu-meter -i input.(mp4|mkv|mov|m4v|webm|aac|m4a|wav|mp3) -t (00) 550 | #+END_SRC 551 | 552 | -t = luf target, eg 16 553 | 554 | + script help 555 | 556 | #+begin_src sh 557 | ebu-meter -h 558 | #+end_src 559 | 560 | #+begin_example 561 | ebu-meter -i input.(mp4|mkv|mov|m4v|webm|aac|m4a|wav|mp3) -t (00) 562 | #+end_example 563 | 564 | ** extract-frame 565 | :PROPERTIES: 566 | :CUSTOM_ID: extract-frame 567 | :END: 568 | 569 | extract a frame from a video and save as a png image 570 | 571 | [[https://trac.ffmpeg.org/wiki/Seeking][ffmpeg wiki seeking]] 572 | 573 | Note that you can use two different time unit formats: sexagesimal (HOURS:MM:SS.MILLISECONDS, as in 01:23:45.678), or in seconds. 574 | If a fraction is used, such as 02:30.05, this is interpreted as "5 100ths of a second", not as frame 5. 575 | For instance, 02:30.5 would be 2 minutes, 30 seconds, and a half a second, which would be the same as using 150.5 in seconds. 576 | 577 | [[https://youtu.be/cOk0i384crE][extract-frame youtube]] 578 | 579 | + script usage 580 | 581 | #+BEGIN_SRC sh 582 | extract-frame -i input.(mp4|mov|mkv|m4v|webm) -s 00:00:00.000 -t (png|jpg) -x width -y height -o output.(png|jpg) 583 | #+END_SRC 584 | 585 | + script help 586 | 587 | #+begin_src sh 588 | extract-frame -h 589 | #+end_src 590 | 591 | #+BEGIN_EXAMPLE 592 | # extract a frame from a video as a png or jpg 593 | https://trac.ffmpeg.org/wiki/Seeking 594 | 595 | extract-frame -i input.(mp4|mov|mkv|m4v|webm) -s 00:00:00.000 -t (png|jpg) -x width -y height -o output.(png|jpg) 596 | -i input.(mp4|mov|mkv|m4v) 597 | -s 00:00:00.000 : optional argument # if option not provided defaults to 00:00:00 598 | -t (png|jpg) : optional argument # if option not provided defaults to png 599 | -x width : optional argument # 600 | -y height : optional argument # 601 | -o output.(png|jpg) : optional argument # if option not provided defaults to input-name-timecode 602 | #+END_EXAMPLE 603 | 604 | *** extract-frame batch process 605 | 606 | Batch process files in the current working directory 607 | 608 | Note we omit the -o option to use the default outfile name, 609 | infile-name-frame-date-time 610 | 611 | + extract frame with default option of 00:00:00 612 | 613 | #+BEGIN_SRC sh 614 | find . -type f -name "*.mp4" -exec sh -c \ 615 | 'extract-frame -i "${0}"' \ 616 | "{}" \; 617 | #+END_SRC 618 | 619 | + extract frame at 30 seconds into the video 620 | 621 | #+BEGIN_SRC sh 622 | find . -type f -name "*.mp4" -exec sh -c \ 623 | 'extract-frame -i "${0}" -s 00:00:30' \ 624 | "{}" \; 625 | #+END_SRC 626 | 627 | ** fade-clip 628 | :PROPERTIES: 629 | :CUSTOM_ID: fade-clip 630 | :END: 631 | 632 | fade video and audio in and out 633 | 634 | [[https://youtu.be/ea3aCK9htsE][fade-clip youtube]] 635 | 636 | + script usage 637 | 638 | #+BEGIN_SRC sh 639 | fade-clip -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -o output.mp4 640 | #+END_SRC 641 | 642 | + script help 643 | 644 | #+begin_src sh 645 | fade-clip -h 646 | #+end_src 647 | 648 | #+BEGIN_EXAMPLE 649 | # fade video and audio in and out 650 | 651 | fade-clip -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -o output.mp4 652 | -i infile.(mp4|mkv|mov|m4v) 653 | -d (0.[0-9]|1) : optional argument # if option not provided defaults to 0.5 654 | -o output.mp4 : optional argument # if option not provided defaults to input-name-fade-date-time 655 | #+END_EXAMPLE 656 | 657 | *** fade-clip batch process 658 | 659 | Batch process files in the current working directory 660 | 661 | Note we omit the -o option to use the default outfile name, 662 | infile-name-fade-date-time 663 | 664 | + fade-clip with default option of 0.5 665 | 666 | #+BEGIN_SRC sh 667 | find . -type f -name "*.mp4" -exec sh -c \ 668 | 'fade-clip -i "${0}"' \ 669 | "{}" \; 670 | #+END_SRC 671 | 672 | + fade-clip and override the default option of 0.5 with -d 1 for a 1 second fade 673 | 674 | #+BEGIN_SRC sh 675 | find . -type f -name "*.mp4" -exec sh -c \ 676 | 'fade-clip -i "${0}" -d 1' \ 677 | "{}" \; 678 | #+END_SRC 679 | 680 | ** fade-normalize 681 | :PROPERTIES: 682 | :CUSTOM_ID: fade-normalize 683 | :END: 684 | 685 | fade video and audio in and out and normalize 686 | 687 | [[https://youtu.be/jufGDRAn8Ec][fade-normalize youtube]] 688 | 689 | + script usage 690 | 691 | #+BEGIN_SRC sh 692 | fade-normalize -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -o output.mp4 693 | #+END_SRC 694 | 695 | + script help 696 | 697 | #+begin_src sh 698 | fade-normalize -h 699 | #+end_src 700 | 701 | #+BEGIN_EXAMPLE 702 | # fade video and normalize audio levels 703 | 704 | fade-normalize -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -o output.mp4 705 | 706 | -d (0.[0-9]|1) : optional argument # if option not provided defaults to 0.5 707 | -o output.mp4 : optional argument # if option not provided defaults to input-name-normalized-date-time 708 | #+END_EXAMPLE 709 | 710 | *** fade-normalize batch process 711 | 712 | Batch process files in the current working directory 713 | 714 | #+BEGIN_SRC sh 715 | find . -type f -name "*.mp4" -exec sh -c \ 716 | 'fade-normalize -i "${0}" -d 0.5' \ 717 | "{}" \; 718 | #+END_SRC 719 | 720 | ** fade-title 721 | :PROPERTIES: 722 | :CUSTOM_ID: fade-title 723 | :END: 724 | 725 | fade video and audio in and out, 726 | normalize the audio and create video a lower third title from the filename 727 | 728 | [[https://youtu.be/RDnhaX_d9B0][fade-title youtube]] 729 | 730 | + script usage 731 | 732 | #+BEGIN_SRC sh 733 | fade-title -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -s 000 -e 000 -o output.mp4 734 | #+END_SRC 735 | 736 | + script help 737 | 738 | #+begin_src sh 739 | fade-title -h 740 | #+end_src 741 | 742 | #+BEGIN_EXAMPLE 743 | # fade video, audio add title from video filename 744 | 745 | fade-title -i input.(mp4|mkv|mov|m4v) -d (0.[0-9]|1) -s 000 -e 000 -o output.mp4 746 | 747 | -i input.(mp4|mkv|mov|m4v) 748 | -d (0.[0-9]|1) : from 0.1 to 0.9 or 1 :optional argument # if option not provided defaults to 0.5 749 | -s 000 : from 000 to 999 750 | -e 000 : from 000 to 999 751 | -o output.mp4 : optional argument # if option not provided defaults to input-name-title-date-time 752 | #+END_EXAMPLE 753 | 754 | *** fade-title batch process 755 | 756 | Batch process files in the current working directory 757 | 758 | #+BEGIN_SRC sh 759 | find . -type f -name "*.mp4" -exec sh -c \ 760 | 'fade-title -i "${0}" -d 0.5 -s 10 -e 20' \ 761 | "{}" \; 762 | #+END_SRC 763 | 764 | ** img2video 765 | :PROPERTIES: 766 | :CUSTOM_ID: img2video 767 | :END: 768 | 769 | convert an image into a video file 770 | 771 | [[https://youtu.be/x_dVVvhKbJE][img2video youtube]] 772 | 773 | + script usage 774 | 775 | #+BEGIN_SRC sh 776 | img2video -i input.(png|jpg|jpeg) -d (000) -o output.mp4 777 | #+END_SRC 778 | 779 | + script help 780 | 781 | #+begin_src sh 782 | img2video -h 783 | #+end_src 784 | 785 | #+BEGIN_EXAMPLE 786 | # image to video 787 | 788 | img2video -i input.(png|jpg|jpeg) -d (000) -o output.mp4 789 | -i input.(mp4|mkv|mov|m4v) 790 | -d (000) : duration 791 | -o output.mp4 : optional argument # if option not provided defaults to input-name-video-date-time 792 | #+END_EXAMPLE 793 | 794 | *** img2video batch process 795 | 796 | Batch process files in the current working directory 797 | 798 | Note we omit the -o option to use the default outfile name, 799 | infile-name-video-date-time 800 | 801 | Batch convert png in the current directory into video clips with a 30 second duration 802 | 803 | #+BEGIN_SRC sh 804 | find . -type f -name "*.png" -exec sh -c \ 805 | 'img2video -i "${0}" -d 30' \ 806 | "{}" \; 807 | #+END_SRC 808 | 809 | ** loudnorm 810 | :PROPERTIES: 811 | :CUSTOM_ID: loudnorm 812 | :END: 813 | 814 | ffmpeg loudnorm 815 | 816 | [[https://youtu.be/8fQpbBCVCRc][loudnorm youtube]] 817 | 818 | + script usage 819 | 820 | #+BEGIN_SRC sh 821 | loudnorm -i infile.(mkv|mp4|mov|m4v|m4a|aac|wav|mp3) 822 | #+END_SRC 823 | 824 | + script help 825 | 826 | #+begin_src sh 827 | loudnorm -h 828 | #+end_src 829 | 830 | #+begin_example 831 | # ffmpeg loudnorm 832 | 833 | loudnorm -i input.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) 834 | #+end_example 835 | 836 | ** normalize 837 | :PROPERTIES: 838 | :CUSTOM_ID: normalize 839 | :END: 840 | 841 | normalize audio levels 842 | 843 | [[https://youtu.be/q_UjwuJmya4][normalize youtube]] 844 | 845 | + script usage 846 | 847 | #+BEGIN_SRC sh 848 | normalize -i input.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) -o output.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) 849 | #+END_SRC 850 | 851 | + script help 852 | 853 | #+begin_src sh 854 | normalize -h 855 | #+end_src 856 | 857 | #+BEGIN_EXAMPLE 858 | # normalize audio levels 859 | 860 | normalize -i input.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) -o output.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) 861 | -i input.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) 862 | -o output.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) : optional argument 863 | # if option not provided defaults to input-name-normalized-date-time-extension 864 | #+END_EXAMPLE 865 | 866 | *** normalize batch process 867 | 868 | Batch process files in the current working directory 869 | 870 | Note we omit the -o option to use the default outfile name, 871 | infile-name-normalize-date-time 872 | 873 | Batch normalize mp4 videos in the current directory 874 | 875 | #+BEGIN_SRC sh 876 | find . -type f -name "*.mp4" -exec sh -c \ 877 | 'normalize -i "${0}"' \ 878 | "{}" \; 879 | #+END_SRC 880 | 881 | ** overlay-clip 882 | :PROPERTIES: 883 | :CUSTOM_ID: overlay-clip 884 | :END: 885 | 886 | overlay one video clip on top of another video clip 887 | 888 | [[https://youtu.be/tfzKo9jy2sI][overay-clip youtube]] 889 | 890 | + script usage 891 | 892 | #+BEGIN_SRC sh 893 | overlay-clip -i input.(mp4|mkv|mov|m4v) -v input.(mp4|mkv|mov|m4v) -p [0-999] -o output.mp4 894 | #+END_SRC 895 | 896 | + script help 897 | 898 | #+begin_src sh 899 | overlay-clip -h 900 | #+end_src 901 | 902 | #+BEGIN_EXAMPLE 903 | # overlay one video clip on top of another video clip 904 | 905 | overlay-clip -i input.(mp4|mkv|mov|m4v) -v input.(mp4|mkv|mov|m4v) -p [0-999] -o output.mp4 906 | -i input.(mp4|mkv|mov|m4v) : bottom video 907 | -v input.(mp4|mkv|mov|m4v) : overlay video 908 | -p [0-999] : time to overlay the video 909 | -o output.mp4 : optional argument # if option not provided defaults to input-name-overlay-date-time 910 | #+END_EXAMPLE 911 | 912 | ** overlay-pip 913 | :PROPERTIES: 914 | :CUSTOM_ID: overlay-pip 915 | :END: 916 | 917 | create a picture in picture 918 | 919 | [[https://youtu.be/bufAVPT3Cvk][overlay-pip youtube]] 920 | 921 | + script usage 922 | 923 | #+BEGIN_SRC sh 924 | overlay-pip -i input.(mp4|mkv|mov|m4v) -v input.(mp4|mkv|mov|m4v) -p [0-999] 925 | -m [00] -x (tl|tr|bl|br) -w [000] -f (0.1-9|1) -b [00] -c colour -o output.mp4 926 | #+END_SRC 927 | 928 | + script help 929 | 930 | #+begin_src sh 931 | overlay-pip -h 932 | #+end_src 933 | 934 | #+BEGIN_EXAMPLE 935 | # create a picture in picture 936 | 937 | overlay-pip -i input.(mp4|mkv|mov|m4v) -v input.(mp4|mkv|mov|m4v) -p [0-999] 938 | -m [00] -x (tl|tr|bl|br) -w [000] -f (0.1-9|1) -b [00] -c colour -o output.mp4 939 | 940 | -i input.(mp4|mkv|mov|m4v) : bottom video 941 | -v input.(mp4|mkv|mov|m4v) : overlay video 942 | -p [0-999] : time to overlay the video 943 | -m [00] : margin defaults to 0 944 | -x (tl|tr|bl|br) : pip position - defaults to tr 945 | -w [000] : width - defaults to 1/4 of video size 946 | -f (0.1-9|1) : fade from 0.1 to 1 - defaults to 0.2 947 | -b [00] : border 948 | -c colour : colour 949 | -o output.mp4 : optional argument # if option not provided defaults to input-name-pip-date-time 950 | #+END_EXAMPLE 951 | 952 | ** pan-scan 953 | :PROPERTIES: 954 | :CUSTOM_ID: pan-scan 955 | :END: 956 | 957 | pan image 958 | 959 | + script usage 960 | 961 | #+BEGIN_SRC sh 962 | pan-scan -i input.(png|jpg|jpeg) -d (000) -p (l|r|u|d) -o output.mp4 963 | #+END_SRC 964 | 965 | + script help 966 | 967 | #+begin_src sh 968 | pan-scan -h 969 | #+end_src 970 | 971 | #+BEGIN_EXAMPLE 972 | # pan scan image 973 | 974 | pan-scan -i input.(png|jpg|jpeg) -d (000) -p (l|r|u|d) -o output.mp4 975 | -i = input.(png|jpg|jpeg) 976 | -d = duration : from 1-999 977 | -p = position : left, right, up, down 978 | -o = output.mp4 : optional argument # default is input-name-pan-date-time 979 | #+END_EXAMPLE 980 | 981 | ** scene-cut 982 | :PROPERTIES: 983 | :CUSTOM_ID: scene-cut 984 | :END: 985 | 986 | scene-cut takes a cut file and video and cuts the video into clips 987 | 988 | + script usage 989 | 990 | #+BEGIN_SRC sh 991 | scene-cut -i input -c cutfile 992 | #+END_SRC 993 | 994 | + script help 995 | 996 | #+begin_src sh 997 | scene-cut -h 998 | #+end_src 999 | 1000 | #+BEGIN_EXAMPLE 1001 | scene-cut -i input -c cutfile 1002 | 1003 | -i input.(mp4|mov|mkv|m4v) 1004 | -c cutfile 1005 | #+END_EXAMPLE 1006 | 1007 | ffmpeg requires a start point and a duration, not an end point 1008 | 1009 | cut file - hours, minutes, seconds 1010 | in this example we create 2 - 30 seconds clips 1011 | 1012 | a 30 second clip that starts at 00:00:00 1013 | and another 30 second clip that starts at 00:01:00 1014 | 1015 | #+begin_example 1016 | 00:00:00,00:00:30 1017 | 00:01:00,00:00:30 1018 | #+end_example 1019 | 1020 | cut file - seconds 1021 | in this example we create 2 - 30 seconds clips 1022 | 1023 | a 30 second clip that starts at 0 1024 | and another 30 second clip that starts at 60 1025 | 1026 | #+begin_example 1027 | 0,30 1028 | 60,30 1029 | #+end_example 1030 | 1031 | ** scene-cut-to 1032 | :PROPERTIES: 1033 | :CUSTOM_ID: scene-cut-to 1034 | :END: 1035 | 1036 | scene-cut-to takes a cut file and video and cuts the video into clips 1037 | using a start time and a end position 1038 | 1039 | + script usage 1040 | 1041 | #+BEGIN_SRC sh 1042 | scene-cut-to -i input -c cutfile 1043 | #+END_SRC 1044 | 1045 | + script help 1046 | 1047 | #+begin_src sh 1048 | scene-cut-to -h 1049 | #+end_src 1050 | 1051 | #+BEGIN_EXAMPLE 1052 | scene-cut-to -i input -c cutfile 1053 | 1054 | -i input.(mp4|mov|mkv|m4v) 1055 | -c cutfile 1056 | #+END_EXAMPLE 1057 | 1058 | cut file - hours, minutes, seconds 1059 | in this example we create 2 - 30 seconds clips 1060 | 1061 | a 30 second clip that starts at 00:00:00 to 00:00:30 1062 | and another 30 second clip that starts at 00:01:00 to 00:01:30 1063 | 1064 | #+begin_example 1065 | 00:00:00,00:00:30 1066 | 00:01:00,00:01:30 1067 | #+end_example 1068 | 1069 | ** scene-detect 1070 | :PROPERTIES: 1071 | :CUSTOM_ID: scene-detect 1072 | :END: 1073 | 1074 | scene-detect takes a video file and a threshold for the scene detection from 0.1 to 0.9 1075 | you can also use the -s and -e options to set a range for thew scene detection, 1076 | if you dont specify a range scene detection will be perform on the whole video 1077 | 1078 | [[https://www.youtube.com/watch?v=nOeaFEHuFyM][ffmpeg scene detection - automatically cut videos into separate scenes]] 1079 | 1080 | [[https://youtu.be/SqvDCpWad9M][ffmpeg scene detection - version 2 - specify a range in the video and cut into separate scenes]] 1081 | 1082 | [[https://youtu.be/GZgE6fYd_wg][ffmpeg scene detect - version 3 - sexagesimal format - hours, minutes, seconds]] 1083 | 1084 | + script usage 1085 | 1086 | #+BEGIN_SRC sh 1087 | scene-detect -s 00:00:00 -i input -e 00:00:00 -t (0.1 - 0.9) -f sec -o output 1088 | #+END_SRC 1089 | 1090 | + script help 1091 | 1092 | #+begin_src sh 1093 | scene-detect -h 1094 | #+end_src 1095 | 1096 | #+BEGIN_EXAMPLE 1097 | scene-detect -s 00:00:00 -i input -e 00:00:00 -t (0.1 - 0.9) -f sec -o output 1098 | 1099 | -s 00:00:00 : start time 1100 | -i input.(mp4|mov|mkv|m4v) 1101 | -e 00:00:00 : end time 1102 | -t (0.1 - 0.9) # threshold 1103 | -f sec # output in seconds 1104 | -o output.txt 1105 | #+END_EXAMPLE 1106 | 1107 | ** scene-images 1108 | :PROPERTIES: 1109 | :CUSTOM_ID: scene-images 1110 | :END: 1111 | 1112 | scene-images takes a video file and a cut file, 1113 | created with the scene-detect script either in seconds or sexagesimal format 1114 | and then creates an image for each cut point 1115 | 1116 | + script usage 1117 | 1118 | #+BEGIN_SRC sh 1119 | scene-images -i input -c cutfile -t (png|jpg) -x width -y height 1120 | #+END_SRC 1121 | 1122 | + script help 1123 | 1124 | #+begin_src sh 1125 | scene-images -h 1126 | #+end_src 1127 | 1128 | #+BEGIN_EXAMPLE 1129 | scene-images -i input -c cutfile -t (png|jpg) -x width -y height 1130 | 1131 | -i input.(mp4|mov|mkv|m4v) 1132 | -c cutfile 1133 | -t (png|jpg) : optional argument # if option not provided defaults to png 1134 | -x width : optional argument # 1135 | -y height : optional argument # 1136 | #+END_EXAMPLE 1137 | 1138 | ** scene-time 1139 | :PROPERTIES: 1140 | :CUSTOM_ID: scene-time 1141 | :END: 1142 | 1143 | scene-time takes a cut file, 1144 | created with the scene-detect script either in seconds or sexagesimal format 1145 | 1146 | #+begin_example 1147 | 0:00:00 1148 | 0:00:11.875000 1149 | 0:00:15.750000 1150 | #+end_example 1151 | 1152 | The script creates clips by subtracting the cut point from the start point 1153 | and converts sexagesimal format and then creates a file with the start point 1154 | a comma and then the duration of the clip 1155 | 1156 | the output of the scene-time script is used with the scene-cut script to create the clips 1157 | 1158 | #+begin_example 1159 | 0,11.875 1160 | 11.875,3.875 1161 | #+end_example 1162 | 1163 | + script usage 1164 | 1165 | #+BEGIN_SRC sh 1166 | scene-time -i input -o output 1167 | #+END_SRC 1168 | 1169 | + script help 1170 | 1171 | #+begin_src sh 1172 | scene-time -h 1173 | #+end_src 1174 | 1175 | #+BEGIN_EXAMPLE 1176 | scene-time -i input -o output 1177 | 1178 | -i input 1179 | -o output 1180 | #+END_EXAMPLE 1181 | 1182 | ** sexagesimal-time 1183 | :PROPERTIES: 1184 | :CUSTOM_ID: sexagesimal-time 1185 | :END: 1186 | 1187 | calculate sexagesimal duration by subtracting the end time from start time for trimming files with ffmpeg 1188 | 1189 | + script help 1190 | 1191 | #+begin_src sh 1192 | sexagesimal-time -h 1193 | #+end_src 1194 | 1195 | example 1196 | 1197 | #+begin_src sh 1198 | sexagesimal-time -s 00:05:30 -e 00:18:47 1199 | #+end_src 1200 | 1201 | ouput 1202 | 1203 | #+begin_example 1204 | 00:13:17 1205 | #+end_example 1206 | 1207 | also works with milliseconds 1208 | 1209 | ** subtitle-add 1210 | :PROPERTIES: 1211 | :CUSTOM_ID: subtitle-add 1212 | :END: 1213 | 1214 | add subtitles to a video file 1215 | 1216 | [[https://youtu.be/p6BHhO5VfEg][subtitle-add youtube]] 1217 | 1218 | + script usage 1219 | 1220 | #+BEGIN_SRC sh 1221 | subtitle-add -i input.(mp4|mkv|mov|m4v) -s subtitle.(srt|vtt) -o output.mp4 1222 | #+END_SRC 1223 | 1224 | + script help 1225 | 1226 | #+begin_src sh 1227 | subtitle-add -h 1228 | #+end_src 1229 | 1230 | #+BEGIN_EXAMPLE 1231 | # add subtitles to a video 1232 | 1233 | subtitle-add -i input.(mp4|mkv|mov|m4v) -s subtitle.srt -o output.mp4 1234 | -i input.(mp4|mkv|mov|m4v) 1235 | -s subtitle.(srt|vtt) 1236 | -o output.mp4 : optional argument # if option not provided defaults to input-name-subs-date-time 1237 | #+END_EXAMPLE 1238 | 1239 | *** subtitle-add batch process 1240 | 1241 | Batch process files in the current working directory 1242 | 1243 | Note we omit the -o option to use the default outfile name, 1244 | infile-name-subs-date-time 1245 | 1246 | The video and subtitle files you want to combine must have the same name 1247 | 1248 | for example 1249 | 1250 | #+BEGIN_EXAMPLE 1251 | file1.mp4 1252 | file1.srt 1253 | file2.mp4 1254 | file2.srt 1255 | #+END_EXAMPLE 1256 | 1257 | running the following code will run the subtitle-add script and combine 1258 | file1.mp4 with file1.srt and 1259 | file2.mp4 with file2.srt 1260 | 1261 | #+BEGIN_SRC sh 1262 | find . -type f -name "*.mp4" -exec sh -c \ 1263 | 'subtitle-add -i "${0}" -s "${0%.*}.srt"' \ 1264 | "{}" \; 1265 | #+END_SRC 1266 | 1267 | ** scopes 1268 | :PROPERTIES: 1269 | :CUSTOM_ID: scopes 1270 | :END: 1271 | 1272 | [[https://www.youtube.com/watch?v=K-ifmNiyFRU][ffplay video scopes youtube video]] 1273 | 1274 | + script usage 1275 | 1276 | #+BEGIN_SRC sh 1277 | scopes -i input = histogram 1278 | scopes -o input = rgb overlay 1279 | scopes -p input = rgb parade 1280 | scopes -s input = rgb overlay and parade 1281 | scopes -w input = waveform 1282 | scopes -v input = vector scope 1283 | #+END_SRC 1284 | 1285 | + script help 1286 | 1287 | #+begin_src sh 1288 | scopes -h 1289 | #+end_src 1290 | 1291 | #+BEGIN_EXAMPLE 1292 | # ffplay video scopes 1293 | 1294 | scopes -i input = histogram 1295 | scopes -o input = rgb overlay 1296 | scopes -p input = rgb parade 1297 | scopes -s input = rgb overlay and parade 1298 | scopes -w input = waveform 1299 | scopes -v input = vector scope 1300 | scopes -h = help 1301 | #+END_EXAMPLE 1302 | 1303 | ** tile-thumbnails 1304 | :PROPERTIES: 1305 | :CUSTOM_ID: tile-thumbnails 1306 | :END: 1307 | 1308 | create thumbnails froma a video and tile into an image 1309 | 1310 | [[https://www.youtube.com/watch?v=gFFvKU9nvZE][tile-thumbnails youtube]] 1311 | 1312 | [[https://ffmpeg.org/ffmpeg-utils.html#color-syntax][ffmpeg colour syntax]] 1313 | 1314 | [[https://trac.ffmpeg.org/wiki/Seeking][ffmpeg wiki seeking]] 1315 | 1316 | Note that you can use two different time unit formats: sexagesimal (HOURS:MM:SS.MILLISECONDS, as in 01:23:45.678), or in seconds. 1317 | If a fraction is used, such as 02:30.05, this is interpreted as "5 100ths of a second", not as frame 5. 1318 | For instance, 02:30.5 would be 2 minutes, 30 seconds, and a half a second, which would be the same as using 150.5 in seconds. 1319 | 1320 | + script usage 1321 | 1322 | #+BEGIN_SRC sh 1323 | tile-thumbnails -i input -s 00:00:00.000 -w 000 -t 0x0 -p 00 -m 00 -c color -f fontcolor -b boxcolor -x on -o output.png 1324 | #+END_SRC 1325 | 1326 | + script help 1327 | 1328 | #+begin_src sh 1329 | tile-thumbnails -h 1330 | #+end_src 1331 | 1332 | #+BEGIN_EXAMPLE 1333 | # create an image with thumbnails from a video 1334 | 1335 | tile-thumbnails -i input -s 00:00:00.000 -w 000 -t 0x0 -p 00 -m 00 -c color -f fontcolor -b boxcolor -x on -o output.png 1336 | 1337 | -i input.(mp4|mkv|mov|m4v|webm) 1338 | -s seek into the video file : default 00:00:05 1339 | -w thumbnail width : 160 1340 | -t tile layout format width x height : 4x3 : default 4x3 1341 | -p padding between images : default 7 1342 | -m margin : default 2 1343 | -c color = https://ffmpeg.org/ffmpeg-utils.html#color-syntax : default black 1344 | -f fontcolor : default white 1345 | -b boxcolor : default black 1346 | -x on : default off, display timestamps 1347 | -o output.png : optional argument 1348 | # if option not provided defaults to input-name-tile-date-time.png" 1349 | #+END_EXAMPLE 1350 | 1351 | If the tiled image only creates one thumbnail from the video and the rest of the image is black, 1352 | then the issue may be the frame rate of the video 1353 | 1354 | you can check the videos frame rate with ffmpeg 1355 | 1356 | #+BEGIN_SRC sh 1357 | ffmpeg -i infile.mp4 1358 | #+END_SRC 1359 | 1360 | if the framerate is 29.97 instead of 30 then you can use ffmpeg to change the framerate and fix the issue 1361 | 1362 | #+BEGIN_SRC sh 1363 | ffmpeg -i infile.mp4 -vf fps=fps=30 outfile.mp4 1364 | #+END_SRC 1365 | 1366 | *** tile-thumbnails batch process 1367 | 1368 | batch process videos and create thumbnails from the videos and tile into an image 1369 | 1370 | #+BEGIN_SRC sh 1371 | find . -type f -name "*.mp4" -exec sh -c \ 1372 | 'tile-thumbnails -i "${0}" -s 00:00:10 -w 200 -t 4x4 -p 7 -m 2 -c white' \ 1373 | "{}" \; 1374 | #+END_SRC 1375 | 1376 | ** trim-clip 1377 | :PROPERTIES: 1378 | :CUSTOM_ID: trim-clip 1379 | :END: 1380 | 1381 | trim video clip 1382 | 1383 | [[https://trac.ffmpeg.org/wiki/Seeking][ffmpeg wiki seeking]] 1384 | 1385 | Note that you can use two different time unit formats: sexagesimal (HOURS:MM:SS.MILLISECONDS, as in 01:23:45.678), or in seconds. 1386 | If a fraction is used, such as 02:30.05, this is interpreted as "5 100ths of a second", not as frame 5. 1387 | For instance, 02:30.5 would be 2 minutes, 30 seconds, and a half a second, which would be the same as using 150.5 in seconds. 1388 | 1389 | [[https://youtu.be/LoKloi5N5p0][trim-clip youtube]] 1390 | 1391 | + script usage 1392 | 1393 | #+BEGIN_SRC sh 1394 | trim-clip -s 00:00:00.000 -i input.(mp4|mov|mkv|m4v|aac|m4a|wav|mp3) -t 00:00:00.000 -o output.(mp4|aac|mp3|wav) 1395 | #+END_SRC 1396 | 1397 | + script help 1398 | 1399 | #+begin_src sh 1400 | trim-clip -h 1401 | #+end_src 1402 | 1403 | #+BEGIN_EXAMPLE 1404 | # trim video or audio clips with millisecond accuracy 1405 | https://trac.ffmpeg.org/wiki/Seeking 1406 | 1407 | trim-clip -s 00:00:00.000 -i input.(mp4|mov|mkv|m4v|aac|m4a|wav|mp3) -t 00:00:00.000 -o output.(mp4|aac|mp3|wav) 1408 | -s 00:00:00.000 : start time 1409 | -i input.(mp4|mov|mkv|m4v|aac|m4a|wav|mp3) 1410 | -t 00:00:00.000 : number of seconds after start time 1411 | -o output.(mp4|aac|mp3|wav) : optional argument 1412 | # if option not provided defaults input-name-[start end].(mp4|webm|aac|mp3|wav|ogg) 1413 | #+END_EXAMPLE 1414 | 1415 | *** trim-clip batch process 1416 | 1417 | Batch process files in the current working directory 1418 | 1419 | Note we omit the -o option to use the default outfile name, 1420 | infile-name-trimmed-date-time 1421 | 1422 | Batch trim all the mp4 files in the current directory, 1423 | from 00:00:00 to 00:00:30 1424 | 1425 | #+BEGIN_SRC sh 1426 | find . -type f -name "*.mp4" -exec sh -c \ 1427 | 'trim-clip -s 00:00:00 -i "${0}" -t 00:00:30' \ 1428 | "{}" \; 1429 | #+END_SRC 1430 | 1431 | ** trim-clip-to 1432 | :PROPERTIES: 1433 | :CUSTOM_ID: trim-clip-to 1434 | :END: 1435 | 1436 | trim video clip 1437 | 1438 | [[https://trac.ffmpeg.org/wiki/Seeking][ffmpeg wiki seeking]] 1439 | 1440 | Note that you can use two different time unit formats: sexagesimal (HOURS:MM:SS.MILLISECONDS, as in 01:23:45.678), or in seconds. 1441 | If a fraction is used, such as 02:30.05, this is interpreted as "5 100ths of a second", not as frame 5. 1442 | For instance, 02:30.5 would be 2 minutes, 30 seconds, and a half a second, which would be the same as using 150.5 in seconds. 1443 | 1444 | the trim-clip-to use's a start time with the -s option, 1445 | and an end time with the -t option 1446 | 1447 | + script usage 1448 | 1449 | #+BEGIN_SRC sh 1450 | trim-clip-to -s 00:00:00.000 -i input.(mp4|mov|mkv|m4v|aac|m4a|wav|mp3) -t 00:00:00.000 -o output.(mp4|aac|mp3|wav) 1451 | #+END_SRC 1452 | 1453 | + script help 1454 | 1455 | #+begin_src sh 1456 | trim-clip-to -h 1457 | #+end_src 1458 | 1459 | #+BEGIN_EXAMPLE 1460 | # trim video or audio clips with millisecond accuracy 1461 | https://trac.ffmpeg.org/wiki/Seeking 1462 | 1463 | trim-clip-to -s 00:00:00.000 -i input.(mp4|mov|mkv|m4v|aac|m4a|wav|mp3) -t 00:00:00.000 -o output.(mp4|aac|mp3|wav) 1464 | -s 00:00:00.000 : start time 1465 | -i input.(mp4|mov|mkv|m4v|aac|m4a|wav|mp3) 1466 | -t 00:00:00.000 : end time 1467 | -o output.(mp4|aac|mp3|wav) : optional argument 1468 | # if option not provided defaults input-name-[start end].(mp4|webm|aac|mp3|wav|ogg) 1469 | #+END_EXAMPLE 1470 | 1471 | *** trim-clip-to batch process 1472 | 1473 | Batch process files in the current working directory 1474 | 1475 | Note we omit the -o option to use the default outfile name 1476 | 1477 | Batch trim all the mp4 files in the current directory, 1478 | from 00:00:00 to 00:00:30 1479 | 1480 | #+BEGIN_SRC sh 1481 | find . -type f -name "*.mp4" -exec sh -c \ 1482 | 'trim-clip-to -s 00:00:00 -i "${0}" -t 00:00:30' \ 1483 | "{}" \; 1484 | #+END_SRC 1485 | 1486 | ** vid2gif 1487 | :PROPERTIES: 1488 | :CUSTOM_ID: vid2gif 1489 | :END: 1490 | 1491 | create a gif animation from a video 1492 | 1493 | [[https://www.youtube.com/watch?v=V59q5DC9y6A][vid2gif youtube]] 1494 | 1495 | + script usage 1496 | 1497 | #+BEGIN_SRC sh 1498 | vid2gif -s 00:00:00.000 -i input.(mp4|mov|mkv|m4v) -t 00:00:00.000 -f [00] -w [0000] -o output.gif 1499 | #+END_SRC 1500 | 1501 | + script help 1502 | 1503 | #+begin_src sh 1504 | vid2gif -h 1505 | #+end_src 1506 | 1507 | #+BEGIN_EXAMPLE 1508 | # convert a video into a gif animation 1509 | 1510 | vid2gif -s 00:00:00.000 -i input.(mp4|mov|mkv|m4v) -t 00:00:00.000 -f [00] -w [0000] -o output.gif 1511 | -s 00:00:00.000 : start time 1512 | -i input.(mp4|mov|mkv|m4v) 1513 | -t 00:00:00.000 : number of seconds after start time 1514 | -f [00] : framerate 1515 | -w [0000] : width 1516 | -o output.gif : optional argument 1517 | # if option not provided defaults input-name-gif-date-time.gif 1518 | #+END_EXAMPLE 1519 | 1520 | ** waveform 1521 | :PROPERTIES: 1522 | :CUSTOM_ID: waveform 1523 | :END: 1524 | 1525 | create a waveform from an audio or video file and save as a png 1526 | 1527 | [[https://youtu.be/OBnYLVahUaA][waveform youtube]] 1528 | 1529 | + script usage 1530 | 1531 | #+BEGIN_SRC sh 1532 | waveform -i input.(mp4|mkv|mov|m4v|webm|aac|m4a|wav|mp3) -o output.png 1533 | #+END_SRC 1534 | 1535 | + script help 1536 | 1537 | #+begin_src sh 1538 | waveform -h 1539 | #+end_src 1540 | 1541 | #+BEGIN_EXAMPLE 1542 | # create a waveform from an audio or video file and save as a png 1543 | 1544 | waveform -i input.(mp4|mkv|mov|m4v|webm|aac|m4a|wav|mp3) -o output.png 1545 | -i output.(mp4|mkv|mov|m4v|aac|m4a|wav|mp3) 1546 | -o output.png : optional argument # if option not provided defaults to input-name-waveform-date-time 1547 | #+END_EXAMPLE 1548 | 1549 | *** waveform batch process 1550 | 1551 | Batch process files in the current working directory 1552 | 1553 | Note we omit the -o option to use the default outfile name, 1554 | infile-name-waveform-date-time 1555 | 1556 | Create waveform images from all the mp4 fies in the current directory 1557 | 1558 | #+BEGIN_SRC sh 1559 | find . -type f -name "*.mp4" -exec sh -c \ 1560 | 'waveform -i "${0}"' \ 1561 | "{}" \; 1562 | #+END_SRC 1563 | 1564 | ** webp 1565 | :PROPERTIES: 1566 | :CUSTOM_ID: webp 1567 | :END: 1568 | 1569 | create a animated webp image from a video with ffmpeg 1570 | 1571 | [[https://www.youtube.com/watch?v=5iXjbQ7uDiM][webp animated images youtube]] 1572 | 1573 | + script usage 1574 | 1575 | #+BEGIN_SRC sh 1576 | webp -i input -c 0-6 -q 0-100 -f 15 -w 600 -p none -o output.webp 1577 | #+END_SRC 1578 | 1579 | + script help 1580 | 1581 | #+begin_src sh 1582 | webp -h 1583 | #+end_src 1584 | 1585 | #+BEGIN_EXAMPLE 1586 | # webp animated image 1587 | 1588 | webp -i input -c 0-6 -q 0-100 -f 15 -w 600 -p none -o output.webp 1589 | -i input 1590 | -c compression level: 0 - 6 : default 4 1591 | -q quality: 0 - 100 : default 80 1592 | -f framerate: default 15 1593 | -w width: default 600px 1594 | -p preset: none|default|picture|photo|drawing|icon|text : default none 1595 | -o output.webp : optional agument 1596 | # if option not provided defaults input-name.webp 1597 | #+END_EXAMPLE 1598 | 1599 | *** webp batch process 1600 | 1601 | Batch process files in the current working directory 1602 | 1603 | #+BEGIN_SRC sh 1604 | find . -type f -name "*.mp4" -exec sh -c 'webp -i "${0}"' "{}" \; 1605 | #+END_SRC 1606 | 1607 | ** xfade 1608 | :PROPERTIES: 1609 | :CUSTOM_ID: xfade 1610 | :END: 1611 | 1612 | + [[https://www.youtube.com/watch?v=McQM3ooNx-4][xfade script demo youtube]] 1613 | 1614 | apply a transition between two clips with the xfade filters 1615 | 1616 | [[https://trac.ffmpeg.org/wiki/Xfade][xfade ffmpeg wiki]] 1617 | 1618 | + script usage 1619 | 1620 | #+begin_src sh 1621 | xfade -a clip1.(mp4|mkv|mov|m4v) -b clip2.(mp4|mkv|mov|m4v) -d duration -t transition -f offset -o output.mp4 1622 | #+end_src 1623 | 1624 | + script help 1625 | 1626 | #+begin_src sh 1627 | xfade -h 1628 | #+end_src 1629 | 1630 | #+begin_example 1631 | # ffmpeg xfade transitions 1632 | 1633 | xfade -a clip1.(mp4|mkv|mov|m4v) -b clip2.(mp4|mkv|mov|m4v) -d duration -t transition -f offset -o output.mp4 1634 | -a clip1.(mp4|mkv|mov|m4v) : first clip 1635 | -b clip2.(mp4|mkv|mov|m4v) : second clip 1636 | -d duration : transition duration 1637 | -t transition : transition 1638 | -f offset : offset 1639 | -o output.mp4 : optional argument # if option not provided defaults to input-name-xfade-date-time 1640 | 1641 | + transitions 1642 | 1643 | circleclose, circlecrop, circleopen, diagbl, diagbr, diagtl, diagtr, dissolve, distance 1644 | fade, fadeblack, fadegrays, fadewhite, hblur, hlslice, horzclose, horzopen, hrslice 1645 | pixelize, radial, rectcrop, slidedown, slideleft, slideright, slideup, smoothdown 1646 | smoothleft, smoothright, smoothup, squeezeh, squeezev, vdslice, vertclose, vertopen, vuslice 1647 | wipebl, wipebr, wipedown, wipeleft, wiperight, wipetl, wipetr, wipeup 1648 | #+end_example 1649 | 1650 | ** zoompan 1651 | :PROPERTIES: 1652 | :CUSTOM_ID: zoompan 1653 | :END: 1654 | 1655 | convert a image to video and apply the ken burns effect to the clip 1656 | 1657 | + script usage 1658 | 1659 | #+BEGIN_SRC sh 1660 | zoompan -i input.(png|jpg|jpeg) -d (000) -z (in|out) -p (tl|c|tc|tr|bl|br) -o output.mp4 1661 | #+END_SRC 1662 | 1663 | + script help 1664 | 1665 | #+begin_src sh 1666 | zoompan -h 1667 | #+end_src 1668 | 1669 | #+BEGIN_EXAMPLE 1670 | # zoompan, ken burns effect 1671 | 1672 | zoompan -i input.(png|jpg|jpeg) -d (000) -z (in|out) -p (tl|c|tc|tr|bl|br) -o output.mp4 1673 | -i = input.(png|jpg|jpeg) 1674 | -d = duration : from 1-999 1675 | -z = zoom : in or out 1676 | -p = position : zoom to location listed below 1677 | -o = outfile.mp4 : optional argument # default is input-name-zoompan-date-time 1678 | 1679 | +------------------------------+ 1680 | +tl tc tr+ 1681 | + + 1682 | + c + 1683 | + + 1684 | +bl br+ 1685 | +------------------------------+ 1686 | #+END_EXAMPLE 1687 | 1688 | *** zoompan batch process 1689 | 1690 | Batch process files in the current working directory 1691 | 1692 | Note we omit the -o option to use the default outfile name, 1693 | infile-name-zoompan-date-time 1694 | 1695 | Batch process all the png files in the current working directory, 1696 | apply the zoompan script with a 5 second duration, zoom in to the center of the image 1697 | 1698 | #+BEGIN_SRC sh 1699 | find . -type f -name "*.png" -exec sh -c \ 1700 | 'zoompan -i "${0}" -d 5 -z in -p c' \ 1701 | "{}" \; 1702 | #+END_SRC 1703 | --------------------------------------------------------------------------------