Linux, Unix, and whatever they call that world these days

MKV Video Bitrate

Getting bitrate used for video is easy using ffmpeg.

ffprobe -v error -select_streams v:0 -show_entries stream=bit_rate -of csv=p=0 "$FILENAME"

That is, unless the file is .mkv where this command will return one big fat N/A. Due to various intricacies of Matroska container, you need to do a few more steps.

First step would be to get duration. And that goes as vel as expected.

ffprobe -v error -show_entries format=duration -of csv=p=0 "$FILENAME"

Then we need to get the size of the video stream. We cannot do this directly since our stream is “chopped” into many pieces. But with the help of awk we can sum all those chunks.

ffprobe -v error -select_streams v:0 -show_entries packet=size -of csv=p=0 "$FILENAME" | awk '{sum += $1} END {print sum+0}'

With those two we have all building blocks to determine video (and just video) bandwidth.

STREAM_DURATION=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$FILENAME" | cut -d. -f1)
STREAM_BYTES=$(ffprobe -v error -select_streams v:0 -show_entries packet=size -of csv=p=0 "$FILENAME" | awk '{sum += $1} END {print sum+0}')
BIT_RATE=$((STREAM_BYTES * 8 / STREAM_DURATION))
echo "$((BIT_RATE / 1000))K"

Timing Time in Bash

As someone who uses bash all the time, I got used to $EPOCHSECONDS as a source of time. As a shell variable, it’s probably the fastest way to get that information. That said, getting down to using date is still something I occassionally do. It’s supported accross the shells and it’s still fast enough.

But, I started wondering - what is the speed difference? As an example I decided to use SECONDS % 60 as it’s not only getting seconds but also processing them a bit. I would say that is a representative usage of the time - not just reading but also adding some light arithmetic operation. So, I prepared a script to execute the following:

echo $EPOCHREALTIME
echo $((EPOCHSECONDS %60))
echo $EPOCHREALTIME
printf '%(%S)T\n'
echo $EPOCHREALTIME
echo $(date +%S)
echo $EPOCHREALTIME

On bash this will run my three equivalent examples of how to get a number of seconds and get their execution time in microseconds. Yes, echo here is bluring results a bit but it’s bluring them for all examples the same. For a quick test, it’s good enough. So, I placed this into a 100x loop and here are the results.

CommandAverageStDevRatio
echo $((EPOCHSECONDS %60))0.0000380.0000141.00
printf '%(%S)T\n'0.0001110.0000362.95
echo $(date +%S)0.0046540.000726123.78

As expected, using $EPOCHSECONDS is the fastest. I am slightly surprised that printf is 3x slower. I guess it makes sense because it is doing additional parsing, but I expected the result to be closer. But both of those are in microseconds on my laptop.

The only test to reach the milliseconds was date. Invoking date was 42x slower than printf and almost 124x slower than using the variable directly. Now, while this looks dreadful in comparison, in reality it’s not that bad as this doesn’t mean CPU was busy for the duration. But I find it still a bit worrying considering how often I used it myself.

But let’s be realistic - you are not echoing date command. You are probably just storing it into variable - what is the effect then?

echo $EPOCHREALTIME
TEST=$((EPOCHSECONDS %60))
echo $EPOCHREALTIME
printf -v TEST '%(%S)T'
echo $EPOCHREALTIME
TEST=$(date +%S)
echo $EPOCHREALTIME

Well, this run resulted in the following table.

CommandAverageStDevRatio
TEST=$((EPOCHSECONDS %60))0.0000180.0000071.00
printf -v TEST '%(%S)T'0.0000930.0000235.17
TEST=$(date +%S)0.0040760.000451226.58

Times did reduce a bit, especially for printf, but the ratios got even worse.

In the end, if you need to deal with a local time zone or you need a POSIX shell, date is still worth it. But, for bash-specific scripts, I will start paying a bit more attention to use EPOCHSECONDS when I can.

Trim on the Framework Expansion Cards

To handle trim on framework expansion cards, you can adjust udev rules:

cat << EOF | sudo tee /etc/udev/rules.d/42-framework-storage.rules
ACTION=="add|change", SUBSYSTEM=="scsi_disk", ATTRS{idVendor}=="13fe", ATTRS{idProduct}=="6500", ATTR{provisioning_mode}="unmap"
ACTION=="add|change", SUBSYSTEM=="scsi_disk", ATTRS{idVendor}=="32ac", ATTRS{idProduct}=="0005", ATTR{provisioning_mode}="unmap"
ACTION=="add|change", SUBSYSTEM=="scsi_disk", ATTRS{idVendor}=="32ac", ATTRS{idProduct}=="0010", ATTR{provisioning_mode}="unmap"
EOF

