-
-
Save interjection/4b83c0790ce82982caec to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# webm.bash | |
# Requires: ffmpeg, mplayer, gawk | |
set -o errexit | |
shopt -s nocaseglob | |
shopt -s nocasematch | |
# Default temporary font dir, rescursively deleted at the end | |
FONTDIR="$HOME/.fonts/tmp/" | |
# Log file ffmpeg uses by default | |
LOG="ffmpeg2pass-0.log" | |
# Default codec | |
CODEC="libvpx" | |
# Default width of output | |
W="-1" | |
# Default height of output | |
H="-1" | |
# Default CRF | |
CRF="4" | |
# Default output limit in megabytes | |
MBLIMIT="3" | |
# Default number of threads that ffmpeg will use | |
THREADS="$(nproc)" | |
# Default lag in frames | |
LAG="16" | |
# Default slices | |
SLICES="8" | |
# Default X display for screen recorder | |
XDISP=":0" | |
# Default screen recorder FPS | |
FPS="60" | |
# Default follow distance for screen recorder when using -follow_mouse | |
DIS="75" | |
# Slave input config file, delete ~/.mplayer/webmcrop if you make changes | |
MPCONFIG="$(cat <<EOF | |
z pausing_keep get_time_pos | |
w pausing_keep change_rectangle 3 -10 | |
a pausing_keep change_rectangle 2 -10 | |
s pausing_keep change_rectangle 3 10 | |
d pausing_keep change_rectangle 2 10 | |
KP8 pausing_keep change_rectangle 1 -10 | |
KP4 pausing_keep change_rectangle 0 -10 | |
KP2 pausing_keep change_rectangle 1 10 | |
KP6 pausing_keep change_rectangle 0 10 | |
EOF | |
)" | |
Usage() | |
{ | |
cat <<EOF | |
webm.bash | |
It just werks. | |
Usage: | |
-i Input file, e.g. inputfilename.mkv (required) | |
-o Output file, e.g. output.webm | |
Defaults to inputfilename_HH:MM:SS.MS_HH:MM:SS.MS.webm if you | |
specify a starting time and ending time, otherwise defaults to | |
inputfilename.webm. | |
-s Starting time in HH:MM:SS.MS, e.g. 02:23:59.00 | |
Defaults to starting time of input file. | |
-e Ending time in HH:MM:SS.MS, e.g. 02:24:01.30 | |
Defaults to ending time of input file. | |
-q CRF, e.g. 10 | |
Enables constant quality mode and sets quality: values 0-63 | |
with lower values being lower quality. | |
Defaults to $CRF | |
-b Output bitrate in kbps, e.g. 1000 | |
-y Segmented MKV mode | |
When using a video player that plays back ordered chapters or | |
segmented MKVs (mpv) to manually get the starting and ending | |
times, use this mode to correct times for ffmpeg automatically. | |
-c Crop output using mplayer (interactive) | |
Launches mplayer to crop with your w, a, s and d keys to move | |
the rectangle and your keypad arrow keys to adjust the size of | |
the rectangle filter. | |
-u Burn subtitles into output (interactive) | |
Prompts you to select the stream containing the subtitles you | |
wish to hardsub into the output video. | |
-v Cut output video times using mplayer (interactive) | |
Launches mplayer so you can select your start and end times | |
using the z key. Only the first and the last inputs will be | |
taken into account. | |
-w Output width, e.g. 640 | |
When overriding either the default width or height, the output | |
will be scaled to the correct aspect ratio, but not when you | |
override both. | |
Defaults to $W | |
-h Output height, e.g. 480 | |
Defaults to $H | |
-m Filesize limit in megabytes, e.g. 4 | |
Defaults to $MBLIMIT | |
-t Threads, e.g. 8 | |
Number of threads to use for encoding, try setting this option | |
to 1 if the file gets cut off (i.e. slightly over the size | |
limit.) | |
Defaults to $THREADS | |
-l Lag in frames, e.g. 25 | |
Number of frames for ffmpeg to look ahead for when encoding. | |
Defaults to $LAG | |
-x Slices, e.g. 4 | |
Directs the encoder to split the coefficient encoding across | |
multiple data partitions that can be encoded independently. | |
A value of 1 or 2 is recommended for small images, and 4 or | |
8 is recommended for high definition images. | |
Defaults to $SLICES | |
-f Additional ffmpeg filters, e.g. "crop=1140:574:450:100," | |
-p Additional ffmpeg parameters, e.g. "-loglevel quiet" | |
-r Record screen with ffmpeg (interactive) | |
-d Screen recorder window cropping (interactive) | |
-z Follow the mouse when recording the screen | |
Requires width and/or height parameter to be passed. | |
-9 Use the vp9 codec instead of vp8 | |
EOF | |
} | |
while getopts "cuvyrdz9i:o:s:e:b:w:h:q:m:t:l:x:f:p:" OPTION; do | |
case "$OPTION" in | |
c) CROP=true;; | |
u) SUB=true;; | |
v) CUT=true;; | |
y) SEGMENTED=true;; | |
r) XRECORD=true;; | |
d) XCROP=true;; | |
z) XMOUSE=true;; | |
9) CODEC="libvpx-vp9";; | |
i) IF="$OPTARG";; | |
o) OF="$OPTARG";; | |
s) ST="$OPTARG";; | |
e) ET="$OPTARG";; | |
b) BR="$OPTARG";; | |
w) W="$OPTARG";; | |
h) H="$OPTARG";; | |
q) CRF="$OPTARG" | |
CQ=true;; | |
m) MBLIMIT="$OPTARG";; | |
t) THREADS="$OPTARG";; | |
l) LAG="$OPTARG";; | |
x) SLICES="$OPTARG";; | |
f) ADDFILTERS="$OPTARG";; | |
p) ADDPARAMS="$OPTARG";; | |
?) Usage | |
exit 1;; | |
:) Usage | |
exit 1;; | |
esac | |
done | |
ScreenRecorder() | |
{ | |
IF="$(date +'%Y-%m-%d %k:%M:%S').mkv" | |
# Current display resolution | |
RES="$(xrandr -q | awk -F 'current' -F ',' 'NR==1 {gsub("( |current)",""); print $2}')" | |
if [[ "$XMOUSE" = true ]] ; then | |
if [[ "$W" -eq "-1" && "$H" -eq "-1" ]] ; then | |
echo "Specify a width and/or height" && exit 1 | |
exit 1 | |
elif [[ "$W" -eq "-1" ]] ; then | |
W="$H" | |
elif [[ "$H" -eq "-1" ]] ; then | |
H="$W" | |
fi | |
RES="${W}x${H}" | |
ffmpeg -f x11grab -follow_mouse "$DIS" -s "$RES" -i "$XDISP" -r "$FPS" \ | |
-threads "$THREADS" -crf 0 -preset ultrafast -c:v libx264 "$IF" | |
elif [[ $XCROP = true ]] ; then | |
echo "Click on the window you wish to record..." | |
XINFO="$(xwininfo)" | |
WINX="$(echo "$XINFO" | grep "Absolute upper-left X" | awk '{print $4}')" | |
WINY="$(echo "$XINFO" | grep "Absolute upper-left Y" | awk '{print $4}')" | |
WINW="$(echo "$XINFO" | grep Width | awk '{print $2}')" | |
WINH="$(echo "$XINFO" | grep Height | awk '{print $2}')" | |
XCROPS="crop=${WINW}:${WINH}:${WINX}:${WINY}" | |
ffmpeg -f x11grab -s "$RES" -i "$XDISP" -r "$FPS" -vf "$XCROPS" \ | |
-threads "$THREADS" -crf 0 -preset ultrafast -c:v libx264 "$IF" | |
else | |
ffmpeg -f x11grab -s "$RES" -i "$XDISP" -r "$FPS" -threads "$THREADS" \ | |
-threads "$THREADS" -crf 0 -preset ultrafast -c:v libx264 "$IF" | |
fi | |
} | |
SlaveInput() | |
{ | |
# Check if ~/.mplayer/webmcrop exists, otherwise create it | |
if [[ ! -f "$HOME/.mplayer/webmcrop" ]] ; then | |
echo "$MPCONFIG" > "$HOME/.mplayer/webmcrop" | |
fi | |
if [[ -z "$STS" ]] ; then | |
# ffmpeg with the rectangle filter, use webmcrop as input conf | |
mplayer -title "Cut Video" -fixed-vo -idle -vf rectangle \ | |
-osdlevel 3 -osd-fractions 2 -input conf=webmcrop "$IF" 2>&1 | |
else | |
mplayer -title "Crop Video" -ss "$STS" -fixed-vo -idle -vf rectangle \ | |
-osdlevel 3 -osd-fractions 2 -input conf=webmcrop "$IF" 2>&1 | |
fi | |
} | |
SecondsToTimestamp() | |
{ | |
# SS.MS to HH:MM:SS.MS, ANS_TIME_POSITION only returns MS to one digit | |
awk -F . '{printf("%02d:%02d:%02d.%1d",($1/60/60%24),($1/60%60),($1%60),($2))}' | |
} | |
CutVideo() | |
{ | |
STARTEND="$(SlaveInput | grep ANS_TIME_POSITION)" | |
# First and last occurance of ANS_TIME_POSITION in $STARTEND | |
STS="$(echo "$STARTEND" | head -1 | awk -F = '{print $2}')" | |
ETS="$(echo "$STARTEND" | tail -1 | awk -F = '{print $2}')" | |
ST="$(echo "$STS" | SecondsToTimestamp)" | |
ET="$(echo "$ETS" | SecondsToTimestamp)" | |
} | |
SetOutputFilename() | |
{ | |
if [[ -z "$ST" || -z "$ET" ]] ; then | |
# Output filename when converting the complete input file | |
OF="$(echo "${IF%.*}" | awk -F / '{print $NF}').webm" | |
else | |
OF="$(echo "${IF%.*}" | awk -F / '{print $NF}')_${ST}_${ET}.webm" | |
fi | |
} | |
CalculateRunningTime() | |
{ | |
# Get start (not sure if this matters) and end time from ffmpeg | |
if [[ -z "$ST" ]] ; then | |
ST="$(ffmpeg -i "$IF" 2>&1 | grep Duration | awk '{print $4}' | tr -d ,)" | |
fi | |
if [[ -z "$ET" ]] ; then | |
ET="$(ffmpeg -i "$IF" 2>&1 | grep Duration | awk '{print $2}' | tr -d ,)" | |
fi | |
if [[ -z "$STARTEND" ]] ; then | |
# Convert HH:MM:SS.MS to simple seconds with awk, if not already set | |
ETS="$(echo "$ET" | awk -F : '{print ($1*3600) + ($2*60) + $3}')" | |
STS="$(echo "$ST" | awk -F : '{print ($1*3600) + ($2*60) + $3}')" | |
fi | |
if [[ "$SEGMENTED" = true ]] ; then | |
OP="$(ffmpeg -i "$IF" 2>&1 | grep 'Chapter #0.0' | awk '{print $6}')" | |
STS="$(bc <<< "$STS - $OP")" | |
ETS="$(bc <<< "$ETS - $OP")" | |
fi | |
# Running time of output selection | |
TIME="$(bc <<< "$ETS - $STS")" | |
} | |
CalculateBitrate() | |
{ | |
if [[ "$(bc <<< "$TIME < 10")" -eq 1 && -z "$BR" ]] || [[ "$CQ" = true ]] ; then | |
# If running time less than 10 and no bitrate specified or CQ mode | |
BITRATE="-crf $CRF -qmax $(bc <<< "$CRF + 6")" | |
elif [[ -z "$BR" ]] ; then | |
# Calculates bitrate depending on $MBLIMIT | |
BITRATE="-b:v $(bc <<< "($MBLIMIT * 8192) / $TIME")K" | |
else | |
BITRATE="-b:v ${BR}K" | |
fi | |
} | |
CropOutput() | |
{ | |
# Last occurence of WIDTH:HEIGHT:X:Y from SlaveInput() | |
WHXY="$(SlaveInput | grep rectangle | tail -1 | awk -F = '{print $2}')" | |
# http://ffmpeg.org/ffmpeg-filters.html#crop | |
CROPS="crop=${WHXY}," | |
} | |
ShiftSubtitleTimings() | |
{ | |
awk -v "offset=$STS" ' | |
function stamp2sec(timestamp) { | |
split(timestamp,tfields,":") | |
ret=0 | |
if (tfields[3]) { | |
ret = tfields[1]*60*60 + tfields[2]*60 + tfields[3] | |
} else if (tfields[2]) { | |
ret = tfields[1]*60 + tfields[2] | |
} else { | |
ret = tfields[1] | |
} | |
return ret | |
} | |
function sec2stamp(time) { | |
hour = sprintf("%d",time/(60*60)) | |
minute = sprintf("%d",time/60) | |
sec = sprintf("%d",time) | |
dec = sprintf("%d",time*100)%100 | |
return hour":"minute":"sec"."dec | |
} | |
BEGIN {FS=",";off=offset} | |
{doprint=1} | |
/\[Events\]/ { events=1 } | |
events && /^Dialogue/ { | |
doprint=1 | |
time=stamp2sec($2) | |
if (time-off >= 0) { | |
sub($2,sec2stamp(time-off)) | |
time=stamp2sec($3) | |
sub($3,sec2stamp(time-off)) | |
} else { doprint=0 } | |
} | |
doprint {print}' | |
} | |
DetermineSubtitleFormat() { | |
SUBTYPE="$(ffmpeg -i "$IF" 2>&1 | grep "#0:${STREAM}" | awk '{print $4}')" | |
} | |
HardsubOutput() | |
{ | |
# Dump fonts if there are any, and continue | |
ffmpeg -v verbose -dump_attachment:t "" -i "$IF" -y 2>/dev/null || true | |
mkdir -p "$FONTDIR" | |
# Move dumped fonts to $FONTDIR if there are any, and continue | |
mv -f ./*.otf ./*.ttf ./*.ttc "$FONTDIR" 2>/dev/null || true | |
# Invoke ffmpeg, because mediainfo is a piece of shit, to grep | |
# subtitles contained in file and two additional lines | |
ffmpeg -v verbose -i "$IF" 2>&1 | grep -A2 Subtitle | |
read -p "Stream number? [e.g. #0.3 would be 3] " STREAM | |
DetermineSubtitleFormat | |
if [[ "$SUBTYPE" = "subrip" ]] ; then | |
# Dump subtitle stream from start of selection to end of selection | |
ffmpeg -v verbose -ss "$(bc<<<"$STS - 30")" -i "$IF" -ss 30 -t "$TIME" \ | |
-an -vn -codec:s:${STREAM} srt webmsubsns.srt | |
# Shift the subtitle timings with awk so we can use fastseek | |
ShiftSubtitleTimings < webmsubsns.srt > webmsubs.srt | |
# http://ffmpeg.org/ffmpeg-filters.html#subtitles-1 | |
SUBS=",subtitles=$(pwd)/webmsubs.srt" | |
elif [[ "$SUBTYPE" = "mov_text" ]] || [[ "$SUBTYPE" = "tx3g" ]] ; then | |
ffmpeg -v verbose -ss "$(bc<<<"$STS - 30")" -i "$IF" -ss 30 -t "$TIME" \ | |
-an -vn -codec:s:${STREAM} mov_text webmsubsns.ttxt | |
ShiftSubtitleTimings < webmsubsns.ttxt > webmsubs.ttxt | |
SUBS=",subtitles=$(pwd)/webmsubs.ttxt" | |
elif [[ "$SUBTYPE" = "ass" ]] || [[ "$SUBTYPE" = "ssa" ]] ; then | |
ffmpeg -v verbose -ss "$(bc<<<"$STS - 30")" -i "$IF" -ss 30 -t "$TIME" \ | |
-an -vn -codec:s:${STREAM} ass webmsubsns.ass | |
ShiftSubtitleTimings < webmsubsns.ass > webmsubs.ass | |
SUBS=",ass=$(pwd)/webmsubs.ass" | |
else | |
echo "Unsupported subtitle format" && exit 1 | |
fi | |
} | |
Encode() | |
{ | |
# http://trac.ffmpeg.org/wiki/FilteringGuide | |
FILTERS="${ADDFILTERS}${CROPS}scale=${W}:${H}${SUBS}" | |
# Do a 2-pass encode, throwing the first output into /dev/null | |
# It may work with '-accurate_seek', but this works for now | |
# See: https://ffmpeg.org/ffmpeg.html#Main-options | |
ffmpeg -v verbose -ss "$STS" -i "$IF" -t "$TIME" -threads "$THREADS" \ | |
-quality best -slices "$SLICES" -auto-alt-ref 1 -lag-in-frames "$LAG" \ | |
-an -sn -fs "${MBLIMIT}M" -vf "$FILTERS" -c:v "$CODEC" $BITRATE \ | |
$ADDPARAMS -pass 1 -f webm /dev/null -y || true | |
ffmpeg -v verbose -ss "$STS" -i "$IF" -t "$TIME" -threads "$THREADS" \ | |
-quality best -slices "$SLICES" -auto-alt-ref 1 -lag-in-frames "$LAG" \ | |
-an -sn -fs "${MBLIMIT}M" -vf "$FILTERS" -c:v "$CODEC" $BITRATE \ | |
$ADDPARAMS -pass 2 -f webm "$OF" -y || true | |
} | |
Cleanup() | |
{ | |
if [[ "$SUB" = true ]] ; then | |
# Remove temporary font dir and subs | |
rm -vrf "$FONTDIR" | |
rm -v webmsubsns.* | |
rm -v webmsubs.* | |
fi | |
if [[ "$XRECORD" = true ]] ; then | |
# Remove screenrecorder output | |
rm -v "$IF" | |
fi | |
rm -v "$LOG" | |
} | |
PrintVariables() | |
{ | |
echo "Input file: $IF" | |
echo "Output file: $OF" | |
echo "Starting time: ${ST}/${STS} seconds" | |
echo "Ending time: ${ET}/${ETS} seconds" | |
echo "Running time of output: $TIME seconds" | |
echo "Bitrate was $BITRATE" | |
echo "Slices: $SLICES, lag in frames: $LAG" | |
echo "Threads: $THREADS, set option to one output was slightly cut off" | |
echo "Filters used: $FILTERS" | |
echo "Additional parameters: $ADDPARAMS" | |
} | |
if [[ "$XRECORD" = true ]] ; then | |
ScreenRecorder | |
fi | |
if [[ -z "$IF" ]] ; then | |
Usage && exit 1 | |
fi | |
if [[ "$CUT" = true ]] ; then | |
CutVideo | |
fi | |
if [[ -z "$OF" ]] ; then | |
SetOutputFilename | |
fi | |
CalculateRunningTime | |
CalculateBitrate | |
if [[ "$CROP" = true ]] ; then | |
CropOutput | |
fi | |
if [[ "$SUB" = true ]] ; then | |
HardsubOutput | |
fi | |
Encode | |
Cleanup | |
PrintVariables |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment