transferring several commands from private repo

This commit is contained in:
Ville Rantanen
2021-10-08 11:19:58 +03:00
parent 754938ca32
commit 707eba3ead
20 changed files with 1140 additions and 289 deletions

51
av/ffmpeg-magic Executable file
View File

@@ -0,0 +1,51 @@
#!/bin/bash
if [[ "$1" = "-h" ]]; then
echo This script prints out various ffmpeg commands
exit
fi
cat <<'EOF' | md-color
# Basic Good Quality
a) -hide_banner -q:v 0 -q:a 0
b) -s hd720 -g 250 -c:v libx264 -crf 23 -c:a aac -movflags faststart -strict -2
YIFY) -c:v libx264 -crf 27 -x264-params cabac=1:ref=5:analyse=0x133:me=umh:subme=9:chroma-me=1:deadzone-inter=21:deadzone-intra=11:b-adapt=2:rc-lookahead=60:vbv-maxrate=10000:vbv-bufsize=10000:qpmax=69:bframes=5:b-adapt=2:direct=auto:crf-max=51:weightp=2:merange=24:chroma-qp-offset=-1:sync-lookahead=2:psy-rd=1.00,0.15:trellis=2:min-keyint=23:partitions=all -c:a aac -ar 44100 -b:a 128k -map 0
# Smooth slow motion
-filter:v "minterpolate='mi_mode=mci:mc_mode=aobmc:vsbmc=1:fps=120'",setpts=1.5*PTS
# BCS + unsharp (works also on -filter_complex)
-vf unsharp,"eq=contrast=1.4:brightness=0.1:saturation=1.5"
# More filters to try
histeq=strength=0.1,normalize=blackpt=red:whitept=yellow:smoothing=150,hqdn3d=4:4:3:3"
# Trim
-vcodec copy -acodec copy -ss 00:01:30 -to 00:02:30
-vcodec copy -acodec copy -ss 50 -to 65
# Vertical2Horizontal
-filter_complex "[0:v]scale=ih*16/9:-1,boxblur=luma_radius=min(h\,w)/20:luma_power=1:chroma_radius=min(cw\,ch)/20:chroma_power=1[bg];[bg][0:v]overlay=(W-w)/2:(H-h)/2,crop=h=iw*9/16"
# Rotate 0=90CCW+flip,1=90CW,2=90CCW,3=90CW+flip. example 180 deg.
-vf "transpose=2,transpose=2"
# Antishake-Stabilize
in=videofile.mp4
ffmpeg -i "$in" -vf vidstabdetect=stepsize=32:shakiness=10:accuracy=10:result="$in".transforms.trf -f null -
ffmpeg -i "$in" -vf vidstabtransform=input="$in".transforms.trf:zoom=0:smoothing=10,unsharp=5:5:0.8:3:3:0.4 -q:v 0 "$in".stabilized.mp4
# To Stills
-vf fps=10 "$1".%06d.png
# From Stills
-r 1/5 -start_number 0 -i img%03d.png -r 30 -pix_fmt yuv420p
# Segment
-segment_time 00:15:00 -f segment -reset_timestamps 1 "$in.%03d.480p.mp4"
# Stacking
-i input0 -i input1 -filter_complex vstack=inputs=2
-i input0 -i input1 -filter_complex h stack=inputs=2
# Chroma key
-f lavfi -i color=c=green:s=1920x1080 -i input.mp4 -filter_complex "[1:v]chromakey=0x297141:0.1:0.0[ckout];[0:v][ckout]overlay[o]" -map [o] -map 1:a
# View live
-pix_fmt yuv420p -f matroska - | ffplay -i -
# Concatenate
find . -type f -printf "file '%P'\n" | sort -V > filelist.txt
ffmpeg -f concat -safe 0 -i filelist.txt -c copy concatenated.mp4
# Audio encode
ffmpeg -i file.wav -codec:a libmp3lame -qscale:a 2 file.mp3
# Audio map
ffmpeg -i input.mp4 -i input.mp3 -c copy -map 0:0 -map 1:1 -shortest out.mp4
EOF

292
av/ffmpeg-parser Executable file
View File

@@ -0,0 +1,292 @@
#!/usr/bin/env python3
import subprocess
import sys
import os
import time
import parse
from ansi import cursor
from datetime import datetime
class Chopper:
def __init__(self, buf):
self.buffer = buf
self.memory = bytearray()
self.eol1 = b"\r"
self.eol2 = b"\n"
self.leneol = len(self.eol1)
def read(self):
line = bytearray()
while True:
c = self.buffer.read(1)
if c:
line += c
if line[-self.leneol :] == self.eol1:
break
if line[-self.leneol :] == self.eol2:
break
else:
break
return bytes(line).decode("utf-8")
class Progress:
def __init__(self):
self.started = time.time()
self.duration = None
self.framedata = {}
self.parsable = (
"frame",
"fps",
"bitrate",
"total_size",
"speed",
"out_time_ms",
)
self.inputs = []
self.inputs_full = []
self.input = "NA"
self.input_size = "NA"
self.outputs = []
self.output = "NA"
def parse(self, line):
if not self.parse_frame(line):
print(line.rstrip())
self.parse_input(line)
self.parse_output(line)
self.parse_duration(line)
self.print()
def parse_input(self, line):
# Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'VID_20210804_141045.mp4':
parsed = parse.parse("Input #{:d}, {}, from '{}':", line.strip())
try:
self.inputs.append(os.path.basename(parsed[2]))
self.inputs_full.append(parsed[2])
self.input = ", ".join(self.inputs)
self.input_size = ", ".join(
[self._mbstr(os.stat(i).st_size) for i in self.inputs_full]
)
except Exception:
return
def parse_output(self, line):
parsed = parse.parse("Output #{:d}, {}, to '{}':", line.strip())
try:
self.outputs.append(os.path.basename(parsed[2]))
self.output = ", ".join(self.outputs)
except Exception:
return
def parse_duration(self, line):
if self.duration:
return
parsed = parse.parse(
"Duration: {:d}:{:d}:{:d}.{:d}, start: {}, bitrate: {} kb/s", line.strip()
)
# ~ Duration: 00:00:48.21, start: 0.000000, bitrate: 17780 kb/s
try:
self.duration = 3600 * parsed[0] + 60 * parsed[1] + parsed[2]
except Exception:
return
def parse_frame(self, line):
try:
values = len(line.strip().split("="))
if values > 2: # and line.startswith("frame="):
return True
if values != 2:
return False
parsed = parse.parse("{}={}", line.strip())
if not parsed:
return False
if parsed[0] in self.parsable:
if parsed[0] == "out_time_ms":
self.framedata["out_time_s"] = float(parsed[1]) / 1000000
else:
self.framedata[parsed[0]] = parsed[1]
except Exception as e:
print(e)
return False
return True
def print(self):
if len(self.framedata) == 0:
return
self.framedata["time_elapsed"] = int(time.time() - self.started)
if "out_time_s" in self.framedata:
self.framedata["percent_done"] = round(
100 * self.framedata["out_time_s"] / self.duration, 1
)
try:
if self.framedata["percent_done"] > 100:
self.framedata["time_remaining"] = "NA"
else:
self.framedata["time_remaining"] = int(
(
100
* float(time.time() - self.started)
/ self.framedata["percent_done"]
)
- self.framedata["time_elapsed"]
)
except Exception:
self.framedata["time_remaining"] = "NA"
try:
self.framedata["projected_size"] = int(
(
100
* float(self.framedata["total_size"])
/ self.framedata["percent_done"]
)
)
except Exception:
self.framedata["time_remaining"] = "NA"
else:
self.framedata["percent_done"] = "NA"
self.framedata["time_remaining"] = "NA"
self.framedata["projected_size"] = "NA"
try:
msg = """{cl}==== Q to exit ===============
{cl}Input: {input_file}
{cl}Output: {output_file}
{cl}Progress: {progress}% Elapsed: H{elapsed}
{cl}Finished in: H{left}
{cl}Frame: {frame} = {out_time}
{cl}Source duration: {duration}
{cl}Processing speed: FPS {fps} / {speed}
{cl}Bitrate: {bitrate}
{cl}File size: {total_size}Mb -> {projected_size}Mb (Input: {input_size}Mb)
{cl}{progress_bar}\r{up}""".format(
input_file=self.input,
input_size=self.input_size,
output_file=self.output,
progress=self.framedata["percent_done"],
progress_bar=self._progress_bar(self.framedata["percent_done"]),
elapsed=self._timestr(self.framedata["time_elapsed"]),
left=self._timestr(self.framedata["time_remaining"]),
duration=self._timestr(self.duration),
out_time=self._timestr(self.framedata["out_time_s"]),
frame=self.framedata.get("frame", "NA"),
fps=self.framedata.get("fps", "NA"),
bitrate=self.framedata["bitrate"],
speed=self.framedata["speed"],
total_size=self._mbstr(self.framedata["total_size"]),
projected_size=self._mbstr(self.framedata["projected_size"]),
up=cursor.up(10),
cl=cursor.erase_line(),
)
sys.stdout.write(msg)
sys.stdout.flush()
except Exception as e:
pass
def finish(self):
for i in range(len(self.framedata) + 3):
sys.stdout.write("\n")
sys.stdout.flush()
def _timestr(self, sec):
try:
hours = int(sec) // 3600 % 24
minutes = int(sec) // 60 % 60
seconds = int(sec) % 60
return "{:02d}:{:02d}:{:02d}".format(hours, minutes, seconds)
except Exception:
return sec
def _mbstr(self, b):
try:
return "{:.1f}".format((float(b) / (1024 ** 2)))
except Exception:
return b
def _progress_bar(self, p):
try:
done_chars = int(float(p) * 30 / 100)
todo_chars = int(30 - done_chars)
return (">" * done_chars + "-" * todo_chars)[0:30]
except Exception:
return ">" * 30
def parse_output(args):
return args[-1]
def parse_overwrite(args):
return "-y" in args
def ask_for_overwrite(commands, path):
print("File {} exists. Overwrite or break? ([y]/n)".format(path))
answer = input()
if answer == "n":
sys.exit(1)
commands.insert(-1, "-y")
return commands
if __name__ == "__main__":
commands = sys.argv[1:]
if len(commands) == 1:
if commands[0] == "-h":
print("This command passes all arguments to FFMPEG, and parses output to readable format with progress")
sys.exit(0)
try:
has_overwrite = parse_overwrite(commands)
if not has_overwrite:
output_file = parse_output(commands)
output_exists = os.path.exists(output_file)
if output_exists:
commands = ask_for_overwrite(commands, output_file)
process = subprocess.Popen(
["", "-progress", "pipe:2", "-hide_banner"] + commands,
executable="ffmpeg",
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
)
chopper = Chopper(process.stdout)
progress = Progress()
started = time.time()
duration = None
while True:
output = chopper.read()
progress.parse(output)
if not output:
if process.poll() != None:
break
rc = process.poll()
progress.finish()
print("Exit code: {}".format(rc))
sys.exit(rc)
except Exception as e:
print(e)
process.terminate()
progress.finish()
while process.poll() != None:
time.sleep(1)
sys.exit(1)

155
av/ffmpeg-screencapture Executable file
View File

@@ -0,0 +1,155 @@
#!/bin/bash
_help() {
echo 'Commands:
-o [filename] -s [size] -p [pos] -r [framerate] -d [delay] -i
default filename: '$filename'
default size: '$size'
default pos: '$pos'
default framerate: '$framerate'
default delay: '$delay'
-i for interactive selection using "slop"
--shell to record current terminal
Hit "q" when finished!
'
}
_helpexit(){
_help
exit
}
function animate() {
x0=$( tput cols )
pre=$( qolop eval "_qPos save; _qPos abs 1 $x0; _qCol R;" )
post=$( qolop eval "_qCol Z; _qPos restore;" )
while true; do
{
printf "%s*%s" "$pre" "$post"
sleep 1
printf "%s %s" "$pre" "$post"
sleep 1
} >&2
done
}
printf -v filename "grab-%(%FT%H-%M)T.mp4"
size=$( xdpyinfo | awk "/dimensions/{print \$2}" )
pos="0,0"
framerate=15
delay=0
interact=0
new_shell=0
for (( i=1; i<=$#; i++ )); do
[[ ${!i} = "-h"* ]] && _helpexit
[[ ${!i} = "--h"* ]] && _helpexit
[[ ${!i} = "help" ]] && _helpexit
done
for (( i=1; i<=$#; i++ )); do
j=$(( $i + 1 ))
if [[ ${!i} = "-o" ]]; then
filename="${!j}"
shift
fi
if [[ ${!i} = "-s" ]]; then
size="${!j}"
shift
fi
if [[ ${!i} = "-p" ]]; then
pos="${!j}"
shift
fi
if [[ ${!i} = "-r" ]]; then
framerate="${!j}"
shift
fi
if [[ ${!i} = "-d" ]]; then
delay="${!j}"
shift
fi
if [[ ${!i} = "-i" ]]; then
interact=1
fi
if [[ ${!i} = "--shell" ]]; then
new_shell=1
fi
done
if [[ $new_shell -eq 0 ]]; then
if [[ $interact -eq 1 ]]; then
slop=$( slop -b 10 -c 1.0,0.8,0.1,0.8 )
size=$( echo $slop | cut -d+ -f1 )
pos=$( echo $slop | cut -d+ -f2-3 )
pos=${pos/+/,}
else
convert -size 256x256 xc:red PNG8:- | timeout 5 feh --geometry $size+${pos//,/+} -
fi
else
eval $(xwininfo -id $(xdotool getactivewindow) |
sed -n -e "s/^ \+Absolute upper-left X: \+\([0-9]\+\).*/x=\1/p" \
-e "s/^ \+Absolute upper-left Y: \+\([0-9]\+\).*/y=\1/p" \
-e "s/^ \+Width: \+\([0-9]\+\).*/w=\1/p" \
-e "s/^ \+Height: \+\([0-9]\+\).*/h=\1/p" )
size="${w}x${h}"
pos="${x},${y}"
fi
if [[ $delay -gt 0 ]]; then
echo Waiting.. for $delay seconds
sleep $delay
fi
echo Grabbing: $filename ${size}+$pos
rm -f "$filename"
if [[ $new_shell -eq 1 ]]; then
echo Starting to record in 5 seconds, or press enter
read -t 5 foo || true
x0=$( tput cols )
pre=$( qolop eval "_qPos save; _qPos abs 1 $x0; _qCol R;" )
post=$( qolop eval "_qCol Z; _qPos restore;" )
animate &
animpid=$!
trap 'trap - SIGTERM && kill -- $animpid' EXIT SIGINT SIGTERM
ffmpeg \
-video_size $size \
-framerate $framerate \
-f x11grab \
-i $DISPLAY+${pos} \
-vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" \
-c:v libx264 -crf 23 -profile:v baseline -level 3.0 -pix_fmt yuv420p \
-an \
-movflags faststart \
-loglevel panic \
-hide_banner \
"$filename" &
ffpid=$!
clear
bash
kill -INT $ffpid
trap - EXIT SIGINT SIGTERM
kill $animpid > /dev/null 2>&1
#animate_clear
printf "%s %s" "$pre" "$post" >&2
else
ffmpeg \
-video_size $size \
-framerate $framerate \
-f x11grab \
-i $DISPLAY+${pos} \
-vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" \
-c:v libx264 -crf 23 -profile:v baseline -level 3.0 -pix_fmt yuv420p \
-an \
-movflags faststart \
-loglevel panic \
-hide_banner \
"$filename"
fi
echo "Saved to: $filename"

216
av/video-compress Executable file
View File

@@ -0,0 +1,216 @@
#!/bin/bash
. qolop
get_toggles() {
toggles=$(
echo "$@" | \
smenu -m "Toggle with t" -n 10 -t 1 -a m:7,bu i:8 c:2 t:7/0 ct:2/0 -T ' '
# -m title
# -n rows
# -t columns
# -a styless: m title, i items, c cursor, t tagged, ct cursor on tagged
# -T multiselect
)
}
get_choice() {
choice=$(
echo "$@" | \
smenu -n 10 -t 1 -a c:10 -s "$lastchoice"
# -m title
# -n rows
# -t columns
# -a styless: m title, i items, c cursor, t tagged, ct cursor on tagged
# -T multiselect
)
}
get_resize() {
title Resize
echo "$resize"
echo ""
get_choice no-resize hd480 hd720 hd1080 640x360 356x200 manual
case $choice in
no-resize) resize="";;
manual)
echo "Type resolution WxH"
read -e size
resize="-s $size"
;;
*) resize="-s $choice";;
esac
}
get_compression() {
title Compression
echo "$compression"
echo ""
get_choice x264 copy similar YIFY
case $choice in
x264)
echo Video quality: 23 = good, 18 = insane, 30 = poor
read -e -i "$crf" crf
compression="-g 250 -c:v libx264 -crf $crf"
;;
copy) compression="-vcodec copy";;
similar) compression="-c:v libx264 -q:v 0";;
YIFY)
compression="$yify_compression"
audio=""
;;
*) return;;
esac
}
get_audio() {
title Audio
echo "$audio"
echo ""
get_choice aac copy similar no-audio
lastchoice="$choice"
case $choice in
aac)
echo Audio quality: 64 poor, 128 good
read -e -i "$abit" abit
audio="-c:a aac -ar 44100 -b:a ${abit}k -strict -2"
;;
copy) audio="-acodec copy";;
similar) audio="-q:a 0";;
no-audio) audio="-an";;
*) return;;
esac
}
title() {
_qCol Y S; echo ""; echo "$1":; _qCol z
}
helpexit() {
echo "
echo convert with ffmpeg: [preset] inputfile [outputfile]
Pressing q will stop, and you can use the command from the screen.
Presets do no ask questions:
-low crf:27 abit:64k size:hd480
-medium crf:23 abit:96k
-high crf:18 abit:128k
-yify
-custom:crf:abit:[resolution]
-y overwrite existing files
"
exit
}
# Defaults
resize=""
crf=23
abit=128
yify_compression="-c:v libx264 -crf 26 -x264-params cabac=1:ref=5:analyse=0x133:me=umh:subme=9:chroma-me=1:deadzone-inter=21:deadzone-intra=11:b-adapt=2:rc-lookahead=60:vbv-maxrate=10000:vbv-bufsize=10000:qpmax=69:bframes=5:b-adapt=2:direct=auto:crf-max=51:weightp=2:merange=24:chroma-qp-offset=-1:sync-lookahead=2:psy-rd=1.00,0.15:trellis=2:min-keyint=23:partitions=all -c:a aac -ar 44100 -b:a 128k -map 0"
preset="compress"
overwrite=""
if [[ -z "$1" ]]; then helpexit; fi
for (( i=1; i<=$#; i++ )); do
[[ ${!i} = "-h" ]] && helpexit
[[ ${!i} = "-low" ]] && {
FORCE=1
crf=27
abit=64
resize="-s hd480"
preset="low"
shift 1
}
[[ ${!i} = "-med"* ]] && {
FORCE=1
crf=23
abit=96
preset="med"
shift 1
}
[[ ${!i} = "-high" ]] && {
FORCE=1
crf=18
abit=128
preset="high"
shift 1
}
[[ ${!i} = "-yify" ]] && {
FORCE=1
YIFY_PRESET=1
preset="yify"
shift 1
}
[[ ${!i} = "-custom"* ]] && {
FORCE=1
options="${!i}"
option_array=( ${options//:/ } )
crf=${option_array[1]}
abit=${option_array[2]}
resize=${option_array[3]}
if [[ -n "$resize" ]]; then
resize="-s $resize"
fi
shift 1
}
[[ ${!i} = "-y" ]] && {
overwrite="-y"
shift 1
}
done
compression="-g 250 -c:v libx264 -crf $crf"
audio="-c:a aac -ar 44100 -b:a ${abit}k -strict -2"
if [[ "$YIFY_PRESET" -eq 1 ]]; then
compression="$yify_compression"
audio=""
fi
inputfile="$1"
outputfile="$2"
inputname="${inputfile%.*}"
if [[ -z "$outputfile" ]]; then outputfile="${inputname}.${preset}.mp4";fi
if [[ ! -e "$inputfile" ]]; then echo "Inputfile '$inputfile' missing"; exit 1; fi
ffprobe -hide_banner "$inputfile"
while true; do
command="ffmpeg-parser -i \"$inputfile\" $resize $compression $audio -movflags faststart $overwrite \"$outputfile\""
if [[ $FORCE -eq 1 ]]; then
choice=EXECUTE
else
title Command
echo "# $command"
echo ""
get_choice EXECUTE resize compression audio
fi
case $choice in
EXECUTE)
title Execute
echo "# $command"
echo ""
echo -n "Frames: "
ffprobe -v error -select_streams v:0 -count_packets -show_entries stream=nb_read_packets -of csv=p=0 "$inputfile"
echo ""
setterm --linewrap off
trap "setterm --linewrap on" 0 1 9 15
eval "$command"
ec=$?
setterm --linewrap on
echo "# $command"
exit $?
;;
resize) get_resize;;
compression) get_compression;;
audio) get_audio;;
*) exit;;
esac
done