If you don’t want to restart, follow that with:

sudo udevadm control --reload-rules
sudo udevadm trigger

And that works.

However, under kernel 7.0 I noticed that unplugging and repluggin device would disable trim (as checked with lsblk -D). To be honest, this might have been happening under older kernel too and I just didn’t notice. Regardless, I needed a solution.

To cut a long story short, my solution was to manually trigger udevadm 2 seconds after USB device has been added. Not the most elegant solution, but it does work.

cat << EOF | sudo tee /etc/udev/rules.d/42-framework-storage.rules
ACTION=="add|change", SUBSYSTEM=="scsi_disk", ATTRS{idVendor}=="13fe", ATTRS{idProduct}=="6500", ATTR{provisioning_mode}="unmap"
ACTION=="add|change", SUBSYSTEM=="scsi_disk", ATTRS{idVendor}=="32ac", ATTRS{idProduct}=="0005", ATTR{provisioning_mode}="unmap"
ACTION=="add|change", SUBSYSTEM=="scsi_disk", ATTRS{idVendor}=="32ac", ATTRS{idProduct}=="0010", ATTR{provisioning_mode}="unmap"
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="32ac", ATTR{idProduct}=="0005", RUN+="/bin/sh -c 'sleep 2; udevadm trigger'"
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="32ac", ATTR{idProduct}=="0010", RUN+="/bin/sh -c 'sleep 2; udevadm trigger'"
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="13fe", ATTR{idProduct}=="6500", RUN+="/bin/sh -c 'sleep 2; udevadm trigger'"
EOF

DaVinci Resolve 18 on Ubuntu 23.10 with Intel GPU

While I am not really a video editing guy I do occassionally play with DaVinci Resolve. Unfortunately, installation was not really straightforward under Ubuntu. Even worse, as I switched to AMD laptop with internal GPU, Resolve didn’t work at all.

As one of those video editing opportunities arose, I decided to try my luck on Kubuntu 26.04. Since this OS is quite newer than my current DaVinci resolve machine (Ubuntu 24.04), I decided to scratch previous procedure and try installation from scratch. Once basic install is done, I can figure which libraries are missing and need adjustment.

So, I downloaded DaVinci Resolve 23.0.1 and executed the following commands:

cd ~/Downloads
unzip DaVinci_Resolve_21.0.3_Linux.zip
chmod +x DaVinci_Resolve_21.0.3_Linux.run
sudo SKIP_PACKAGE_CHECK=1 ./DaVinci_Resolve_21.0.3_Linux.zip

Once install was done, I started the application expecting errors but everything worked just fine. Yes, even on AMD internal GPU.

Now, free version still doesn’t deal with MP4 files and AAC under Linux - darn licenses. Regardless, I am happy to do some pre-conversion before editing rather then dealing with Windows.

Larger Live File System on Fedora 44

Illustration

While I am spending most of my time on Ubuntu these days, I do like to see what Fedora is up to. So, with Fedora 44 out, I started medling with installing some extra ZFS packages on top of it. However, I was soon hit with “Npt enough space” message. Fedora’s in-memory root pertition was a bit too small. But there’s a way around it.

During boot just select the main boot option and press e key. This will lead you into text editor and there just add

rd.live.overlay.size=10G

to the “linux” line.

This will make in-memory store larger (albeit you won’t see it be 10G in size). With this, I was finally able to fit ZFS on it.

Dealing with "No Signal" on HDMI Input

Illustration

For my media PC I use old Intel’s gen 11 board. As I upgraded my Framework laptop, it just made sense to use it. Bazzite works flawlessly on it. At least until paired with my Samsung “smart” TV.

I might be turning into an old man yelling at clouds but it seems that smart TVs are getting dumber and dumber. My PC is connected to TV via HDMI cable and it works wonderfully. Until TV is off for some time. Then suddenly, TV cannot find HDMI source.

I already wrote about this so I will spare you details on how I figured the TV was to blame. Suffice it to say that I found the fix even back then. But now I had an issue with my fix. It was too slow.

You see, I decided to use 1 minute timer. Which means that, once TV has gone crazy, one had to wait a minute at worst. And that caused some impatience in my household. So, I needed a faster detection…

The new script at /usr/local/bin/dp-reconnect looks something like this:

#!/usr/bin/env bash

while(true); do
    SLEEP_INTERVAL=$(( 3 - `date +%S` % 3 ))
    sleep $SLEEP_INTERVAL

    DP_CONNECTED=0
    for DP_PATH in /sys/class/drm/card1-DP-*; do
        DP_STATUS=$( cat "$DP_PATH/status" | grep '^connected$' | xargs )
        if [[ "$DP_STATUS" == "connected" ]]; then
            DP_CONNECTED=1
            break
        fi
    done

    if [[ $DP_CONNECTED -ne 0 ]]; then
        echo "$(date '+%Y-%m-%d %H:%M:%S') Display $( basename "$DP_PATH" ) connected"
    else
        echo "$(date '+%Y-%m-%d %H:%M:%S') No connected displays"
        chvt 2
        sleep 1
        chvt 1
    fi
done

Instead of “one-shot”, this script loops forever. The very first calculation is there just to slow things a bit and ensure payload is executed every 3 seconds. And yes, this could have been replaced with sleep 3 without any functionality loss. However, I like to “align” my execution times so I did it in slightly more complicated manner.

To run this code, we can simply create /etc/systemd/system/dp-reconnect.service with the following content:

[Unit]
Description=Switch terminal if no DP is connected

[Service]
ExecStart=/usr/local/bin/dp-reconnect
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

With all files in place, the only remaining thing was enabling the service:

sudo systemctl daemon-reload
sudo systemctl enable --now dp-reconnect.service

Now my TV reacts much faster, hopefully putting this story to an end.

Chrony Tracking in Milliseconds

If you are running NTP server based on Chrony, you are probably checking its status too. For me chronyc tracking has been almost perfect. The only thing I dislike is having seconds in outputs. For any decent NTP server, these values are sub-millisecond and thus output has a lot of zeros.

Fortunatelly, changing seconds to milliseconds is easy:

chronyc tracking | awk '
  /seconds/ {
    sub(/seconds/, "milliseconds")
    for (I=1; I<=NF; I++) {
      if ($I ~ /^[+-]?[0-9.]+$/) {
        $I = sprintf("%.6f", $I * 1000)
      }
    }
  } { print }'

This awk command will match any line that has word seconds with milliseconds and then multiply any number it finds with 1000. Once printed again, this means all numbers are in milliseconds, just as we wanted.

This does work but it will also adjust Update interval, which is one field that makes sense to be in seconds. So, we can limit how many changes are to be done.

chronyc tracking | awk '
  /seconds/ && count < 5 {
    sub(/seconds/, "milliseconds")
    for (I=1; I<=NF; I++) {
      if ($I ~ /^[+-]?[0-9.]+$/) {
        $I = sprintf("%.6f", $I * 1000)
      }
    }
    count++
  } { print }'

Here we just add counter to limit our changes to only the first 5 fields. Since chronyc tracking output is relatively stable, we got to “cheat” a bit.

If you’re using this as input for something else, this is sufficient. However, if you plan to look at it on the command line, you will notice that changed lines also had their spaces messed up. For that, here is the final form.

chronyc tracking | awk '
  /seconds/ && count < 5 {
    sub(/seconds/, "milliseconds")
    for (I=1; I<=NF; I++) {
      if ($I ~ /^[+-]?[0-9.]+$/) {
        $I = sprintf("%.6f", $I * 1000)
      }
    }
    count++
  } { print }' | awk '{
    sub(/:/, "\t")
    print
  }' | column -t -s$'\t' -o:

Here we make use of the column command to make a nice table using tab (\t) as an input and colon (:) as output separator. Reason we cannot use colons for input separation lies in ref time field which includes colons in the time string. So, in order for this to work, we also adjust input to column by replacing the first colon with tab and leaving the rest intact.

Now your output should look something like this:

Reference ID    : 81060F1C (time-a-g.nist.gov)
Stratum         : 2
Ref time (UTC)  : Sun May 24 00:00:01 2026
System time     : 0.207166 milliseconds fast of NTP time
Last offset     : 0.010373 milliseconds
RMS offset      : 0.052737 milliseconds
Frequency       : 23.102 ppm slow
Residual freq   : +0.000 ppm
Skew            : 0.006 ppm
Root delay      : 24.370797 milliseconds
Root dispersion : 1.106555 milliseconds
Update interval : 1033.8 seconds
Leap status     : Normal

PTClipboard

As I was switching to Linux, so did my code base. I admit, most of code didn’t really care on which platform it was running but I wasn’t so fortunate with all of it. One annoying issue was clipboard handling. Linux simply did its clipboard differently. So I decided to solve this problem for me. Twice.

My first attempt at solving this problem was a simple X11 clipboard library. It worked like a charm. And then the whole migration toward Wayland happened. While most desktop environments still have X11 compatibility, writing was on the wall for this one.

So, I decided to expand functionality a bit. The new library still supports handling X11 clipboard text. And to it adds both Wayland and Windows support. That way the same library can be used in a really cross-platform manner. Well, almost cross-platform - MacOS support is missing as I don’t really have any devices to test it on.

Now, this library is small in scope - only plain-text is supported. While I do have plans to adjust this a bit, I doubt it will go much beyond this. Idea is to have it as simple as possible, not to provide the full clipboard access. And simple it is:

using Medo;

PTClipboard.SetText("My text.");
var text = PTClipboard.GetText();

And that is pretty much all. Yes, there is also support for primary selection under linux (aka as “middle-click clipboard”) with syntax just slightly longer:

using Medo;

PTClipboard.Selection.SetText("My text.");
var text = PTClipboard.Selection.GetText();

And setup is pretty much non-existent as code should detect system it is running on automatically.

You can get package at NuGet and check source code at GitHub.

Reinstalling Kubuntu 26.04 on Encrypted ZFS Root

Recently I wrote about installing Kubuntu on ZFS. However, what to do if you already have a working setup and you just want reinstall OS?

In my case, I want to preserve my ZFS pool. Additionally, I want to retain all my ZFS datasets, except for the dataset that holds my file system. And yes, system is a bit peculiar but it’s nothing too special.

As one would assume, procedure is quite similar to the original installation. In order to fully understand why certain steps are in, I would suggest reading the original post. If you have a different setup, steps will differ, but the gist of it will remain the same.

As before, we start with root terminal.

sudo -i

Step to install the packages is also the same, as one would expect.

apt update
apt install -y gdisk zfsutils-linux

I setup the variables to help me to work.

DISK1=/dev/disk/by-id/<disk>
HOST=<host>
USERNAME=<user>

Since we don’t want to repartition disk, we can immediately skip to the step of fetching partition UUIDs.

DISK1P1=`blkid -s PARTUUID -o value $DISK1-part1`
DISK1P2=`blkid -s PARTUUID -o value $DISK1-part2`
DISK1P3=`blkid -s PARTUUID -o value $DISK1-part3`
DISK1P4=`blkid -s PARTUUID -o value $DISK1-part4`

As my setup is on top of LUKS, it is necessary to open the mapper.

cryptsetup luksOpen \
    --persistent --allow-discards \
    --perf-no_write_workqueue --perf-no_read_workqueue \
    /dev/disk/by-partuuid/$DISK1P4 $DISK1P4

And now we can import the pool. Note that we will load it at /mnt/install so that it will match the original setup.

zpool import -N -R /mnt/install -f ${HOST^}

Now we can destroy our previous system. There is no coming back after this step.

zfs destroy ${HOST^}/System

Alternatively, if you want to keep it for reference, you can just rename it.

zfs rename ${HOST^}/System ${HOST^}/System.Old
zfs set mountpoint=/oldsys ${HOST^}/System.Old

With this in place, we get to create a new system dataset.

zfs create \
    -o devices=on \
    -o canmount=noauto -o mountpoint=/ \
    ${HOST^}/System
zfs mount ${HOST^}/System

Importantly, I also mount my home dataset so that my user stuff is there if I need it.

zfs mount ${HOST^}/Home

Lastly, we clean boot and EFI partition.

dd if=/dev/zero of=/dev/disk/by-partuuid/$DISK1P1 bs=1M
dd if=/dev/zero of=/dev/disk/by-partuuid/$DISK1P2 bs=1M

With all this in place, you can simply continue with the original guide at the step of formatting boot partition (mkfs.ext4). And that’s it - your OS will be as new.

Thou Shalt Not Init 6

I have been using different *nixes for a while now. Long enough that one simply gets used to some things. For example, I still turn off my OS using init 0 and reboot it using init 6. It’s just a habit I couldn’t shake.

Well, I guess those days are over. As of Ubuntu 26.04 these old commands will now give error:

Excess arguments

Well, there go decades of muscle memory…