Last active
July 26, 2026 19:14
-
-
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 | |
| #diff: curl -L https://gist.githubusercontent.com/donnaken15/98e665de860e95521f81c1fdb3907a8c/raw/slopper_skimmer.zsh | git diff --no-index - slopper_skimmer.zsh | |
| #set -e # exits early without pressing anything, POS | |
| # DON'T RUN THIS ON AV1 VIDEO | |
| (($# == 0)) && { # LOL # $((?)) also works, amazing # NOT IN NORMAL SHELL | |
| echo "No files entered"; exit 1 | |
| } | |
| chcp.com 65001 | |
| 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 | |
| #FIX!!: | |
| ##(( h = ((m = (s = ${time#-} / 1000) / 60) / 60) % 60, m %= 60, s %= 60, ms %= 1000, time < 0 )) && sign=- | |
| ##printf '%s%s%02d:%02d:%06.3f' "$2" "$sign" $h $m $s | |
| } | |
| function get_seconds() { | |
| valuefb="${(P)1:-${2:-00:00:00}}" | |
| if [[ "$valuefb" =~ ^((([0-9]+):)?([0-9]{1,2}):)?([0-9]{1,2}(\.[0-9]+)?)$ ]]; then | |
| (( $1 = ((match[3]*3600)+(match[4]*60)+match[5]) )) | |
| else | |
| (( $1 = "$valuefb" )) | |
| fi | |
| echo "Set $1 = `timefmt ${(P)1}`" | |
| } | |
| get_seconds start # KEEP START FOR TESTING SPECIFIC SEGMENT | |
| function userval() { export "$1"="${(P)1:-${2:-0}}"; } | |
| # figure this out like audacity duration and truncate to | |
| userval sthres '-16.8dB' | |
| userval sdur 0.13 | |
| userval srem 0.13 | |
| userval nick 0 | |
| userval speed 1.5 | |
| export speed | |
| (( chunk > 0 )) && (( start += 4600.8 * speed * chunk )) | |
| whywhy=(-hide_banner -hwaccel none) | |
| #fargs=( # tfw draft preset | |
| # -stats -stats_period 0.01 -pix_fmt yuv422p -g 900 # try asp | |
| # -c:v libx264 -crf 29 -preset placebo -tune ssim -threads 15 -refs 2 -bf 8 -sc_threshold 40 | |
| # -c:a libopus -ar 24k -ab 56k -frame_duration 120 -flags:a +bitexact -compression_level:a 10 | |
| # -application voip -flush_packets 1 -max_muxing_queue_size 1 -blocksize 2048 -vbr:a on | |
| # #-crf 43 -qmin 28 -qmax 45 -vb 1100k -minrate 40k -maxrate 2500k -c:v libvpx-vp9 -bf 64 -refs 8 | |
| # #-tile-columns 6 -tile-rows 2 -row-mt 1 -speed 6 -cpu-used 3 -deadline realtime | |
| # #-tune ssim -tune-content screen -corpus-complexity 10000 -enable-tpl 1 -rc_lookahead 25 | |
| # #-frame-parallel 1 -aq-mode 2 -arnr-strength 0.4 -arnr-maxframes 7 -arnr-type 3 -sharpness 2 | |
| # #-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 | |
| #) | |
| cr=$'\r'; lf=$'\n'; imark='\[(silencedetect|Parsed_blackframe_[0-9]+) @ [0-9a-f]{16}\]' | |
| kv='( ([A-Za-z0-9_]+): ?([^ ]+))?'$cr?; god="( \|)?$kv"; timecode='([0-9]{2}):([0-9]{2}):([0-9]{2}\.[0-9]+)' | |
| comp='dynaudnorm=f=500:g=7' #comp='acompressor=threshold=0.07:ratio=4.2:attack=100:release=1000:makeup=1' # broken (clips hardcore): acompressor=threshold=-9dB:ratio=5.1:attack=0.2:release=1.0:makeup=1 | |
| ((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 | |
| afix='pan=mono|c0=c0+c1,aresample=36k: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 ''" | |
| cut_fmt='silence \x1b[92m%9.3f\x1b[97m ==> \x1b[92m%9.3f\x1b[0m, \x1b[97mcut \x1b[96m%6.3fs\x1b[0m, \x1b[97mtotal\x1b[0m: \x1b[95m%8.3f\x1b[0m, \x1b[97mcuts\x1b[0m: \x1b[93m%4d\x1b[0m\n' | |
| trap 'wait; exit 1;' TERM # don't have cuts print past termination # still happening i think... | |
| ((nick!=0)) && block=",drawbox=x=iw-w:y=ih-h+2:w=(iw*(1-0.66)):h=(ih*(1-0.5))+2:t=fill:c=red" | |
| vd2='c:/program files (x86)/virtualdub/2/virtualdub64.exe' | |
| 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/}" | |
| salt="${salt//:/:}" | |
| base="`ffprobe -hide_banner -i "$a" -show_streams -print_format json | jq -c --arg "fname" "$a" '{ sil: [], mas: [], str: .streams, fname: \$fname }'`" | |
| jq -nr --argjson base "$base" 'first($base.str.[] | select(.codec_type == "video" and .disposition.attached_pic == 0)).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 -bnr --argjson base "$base" --argjson i "$v" '$base.str[$i].r_frame_rate')" && \ | |
| 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 | |
| } | |
| 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 processing doesn't pause when marking | |
| if ((nick!=0)); then | |
| scr+="[1][0:v]scale2ref[why1][trash1];[trash1][why1]$bthres" | |
| why=( -i "$a" -stream_loop -1 -i 'A:/dummy_target.jpg') | |
| else | |
| #scr+="[1]nullsink" | |
| why=(-vn -i "$a") | |
| fi | |
| ((hate=cut_res*1000,cuts=0)) | |
| ffmpeg "${whywhy[@]}" -v info -nostats -ss $start "${why[@]}" -lavfi "$scr" \ | |
| -threads 13 -t $((4600.8 * speed)) -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]") | |
| [ "$k" = silence_duration ] && { | |
| (( cuts++ )) | |
| 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 -bncr --argjson r "$srem" --argjson j "$test" '$j | [.start, .end, .cut] | join(" ")')) | |
| printf "$cut_fmt" "${cut[@]}" "$tc" "$cuts" | |
| ) & | |
| sil+=("${test//$cr/}") | |
| } | |
| continue | |
| } | |
| [[ "$filt" = Parsed_blackframe_[0-9]* ]] && { | |
| [ "$k" = type ] && continue # THANKS STUPIDS | |
| temp+=("{\"key\":\"$k\",\"value\":$v}") | |
| [ "$k" = 'last_keyframe' ] && { | |
| jq -nc '$ARGS.positional | from_entries' --jsonargs "${temp[@]}" | readinto test | |
| jq -ncr '$j | "\u001b[97mBAD: \u001b[91m"+ | |
| (.t | strftime("%H:%M:%S.")+((.*$hate%$hate+$hate)|tostring|.[1:]))+ | |
| "\u001b[0m"' --argjson hate "$hate" --argjson j "$test" & | |
| 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 | |
| why=(); (( nick != 0 )) && { | |
| ((s=-1,end=-1,m_dist*=hate)) | |
| jq -bnrc '$ARGS.positional | .[].t' --jsonargs "${mas[@]}" | while read -r t; do | |
| ((s<(t*=hate)-m_dist)) && { | |
| ((s!=-1)) && why+=("$s $e"); ((s=t)) | |
| }; ((e=t)) | |
| done; ((s!=-1)) && why+=("$s $e") | |
| } | |
| echo 'writing detected data' | |
| 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(" ") | map(.|tonumber/$hate)))' \ | |
| --args "${why[@]}" | tee "c:/skim_$salt.json" | readinto base; base="${base//$cr/}" | |
| # could i possibly somehow merge below into above part | |
| wait | |
| echo 'generating cut points'; jq -ncr \ | |
| --argjson hate "$((hate/cut_res))" --argjson ml "$m_lead" --argjson mt "$m_trail" \ | |
| --argjson tb "$cut_res" --argjson j "$base" '($j | .sil | map( | |
| [ (.[0] + .[2]), (.[1] - .[2]) ] | map((. * $tb | round / $tb) * $hate | floor / $hate) | |
| ) | . + ( $j | .mas | map( [ (.[0] - $ml), (.[1] + $mt) ] ) ) ) | flatten | sort_by(.) | # die | |
| [ range(0; (length/2)) as $i | [ .[(2*$i)], .[(2*$i)+1] ] ]#[1:] | |
| # i hate this language now, why cant this just be like actual javascript | |
| ' | tee | 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 | |
| echo 'assembling script' | |
| hack=("TOGGLE") # change to substring | |
| # generative programming!!! | |
| jq -nr --argjson t "$trim" --argjson s "$start" --argjson fps "$cut_res" '($t | flatten | [0]+.+[.[-1]+1] | map(.+$s) | # awful | |
| [ range(0; (length/2)) as $i | [ .[(2*$i)], .[(2*$i)+1]-.[(2*$i)] ] ] | map( | |
| "cuts.AddRange("+(. | to_entries | map(((((.value)*1000)|round/1000)*$fps|round)|tostring) | join(","))+");" | |
| ) | join("\n"))' | sed -e "/^\/\/ ${hack[nick]:-NOTOGGLE}/a vfilts.Add(\"fill\");\nvfilts.instance[3].Config(360,180,0,0,0x0000ff);" \ | |
| -e "/^cuts\.Clear()/r /dev/stdin" <(cat <<BASE_SCRIPT | |
| declare aud; aud = VirtualDub.audio; declare afilts; afilts = aud.filters; | |
| declare vid; vid = VirtualDub.video; declare vfilts; vfilts = vid.filters; | |
| declare cuts; cuts = VirtualDub.subset; | |
| declare argv; argv = VirtualDub.params; | |
| VirtualDub.Close(); | |
| VirtualDub.Open(argv[0]); | |
| // ---------- | |
| aud.SetSource(1); | |
| aud.SetMode(1); | |
| aud.SetEditMode(1); | |
| aud.SetInterleave(1,500,1,0,0); | |
| aud.SetClipMode(1,1); | |
| aud.SetConversion(0,0,0,0,0); | |
| aud.SetVolume(); | |
| aud.SetCompressionWithHint(65534,48000,2,0,18615,0,45,"wAMAAAAAlpmTVEn110Koc72wY4Xlnz1QAQBPcHVzSGVhZAECOAGAuwAAAAAA","ffmpeg_opus"); | |
| aud.SetCompData(16,"AAAAAAoAAABnbAEAAAAAAA=="); | |
| // ---------- | |
| vid.SetInputFormat(0); | |
| vid.SetOutputFormat(0); | |
| vid.SetMode(3); | |
| vid.SetSmartRendering(0); | |
| vid.SetPreserveEmptyFrames(0); | |
| vid.SetCompression(0x30395056,0,10000,0,"avlib-1.vdplugin"); | |
| vid.SetCompData(20,"AAAAAAMAAAAIAAAAHwAAAAAAAAA="); | |
| VirtualDub.SaveFormatAVI(); // ??? | |
| VirtualDub.SaveAudioFormat(""); | |
| VirtualDub.SaveFormat("ffmpeg", "webm"); // ??? | |
| // ---------- | |
| vfilts.BeginUpdate(); | |
| vfilts.Clear(); | |
| vfilts.Add("resize"); | |
| vfilts.instance[0].Config(640,360,0,4,3,1,320,240,4,3,0,4,1,0x000000); | |
| vfilts.Add("logo"); | |
| vfilts.instance[1].Config(U"E:\kys_vd2.png", 4, 4, 0, 0, 0, 0, 39322); | |
| vfilts.Add("DrawTime"); | |
| vfilts.instance[2].DataPrefix("75ae"); | |
| vfilts.instance[2].Config(4,4,0,0, 0xffffff, 0x000000, 0,24,400,0,"Gossix",1); | |
| // TOGGLE!!!!!!!! | |
| // ---------- | |
| cuts.Clear(); | |
| vfilts.EndUpdate(); | |
| // ---------- | |
| VirtualDub.ExportViaEncoderSet(argv[1], "_skim test"); | |
| //VirtualDub.SaveAVI(argv[1]); | |
| BASE_SCRIPT | |
| ) > "c:\\skim_${salt}.vdscript" | |
| # move base script here # jq -Rs ". | split(\"\n\")" <<HEREDOC | |
| echo 'rendering' | |
| # <s>SPEED UP USING MPC AUDIO RENDERER!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!</s> use external encoder | |
| # TURNS OUT VIRTUALDUB SUCKS JUST AS WELL (pause)!!!!!!!!!!!!!!!!!!!!!!! | |
| # NOW RENDER CUTS OFF AT 1H10M | |
| # BREAKS ON THEBROKEN 3 | |
| # remove /x to get log # how do i even route the log to my terminal | |
| "$vd2" /i "c:\\skim_${salt}.vdscript" "${a//\//\\}" "c:\\skim_$salt.webm" /x & #vd=$! | |
| for (( i = 0; i < 4; i++ )); do | |
| nircmdc win min etitle '(dub in progress)' | |
| sleep 0.5 | |
| done | |
| #wait $vd | |
| done | |
| #-ss "$start" -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