Skip to content

Instantly share code, notes, and snippets.

@donnaken15
Last active December 8, 2025 19:45
Show Gist options
  • Select an option

  • Save donnaken15/98e665de860e95521f81c1fdb3907a8c to your computer and use it in GitHub Desktop.

Select an option

Save donnaken15/98e665de860e95521f81c1fdb3907a8c to your computer and use it in GitHub Desktop.
speed up videos slightly and cut silence audacity-style for skimming commentary slop
#!/bin/zsh
#set -e # exits early without pressing anything, POS
((# == 0)) && { # LOL # $((?)) also works, amazing
echo "No files entered"; exit 1
}
hms=(00 00 00); (( speed=1.18, limit=9999999, delay=((hms[1]*3600)+(hms[2]*60)+hms[3]) )) # seek to
unset hms; #limit=6000 # seconds to shorten for skimming (testing)
fargs=( # tfw draft preset
-stats -stats_period 0.01 -c:v libvpx-vp9 -pix_fmt yuv444p -aspect 16:9
-crf 43 -qmin 28 -qmax 45 -vb 1100k -minrate 40k -maxrate 2500k -g 900 -bf 64 -refs 8
-threads 16 -tile-columns 6 -tile-rows 2 -row-mt 1 -deadline best -speed -16 -cpu-used -8
-tune ssim -tune-content screen -corpus-complexity 10000 -enable-tpl 1 -rc_lookahead 25
-frame-parallel 0 -aq-mode 2 -arnr-strength 4 -arnr-maxframes 7 -arnr-type 3 -sharpness 4
-static-thresh 0 -color_primaries bt709 -color_trc bt709 -colorspace bt709 -auto-alt-ref 1
-color_range pc -bufsize 512M -lag-in-frames 25 -fflags +genpts+bitexact+nobuffer
-flags +qpel+mv4+aic+global_header -map_metadata -1 -ar 24k -c:a libopus -ab 56k
-frame_duration 120 -vbr:a on -flags:a +bitexact -compression_level:a 10 -quality best
-application voip -flush_packets 1 -max_muxing_queue_size 1 -blocksize 2048
); cr=$'\r'; lf=$'\n'; imark='\[(silencedetect|Parsed_blackframe_[0-9]+) @ [0-9a-f]{16}\]'; kv='( ([A-Za-z0-9_]+): ?([^ ]+))?'$cr?; god="( \|)?$kv";
comp='volume=-2dB,dynaudnorm' # broken (clips hardcore): acompressor=threshold=-9dB:ratio=5.1:attack=0.2:release=1.0:makeup=1
timecode='([0-9]{2}):([0-9]{2}):([0-9]{2}\.[0-9]+)'; sthres='-19.7dB'; ((sdur=0.13,srem=0.13,nick=0)); # figure this out like audacity duration and truncate to
((m_notice = 10.0)) # heads up warning (not using yet)
((m_lead = 0.3)) # seconds before/after detected frames
((m_trail = 1.9)) # minimal time skip (from last frame) because fatty has to get in that stupid word
((m_dist = 4.0)) # allowable distance between seconds before creating new cut, this should be a little more than reasonable
((cut_res = 30.0)) # trying hard to avoid video drifting, unironically doing more damage with more "bandaids" # bandages on my legs and my arms for you bandages bandages bandages
# ive never had such strong contempt in my life for something as (ironically) insignificant as a freaking adjective
# and its all because of you zoomer ******s, im on team flam now, oreo should neck
fontparams='fontfile=e\\\:/Gossix.ttf:fontsize=32:box=1:boxcolor=black@0.6'
afix='pan=mono|c0=c0+c1,aresample=32k:async=0'; bthres='blend=difference,blackframe=94:38' # trailing 200ms visibility left with this
# cut processing literally zooms when video is disabled, like a zoomer.........
echo "\x1b[91mKILL FFMPEG IF IT GETS STUCK, BECAUSE IT'S BAD SOFTWARE!!!!!!\x1b[0m"
alias readinto="read -r -d ''"
function timefmt() {
local h m s ms time="$(($1))"
(( h = ((m = (s = (ms = ${time#-})) / 60) / 60) % 60, m %= 60, s %= 60, ms %= 1000, time < 0 )) && sign=-
# wish i could cram this in one statement
printf '%s%s%02d:%02d:%02d.%03d' "$2" "$sign" $h $m $s $ms
}
#trap 'exit 1' INT TERM # leaves ffmpeg open # use kill -9 `jobs` if seriously
trap 'wait; exit 1;' TERM # don't have cuts print past termination
for a in "$@"; do
[ ! -f "$a" ] && {
echo "Invalid or non-existent file: $a"
continue
}
date +'%F_%H-%M-%S.%3N' | readinto salt; salt="${salt//$cr/}"
base="`ffprobe -hide_banner -i "$a" -show_streams -print_format json | jq -c --arg delay "$delay" --arg limit "$limit" --arg "fname" "$a" '{ off: (\$delay | tonumber), len: (\$limit | tonumber), sil: [], mas: [], str: .streams, fname: \$fname }'`"
jq -nr --argjson base "$base" 'first($base.str.[] | select(.codec_type == "video")).index' | readinto v; v="${v//$cr/}"
jq -ne --argjson base "$base" --argjson i "$v" '$base.str[$i].codec_type != "video"' >/dev/null && {
echo 'This file does not have a video stream'
jq -n --indent 4 --argjson base "$base" '$base.str'
continue
}
cut_res="$(jq -nr --argjson base "$base" --argjson i "$v" '$base.str[$i].r_frame_rate' | tr -d '\r')" && \
echo "FPS eval: $cut_res" && cut_res=$(($cut_res)) || {
echo 'Something went wrong when parsing frame rate'
jq -n --indent 4 --argjson base "$base" '$base.str'
continue
}
echo "offset `timefmt ${delay}`:skim `timefmt ${limit}`:res 1/${cut_res}"
scr="[0:a]$afix,$comp,silencedetect=n=$sthres:d=$sdur;"; sil=(); mas=(); tc=0.0 # total time cut
# disable if not skimming nick's vods
# 999999iq amogus: regex check to determine whose title is who's
# now that i think about it, graph time shouldnt be noticeable if printing is
# nonblocking, so it'll keep going while the read loop takes its sweet @$$ time....maybe...
# ...just going off the fact that cpu usage doesn't pause when marking
if ((nick!=0)); then
scr+="[1][0:v]scale2ref[why1][trash1];[trash1][why1]$bthres"
why=( -t "$limit" -i "$a" -loop 1 -t "$limit" -i 'C:/dummy_target.jpg')
else
#scr+="[1]nullsink"
why=(-vn -t "$limit" -i "$a")
fi
ffmpeg -hide_banner -hwaccel dxva2 -v info -nostats -ss "$delay" "${why[@]}" -lavfi "$scr" \
-threads 13 -f null nul 2>&1 | while read -r i; do
# old: 96:32
if [[ "${i//$cr/}" =~ ${imark}${kv}${god}${god}${god}${god}${god} ]]; then
filt="${match[1]}"
#echo "--- $filt ---"
case "$filt" in
Parsed_blackframe_[0-9]*) ;&
silencedetect)
for ((y=0;y<6;y++)); do
((x=3+(y*4))); k="${match[x]}"; v="${match[++x]}"
[ -z "$k" ] && break; [ "$k" = 'silence_start' -o "$k" = 'frame' ] && temp=()
[[ "$k" = silence_* ]] && { # redundant
temp+=("[\"${k:8}\",$v]")
# inb4 associative array and fromentries to save time from subprocesses when building up item
# or entries
[ "$k" = 'silence_duration' ] && {
jq -nc --argjson r "$srem" '$ARGS.positional | map({key: .[0], value: .[1]}) | from_entries | . + {cut: ([$r,(.duration - $r)] | max)}' --jsonargs "${temp[@]}" | readinto test
[[ "$test" =~ '"cut":'([0-9]+(\.[0-9]+)?) ]] && ((tc+=(match[1]))) && (
cut=($(jq -ncr --argjson r "$srem" --argjson j "$test" '$j | [.start, .end, .cut] | join(" ")' | tr -d '\r'))
printf 'silence \x1b[92m%9.3f\x1b[97m ==> \x1b[92m%9.3f\x1b[0m, \x1b[97mcut \x1b[96m%6.3fs\x1b[0m, total: \x1b[95m%8.3f\x1b[0m\n' "${cut[@]}" "$tc"
) &
sil+=("${test//$cr/}")
}
continue
}
[[ "$filt" = Parsed_blackframe_[0-9]* ]] && {
temp+=("[\"$k\",$v]")
[ "$k" = 'last_keyframe' ] && {
jq -nc '$ARGS.positional | from_entries' --jsonargs "${temp[@]}" | readinto test
jq -ncr --argjson hate "$hate" --argjson j "$test" '$j |
"\u001b[97mBAD: \u001b[91m"+
(.t | strftime("%H:%M:%S.")+((.*$hate%$hate+$hate)|tostring|.[1:]))+
"\u001b[0m"' &
mas+=("${test//$cr/}")
}
continue
}
echo "?????: $k = $v"
done
;;
*) echo "Uncaught $filt: $MATCH"
;;
esac
else
if ! [[ "${i:l}" =~ ^(metadata|m(aj|in)or_|encoder|output \#|stream \#[0-9]:[0-9](\[[0-9]x[0-9]\([a-z]*\)\])?: [av]|duration: |handler_|vendor|compati) ]]; then
printf "\x1b[90m%s\x1b[0m\n" "${i//$cr/}"
fi
fi
done
wait
why=(); ((start=-1,end=-1,hate=cut_res*100000,m_dist*=hate))
jq -nc -r '$ARGS.positional | .[].t' --jsonargs "${mas[@]}" | tr -d $'\r' | while read -r t; do
((start<(t*=hate)-m_dist)) && {
((start!=-1)) && why+=("$start $end"); ((start=t))
}; ((end=t))
done; ((start!=-1)) && why+=("$start $end")
jq -nc --argjson hate "$hate" --argjson r "$srem" --argjson j "$base" \
'$j|.sil = ($ARGS.positional | map([.start, .end, ([$r,.duration] | min/2)]))' \
--jsonargs "${sil[@]}" | \
jq -c --argjson hate "$hate" '.mas = ($ARGS.positional | map(. | split(" ")))' --args "${why[@]}" | readinto base; base="${base//$cr/}"
echo 'writing detected data'; jq -nc --argjson j "$base" '$j' > "c:/skim_$salt.json"
((nick!=0)) && block=",drawbox=x=iw-w:y=ih-h+2:w=(iw*(1-0.66)):h=(ih*(1-0.58))+2:t=fill:c=red"
echo 'generating cut points'; jq -ncr \
--argjson hate "$((hate/cut_res))" --argjson ml "$m_lead" --argjson tb "$cut_res" \
--argjson mx "$limit" --argjson j "$base" --argjson mt "$m_trail" \
'($j | .sil | map(
[ (.[0] + .[2]), (.[1] - .[2]) ] | map((. * $tb | round / $tb) * $hate | floor / $hate)
) | . + ( $j | .mas | map( [ (.[0] - $ml), (.[1] + $mt) ] ) ) ) | flatten | [0,0,0]+.+[$mx] |
[ range(0; (length/2)) as $i | [ .[(2*$i)+0], .[(2*$i)+1], .[(2*$i)]-.[(2*$i)-1] ] ][1:]
# i hate this language now, why cant this just be like actual javascript
' | readinto trim # halve silence buffer time and distribute (forgot what to write for why...probably something about preserving a little more of the end sound and start of a new one)
# need to fix inverse ranges when srem < sdur
splits="$(jq -nr --argjson t "$trim" '$t | length' | tr -d '\r')"
blocks="$(jq -nr --argjson t "$trim" '$t | to_entries | map("[v"+(.key | tostring)+"s]") | join("")')"
echo 'assembling graph, rendering (no printing yet = seeking, maybe...)' # crop=480:130:0:0
#range="trim='$delay':'$((delay+limit))'"
tee "c:/skim_$salt.filt.txt" <<!! | ffmpeg -hide_banner -hwaccel dxva2 -v warning -stats -ss "$delay" -t "$limit" -i "$a" -filter_complex_script pipe: "${fargs[@]}" -map '[right]' -map '[now]' "c:/skim_$salt.webm" -y && echo "--------- $a done (${pipestatus[@]})"
[0:v]scale=-1:360,drawtext=text='%{pts\:hms}':${fontparams}:x=8:y=8:fontcolor=white${block},
split=$splits${blocks};
[0:a]aformat=f=flt,asplit=$splits${blocks//v/a};
$(jq -nr --arg fp "$fontparams" --argjson t "$trim" '
$t | to_entries | map(
("trim="+(.value | .[:2] | map(. | tostring) | join(":"))+",") as $trim | (.key | tostring) as $i |
"[v"+$i+"s]"+$trim+"setpts=PTS-STARTPTS,drawtext=text='$'\'''  >> "+
((.value[2] * 1000 | floor) | tostring | (((.|tonumber/1000|floor|tostring|length)-3) as $pad | " " |
.[$pad:])+(("0"+.[0:-3]) | tonumber | tostring)+"."+(.[-3:]+"000" | .[:3]))+"s'$'\''':"+$fp+
":x=162:y=8:fontcolor=lime[v"+$i+"c];"+
"[a"+$i+"s]a"+$trim+"asetpts=PTS-STARTPTS[a"+$i+"c];\n"
) | join("")' | tr -d '\r')$(jq -nr --argjson t "$trim" '
$t | to_entries | map((.key | tostring) as $i | ("[v"+$i+"c][a"+$i+"c]")) |
join("")' | tr -d '\r')concat=n=$splits:v=1:a=1[kys][die];
[kys]setpts='PTS/$speed'[right];
[die]$afix,$comp,atempo='$speed'[now];
!!
# KILLS ITSELF AROUND 8 MINUTES (THE CRINGE IS THAT BAD), NO CRASH DUMP?!
# offset pts
done
#-ss "$delay" -t "$limit" -i "$a" -loop 1 \
# -i 'C:\dummy_target.jpg' -i 'C:\dummy_target2.jpg' -lavfi "
# [0:a]$comp,silencedetect=n=-25dB:d=0.16,anullsink;
# [1][0:v]scale2ref[why1][trash1];
# [2][trash1]scale2ref[why2][trash2];
# [trash1][why1]$bthresh,nullsink;
# [trash2][why2]$bthresh
#" -f null nul 2>&1 | while read -r i; do
# USES 7 GB COMMIT JUST FOR A SECOND COMPARISON FRAME WTFFFFFFF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment