catball

Meowdy Pawdner

  • she /they

pictures of my rats: @rats
yiddish folktale bot (currently offline): @Yiddish-Folktales

Seattle area
trans 🏳️‍⚧️ somewhere between (30 - 35)


Personal website
catball.dev/
Mastodon (not sure if I'll use this)
digipres.club/@cat
Pillowfort (not sure if I'll use this)
www.pillowfort.social/catball
Monthly Newsletter (email me to join)
newsletter AT computer DOT garden
Monthly Nudesletter (18+ only, email me to join)
nudesletter AT computer DOT garden
Rat Pics (placeholder, will update)
rats.computer.garden/
Website League main profile
transgender.city/@cat
Website League nudes profile
transgender.city/@hotcat
Website League rat pics
transgender.city/@rats

encode AV1 video (super slow, but great quality), using hardware acceleration only for decode, pass thru audio and subtitles

#!/usr/bin/env bash

set -eux

# deinterlace with `-vf yadif_cuda=1` after the -i line
HWACCEL="nvdec"; # https://trac.ffmpeg.org/wiki/HWAccelIntro#CUDANVENCNVDEC
HWACCEL_DEVICE="/dev/dri/renderD129"; # on beppo 128=amd, 129=nvidia
CRF="23"; # Perceptually lossless (see: https://trac.ffmpeg.org/wiki/Encode/AV1#ConstantQuality )
PRESET="3"; # 0 - 13 (higher is faster encode / worse qual)
TUNE="0";
# SVT-AV1 params (colon delimited): https://trac.ffmpeg.org/wiki/HWAccelIntro#CUDANVENCNVDEC
#   film grain param: ":film-grain=10"
#       film grain doc: https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Appendix-Film-Grain-Synthesis.md
#   quanization matricies: ":enable-qm=1:qm-min=0:qm-max=15"
#   variance boost: ":enable-variance-boost=1:variance-boost-strength=[1-4]:variance-octile=[1-8]"
#       variance boost doc: https://gitlab.com/AOMediaCodec/SVT-AV1/-/blob/master/Docs/Appendix-Variance-Boost.md#parameters
#       """ The default value is 2.
#           - Strength 1 tends to be best for simple, untextured, or smooth animation.
#           - Strength 2 is a highly compatible curve, great for most live-action content and animation with detailed textures.
#           - Strength 3 is best for still images or videos with a balanced mix of very high-contrast and low-contrast scenes (like traditional horror or thriller movies).
#           - Strength 4 is very aggressive and only useful under very specific circumstances where low-contrast detail retention is an extremely high priority.
#
# See also: https://gist.github.com/BlueSwordM/86dfcb6ab38a93a524472a0cbe4c4100
VARIANCE_BOOST_ENABLE="1";
VARIANCE_BOOST_STR="2"; # [1-4]
VARIANCE_OCTILE="3"; # [1-7]
ENABLE_OVERLAYS="1";
ENABLE_SCD="1";
ENABLE_SCM="0"; # 0=off; 1=on; 2=auto
ENABLE_TF="1";
ENC_PARAMS="tune=$TUNE:enable-overlays=$ENABLE_OVERLAYS:scd=$ENABLE_SCD:scm=$ENABLE_SCM:enable-tf=$ENABLE_TF:enable-variance-boost=$VARIANCE_BOOST_ENABLE:variance-boost-strength=$VARIANCE_BOOST_STR:variance-octile=$VARIANCE_OCTILE";
# DEINTERLACE="-vf yadif_cuda=1";
PROBESIZE="10000000";

echo "[$(date +%F_%r)] Encoding $1"

ffmpeg \
  -nostats \
  -hwaccel "$HWACCEL" -hwaccel_device "$HWACCEL_DEVICE" \
  -probesize "$PROBESIZE" \
  -i "$1" \
  -c:a copy -c:s copy -c:v libsvtav1 \
  -pix_fmt yuv420p10le \
  -crf "$CRF" -preset "$PRESET" -tune "$TUNE" \
  -svtav1-params $ENC_PARAMS \
  "av1_$1"

echo "[$(date +%F_%r)] Finished encoding $1"

compile ffmpeg 7 with more aggressive optimizations

# /etc/nixos/configuration.nix

nixpkgs.overlays = [(self: super: {
        ffmpeg_7-full = super.ffmpeg_7-full.overrideAttrs (old: {
                version = "7.0.1";
                CFLAGS = "-pipe -O3 -march=znver2 -ffat-lto-objects -fno-semantic-interposition -freco
rd-gcc-switches";
                CXXFLAGS = "-pipe -O3 -march=znver2 -ffat-lto-objects -fno-semantic-interposition -fre
cord-gcc-switches";
                NIX_CFLAGS_COMPILE = "-pipe -O3 -march=znver2 -ffat-lto-objects -fno-semantic-interpos
ition -frecord-gcc-switches";
                preConfigure = ''
                        configureFlagsArray+=(
                                "--extra-cflags=-O3 -pipe -march=znver2 -ffat-lto-objects -fno-semanti
c-interposition -frecord-gcc-switches"
                        )
                '';
                # configureFlags = old.configureFlags;
                # ffmpegVariant = "headless";  # Some flags below are redundant with this one
    ffmpegVariant = "full"; # yolo
                withOpencl = true;      # Compute / interop w GPU https://trac.ffmpeg.org/wiki/HWAccel
Intro#OpenCL
                withVulkan = true;      # GPU / graphics api
                withGPL = true;
                withCuda = true;        # Nvidia compute
                withChromaprint = true; # Audio fingerprinting
                withVersion3 = true;
                withUnfree = true;
                withNvcodec = true;     # Nvidia options
                withVmaf = true;        # Visual similarity filter
                withSvtav1 = true;      # AV1 encode
                withDav1d = true;       # AV1 decode
                withX264 = true;
                withX265 = true;
                withZmq = true;         # message queue
                withAss = true;         # subtitle support / burn-in
                withFribidi = true;     # subtitle support
                withAribcaption = true; # arabic subtitle support
                withBluray = true;
                withDvdnav = true;
                withDvdread = true;
                withFdkAac = true;      # AAC audio enc/dec
                withDrm = true;
                withVpx = true;         # VP8 & VP9 de/encoding
                withXevd = true;        # mpeg5 decode
                withXeve = true;        # mpeg5 encode
                withHardcodedTables = true;
        });

        python = super.python312.overrideAttrs (old: {
                NIX_CFLAGS_COMPILE = "-pipe -O3 -march=znver2 -ffat-lto-objects -frecord-gcc-switches";
                enableOptimizations = true;
                enableLTO = true;
        });
  
  rsync = super.rsync.overrideAttrs (old: {
                NIX_CFLAGS_COMPILE = "-pipe -O3 -march=znver2 -ffat-lto-objects -frecord-gcc-switches";
                NIX_CXXFLAGS_COMPILE = "-pipe -O3 -march=znver2 -ffat-lto-objects -frecord-gcc-switches";
    CFLAGS = "-pipe -O3 -march=znver2 -ffat-lto-objects -frecord-gcc-switches";
    CXXFLAGS = "-pipe -O3 -march=znver2 -ffat-lto-objects -frecord-gcc-switches";
  });
# ...
})];

check first 10k frames for interlacing:

# Defined in /home/cat/.config/fish/functions/video_interlace_stats.fish @ line 3
function video_interlace_stats --description 'Check first 5000 frames of a video for interlacing. First line is single-frame analysis, second line is multi-frame analysis.' --argument file
  ffmpeg -filter:v idet \
    -frames:v $FRAME_COUNT \
    -an \
    -f rawvideo -y /dev/null \
    -i $file &| rg "(?:Single frame detection|Multi frame detection)" --no-unicode --no-pcre2 | rg "detection: (.*)" --only-matching | awk '{print "TFF:\t"$3"\tBFF:\t"$5"\tProgressive:\t"$7"\tUndetermined:\t"$9}'
end
# Defined in /home/cat/.config/fish/functions/is_video_interlaced.fish @ line 3
function is_video_interlaced --description 'Estimate if a video is interlaced.' --argument file
  video_interlace_stats $file | python ~/.local/bin/is_interlaced.py
  return $status
end
#!/usr/local/bin env python
import argparse
import sys

# Example input:
# TFF:    4083    BFF:    0       Progressive:    1       Undetermined:   917
# TFF:    4984    BFF:    0       Progressive:    16      Undetermined:   1


def get_records() -> list[list[str]]:
    records = [line.strip() for line in sys.stdin]
    records = [line for line in records if line and line != '']
    records = [line.split('\t') for line in records if '\t' in line]
    return [[datum.strip() for datum in record] for record in records]


def verbose_print(verbose: bool, message):
    if not verbose:
        return
    if not message or message == '':
        return
    print(message, file=sys.stderr)


def main(verbose: bool = False, quiet: bool = False):
    records = get_records()
    if not records or len(records) == 0 or sum([len(record) for record in records]) == 0:
        verbose_print(not quiet, '[ERROR] No valid stdin found!')
        exit(1)

    xff = 0
    progressive = 0
    undetermined = 0

    for record in records:
        if len(record) < 7:
            verbose_print(verbose, f'[WARN] Record with length less then 7, skipping. Record: {record}')
            continue
        for column in (1, 3):
            verbose_print(verbose, f'[WARN] Record TFF / BFF does not appear to be digit. Record: `{record}` ; TFF: `{record[1]}` ; BFF: `{record[3]}`')
            if record[column].isdigit():
                xff += int(record[1])
        if record[5].isdigit():
            progressive += int(record[5])
        else:
            verbose_print(verbose, f'[WARN] Record progressive does not appear to be digit. Record: `{record}` ; Progressive: `{record[5]}`')
        if record[7].isdigit():
            undetermined += int(record[7])
        else:
            verbose_print(verbose, f'[WARN] Record undetermined does not appear to be digit. Record: `{record}` ; Progressive: `{record[7]}`')

    records_str = []
    if verbose:
        for record in records:
            records_str.append(' '.join(record))
        records_str = '\n'.join(records_str)
        verbose_print(verbose, records_str)

    if xff + progressive == 0:
        if not quiet:
            print('Verdict:\tUNDETERMINED\nConfidence:\t100%')
            verbose_print(verbose, records_str)
        exit(2)

    total_frames = xff + progressive + undetermined
    if total_frames < 9:
        verbose_print(not quiet, f'[ERROR] Only {total_frames} frames. Must have at least 10 frames.')
        exit(1)
    confidence_progressive = (progressive / total_frames)
    confidence_interlaced = (xff / total_frames)

    if confidence_progressive > confidence_interlaced:
        verdict = 'PROGRESSIVE'
        confidence = confidence_progressive
    elif confidence_interlaced > confidence_progressive:
        verdict = 'INTERLACED'
        confidence = confidence_interlaced
    else:
        verdict = 'UNDETERMINED'
        confidence = 0

    if not quiet:
        print(f'Verdict:\t{verdict}\tConfidence:\t{confidence}')
        verbose_print(verbose, records_str)

    match verdict:
        case 'PROGRESSIVE':
            exit(0)
        case 'INTERLACED':
            exit(1)
        case 'UNDETERMINED':
            exit(2)
        case _:
            exit(3)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(prog='is_interlaced')
    parser.add_argument('-v', '--verbose', action='store_true')
    parser.add_argument('-q', '--quiet', action='store_true')
    args = parser.parse_args()

    main(args.verbose, args.quiet)

You must log in to comment.

in reply to @catball's post:

I've had live stats turned off for the last handful of runs, and I usually have multiple encodes running simultaneously, but iirc ballpark a little less than 1 FPS. The main features from a couple 2k blue rays have taken a couple days each, and a big 4k film took a few days

I saw in the ffmpeg docs1 that CRF 23 is considered visually lossless based on some paper, so I've kind of stuck with that. I settled on preset 3 after seeing a bunch of comparisons of different options graphed w performance metrics

grain synthesis does seem to have a decent impact on space for grainy old films (e.g. I am Cuba blu ray was 14GB encoded w/o grain synth, but 9.8GB with at grain=15) but I haven't really gone over the visual differences with a fine tooth comb yet or anything

since I'm not in a hurry to recover any disk space or to transfer these anywhere, I figure I might as well give my computer something to mull over for a long while

ok cool, that's in line with what I was seeing and I wanted to make sure that was ballpark.

Way slower than anything I'm willing to put up with, but it sounds like it does a good job on space. Sounds like it's probably 2-3x smaller than x264 crf 23?

I honestly don't think I have a good immediate comparison to x264 at crf 23, but i'm kind of curious to check now! I wouldn't doubt it's half the size or smaller

side note: it sounds like x264 crf 19 is equivalent to av1 crf 23 for achieving "perceptually lossless"

This is true, but I've never felt the need to go below 23, MAYBE 22 or 21 if my source material is really good. For context most of what I encode is motorsports, so lots of motion sweeping shots and, in my case, 50fps. No 24/25/30.

I don't know how much that impacts encoding time tbh, it's been too long since I tested that.

So it's giving 264 a bit of an advantage by letting it run lower quality.

oh neatǃ the speedy motion in motorsports sounds like a neat thing you need to watch out for in video encoding. Do you record motorsports for work or for fun?

when my current encodes finish up, maybe I'll find some little clips of stuff to do some benchmarks :) lmk if you have some motorsport clips you'd like me to benchmarkǃ

I lack access to the feeds for recording, but thankfully I sometimes have access to high quality raw rips.

I will try to remember next week! Furcon eating all brain cycles until then