Last active
November 21, 2025 16:09
-
-
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
This file contains hidden or 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/zsh | |
| #set -e # exits early without pressing anything, POS | |
| ((# == 0)) && { # LOL # $((?)) also works, amazing | |
| echo "No files entered" | |
| exit 1 | |
| } | |
| # accodomate for instances of <> 30fps or figure out how to change fps before processing my full graph | |
| hms=(01 22 05); (( speed=1.18, limit=9999999, delay=((hms[1]*3600)+(hms[2]*60)+hms[3]) )) # seek to | |
| unset hms; limit=5400 # 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 0 -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 | |
| -flags +qpel+mv4+aic+global_header -map_metadata -1 -ar 24k -c:a libopus -ab 66k | |
| -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 -fflags nobuffer -blocksize 2048 | |
| ) | |
| cr=$'\r'; lf=$'\n'; ins='$b + { $k: ($v|tonumber? // .) }' | |
| imark='\[(silencedetect|Parsed_blackframe_[0-9]+) @ [0-9a-f]{16}\]' | |
| kv='( ([A-Za-z0-9_]+): ?([^ ]+))?'$cr?; stupid='( \|)?'; god="$stupid$kv"; temp='{}' | |
| comp='volume=-2dB,dynaudnorm' # broken: acompressor=threshold=-9dB:ratio=5.1:attack=0.2:release=1.0:makeup=1 | |
| #trap 'exit 1' INT TERM # leaves ffmpeg open # use kill -9 `jobs` if seriously | |
| sthres='-20.0dB'; ((sdur=0.16,srem=0.2,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" | |
| # 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 | |
| bthres='blend=difference,blackframe=94:38' # trailing 200ms visibility left with this | |
| afix='pan=mono|c0=c0+c1,aresample=32k' | |
| profile=() | |
| #profile=(time -f"%E") # not useful rn, keeps showing 0.01s, probably if subprocess management stuff isn't actually accounted for | |
| # 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="tr -d '\r' | read -r -d ''" | |
| for a in "$@"; do | |
| [ ! -f "$a" ] && { | |
| echo "Invalid or non-existent file: $a" | |
| continue | |
| } | |
| base="`ffprobe -hide_banner -i "$a" -show_streams -print_format json | jq -c --arg delay "$delay" --arg limit "$limit" '{ off: (\$delay | tonumber), len: (\$limit | tonumber), sil: [], mas: [], str: .streams }'`" | |
| jq -nr --argjson base "$base" 'first($base.str.[] | select(.codec_type == "video")).index' | readinto v | |
| 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 | |
| } | |
| sil=(); mas=(); tc=0.0 # total time cut | |
| scr="[0:a]$afix,$comp,silencedetect=n=$sthres:d=$sdur;" | |
| # 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 | |
| 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 | |
| echo "offset ${delay}:skim ${limit}s" | |
| ffmpeg -hide_banner -hwaccel dxva2 -v info -nostats -ss "$delay" "${why[@]}" -lavfi "$scr" \ | |
| -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 | |
| "${profile[@]}" jq -nc --arg k "${k:8}" --arg v "$v" --argjson b "$temp" "$ins" | readinto temp | |
| # inb4 associative array and fromentries to save time from subprocesses when building up item | |
| # or entries | |
| [ "$k" = 'silence_duration' ] && { | |
| cut=($(jq -ncr --argjson r "$srem" --argjson j "$temp" '$j | [.start, .end, ([$r,(.duration - $r)] | max)] | join(" ")' | tr -d '\r')) | |
| "${profile[@]}" 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+=(cut[3])))" | |
| sil+=("$temp") | |
| } | |
| continue | |
| } | |
| [[ "$filt" = Parsed_blackframe_[0-9]* ]] && { | |
| "${profile[@]}" jq -nc --arg k "$k" --arg v "$v" --argjson b "$temp" "$ins" | readinto temp | |
| [ "$k" = 'last_keyframe' ] && { | |
| "${profile[@]}" jq -ncr --argjson hate 1000 --argjson j "$temp" '$j | | |
| "\u001b[97mBAD: \u001b[91m"+ | |
| (.t | strftime("%H:%M:%S.")+((.*$hate%$hate+$hate)|tostring|.[1:]))+ | |
| "\u001b[0m"' | |
| mas+=("$temp") | |
| } | |
| 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 | |
| why=(); ((start=-1,end=-1,hate=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 | |
| #jq -nc --tab --argjson j "$base" '$j' | |
| date --date="TZ=\"%TZ%\" now" +'%F_%H-%M-%S.%3N' | readinto salt | |
| echo 'writing detected data'; jq -nc --argjson j "$base" '$j' > "c:/skim_$salt.json" | |
| echo 'generating cut points'; trim="select='not($(jq -ncr \ | |
| --argjson hate "$hate" --argjson ml "$m_lead" --argjson tb "$cut_res" \ | |
| --argjson j "$base" --argjson mt "$m_trail" \ | |
| '$j | .sil | map( | |
| "between(t," + | |
| (((.[0] + .[2]) * $tb | round / $tb) * $hate | floor / $hate | tostring) + "," + | |
| (((.[1] - .[2]) * $tb | round / $tb) * $hate | floor / $hate | tostring) + | |
| ")" | |
| ) | . + ( | |
| $j | .mas | map( | |
| "between(t," + | |
| (.[0] - $ml | tostring) + "," + | |
| (.[1] + $mt | tostring) + | |
| ")" | |
| ) | |
| ) + ["0"] | "\n"+join("+")' | tr -d '\r' | |
| ))'" # halve silence buffer time and distribute (forgot what to write for why...) | |
| piss='setpts=N/FRAME_RATE/TB' | |
| # life saver: https://stackoverflow.com/questions/64866231 | |
| # everyone/everything else is so unhelpful and useless | |
| ((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 'rendering (no printing yet = seeking, maybe...)' # crop=480:130:0:0 | |
| tee "c:/skim_$salt.filt.txt" <<!! | ffmpeg -hide_banner -hwaccel dxva2 -v verbose -stats -ss "$delay" -i "$a" -t "$(((limit/speed)-tc))" -filter_complex_script pipe: "${fargs[@]}" "c:/skim_$salt.webm" -y | |
| [0:v]scale=-1:360,${block} | |
| drawtext=text='%{pts\:hms}':fontfile=e\\\:/Gossix.ttf:fontsize=32:fontcolor=white:x=0:y=16:box=1:[email protected], | |
| ${trim},setpts=N/FRAME_RATE/TB,setpts=PTS/$speed; | |
| [0:a]$afix,aformat=f=flt,a${trim},asetpts=N/FRAME_RATE/TB,atempo=$speed,$comp; | |
| !! | |
| # KILLS ITSELF AROUND 8 MINUTES (THE CRINGE IS THAT BAD), NO CRASH DUMP?! | |
| # offset pts | |
| done | |
| # %{localtime} | |
| # -vf "scale=480:-1,drawbox=x=iw-w:y=ih-h+2:w=(iw*(1-0.66)):h=(ih*(1-0.58))+2:t=fill:c=red, | |
| # drawtext=text='%{pts\:hms}':fontfile=e\\\:/Gossix.ttf:fontsize=32:fontcolor=white:x=8:y=8:box=1:[email protected], | |
| # ${trim},${piss},setpts=PTS/$speed" \ | |
| # -af "pan=mono|c0=c0+c1,aresample=32k,aformat=f=flt,a${trim},a${piss},atempo=$speed,$comp" | |
| # ffmpeg -hide_banner -hwaccel dxva2 -stats -ss 5:00 -i "F:\New folder\The Worst iDubbbz Interview Ever , Caleb Hammer VS Jake Weddle 2 & Pokimane [AULY1qhyRRQ].mp4" -loop 1 -i C:\dummy_target.jpg -an -lavfi "[0][1]scale2ref,blend=difference,blackframe=96:32" -f null nul | |
| # ffmpeg -hide_banner -hwaccel dxva2 -stats -ss 10:00 -i "Ethan Klein's Full Response To Ian Jomha (iDubbbz) [Anisa Jomha's Husband] [fnHgShT1qlE].mp4" -loop 1 -i C:\dummy_target.jpg -an -lavfi "[0][1]scale2ref,blend=difference,blackframe=96:32" -f null nul | |
| # ffprobe -v info -f lavfi -i "movie=die_foss:sp=600:dec_threads=15,select=gte(n\,0)[trash_stream];movie='c\:\\dummy_target.jpg',loop=loop=-1:size=1:start=0[nhyper];[trash_stream][nhyper]scale2ref,blend=difference,blackframe=96:32" -show_frames -print_format json | grep blackframe | |
| # minimal case: 00:41:23.377 -- 00:41:25.458 | |
| #-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 | |
| # probably useless: sha256sum skim.json | cut -d' ' -f1 | xxd -r -p | base64 -w 110 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment