various improvements. move mpv into priv repo due to media specific stuff
authorIan Kelling <ian@iankelling.org>
Wed, 30 Jul 2025 04:19:08 +0000 (00:19 -0400)
committerIan Kelling <ian@iankelling.org>
Wed, 30 Jul 2025 04:19:08 +0000 (00:19 -0400)
20 files changed:
brc2
distro-end
filesystem/usr/local/bin/i3-event-hook
filesystem/usr/local/bin/mon-brightness-cycle [new file with mode: 0755]
i3-sway/common.conf
i3-sway/i3.conf
icc-load [new file with mode: 0755]
icc/U3225QE #1 2025-07-28 23-51 D6500 2.2 F-S XYZLUT+MTX.icc [new file with mode: 0644]
icc/U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc [new file with mode: 0644]
icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc [new file with mode: 0644]
machine_specific/frodo/filesystem/etc/systemd/system/btrbkr.service
machine_specific/frodo/subdir_files/.config/pipewire/pipewire-pulse.conf [deleted file]
machine_specific/frodo/subdir_files/.config/pipewire/pipewire.conf
pkgs
primary-setup
subdir_files/.config/mpv/input.conf [deleted file]
subdir_files/.config/mpv/mpv.conf [deleted file]
subdir_files/.config/mpv/scripts/playlistmanager.lua [deleted file]
subdir_files/.config/sqlfluff/.sqlfluff [new file with mode: 0644]
subdir_files/.local/share/icc [new symlink]

diff --git a/brc2 b/brc2
index df9e17ee3d20f5f0ff42b7ab992b6d7bf4c17485..a9c3a6d4490bcf3b49eeb22750b977cac73113fb 100644 (file)
--- a/brc2
+++ b/brc2
@@ -2976,8 +2976,8 @@ myprof() {
     if [[ -s office_at_conference.fsf.org/$log_base ]]; then
       day_logs+=(office_at_conference.fsf.org/$log_base)
     fi
-    if [[ -s sys_at_conference.fsf.org/$log_base ]]; then
-      day_logs+=(sys_at_conference.fsf.org/$log_base)
+    if [[ -s sys-private_at_conference.fsf.org/$log_base ]]; then
+      day_logs+=(sys-private_at_conference.fsf.org/$log_base)
     fi
     if (( ${#day_logs[@]} == 0 )); then
       continue
@@ -2999,6 +2999,7 @@ myprof() {
 profr() {
   source /p/c/domain-info
   if [[ $HOSTNAME == $d_host ]]; then
+    : # todo: fill this in?
   else
     ssh b8.nz profr-local
   fi
@@ -4863,6 +4864,44 @@ mdread() {
   o $tmpf
 }
 
+# Note: after swapping monitor, I had to run cmd to get improved refresh rate. I suspect this will be setup automatically on the next X restart:
+# xrandr --output DP-0 --mode 3840x2160  --rate 120
+
+# Note: to get started with ddcutil:
+#
+# ddcutil detect
+# ddcutil --display 1 capabilities
+mon-brightness() {
+  local percent
+  if [[ $1 ]]; then
+    percent="$1"
+    if [[ ! $percent =~ $uint_regex ]] || (( percent < 1 || percent > 100 )) ; then
+      e "$0: error: arg must be 1-100"
+      return 1
+    fi
+    ddcutil --display 1 setvcp 10 $percent
+  else
+    e "$0: getting current brightness. To set brightness, add percentage arg of 1-100."
+    ddcutil --display 1 getvcp 10
+  fi
+
+  # note, for setting color preset, quote from capabilities:
+
+  # Feature: 14 (Select color preset)
+  #     Values:
+  #        01: sRGB
+  #        04: 5000 K
+  #        05: 6500 K
+  #        06: 7500 K
+  #        08: 9300 K
+  #        09: 10000 K
+  #        0b: User 1
+  #        0c: User 2
+
+  # However, options 08+ do not work. Perhaps the user settings need setup first.
+}
+
+
 export BASEFILE_DIR=/a/bin/fai-basefiles
 
 #export ANDROID_HOME=/a/opt/android-home
index 037b66e921375f6383a3e4fead65ddb032e6e8a5..1bdec04d6a5c660989b3a57f6c30190a26635f2c 100755 (executable)
@@ -1699,6 +1699,9 @@ DEVICESCAN -a -o on -S on -n standby,q $sched \
 
 ########### misc stuff
 
+# group used by ddcutil
+sudo usermod -a -G i2c iank
+
 # according to
 # /a/c/machine_specific/frodo/.config/pipewire/pipewire.conf pipewire
 # uses RTKit to prioritize pipewire process by default, but if you are
@@ -2171,6 +2174,7 @@ case $HOSTNAME in
       sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y noble
       pi postgresql-17
       sudo -u postgres createuser -d iank
+      sudo usermod -a -G postgres iank
     fi
     ;;
 esac
index 87487c6ebdcc0fac84584bf1045533e1d4917a30..6a1dbb7565624d890d25eeaa2318947674a2b7d7 100755 (executable)
@@ -112,7 +112,8 @@ def focus_hook(i3, e):
         #     print("pnodes: ", pnode.nodes)
 
         for node in pnode.nodes:
-            kill_single_win_containers(i3, e, node, pnode)
+            for node_l2 in node.nodes:
+                kill_single_win_containers(i3, e, node_l2, node)
 
 def main():
     i3 = Connection()
diff --git a/filesystem/usr/local/bin/mon-brightness-cycle b/filesystem/usr/local/bin/mon-brightness-cycle
new file mode 100755 (executable)
index 0000000..32ac218
--- /dev/null
@@ -0,0 +1,45 @@
+#!/bin/bash
+# I, Ian Kelling, follow the GNU license recommendations at
+# https://www.gnu.org/licenses/license-recommendations.en.html. They
+# recommend that small programs, < 300 lines, be licensed under the
+# Apache License 2.0. This file contains or is part of one or more small
+# programs. If a small program grows beyond 300 lines, I plan to change
+# to a recommended GPL license.
+
+# Copyright 2025 Ian Kelling
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set -e; . /usr/local/lib/bash-bear; set +e
+
+state_f=/tmp/user/1000/tmp_mon_bright.KROSZousbE
+
+low_level=70
+
+set-bright() {
+  ddcutil --display 1 setvcp 10 $1
+  echo $1 >$state_f
+}
+
+if [[ -s $state_f ]]; then
+  cur_bright=$(cat $state_f)
+  # echo "state_f: $cur_bright" # debug
+else
+  cur_bright=$(ddcutil -t --display 1 getvcp 10 | awk '{print $4}')
+fi
+
+if [[ $cur_bright == "$low_level" ]]; then
+  set-bright 100
+else
+  set-bright $low_level
+fi
index 695c5cda3031f6685125b888dc39eba14246eb17..f1cb675c976a8b17bf149e059d445b4f9f32da4f 100644 (file)
@@ -23,8 +23,7 @@ set $ex exec --no-startup-id
 bindsym $mod+2 $ex "i3-split-maybe"; exec "pavucontrol"
 # calling without --new-instance makes this to be the instance that links
 # will open in from other applications.
-# unused. todo: consider binding this to some key on the right side of keyboard.
-#bindsym $mod+3 $ex "i3-split-maybe"; exec "abrowser"
+bindsym $mod+3 $ex "mon-brightness-cycle"
 # calling just abrowser mysteriously stopped working,
 # so I figured out this is how to get output, but then
 # it suddenly started working again.
@@ -230,3 +229,5 @@ for_window [class="focus" instance="focus"] floating disable
 client.focused          #4c7899 #285577 #ffffff #2e9ef4   #ff4400
 client.focused_inactive #333333 #5f676a #ffffff #484e50   #B8C8CD
 client.unfocused        #333333 #222222 #888888 #292d2e   #B8C8CD
+
+assign [instance="mpv-i3-ws6"] 6
index a29f08f72f2bfd69ba1d827de9e7798290998583..631a966745b4670dfb8f5309a355ed3e77377604 100644 (file)
@@ -4,11 +4,12 @@ bindsym $mod+Shift+o exec "i3-nagbar -t warning -m 'You pressed the exit shortcu
 bindsym $mod+Shift+i $ex /b/ds/i3-sway/gen
 bindsym $mod+Shift+p restart
 
-
+$ex /a/bin/ds/icc-load
 $ex copyq
 $ex dunst
 # haven't been using it enough to justify automatically running it.]
 #$ex /usr/lib/x86_64-linux-gnu/libexec/kdeconnectd
-$ex /usr/local/bin/awatch
+
+#$ex /usr/local/bin/awatch
 # this dies when we restart i3.
 exec_always --no-startup-id i3-event-hook
diff --git a/icc-load b/icc-load
new file mode 100755 (executable)
index 0000000..4c72051
--- /dev/null
+++ b/icc-load
@@ -0,0 +1,67 @@
+#!/bin/bash
+# I, Ian Kelling, follow the GNU license recommendations at
+# https://www.gnu.org/licenses/license-recommendations.en.html. They
+# recommend that small programs, < 300 lines, be licensed under the
+# Apache License 2.0. This file contains or is part of one or more small
+# programs. If a small program grows beyond 300 lines, I plan to change
+# to a recommended GPL license.
+
+# Copyright 2025 Ian Kelling
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# I generally followed this guide:
+# https://phototacopodcast.com/photographers-guide-to-screen-calibration-with-displaycal/
+# plus some displaycal forum searching. I ignored the part about
+# brightness because I prefer to view images extra bright.
+#
+# This was also a useful intro: https://web.archive.org/web/20221226110150/https://encrypted.pcode.nl/blog/index.html%3Fp=853.html
+#
+# It wasn't clear to me whether I should use the builtin correction
+# profile (as recommended on the forum), or the correction profile in
+# the user database. Ultimately, I decided to the one in the user
+# database, as theoretically, it is the result of a measurement with a
+# Spectrometer, and is likely more accurate. However, I also know that I
+# did measurements with brightness 100%, more than most people use,
+# which could change things.
+#
+# When doing the initial monitor calibration, the builtin correction was
+# best with 98 red, 100 gree, 98 blue. The user db one was roughly 97
+# red, 100 green, 97 blue. I stuck with the 98 one, and figured that way
+# I'd be slightly more conservative and maybe keep 1% more brightness.
+#
+# U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc
+# This uses the user-db correction, i1 DisplayPro, ColorMunki Display & Dell, DELL U3225QE (i1 Pro 2) by 4k-monitor.ccmx,
+# and was taken with my typical ambiant light, including me sitting in front of the computer.
+#
+# 'U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc'
+# This uses the builtin correction with my typical ambiant light, except I was not in front of the monitor.
+#
+#
+# The installation process done by displaycal didn't work, but looking
+# at the console output, I figured out a simple way to make it work (as
+# below). Not sure if colord could have worked, but
+# https://wiki.archlinux.org/title/ICC_profiles said it was less
+# compatible, so I didn't bother.
+
+case $HOSTNAME in
+  frodo)
+
+    /usr/bin/dispwin -v -d1 -c -I '/a/bin/ds/icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc'
+    # test that it worked with:
+    # dispwin -v -d1 -V '/etc/xdgcolor/icc/devices/display/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc'
+    #
+    # You can also watch the grey color of konsole change slightly when it loads.
+    ;;
+esac
diff --git a/icc/U3225QE #1 2025-07-28 23-51 D6500 2.2 F-S XYZLUT+MTX.icc b/icc/U3225QE #1 2025-07-28 23-51 D6500 2.2 F-S XYZLUT+MTX.icc
new file mode 100644 (file)
index 0000000..e68d7fa
Binary files /dev/null and b/icc/U3225QE #1 2025-07-28 23-51 D6500 2.2 F-S XYZLUT+MTX.icc differ
diff --git a/icc/U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc b/icc/U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc
new file mode 100644 (file)
index 0000000..b4a2909
Binary files /dev/null and b/icc/U3225QE #1 2025-07-29 01-16 D6500 2.2 F-S XYZLUT+MTX.icc differ
diff --git a/icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc b/icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc
new file mode 100644 (file)
index 0000000..ed763c4
Binary files /dev/null and b/icc/U3225QE #1 2025-07-29 01-57 D6500 2.2 F-S XYZLUT+MTX.icc differ
index 8938e9c2bcdbec977bd79f07daca17a9cbedd4a4..234a62464c3be4e86ac0a6f6bc58f760e520e163 100644 (file)
@@ -4,4 +4,4 @@ After=multi-user.target
 
 [Service]
 Type=oneshot
-ExecStart=/usr/local/bin/sysd-mail-once btrbkrust btrbk -c /etc/btrbk/r.conf run
+ExecStart=/usr/local/bin/sysd-mail-once btrbkr btrbk -c /etc/btrbk/r.conf run
diff --git a/machine_specific/frodo/subdir_files/.config/pipewire/pipewire-pulse.conf b/machine_specific/frodo/subdir_files/.config/pipewire/pipewire-pulse.conf
deleted file mode 100644 (file)
index 77110b9..0000000
+++ /dev/null
@@ -1,173 +0,0 @@
-# PulseAudio config file for PipeWire version "1.0.5" #
-#
-# Copy and edit this file in /etc/pipewire for system-wide changes
-# or in ~/.config/pipewire for local changes.
-#
-# It is also possible to place a file with an updated section in
-# /etc/pipewire/pipewire-pulse.conf.d/ for system-wide changes or in
-# ~/.config/pipewire/pipewire-pulse.conf.d/ for local changes.
-#
-
-context.properties = {
-    ## Configure properties in the system.
-    #mem.warn-mlock  = false
-    #mem.allow-mlock = true
-    #mem.mlock-all   = false
-    #log.level       = 2
-
-    #default.clock.quantum-limit = 8192
-}
-
-context.spa-libs = {
-    audio.convert.* = audioconvert/libspa-audioconvert
-    support.*       = support/libspa-support
-}
-
-context.modules = [
-    { name = libpipewire-module-rt
-        args = {
-            nice.level   = -11
-            #rt.prio      = 83
-            #rt.time.soft = -1
-            #rt.time.hard = -1
-            #uclamp.min = 0
-            #uclamp.max = 1024
-        }
-        flags = [ ifexists nofail ]
-    }
-    { name = libpipewire-module-protocol-native }
-    { name = libpipewire-module-client-node }
-    { name = libpipewire-module-adapter }
-    { name = libpipewire-module-metadata }
-
-    { name = libpipewire-module-protocol-pulse
-        args = {
-           # contents of pulse.properties can also be placed here
-           # to have config per server.
-        }
-    }
-]
-
-# Extra scripts can be started here. Setup in default.pa can be moved in
-# a script or in pulse.cmd below
-context.exec = [
-    #{ path = "pactl"        args = "load-module module-always-sink" }
-    #{ path = "pactl"        args = "upload-sample my-sample.wav my-sample" }
-    #{ path = "/usr/bin/sh"  args = "~/.config/pipewire/default.pw" }
-]
-
-# Extra commands can be executed here.
-#   load-module : loads a module with args and flags
-#      args = "<module-name> <module-args>"
-#      ( flags = [ nofail ] )
-pulse.cmd = [
-    { cmd = "load-module" args = "module-always-sink" flags = [ ] }
-    #{ cmd = "load-module" args = "module-switch-on-connect" }
-    #{ cmd = "load-module" args = "module-gsettings" flags = [ nofail ] }
-]
-
-stream.properties = {
-    #node.latency          = 1024/48000
-    #node.autoconnect      = true
-    #resample.quality      = 4
-    #channelmix.normalize  = false
-    #channelmix.mix-lfe    = true
-    #channelmix.upmix      = true
-    #channelmix.upmix-method = psd  # none, simple
-    #channelmix.lfe-cutoff = 150
-    #channelmix.fc-cutoff  = 12000
-    #channelmix.rear-delay = 12.0
-    #channelmix.stereo-widen = 0.0
-    #channelmix.hilbert-taps = 0
-    #dither.noise = 0
-}
-
-pulse.properties = {
-    # the addresses this server listens on
-    server.address = [
-        "unix:native"
-        #"unix:/tmp/something"              # absolute paths may be used
-        #"tcp:4713"                         # IPv4 and IPv6 on all addresses
-        #"tcp:[::]:9999"                    # IPv6 on all addresses
-        #"tcp:127.0.0.1:8888"               # IPv4 on a single address
-        #
-        #{ address = "tcp:4713"             # address
-        #  max-clients = 64                 # maximum number of clients
-        #  listen-backlog = 32              # backlog in the server listen queue
-        #  client.access = "restricted"     # permissions for clients
-        #}
-    ]
-    #server.dbus-name       = "org.pulseaudio.Server"
-    #pulse.min.req          = 128/48000     # 2.7ms
-    #pulse.default.req      = 960/48000     # 20 milliseconds
-    #pulse.min.frag         = 128/48000     # 2.7ms
-    #pulse.default.frag     = 96000/48000   # 2 seconds
-    #pulse.default.tlength  = 96000/48000   # 2 seconds
-    # iank. ran out of time to test this to prevent io stuttering
-    # I set this in the pipewire config too. I don't understand quite how they interact.
-    #pulse.min.quantum      = 1024/44100
-
-    #pulse.min.quantum      = 128/48000     # 2.7ms
-    #pulse.idle.timeout     = 0             # don't pause after underruns
-    #pulse.default.format   = F32
-    #pulse.default.position = [ FL FR ]
-    # These overrides are only applied when running in a vm.
-    vm.overrides = {
-        pulse.min.quantum = 1024/48000      # 22ms
-    }
-}
-
-# client/stream specific properties
-pulse.rules = [
-    {
-        matches = [
-            {
-                # all keys must match the value. ! negates. ~ starts regex.
-                #client.name                = "Firefox"
-                #application.process.binary = "teams"
-                #application.name           = "~speech-dispatcher.*"
-            }
-        ]
-        actions = {
-            update-props = {
-                #node.latency = 512/48000
-            }
-            # Possible quirks:"
-            #    force-s16-info                 forces sink and source info as S16 format
-            #    remove-capture-dont-move       removes the capture DONT_MOVE flag
-            #    block-source-volume            blocks updates to source volume
-            #    block-sink-volume              blocks updates to sink volume
-            #quirks = [ ]
-        }
-    }
-    {
-        # skype does not want to use devices that don't have an S16 sample format.
-        matches = [
-             { application.process.binary = "teams" }
-             { application.process.binary = "teams-insiders" }
-             { application.process.binary = "skypeforlinux" }
-        ]
-        actions = { quirks = [ force-s16-info ] }
-    }
-    {
-        # firefox marks the capture streams as don't move and then they
-        # can't be moved with pavucontrol or other tools.
-        matches = [ { application.process.binary = "firefox" } ]
-        actions = { quirks = [ remove-capture-dont-move ] }
-    }
-    {
-        # speech dispatcher asks for too small latency and then underruns.
-        matches = [ { application.name = "~speech-dispatcher.*" } ]
-        actions = {
-            update-props = {
-                pulse.min.req          = 512/48000      # 10.6ms
-                pulse.min.quantum      = 512/48000      # 10.6ms
-                pulse.idle.timeout     = 5              # pause after 5 seconds of underrun
-            }
-        }
-    }
-    #{
-    #    matches = [ { application.process.binary = "Discord" } ]
-    #    actions = { quirks = [ block-source-volume ] }
-    #}
-]
index c66bc2b697d3198a251dc4e79032073f6c8b30bf..2ef7e8575f087d4e1fa2541b6d63c0f63149f061 100644 (file)
@@ -9,24 +9,6 @@
 #
 
 context.properties = {
-    ## Configure properties in the system.
-    #library.name.system                   = support/libspa-support
-    #context.data-loop.library.name.system = support/libspa-support
-    #support.dbus                          = true
-    #link.max-buffers                      = 64
-    link.max-buffers                       = 16                       # version < 3 clients can't handle more
-    #mem.warn-mlock                        = false
-    #mem.allow-mlock                       = true
-    #mem.mlock-all                         = false
-    #clock.power-of-two-quantum            = true
-    #log.level                             = 2
-    #cpu.zero.denormals                    = false
-
-    core.daemon = true              # listening for socket connections
-    core.name   = pipewire-0        # core name and socket name
-
-    ## Properties for the DSP configuration.
-    #default.clock.rate          = 48000
 
     # iank: adding this made us output at the same sample rate as the audio we were playing, which seems like a good thing. from arch wiki: "PipeWire can also change dynamically the output sample rates supported by your DAC", find them with:
     # grep -E 'Codec|Audio Output|rates' /proc/asound/card*/codec#*
@@ -41,293 +23,12 @@ context.properties = {
     #default.clock.quantum       = 1024
     # iank. ran out of time to test this to prevent io stuttering
     #default.clock.min-quantum   = 1024
-
-    #default.clock.min-quantum   = 32
-    #default.clock.max-quantum   = 2048
-    #default.clock.quantum-limit = 8192
-    #default.clock.quantum-floor = 4
-    #default.video.width         = 640
-    #default.video.height        = 480
-    #default.video.rate.num      = 25
-    #default.video.rate.denom    = 1
-    #
-    #settings.check-quantum      = false
-    #settings.check-rate         = false
-    #
-    # These overrides are only applied when running in a vm.
-    vm.overrides = {
-        default.clock.min-quantum = 1024
-    }
-
-    # keys checked below to disable module loading
-    module.x11.bell = true
-    # enables autoloading of access module, when disabled an alternative
-    # access module needs to be loaded.
-    module.access = true
-    # enables autoloading of module-jackdbus-detect
-    module.jackdbus-detect = true
-}
-
-context.spa-libs = {
-    #<factory-name regex> = <library-name>
-    #
-    # Used to find spa factory names. It maps an spa factory name
-    # regular expression to a library name that should contain
-    # that factory.
-    #
-    audio.convert.* = audioconvert/libspa-audioconvert
-    avb.*           = avb/libspa-avb
-    api.alsa.*      = alsa/libspa-alsa
-    api.v4l2.*      = v4l2/libspa-v4l2
-    api.libcamera.* = libcamera/libspa-libcamera
-    api.bluez5.*    = bluez5/libspa-bluez5
-    api.vulkan.*    = vulkan/libspa-vulkan
-    api.jack.*      = jack/libspa-jack
-    support.*       = support/libspa-support
-    #videotestsrc   = videotestsrc/libspa-videotestsrc
-    #audiotestsrc   = audiotestsrc/libspa-audiotestsrc
 }
 
-context.modules = [
-    #{ name = <module-name>
-    #    ( args  = { <key> = <value> ... } )
-    #    ( flags = [ ( ifexists ) ( nofail ) ] )
-    #    ( condition = [ { <key> = <value> ... } ... ] )
-    #}
-    #
-    # Loads a module with the given parameters.
-    # If ifexists is given, the module is ignored when it is not found.
-    # If nofail is given, module initialization failures are ignored.
-    # If condition is given, the module is loaded only when the context
-    # properties all match the match rules.
-    #
-
-    # Uses realtime scheduling to boost the audio thread priorities. This uses
-    # RTKit if the user doesn't have permission to use regular realtime
-    # scheduling. You can also clamp utilisation values to improve scheduling
-    # on embedded and heterogeneous systems, e.g. Arm big.LITTLE devices.
-    { name = libpipewire-module-rt
-        args = {
-            nice.level    = -11
-            rt.prio       = 88
-            #rt.time.soft = -1
-            #rt.time.hard = -1
-            #uclamp.min = 0
-            #uclamp.max = 1024
-        }
-        flags = [ ifexists nofail ]
-    }
-
-    # The native communication protocol.
-    { name = libpipewire-module-protocol-native
-        args = {
-            # List of server Unix sockets, and optionally permissions
-            #sockets = [ { name = "pipewire-0" }, { name = "pipewire-0-manager" } ]
-        }
-    }
-
-    # The profile module. Allows application to access profiler
-    # and performance data. It provides an interface that is used
-    # by pw-top and pw-profiler.
-    { name = libpipewire-module-profiler }
-
-    # Allows applications to create metadata objects. It creates
-    # a factory for Metadata objects.
-    { name = libpipewire-module-metadata }
-
-    # Creates a factory for making devices that run in the
-    # context of the PipeWire server.
-    { name = libpipewire-module-spa-device-factory }
-
-    # Creates a factory for making nodes that run in the
-    # context of the PipeWire server.
-    { name = libpipewire-module-spa-node-factory }
-
-    # Allows creating nodes that run in the context of the
-    # client. Is used by all clients that want to provide
-    # data to PipeWire.
-    { name = libpipewire-module-client-node }
-
-    # Allows creating devices that run in the context of the
-    # client. Is used by the session manager.
-    { name = libpipewire-module-client-device }
-
-    # The portal module monitors the PID of the portal process
-    # and tags connections with the same PID as portal
-    # connections.
-    { name = libpipewire-module-portal
-        flags = [ ifexists nofail ]
-    }
-
-    # The access module can perform access checks and block
-    # new clients.
-    { name = libpipewire-module-access
-        args = {
-            # Socket-specific access permissions
-            #access.socket = { pipewire-0 = "default", pipewire-0-manager = "unrestricted" }
-
-            # Deprecated legacy mode (not socket-based),
-            # for now enabled by default if access.socket is not specified
-            #access.legacy = true
-        }
-        condition = [ { module.access = true } ]
-    }
-
-    # Makes a factory for wrapping nodes in an adapter with a
-    # converter and resampler.
-    { name = libpipewire-module-adapter }
-
-    # Makes a factory for creating links between ports.
-    { name = libpipewire-module-link-factory }
-
-    # Provides factories to make session manager objects.
-    { name = libpipewire-module-session-manager }
-
-    # Use libcanberra to play X11 Bell
-    { name = libpipewire-module-x11-bell
-        args = {
-            #sink.name = "@DEFAULT_SINK@"
-            #sample.name = "bell-window-system"
-            #x11.display = null
-            #x11.xauthority = null
-        }
-        flags = [ ifexists nofail ]
-        condition = [ { module.x11.bell = true } ]
-    }
-    { name = libpipewire-module-jackdbus-detect
-        args = {
-            #jack.library     = libjack.so.0
-            #jack.server      = null
-            #jack.client-name = PipeWire
-            #jack.connect     = true
-            #tunnel.mode      = duplex  # source|sink|duplex
-            source.props = {
-                #audio.channels = 2
-               #midi.ports = 1
-                #audio.position = [ FL FR ]
-                # extra sink properties
-            }
-            sink.props = {
-                #audio.channels = 2
-               #midi.ports = 1
-                #audio.position = [ FL FR ]
-                # extra sink properties
-            }
-        }
-        flags = [ ifexists nofail ]
-        condition = [ { module.jackdbus-detect = true } ]
-    }
-]
-
-context.objects = [
-    #{ factory = <factory-name>
-    #    ( args  = { <key> = <value> ... } )
-    #    ( flags = [ ( nofail ) ] )
-    #    ( condition = [ { <key> = <value> ... } ... ] )
-    #}
-    #
-    # Creates an object from a PipeWire factory with the given parameters.
-    # If nofail is given, errors are ignored (and no object is created).
-    # If condition is given, the object is created only when the context properties
-    # all match the match rules.
-    #
-    #{ factory = spa-node-factory   args = { factory.name = videotestsrc node.name = videotestsrc node.description = videotestsrc "Spa:Pod:Object:Param:Props:patternType" = 1 } }
-    #{ factory = spa-device-factory args = { factory.name = api.jack.device foo=bar } flags = [ nofail ] }
-    #{ factory = spa-device-factory args = { factory.name = api.alsa.enum.udev } }
-    #{ factory = spa-node-factory   args = { factory.name = api.alsa.seq.bridge node.name = Internal-MIDI-Bridge } }
-    #{ factory = adapter            args = { factory.name = audiotestsrc node.name = my-test node.description = audiotestsrc } }
-    #{ factory = spa-node-factory   args = { factory.name = api.vulkan.compute.source node.name = my-compute-source } }
-
-    # A default dummy driver. This handles nodes marked with the "node.always-process"
-    # property when no other driver is currently active. JACK clients need this.
-    { factory = spa-node-factory
-        args = {
-            factory.name    = support.node.driver
-            node.name       = Dummy-Driver
-            node.group      = pipewire.dummy
-            priority.driver = 20000
-            #clock.id       = monotonic # realtime | tai | monotonic-raw | boottime
-            #clock.name     = "clock.system.monotonic"
-        }
-    }
-    { factory = spa-node-factory
-        args = {
-            factory.name    = support.node.driver
-            node.name       = Freewheel-Driver
-            priority.driver = 19000
-            node.group      = pipewire.freewheel
-            node.freewheel  = true
-        }
-    }
-
-    # This creates a new Source node. It will have input ports
-    # that you can link, to provide audio for this source.
-    #{ factory = adapter
-    #    args = {
-    #        factory.name     = support.null-audio-sink
-    #        node.name        = "my-mic"
-    #        node.description = "Microphone"
-    #        media.class      = "Audio/Source/Virtual"
-    #        audio.position   = "FL,FR"
-    #        monitor.passthrough = true
-    #    }
-    #}
-
-    # This creates a single PCM source device for the given
-    # alsa device path hw:0. You can change source to sink
-    # to make a sink in the same way.
-    #{ factory = adapter
-    #    args = {
-    #        factory.name           = api.alsa.pcm.source
-    #        node.name              = "alsa-source"
-    #        node.description       = "PCM Source"
-    #        media.class            = "Audio/Source"
-    #        api.alsa.path          = "hw:0"
-    #        api.alsa.period-size   = 1024
-    #        api.alsa.headroom      = 0
-    #        api.alsa.disable-mmap  = false
-    #        api.alsa.disable-batch = false
-    #        audio.format           = "S16LE"
-    #        audio.rate             = 48000
-    #        audio.channels         = 2
-    #        audio.position         = "FL,FR"
-    #    }
-    #}
-
-    # Use the metadata factory to create metadata and some default values.
-    #{ factory = metadata
-    #    args = {
-    #        metadata.name = my-metadata
-    #        metadata.values = [
-    #            { key = default.audio.sink   value = { name = somesink } }
-    #            { key = default.audio.source value = { name = somesource } }
-    #        ]
-    #    }
-    #}
-]
-
-context.exec = [
-    #{   path = <program-name>
-    #    ( args = "<arguments>" )
-    #    ( condition = [ { <key> = <value> ... } ... ] )
-    #}
-    #
-    # Execute the given program with arguments.
-    # If condition is given, the program is executed only when the context
-    # properties all match the match rules.
-    #
-    # You can optionally start the session manager here,
-    # but it is better to start it as a systemd service.
-    # Run the session manager with -h for options.
-    #
-    #{ path = "/usr/bin/pipewire-media-session" args = ""
-    #  condition = [ { exec.session-manager = null } { exec.session-manager = true } ] }
-    #
-    # You can optionally start the pulseaudio-server here as well
-    # but it is better to start it as a systemd service.
-    # It can be interesting to start another daemon here that listens
-    # on another address with the -a option (eg. -a tcp:4713).
-    #
-    #{ path = "/usr/bin/pipewire" args = "-c pipewire-pulse.conf"
-    #  condition = [ { exec.pipewire-pulse = null } { exec.pipewire-pulse = true } ] }
-]
+# found a rancom config suggestion of a file which is just:
+# context.properties = {
+#     default.clock.rate = 48000
+#     default.clock.quantum = 1024
+#     default.clock.min-quantum = 32
+#     default.clock.max-quantum = 2048
+# }
diff --git a/pkgs b/pkgs
index 782b00f28640c04e6dc0e2ce865db7801be97472..e6a182161cd84cd18557d005d0dac738297405f9 100644 (file)
--- a/pkgs
+++ b/pkgs
@@ -124,12 +124,14 @@ p3=(
   debootstrap
   debconf-doc
   devscripts
+  ddcutil
   dillo
   # using self-compiled version
   #digikam
   # used by digikam for icons
   breeze-icon-theme
   dirmngr
+  displaycal
   dos2unix
   dosfstools
   dnsutils
@@ -212,6 +214,7 @@ p3=(
   info
   inotify-tools
   ipcalc
+  iputils-arping
   iputils-tracepath
   iperf3
   iproute2-doc
@@ -221,11 +224,12 @@ p3=(
   kid3-cli
   konsole
   knot-dnsutils
+  # for skdump
+  libatasmart-bin
   libterm-readkey-perl
   libreoffice
   libreoffice-help-en-us
   linphone-desktop
-  linux-doc
   lshw
   make-doc
   manpages
@@ -307,6 +311,8 @@ p3=(
   software-properties-common
   speex
   sox
+  # used by emacs package sqlformat with the same name.
+  sqlfluff
   sqlite3-doc
   squashfs-tools
   strace
index 5df41490623b6d124417adaebb5dc6766b598f78..c5f528a0a6a296d56a7587e300cb80587572718b 100755 (executable)
@@ -80,6 +80,9 @@ fi
 if [[ $HOSTNAME == "$MAIL_HOST" || $HOSTNAME == kd ]]; then
   m systemctl --now enable btrbk.timer
 else
+  # ssh might have been disabled by mailtest-check.
+  m systemctl --now enable ssh.socket
+  m systemctl --no-block start ssh
   serm systemctl --now disable btrbk.timer
 fi
 
diff --git a/subdir_files/.config/mpv/input.conf b/subdir_files/.config/mpv/input.conf
deleted file mode 100644 (file)
index 9ce157d..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-a cycle_values video-rotate "90" "180" "270" "0"
-Alt+h add video-zoom 0.25
-Alt+g add video-zoom -0.25
-
-# Skip to the next file
-b playlist-next
-
-# Skip to the previous file
-c playlist-prev
-
-# default is 5 seconds and non-exact, often much longer jump.
-RIGHT seek  3 exact
-LEFT  seek -4 exact
diff --git a/subdir_files/.config/mpv/mpv.conf b/subdir_files/.config/mpv/mpv.conf
deleted file mode 100644 (file)
index fdb32f3..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-#loop=inf
-#pause
-#save-position-on-quit
-#loop-file=inf
-loop-file=no
-volume=50
-player-operation-mode=pseudo-gui
-replaygain=track
-
-# by default mpv does not hardware decode, but the distro package has
-# /etc/mpv/mpv.conf : hwdec=vaapi that makes android videos black and
-# white on one of my computers. vlc uses this library, which works on
-# that computer, so going with that for now. This config file overrides
-# the /etc one.
-hwdec=vdpau
-
-# like [l] but shuffled
-[k]
-loop-file=inf
-save-position-on-quit
-resume-playback
-shuffle
-
-# loop. saves playlist position. good for looking at gifs
-[l]
-loop-file=inf
-save-position-on-quit
-resume-playback
-#no-resume-playback
-#no-save-position-on-quit
-
-
-
-# use --profile d
-[d]
-loop-file=inf
-shuffle
-#vo=gpu
-no-resume-playback
-no-save-position-on-quit
-
-[s]
-shuffle
-
-# audio, especially with beetag
-[a]
-volume=100
-player-operation-mode=cplayer
-audio-display=no
-# dont display any tags
-display-tags=
-# this gets rid of lines like:
-# [ipc_398] Write error (Broken pipe)
-#
-# which I suspect are happening because we do 2 socats too fast. But it
-# never seems to cause any other problem, so whatever, just hide it.
-really-quiet
-
-# note, useful cli option:
-# --script-opts=osc-visibility=always
-#
-# gets rid of lines like:
-# (+) Audio --aid=1 (flac 2ch 44100Hz)
-# AO: [pulse] 44100Hz stereo 2ch s16
-# and
-# ffmpeg/demuxer] mp3: Estimating duration from bitrate, this may be inaccurate
-#
-# Found via man mpv and its suggestion to use
-# --msg-level=all=trace
-#
-# for supressing
-# "Audio device underrun detected." it requires all=error.
-
-msg-level=cplayer=warn,ffmpeg/demuxer=error
-
-input-ipc-server=/tmp/mpvsock
diff --git a/subdir_files/.config/mpv/scripts/playlistmanager.lua b/subdir_files/.config/mpv/scripts/playlistmanager.lua
deleted file mode 100644 (file)
index 3a3027c..0000000
+++ /dev/null
@@ -1,1706 +0,0 @@
--- iank wget https://raw.githubusercontent.com/jonniek/mpv-playlistmanager/16e18949e3d604c2ffe43e95391f420227881139/playlistmanager.lua
-local settings = {
-  --navigation keybindings force override only while playlist is visible
-  --if "no" then you can display the playlist by any of the navigation keys
-  dynamic_binds = true,
-
-  -- to bind multiple keys separate them by a space
-
-  -- main key to show playlist
-  key_showplaylist = "SHIFT+ENTER",
-
-  -- display playlist while key is held down
-  key_peek_at_playlist = "",
-
-  -- dynamic keys
-  key_moveup = "UP",
-  key_movedown = "DOWN",
-  key_movepageup = "PGUP",
-  key_movepagedown = "PGDWN",
-  key_movebegin = "HOME",
-  key_moveend = "END",
-  key_selectfile = "RIGHT LEFT",
-  key_unselectfile = "",
-  key_playfile = "ENTER",
-  key_removefile = "BS",
-  key_closeplaylist = "ESC SHIFT+ENTER",
-
-  -- extra functionality keys
-  key_sortplaylist = "",
-  key_shuffleplaylist = "r",
-  key_reverseplaylist = "",
-  key_loadfiles = "",
-  key_saveplaylist = "",
-
-  --replaces matches on filenames based on extension, put as empty string to not replace anything
-  --replace rules are executed in provided order
-  --replace rule key is the pattern and value is the replace value
-  --uses :gsub('pattern', 'replace'), read more http://lua-users.org/wiki/StringLibraryTutorial
-  --'all' will match any extension or protocol if it has one
-  --uses json and parses it into a lua table to be able to support .conf file
-
-  filename_replace = [[
-    [
-      {
-        "protocol": { "all": true },
-        "rules": [
-          { "%%(%x%x)": "hex_to_char" }
-        ]
-      }
-    ]
-  ]],
-
---[=====[ START OF SAMPLE REPLACE - Remove this line to use it
-  --Sample replace: replaces underscore to space on all files
-  --for mp4 and webm; remove extension, remove brackets and surrounding whitespace, change dot between alphanumeric to space
-  filename_replace = [[
-    [
-      {
-        "ext": { "all": true},
-        "rules": [
-          { "_" : " " }
-        ]
-      },{
-        "ext": { "mp4": true, "mkv": true },
-        "rules": [
-          { "^(.+)%..+$": "%1" },
-          { "%s*[%[%(].-[%]%)]%s*": "" },
-          { "(%w)%.(%w)": "%1 %2" }
-        ]
-      },{
-        "protocol": { "http": true, "https": true },
-        "rules": [
-          { "^%a+://w*%.?": "" }
-        ]
-      }
-    ]
-  ]],
---END OF SAMPLE REPLACE ]=====]
-
-  --json array of filetypes to search from directory
-  loadfiles_filetypes = [[
-    [
-      "jpg", "jpeg", "png", "tif", "tiff", "gif", "webp", "svg", "bmp",
-      "mp3", "wav", "ogm", "flac", "m4a", "wma", "ogg", "opus",
-      "mkv", "avi", "mp4", "ogv", "webm", "rmvb", "flv", "wmv", "mpeg", "mpg", "m4v", "3gp"
-    ]
-  ]],
-
-  --loadfiles at startup if 1 or more items in playlist
-  loadfiles_on_start = false,
-  -- loadfiles from working directory on idle startup
-  loadfiles_on_idle_start = false,
-  --always put loaded files after currently playing file
-  loadfiles_always_append = false,
-
-  --sort playlist when files are added to playlist
-  sortplaylist_on_file_add = false,
-
-  --default sorting method, must be one of: "name-asc", "name-desc", "date-asc", "date-desc", "size-asc", "size-desc".
-  default_sort = "name-asc",
-
-  --"linux | windows | auto"
-  system = "auto",
-
-  --Use ~ for home directory. Leave as empty to use mpv/playlists
-  playlist_savepath = "",
-
-  -- constant filename to save playlist as. Note that it will override existing playlist. Leave empty for generated name.
-  playlist_save_filename = "",
-
-  --save playlist automatically after current file was unloaded
-  save_playlist_on_file_end = false,
-
-
-  --show file title every time a new file is loaded
-  show_title_on_file_load = false,
-  --show playlist every time a new file is loaded
-  show_playlist_on_file_load = false,
-  --close playlist when selecting file to play
-  close_playlist_on_playfile = false,
-
-  --sync cursor when file is loaded from outside reasons(file-ending, playlist-next shortcut etc.)
-  --has the sideeffect of moving cursor if file happens to change when navigating
-  --good side is cursor always following current file when going back and forth files with playlist-next/prev
-  sync_cursor_on_load = true,
-
-  --allow the playlist cursor to loop from end to start and vice versa
-  loop_cursor = true,
-
-
-  -- allow playlistmanager to write watch later config when navigating between files
-  allow_write_watch_later_config = true,
-
-  -- reset cursor navigation when closing or opening playlist
-  reset_cursor_on_close = true,
-  reset_cursor_on_open = true,
-
-  --prefer to display titles for following files: "all", "url", "none". Sorting still uses filename.
-  prefer_titles = "url",
-
-  --youtube-dl executable for title resolving if enabled, probably "youtube-dl" or "yt-dlp", can be absolute path
-  youtube_dl_executable = "yt-dlp",
-
-  --call youtube-dl to resolve the titles of urls in the playlist
-  resolve_url_titles = false,
-
-  --call ffprobe to resolve the titles of local files in the playlist (if they exist in the metadata)
-  resolve_local_titles = false,
-
-  -- timeout in seconds for url title resolving
-  resolve_title_timeout = 15,
-
-  -- how many url titles can be resolved at a time. Higher number might lead to stutters.
-  concurrent_title_resolve_limit = 10,
-
-  --osd timeout on inactivity in seconds, use 0 for no timeout
-  playlist_display_timeout = 0,
-
-  -- when peeking at playlist, show playlist at the very least for display timeout
-  peek_respect_display_timeout = false,
-
-  -- the maximum amount of lines playlist will render. -1 will automatically calculate lines.
-  showamount = -1,
-
-  --playlist ass style overrides inside curly brackets, \keyvalue is one field, extra \ for escape in lua
-  --example {\\q2\\an7\\fnUbuntu\\fs10\\b0\\bord1} equals: line-wrap=no, align=top left, font=Ubuntu, size=10, bold=no, border=1
-  --read http://docs.aegisub.org/3.2/ASS_Tags/ for reference of tags
-  --undeclared tags will use default osd settings
-  --these styles will be used for the whole playlist
-  --\\q2 style is recommended since filename wrapping may lead to unexpected rendering
-  --\\an7 style is recommended to align to top left otherwise, osd-align-x/y is respected
-  style_ass_tags = "{\\q2\\an7}",
-  --paddings for left right and top bottom
-  text_padding_x = 30,
-  text_padding_y = 60,
-
-  --screen dim when menu is open 0.0 - 1.0 (0 is no dim, 1 is black)
-  curtain_opacity=0.0,
-
-  --set title of window with stripped name
-  set_title_stripped = false,
-  title_prefix = "",
-  title_suffix = " - mpv",
-
-  --slice long filenames, and how many chars to show
-  slice_longfilenames = false,
-  slice_longfilenames_amount = 70,
-
-  --Playlist header template
-  --%mediatitle or %filename = title or name of playing file
-  --%pos = position of playing file
-  --%cursor = position of navigation
-  --%plen = playlist length
-  --%N = newline
-  playlist_header = "[%cursor/%plen]",
-
-  --Playlist file templates
-  --%pos = position of file with leading zeros
-  --%name = title or name of file
-  --%N = newline
-  --you can also use the ass tags mentioned above. For example:
-  --  selected_file="{\\c&HFF00FF&}➔ %name"   | to add a color for selected file. However, if you
-  --  use ass tags you need to reset them for every line (see https://github.com/jonniek/mpv-playlistmanager/issues/20)
-  normal_file = "○ %name",
-  hovered_file = "● %name",
-  selected_file = "➔ %name",
-  playing_file = "▷ %name",
-  playing_hovered_file = "▶ %name",
-  playing_selected_file = "➤ %name",
-
-
-  -- what to show when playlist is truncated
-  playlist_sliced_prefix = "...",
-  playlist_sliced_suffix = "...",
-
-  --output visual feedback to OSD for tasks
-  display_osd_feedback = true,
-}
-local opts = require("mp.options")
-opts.read_options(settings, "playlistmanager", function(list) update_opts(list) end)
-
-local utils = require("mp.utils")
-local msg = require("mp.msg")
-local assdraw = require("mp.assdraw")
-
-local alignment_table = {
-    [1] = { ["x"] = "left",   ["y"] = "bottom" },
-    [2] = { ["x"] = "center", ["y"] = "bottom" },
-    [3] = { ["x"] = "right",  ["y"] = "bottom" },
-    [4] = { ["x"] = "left",   ["y"] = "center" },
-    [5] = { ["x"] = "center", ["y"] = "center" },
-    [6] = { ["x"] = "right",  ["y"] = "center" },
-    [7] = { ["x"] = "left",   ["y"] = "top" },
-    [8] = { ["x"] = "center", ["y"] = "top" },
-    [9] = { ["x"] = "right",  ["y"] = "top" },
-}
-
---check os
-if settings.system=="auto" then
-  local o = {}
-  if mp.get_property_native('options/vo-mmcss-profile', o) ~= o then
-    settings.system = "windows"
-  else
-    settings.system = "linux"
-  end
-end
-
--- auto calculate showamount
-if settings.showamount == -1 then
-  -- same as draw_playlist() height
-  local h = 720
-
-  local playlist_h = h
-  -- both top and bottom with same padding
-  playlist_h = playlist_h - settings.text_padding_y * 2
-
-  -- osd-font-size is based on 720p height
-  -- see https://mpv.io/manual/stable/#options-osd-font-size
-  -- details in https://mpv.io/manual/stable/#options-sub-font-size
-  -- draw_playlist() is based on 720p, need some conversion
-  local fs = mp.get_property_native('osd-font-size') * h / 720
-  -- get the ass font size
-  if settings.style_ass_tags ~= nil then
-    local ass_fs_tag = settings.style_ass_tags:match('\\fs%d+')
-    if ass_fs_tag ~= nil then
-      fs = tonumber(ass_fs_tag:match('%d+'))
-    end
-  end
-
-  settings.showamount = math.floor(playlist_h / fs)
-
-  -- exclude the header line
-  if settings.playlist_header ~= "" then
-    settings.showamount = settings.showamount - 1
-    -- probably some newlines (%N or \N) in the header
-    for _ in settings.playlist_header:gmatch('%%N') do
-      settings.showamount = settings.showamount - 1
-    end
-    for _ in settings.playlist_header:gmatch('\\N') do
-      settings.showamount = settings.showamount - 1
-    end
-  end
-
-  msg.info('auto showamount: ' .. settings.showamount)
-end
-
---global variables
-local playlist_overlay = mp.create_osd_overlay("ass-events")
-local playlist_visible = false
-local strippedname = nil
-local path = nil
-local directory = nil
-local filename = nil
-local pos = 0
-local plen = 0
-local cursor = 0
---table for saved media titles for later if we prefer them
-local title_table = {}
--- table for urls and local file paths that we have requested to be resolved to titles
-local requested_titles = {}
-
-local filetype_lookup = {}
-
-function refresh_UI()
-  if not playlist_visible then return end
-  refresh_globals()
-  if plen == 0 then return end
-  draw_playlist()
-end
-
-function update_opts(changelog)
-  msg.verbose('updating options')
-
-  --parse filename json
-  if changelog.filename_replace then
-    if(settings.filename_replace~="") then
-      settings.filename_replace = utils.parse_json(settings.filename_replace)
-    else
-      settings.filename_replace = false
-    end
-  end
-
-  --parse loadfiles json
-  if changelog.loadfiles_filetypes then
-    settings.loadfiles_filetypes = utils.parse_json(settings.loadfiles_filetypes)
-
-    filetype_lookup = {}
-    --create loadfiles set
-    for _, ext in ipairs(settings.loadfiles_filetypes) do
-      filetype_lookup[ext] = true
-    end
-  end
-
-  if changelog.resolve_url_titles then
-    resolve_titles()
-  end
-
-  if changelog.resolve_local_titles then
-    resolve_titles()
-  end
-
-  if changelog.playlist_display_timeout then
-    keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
-    keybindstimer:kill()
-  end
-
-  refresh_UI()
-end
-
-update_opts({filename_replace = true, loadfiles_filetypes = true})
-
------ winapi start -----
--- in windows system, we can use the sorting function provided by the win32 API
--- see https://learn.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strcmplogicalw
-local winapisort = nil
-if settings.system == "windows" then
-  -- ffiok is false usually means the mpv builds without luajit
-  local ffiok, ffi = pcall(require, "ffi")
-  if ffiok then
-    ffi.cdef[[
-      int MultiByteToWideChar(unsigned int CodePage, unsigned long dwFlags, const char *lpMultiByteStr, int cbMultiByte, wchar_t *lpWideCharStr, int cchWideChar);
-      int StrCmpLogicalW(const wchar_t * psz1, const wchar_t * psz2);
-    ]]
-
-    local shlwapi = ffi.load("shlwapi.dll")
-
-    function MultiByteToWideChar(MultiByteStr)
-      local UTF8_CODEPAGE = 65001
-      if MultiByteStr then
-        local utf16_len = ffi.C.MultiByteToWideChar(UTF8_CODEPAGE, 0, MultiByteStr, -1, nil, 0)
-        if utf16_len > 0 then
-          local utf16_str = ffi.new("wchar_t[?]", utf16_len)
-          if ffi.C.MultiByteToWideChar(UTF8_CODEPAGE, 0, MultiByteStr, -1, utf16_str, utf16_len) > 0 then
-            return utf16_str
-          end
-        end
-      end
-      return ""
-    end
-
-    winapisort = function (a, b)
-      return shlwapi.StrCmpLogicalW(MultiByteToWideChar(a), MultiByteToWideChar(b)) < 0
-    end
-
-  end
-end
------ winapi end -----
-
-local sort_modes = {
-  {
-    id="name-asc",
-    title="name ascending",
-    sort_fn=function (a, b, playlist)
-      if winapisort ~= nil then
-        return winapisort(playlist[a].string, playlist[b].string)
-      end
-      return alphanumsort(playlist[a].string, playlist[b].string)
-    end,
-  },
-  {
-    id="name-desc",
-    title="name descending",
-    sort_fn=function (a, b, playlist)
-      if winapisort ~= nil then
-        return winapisort(playlist[b].string, playlist[a].string)
-      end
-      return alphanumsort(playlist[b].string, playlist[a].string)
-    end,
-  },
-  {
-    id="date-asc",
-    title="date ascending",
-    sort_fn=function (a, b)
-      return (get_file_info(a).mtime or 0) < (get_file_info(b).mtime or 0)
-    end,
-  },
-  {
-    id="date-desc",
-    title="date descending",
-    sort_fn=function (a, b)
-      return (get_file_info(a).mtime or 0) > (get_file_info(b).mtime or 0)
-    end,
-  },
-  {
-    id="size-asc",
-    title="size ascending",
-    sort_fn=function (a, b)
-      return (get_file_info(a).size or 0) < (get_file_info(b).size or 0)
-    end,
-  },
-  {
-    id="size-desc",
-    title="size descending",
-    sort_fn=function (a, b)
-      return (get_file_info(a).size or 0) > (get_file_info(b).size or 0)
-    end,
-  },
-}
-
-local sort_mode = 1
-for mode, sort_data in pairs(sort_modes) do
-  if sort_data.id == settings.default_sort then
-    sort_mode = mode
-  end
-end
-
-function is_protocol(path)
-  return type(path) == 'string' and path:match('^%a[%a%d-_]+://') ~= nil
-end
-
-function on_file_loaded()
-  refresh_globals()
-  if settings.sync_cursor_on_load then cursor=pos end
-  refresh_UI() -- refresh only after moving cursor
-
-  filename = mp.get_property("filename")
-  path = mp.get_property('path')
-  local media_title = mp.get_property("media-title")
-  if is_protocol(path) and not title_table[path] and path ~= media_title then
-    title_table[path] = media_title
-  end
-
-  strippedname = stripfilename(mp.get_property('media-title'))
-  if settings.show_title_on_file_load then
-    mp.commandv('show-text', strippedname)
-  end
-  if settings.show_playlist_on_file_load then
-    showplaylist()
-  end
-  if settings.set_title_stripped then
-    mp.set_property("title", settings.title_prefix..strippedname..settings.title_suffix)
-  end
-end
-
-function on_start_file()
-  refresh_globals()
-  filename = mp.get_property("filename")
-  path = mp.get_property('path')
-  --if not a url then join path with working directory
-  if not is_protocol(path) then
-    path = utils.join_path(mp.get_property('working-directory'), path)
-    directory = utils.split_path(path)
-  else
-    directory = nil
-  end
-
-  if settings.loadfiles_on_start and plen == 1 then
-    local ext = filename:match("%.([^%.]+)$")
-    -- a directory or playlist has been loaded, let's not do anything as mpv will expand it into files
-    if ext and filetype_lookup[ext:lower()] then
-      msg.info("Loading files from playing files directory")
-      playlist()
-    end
-  end
-end
-
-function on_end_file()
-  if settings.save_playlist_on_file_end then save_playlist() end
-  strippedname = nil
-  path = nil
-  directory = nil
-  filename = nil
-end
-
-function refresh_globals()
-  pos = mp.get_property_number('playlist-pos', 0)
-  plen = mp.get_property_number('playlist-count', 0)
-end
-
-function escapepath(dir, escapechar)
-  return string.gsub(dir, escapechar, '\\'..escapechar)
-end
-
-function replace_table_has_value(value, valid_values)
-  if value == nil or valid_values == nil then
-    return false
-  end
-  return valid_values['all'] or valid_values[value]
-end
-
-local filename_replace_functions = {
-  --decode special characters in url
-  hex_to_char = function(x) return string.char(tonumber(x, 16)) end
-}
-
--- from http://lua-users.org/wiki/LuaUnicode
-local UTF8_PATTERN = '[%z\1-\127\194-\244][\128-\191]*'
-
--- return a substring based on utf8 characters
--- like string.sub, but negative index is not supported
-local function utf8_sub(s, i, j)
-  if i > j then
-    return s
-  end
-
-  local t = {}
-  local idx = 1
-  for char in s:gmatch(UTF8_PATTERN) do
-    if i <= idx and idx <= j then
-      local width = #char > 2 and 2 or 1
-      idx = idx + width
-      t[#t + 1] = char
-    end
-  end
-  return table.concat(t)
-end
-
---strip a filename based on its extension or protocol according to rules in settings
-function stripfilename(pathfile, media_title)
-  if pathfile == nil then return '' end
-  local ext = pathfile:match("%.([^%.]+)$")
-  local protocol = pathfile:match("^(%a%a+)://")
-  if not ext then ext = "" end
-  local tmp = pathfile
-  if settings.filename_replace and not media_title then
-    for k,v in ipairs(settings.filename_replace) do
-      if replace_table_has_value(ext, v['ext']) or replace_table_has_value(protocol, v['protocol']) then
-        for ruleindex, indexrules in ipairs(v['rules']) do
-          for rule, override in pairs(indexrules) do
-            override = filename_replace_functions[override] or override
-            tmp = tmp:gsub(rule, override)
-          end
-        end
-      end
-    end
-  end
-  local tmp_clip = utf8_sub(tmp, 1, settings.slice_longfilenames_amount)
-  if settings.slice_longfilenames and tmp ~= tmp_clip then
-    tmp = tmp_clip .. "..."
-  end
-  return tmp
-end
-
---gets the file info of an item
-function get_file_info(item)
-  local path = mp.get_property('playlist/' .. item - 1 .. '/filename')
-  if is_protocol(path) then return {} end
-  local file_info = utils.file_info(path)
-  if not file_info then
-    msg.warn('failed to read file info for', path)
-    return {}
-  end
-
-  return file_info
-end
-
---gets a nicename of playlist entry at 0-based position i
-function get_name_from_index(i, notitle)
-  refresh_globals()
-  if plen <= i then msg.error("no index in playlist", i, "length", plen); return nil end
-  local _, name = nil
-  local title = mp.get_property('playlist/'..i..'/title')
-  local name = mp.get_property('playlist/'..i..'/filename')
-
-  local should_use_title = settings.prefer_titles == 'all' or is_protocol(name) and settings.prefer_titles == 'url'
-
-  --check if file has a media title stored
-  if not title and should_use_title and title_table[name] then
-    title = title_table[name]
-  end
-
-  --if we have media title use a more conservative strip
-  if title and not notitle and should_use_title then
-    -- Escape a string for verbatim display on the OSD
-    -- Ref: https://github.com/mpv-player/mpv/blob/94677723624fb84756e65c8f1377956667244bc9/player/lua/stats.lua#L145
-    return stripfilename(title, true):gsub("\\", '\\\239\187\191'):gsub("{", "\\{"):gsub("^ ", "\\h")
-  end
-
-  --remove paths if they exist, keeping protocols for stripping
-  if string.sub(name, 1, 1) == '/' or name:match("^%a:[/\\]") then
-    _, name = utils.split_path(name)
-  end
-  return stripfilename(name):gsub("\\", '\\\239\187\191'):gsub("{", "\\{"):gsub("^ ", "\\h")
-end
-
-function parse_header(string)
-  local esc_title = stripfilename(mp.get_property("media-title"), true):gsub("%%", "%%%%")
-  local esc_file = stripfilename(mp.get_property("filename")):gsub("%%", "%%%%")
-  return string:gsub("%%N", "\\N")
-               -- add a blank character at the end of each '\N'  to ensure that the height of the empty line is the same as the non empty line
-               :gsub("\\N", "\\N ")
-               :gsub("%%pos", mp.get_property_number("playlist-pos",0)+1)
-               :gsub("%%plen", mp.get_property("playlist-count"))
-               :gsub("%%cursor", cursor+1)
-               :gsub("%%mediatitle", esc_title)
-               :gsub("%%filename", esc_file)
-               -- undo name escape
-               :gsub("%%%%", "%%")
-end
-
-function parse_filename(string, name, index)
-  local base = tostring(plen):len()
-  local esc_name = stripfilename(name):gsub("%%", "%%%%")
-  return string:gsub("%%N", "\\N")
-               :gsub("%%pos", string.format("%0"..base.."d", index+1))
-               :gsub("%%name", esc_name)
-               -- undo name escape
-               :gsub("%%%%", "%%")
-end
-
-function parse_filename_by_index(index)
-  local template = settings.normal_file
-
-  local is_idle = mp.get_property_native('idle-active')
-  local position = is_idle and -1 or pos
-
-  if index == position then
-    if index == cursor then
-      if selection then
-        template = settings.playing_selected_file
-      else
-        template = settings.playing_hovered_file
-      end
-    else
-      template = settings.playing_file
-    end
-  elseif index == cursor then
-    if selection then
-      template = settings.selected_file
-    else
-      template = settings.hovered_file
-    end
-  end
-
-  return parse_filename(template, get_name_from_index(index), index)
-end
-
-function is_terminal_mode()
-  local width, height, aspect_ratio = mp.get_osd_size()
-  return width == 0 and height == 0 and aspect_ratio == 0
-end
-
-function draw_playlist()
-  refresh_globals()
-
-  -- if there is no playing file, then cursor can be -1. That would break rendering of playlist.
-  if cursor == -1 then
-    cursor = 0
-  end
-
-  local ass = assdraw.ass_new()
-  local terminaloutput = ""
-
-  local _, _, a = mp.get_osd_size()
-  local h = 720
-  local w = math.ceil(h * a)
-
-  if settings.curtain_opacity ~= nil and settings.curtain_opacity ~= 0 and settings.curtain_opacity <= 1.0 then
-  -- curtain dim from https://github.com/christoph-heinrich/mpv-quality-menu/blob/501794bfbef468ee6a61e54fc8821fe5cd72c4ed/quality-menu.lua#L699-L707
-    local alpha = 255 - math.ceil(255 * settings.curtain_opacity)
-    ass.text = string.format('{\\pos(0,0)\\rDefault\\an7\\1c&H000000&\\alpha&H%X&}', alpha)
-    ass:draw_start()
-    ass:rect_cw(0, 0, w, h)
-    ass:draw_stop()
-    ass:new_event()
-  end
-
-  ass:append(settings.style_ass_tags)
-
-  -- add \clip style
-  -- make both left and right follow text_padding_x
-  --      both top and bottom follow text_padding_y
-  local border_size = mp.get_property_number('osd-border-size')
-  if settings.style_ass_tags ~= nil then
-    local bord = tonumber(settings.style_ass_tags:match('\\bord(%d+%.?%d*)'))
-    if bord ~= nil then border_size = bord end
-  end
-  ass:append(string.format('{\\clip(%f,%f,%f,%f)}',
-    settings.text_padding_x - border_size,         settings.text_padding_y - border_size,
-    w - 1 - settings.text_padding_x + border_size, h - 1 - settings.text_padding_y + border_size))
-
-  -- align from mpv.conf
-  local align_x = mp.get_property("osd-align-x")
-  local align_y = mp.get_property("osd-align-y")
-  -- align from style_ass_tags
-  if settings.style_ass_tags ~= nil then
-    local an = tonumber(settings.style_ass_tags:match('\\an(%d)'))
-    if an ~= nil and alignment_table[an] ~= nil then
-      align_x = alignment_table[an]["x"]
-      align_y = alignment_table[an]["y"]
-    end
-  end
-  -- range of x [0, w-1]
-  local pos_x
-  if align_x == 'left' then
-    pos_x = settings.text_padding_x
-  elseif align_x == 'right' then
-    pos_x = w - 1 - settings.text_padding_x
-  else
-    pos_x = math.floor((w - 1) / 2)
-  end
-  -- range of y [0, h-1]
-  local pos_y
-  if align_y == 'top' then
-    pos_y = settings.text_padding_y
-  elseif align_y == 'bottom' then
-    pos_y = h - 1 - settings.text_padding_y
-  else
-    pos_y = math.floor((h - 1) / 2)
-  end
-  ass:pos(pos_x, pos_y)
-
-  if settings.playlist_header ~= "" then
-    local header = parse_header(settings.playlist_header)
-    ass:append(header.."\\N")
-    terminaloutput = terminaloutput..header.."\n"
-  end
-
-  -- (visible index, playlist index) pairs of playlist entries that should be rendered
-  local visible_indices = {}
-
-  local one_based_cursor = cursor + 1
-  table.insert(visible_indices, one_based_cursor)
-
-  local offset = 1;
-  local visible_indices_length = 1;
-  while visible_indices_length < settings.showamount and visible_indices_length < plen do
-    -- add entry for offset steps below the cursor
-    local below = one_based_cursor + offset
-    if below <= plen then
-      table.insert(visible_indices, below)
-      visible_indices_length = visible_indices_length + 1;
-    end
-
-    -- add entry for offset steps above the cursor
-    -- also need to double check that there is still space, this happens if we have even numbered limit
-    local above = one_based_cursor - offset
-    if above >= 1 and visible_indices_length < settings.showamount and visible_indices_length < plen then
-      table.insert(visible_indices, 1, above)
-      visible_indices_length = visible_indices_length + 1;
-    end
-
-    offset = offset + 1
-  end
-
-  -- both indices are 1 based
-  for display_index, playlist_index in pairs(visible_indices) do
-    if display_index == 1 and playlist_index ~= 1 then
-      ass:append(settings.playlist_sliced_prefix.."\\N")
-      terminaloutput = terminaloutput..settings.playlist_sliced_prefix.."\n"
-    elseif display_index == settings.showamount and playlist_index ~= plen then
-      ass:append(settings.playlist_sliced_suffix)
-      terminaloutput = terminaloutput..settings.playlist_sliced_suffix.."\n"
-    else
-      -- parse_filename_by_index expects 0 based index
-      local fname = parse_filename_by_index(playlist_index - 1)
-      ass:append(fname.."\\N")
-      terminaloutput = terminaloutput..fname.."\n"
-    end
-  end
-
-  if is_terminal_mode() then
-    local timeout_setting = settings.playlist_display_timeout
-    local timeout = timeout_setting == 0 and 2147483 or timeout_setting
-    -- TODO: probably have to strip ass tags from terminal output
-    -- would maybe be possible to use terminal color output instead
-    mp.osd_message(terminaloutput, timeout)
-  else
-    playlist_overlay.data = ass.text
-    playlist_overlay:update()
-  end
-end
-
-local peek_display_timer = nil
-local peek_button_pressed = false
-
-function peek_timeout()
-  peek_display_timer:kill()
-  if not peek_button_pressed and not playlist_visible then
-    remove_keybinds()
-  end
-end
-
-function handle_complex_playlist_toggle(table)
-  local event = table["event"]
-  if event == "press" then
-    msg.error("Complex key event not supported. Falling back to normal playlist display.")
-    showplaylist()
-  elseif event == "down" then
-    showplaylist(1000000)
-    if settings.peek_respect_display_timeout then
-      peek_button_pressed = true
-      peek_display_timer = mp.add_periodic_timer(settings.playlist_display_timeout, peek_timeout)
-    end
-  elseif event == "up" then
-    -- set playlist state to not visible, doesn't actually hide playlist yet
-    -- this will allow us to check if other functionality has rendered playlist before removing binds
-    playlist_visible = false
-
-    function remove_keybinds_after_timeout()
-      -- if playlist is still not visible then lets actually hide it
-      -- this lets other keys that interupt the peek to render playlist without peek up event closing it
-      if not playlist_visible then
-        remove_keybinds()
-      end
-    end
-
-    if settings.peek_respect_display_timeout then
-      peek_button_pressed = false
-      if not peek_display_timer:is_enabled() then
-        mp.add_timeout(0.01, remove_keybinds_after_timeout)
-      end
-    else
-      -- use small delay to let dynamic binds run before keys are potentially unbound
-      mp.add_timeout(0.01, remove_keybinds_after_timeout)
-    end
-  end
-end
-
-function toggle_playlist(show_function)
-  local show = show_function or showplaylist
-  if playlist_visible then
-    remove_keybinds()
-  else
-    -- toggle always shows without timeout
-    show(0)
-  end
-end
-
-function showplaylist(duration)
-  refresh_globals()
-  if plen == 0 then return end
-  if not playlist_visible and settings.reset_cursor_on_open then
-    resetcursor()
-  end
-
-  playlist_visible = true
-  add_keybinds()
-
-  draw_playlist()
-  keybindstimer:kill()
-
-  local dur = tonumber(duration) or settings.playlist_display_timeout
-  if dur > 0 then
-    keybindstimer = mp.add_periodic_timer(dur, remove_keybinds)
-  end
-end
-
-function showplaylist_non_interactive(duration)
-  refresh_globals()
-  if plen == 0 then return end
-  if not playlist_visible and settings.reset_cursor_on_open then
-    resetcursor()
-  end
-  playlist_visible = true
-  draw_playlist()
-  keybindstimer:kill()
-
-  local dur = tonumber(duration) or settings.playlist_display_timeout
-  if dur > 0 then
-    keybindstimer = mp.add_periodic_timer(dur, remove_keybinds)
-  end
-end
-
-selection=nil
-function selectfile()
-  refresh_globals()
-  if plen == 0 then return end
-  if not selection then
-    selection=cursor
-  else
-    selection=nil
-  end
-  showplaylist()
-end
-
-function unselectfile()
-  selection=nil
-  showplaylist()
-end
-
-function resetcursor()
-  selection = nil
-  cursor = mp.get_property_number('playlist-pos', 1)
-end
-
-function removefile()
-  refresh_globals()
-  if plen == 0 then return end
-  selection = nil
-  if cursor==pos then mp.command("script-message unseenplaylist mark true \"playlistmanager avoid conflict when removing file\"") end
-  mp.commandv("playlist-remove", cursor)
-  if cursor==plen-1 then cursor = cursor - 1 end
-  if plen == 1 then
-    remove_keybinds()
-  else
-    showplaylist()
-  end
-end
-
-function moveup()
-  refresh_globals()
-  if plen == 0 then return end
-  if cursor~=0 then
-    if selection then mp.commandv("playlist-move", cursor,cursor-1) end
-    cursor = cursor-1
-  elseif settings.loop_cursor then
-    if selection then mp.commandv("playlist-move", cursor,plen) end
-    cursor = plen-1
-  end
-  showplaylist()
-end
-
-function movedown()
-  refresh_globals()
-  if plen == 0 then return end
-  if cursor ~= plen-1 then
-    if selection then mp.commandv("playlist-move", cursor,cursor+2) end
-    cursor = cursor + 1
-  elseif settings.loop_cursor then
-    if selection then mp.commandv("playlist-move", cursor,0) end
-    cursor = 0
-  end
-  showplaylist()
-end
-
-
-function movepageup()
-  refresh_globals()
-  if plen == 0 or cursor == 0 then return end
-  local offset = settings.showamount % 2 == 0 and 1 or 0
-  local last_file_that_doesnt_scroll = math.ceil(settings.showamount / 2)
-  local reverse_cursor = plen - cursor
-  local files_to_jump = math.max(last_file_that_doesnt_scroll + offset - reverse_cursor, 0) + settings.showamount - 2
-  local prev_cursor = cursor
-  cursor = cursor - files_to_jump
-  if cursor < last_file_that_doesnt_scroll then
-    cursor = 0
-  end
-  if selection then
-    mp.commandv("playlist-move", prev_cursor, cursor)
-  end
-  showplaylist()
-end
-
-function movepagedown()
-  refresh_globals()
-  if plen == 0 or cursor == plen - 1 then return end
-  local last_file_that_doesnt_scroll = math.ceil(settings.showamount / 2) - 1
-  local files_to_jump = math.max(last_file_that_doesnt_scroll - cursor, 0) + settings.showamount - 2
-  local prev_cursor = cursor
-  cursor = cursor + files_to_jump
-
-  local cursor_on_last_page = plen - (settings.showamount - 3)
-  if cursor > cursor_on_last_page then
-    cursor = plen - 1
-  end
-  if selection then
-    mp.commandv("playlist-move", prev_cursor, cursor + 1)
-  end
-  showplaylist()
-end
-
-
-function movebegin()
-  refresh_globals()
-  if plen == 0 or cursor == 0 then return end
-  local prev_cursor = cursor
-  cursor = 0
-  if selection then mp.commandv("playlist-move", prev_cursor, cursor) end
-  showplaylist()
-end
-
-function moveend()
-  refresh_globals()
-  if plen == 0 or cursor == plen-1 then return end
-  local prev_cursor = cursor
-  cursor = plen-1
-  if selection then mp.commandv("playlist-move", prev_cursor, cursor+1) end
-  showplaylist()
-end
-
-function write_watch_later(force_write)
-  if settings.allow_write_watch_later_config then
-    if mp.get_property_bool("save-position-on-quit") or force_write then
-      mp.command("write-watch-later-config")
-    end
-  end
-end
-
-function playlist_next()
-  write_watch_later(true)
-  mp.commandv("playlist-next", "weak")
-  if settings.close_playlist_on_playfile then
-    remove_keybinds()
-  end
-  refresh_UI()
-end
-
-function playlist_prev()
-  write_watch_later(true)
-  mp.commandv("playlist-prev", "weak")
-  if settings.close_playlist_on_playfile then
-    remove_keybinds()
-  end
-  refresh_UI()
-end
-
-function playlist_random()
-  write_watch_later()
-  refresh_globals()
-  if plen < 2 then return end
-  math.randomseed(os.time())
-  local random = pos
-  while random == pos do
-    random = math.random(0, plen-1)
-  end
-  mp.set_property("playlist-pos", random)
-  if settings.close_playlist_on_playfile then
-    remove_keybinds()
-  end
-end
-
-function playfile()
-  refresh_globals()
-  if plen == 0 then return end
-  selection = nil
-  local is_idle = mp.get_property_native('idle-active')
-  if cursor ~= pos or is_idle then
-    write_watch_later()
-    mp.set_property("playlist-pos", cursor)
-  else
-    if cursor~=plen-1 then
-      cursor = cursor + 1
-    end
-    write_watch_later()
-    mp.commandv("playlist-next", "weak")
-  end
-  if settings.close_playlist_on_playfile then
-    remove_keybinds()
-  elseif playlist_visible then
-    showplaylist()
-  end
-end
-
-function file_filter(filenames)
-    local files = {}
-    for i = 1, #filenames do
-        local file = filenames[i]
-        local ext = file:match('%.([^%.]+)$')
-        if ext and filetype_lookup[ext:lower()] then
-            table.insert(files, file)
-        end
-    end
-    return files
-end
-
-function get_playlist_filenames_set()
-  local filenames = {}
-  for n=0,plen-1,1 do
-    local filename = mp.get_property('playlist/'..n..'/filename')
-    local _, file = utils.split_path(filename)
-    filenames[file] = true
-  end
-  return filenames
-end
-
---Creates a playlist of all files in directory, will keep the order and position
---For exaple, Folder has 12 files, you open the 5th file and run this, the remaining 7 are added behind the 5th file and prior 4 files before it
-function playlist(force_dir)
-  refresh_globals()
-  if not directory and plen > 0 then return end
-  local hasfile = true
-  if plen == 0 then
-    hasfile = false
-    dir = mp.get_property('working-directory')
-  else
-    dir = directory
-  end
-
-  if dir == "." then dir = "" end
-  if force_dir then dir = force_dir end
-
-  local files = file_filter(utils.readdir(dir, "files"))
-  if winapisort ~= nil then
-    table.sort(files, winapisort)
-  else
-    table.sort(files, alphanumsort)
-  end
-
-
-  if files == nil then
-    msg.verbose("no files in directory")
-    return
-  end
-
-  local filenames = get_playlist_filenames_set()
-  local c, c2 = 0,0
-  if files then
-    local cur = false
-    local filename = mp.get_property("filename")
-    for _, file in ipairs(files) do
-      if file == nil or file[1] == "." then
-          break
-      end
-      local appendstr = "append"
-      if not hasfile then
-        cur = true
-        appendstr = "append-play"
-        hasfile = true
-      end
-      if filename == file then
-        cur = true
-      elseif filenames[file] then
-        -- skip files already in playlist
-      elseif cur == true or settings.loadfiles_always_append then
-        mp.commandv("loadfile", utils.join_path(dir, file), appendstr)
-        msg.info("Appended to playlist: " .. file)
-        c2 = c2 + 1
-      else
-        mp.commandv("loadfile", utils.join_path(dir, file), appendstr)
-        msg.info("Prepended to playlist: " .. file)
-        mp.commandv("playlist-move", mp.get_property_number("playlist-count", 1)-1,  c)
-        c = c + 1
-      end
-    end
-    if c2 > 0 or c>0 then
-      msg.info("Added "..c + c2.." files to playlist")
-    else
-      msg.info("No additional files found")
-    end
-    cursor = mp.get_property_number('playlist-pos', 1)
-  else
-    msg.error("Could not scan for files: "..(error or ""))
-  end
-  refresh_globals()
-  if playlist_visible then
-    showplaylist()
-  end
-  if settings.display_osd_feedback then
-    if c2 > 0 or c>0 then
-      mp.osd_message("Added "..c + c2.." files to playlist")
-    else
-      mp.osd_message("No additional files found")
-    end
-  end
-  return c + c2
-end
-
-function parse_home(path)
-  if not path:find("^~") then
-    return path
-  end
-  local home_dir = os.getenv("HOME") or os.getenv("USERPROFILE")
-  if not home_dir then
-    local drive = os.getenv("HOMEDRIVE")
-    local path = os.getenv("HOMEPATH")
-    if drive and path then
-      home_dir = utils.join_path(drive, path)
-    else
-      msg.error("Couldn't find home dir.")
-      return nil
-    end
-  end
-  local result = path:gsub("^~", home_dir)
-  return result
-end
-
-local interactive_save = false
-function activate_playlist_save()
-  if interactive_save then
-    remove_keybinds()
-    mp.command("script-message playlistmanager-save-interactive \"start interactive filenaming process\"")
-  else
-    save_playlist()
-  end
-end
-
---saves the current playlist into a m3u file
-function save_playlist(filename)
-  local length = mp.get_property_number('playlist-count', 0)
-  if length == 0 then return end
-
-  --get playlist save path
-  local savepath
-  if settings.playlist_savepath == nil or settings.playlist_savepath == "" then
-    savepath = mp.command_native({"expand-path", "~~home/"}).."/playlists"
-  else
-    savepath = parse_home(settings.playlist_savepath)
-    if savepath == nil then return end
-  end
-
-  --create savepath if it doesn't exist
-  if utils.readdir(savepath) == nil then
-    local windows_args = {'powershell', '-NoProfile', '-Command', 'mkdir', savepath}
-    local unix_args = { 'mkdir', savepath }
-    local args = settings.system == 'windows' and windows_args or unix_args
-    local res = utils.subprocess({ args = args, cancellable = false })
-    if res.status ~= 0 then
-      msg.error("Failed to create playlist save directory "..savepath..". Error: "..(res.error or "unknown"))
-      return
-    end
-  end
-
-  local name = filename
-  if name == nil then
-    if settings.playlist_save_filename == nil or settings.playlist_save_filename == "" then
-      local date = os.date("*t")
-      local datestring = ("%02d-%02d-%02d_%02d-%02d-%02d"):format(date.year, date.month, date.day, date.hour, date.min, date.sec)
-
-      name = datestring.."_playlist-size_"..length..".m3u"
-    else
-      name = settings.playlist_save_filename
-    end
-  end
-
-  local savepath = utils.join_path(savepath, name)
-  local file, err = io.open(savepath, "w")
-  if not file then
-    msg.error("Error in creating playlist file, check permissions. Error: "..(err or "unknown"))
-  else
-    file:write("#EXTM3U\n")
-    local i=0
-    while i < length do
-      local pwd = mp.get_property("working-directory")
-      local filename = mp.get_property('playlist/'..i..'/filename')
-      local fullpath = filename
-      if not is_protocol(filename) then
-        fullpath = utils.join_path(pwd, filename)
-      end
-      local title = mp.get_property('playlist/'..i..'/title') or title_table[filename]
-      if title then
-        file:write("#EXTINF:,"..title.."\n")
-      end
-      file:write(fullpath, "\n")
-      i=i+1
-    end
-    local saved_msg = "Playlist written to: "..savepath
-    if settings.display_osd_feedback then mp.osd_message(saved_msg) end
-    msg.info(saved_msg)
-    file:close()
-  end
-end
-
-function alphanumsort(a, b)
-  local function padnum(d)
-    local dec, n = string.match(d, "(%.?)0*(.+)")
-    return #dec > 0 and ("%.12f"):format(d) or ("%s%03d%s"):format(dec, #n, n)
-  end
-  return tostring(a):lower():gsub("%.?%d+",padnum)..("%3d"):format(#b)
-       < tostring(b):lower():gsub("%.?%d+",padnum)..("%3d"):format(#a)
-end
-
--- fast sort algo from https://github.com/zsugabubus/dotfiles/blob/master/.config/mpv/scripts/playlist-filtersort.lua
-function sortplaylist(startover)
-  local playlist = mp.get_property_native('playlist')
-  if #playlist < 2 then return end
-
-  local order = {}
-  for i=1, #playlist do
-               order[i] = i
-    playlist[i].string = get_name_from_index(i - 1, true)
-       end
-
-  table.sort(order, function(a, b)
-    return sort_modes[sort_mode].sort_fn(a, b, playlist)
-  end)
-
-  for i=1, #playlist do
-    playlist[order[i]].new_pos = i
-  end
-
-  for i=1, #playlist do
-    while true do
-      local j = playlist[i].new_pos
-      if i == j then
-        break
-      end
-      mp.commandv('playlist-move', (i)     - 1, (j + 1) - 1)
-      mp.commandv('playlist-move', (j - 1) - 1, (i)     - 1)
-      playlist[j], playlist[i] = playlist[i], playlist[j]
-    end
-  end
-
-  for i = 1, #playlist do
-    local filename = mp.get_property('playlist/' .. i - 1 .. '/filename')
-    local ext = filename:match("%.([^%.]+)$")
-    if not ext or not filetype_lookup[ext:lower()] then
-      --move the directory to the end of the playlist
-      mp.commandv('playlist-move', i - 1, #playlist)
-    end
-  end
-
-  cursor = mp.get_property_number('playlist-pos', 0)
-  if startover then
-    mp.set_property('playlist-pos', 0)
-  end
-  if playlist_visible then
-    showplaylist()
-  end
-  if settings.display_osd_feedback then
-    mp.osd_message("Playlist sorted with "..sort_modes[sort_mode].title)
-  end
-end
-
-function reverseplaylist()
-  local length = mp.get_property_number('playlist-count', 0)
-  if length < 2 then return end
-  for outer=1, length-1, 1 do
-    mp.commandv('playlist-move', outer, 0)
-  end
-  if playlist_visible then
-    showplaylist()
-  end
-  if settings.display_osd_feedback then
-    mp.osd_message("Playlist reversed")
-  end
-end
-
-function shuffleplaylist()
-  refresh_globals()
-  if plen < 2 then return end
-  mp.command("playlist-shuffle")
-  math.randomseed(os.time())
-  mp.commandv("playlist-move", pos, math.random(0, plen-1))
-
-  local playlist = mp.get_property_native('playlist')
-  for i = 1, #playlist do
-    local filename = mp.get_property('playlist/' .. i - 1 .. '/filename')
-    local ext = filename:match("%.([^%.]+)$")
-    if not ext or not filetype_lookup[ext:lower()] then
-      --move the directory to the end of the playlist
-      mp.commandv('playlist-move', i - 1, #playlist)
-    end
-  end
-
-  mp.set_property('playlist-pos', 0)
-  refresh_globals()
-  if playlist_visible then
-    showplaylist()
-  end
-  if settings.display_osd_feedback then
-    mp.osd_message("Playlist shuffled")
-  end
-end
-
-function bind_keys(keys, name, func, opts)
-  if keys == nil or keys == "" then
-    mp.add_key_binding(keys, name, func, opts)
-    return
-  end
-  local i = 1
-  for key in keys:gmatch("[^%s]+") do
-    local prefix = i == 1 and '' or i
-    mp.add_key_binding(key, name..prefix, func, opts)
-    i = i + 1
-  end
-end
-
-function bind_keys_forced(keys, name, func, opts)
-  if keys == nil or keys == "" then
-    mp.add_forced_key_binding(keys, name, func, opts)
-    return
-  end
-  local i = 1
-  for key in keys:gmatch("[^%s]+") do
-    local prefix = i == 1 and '' or i
-    mp.add_forced_key_binding(key, name..prefix, func, opts)
-    i = i + 1
-  end
-end
-
-function unbind_keys(keys, name)
-  if keys == nil or keys == "" then
-    mp.remove_key_binding(name)
-    return
-  end
-  local i = 1
-  for key in keys:gmatch("[^%s]+") do
-    local prefix = i == 1 and '' or i
-    mp.remove_key_binding(name..prefix)
-    i = i + 1
-  end
-end
-
-function add_keybinds()
-  bind_keys_forced(settings.key_moveup, 'moveup', moveup, "repeatable")
-  bind_keys_forced(settings.key_movedown, 'movedown', movedown, "repeatable")
-  bind_keys_forced(settings.key_movepageup, 'movepageup', movepageup, "repeatable")
-  bind_keys_forced(settings.key_movepagedown, 'movepagedown', movepagedown, "repeatable")
-  bind_keys_forced(settings.key_movebegin, 'movebegin', movebegin, "repeatable")
-  bind_keys_forced(settings.key_moveend, 'moveend', moveend, "repeatable")
-  bind_keys_forced(settings.key_selectfile, 'selectfile', selectfile)
-  bind_keys_forced(settings.key_unselectfile, 'unselectfile', unselectfile)
-  bind_keys_forced(settings.key_playfile, 'playfile', playfile)
-  bind_keys_forced(settings.key_removefile, 'removefile', removefile, "repeatable")
-  bind_keys_forced(settings.key_closeplaylist, 'closeplaylist', remove_keybinds)
-end
-
-function remove_keybinds()
-  keybindstimer:kill()
-  keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
-  keybindstimer:kill()
-  playlist_overlay.data = ""
-  playlist_overlay:remove()
-  if is_terminal_mode() then
-    mp.osd_message("")
-  end
-  playlist_visible = false
-  if settings.reset_cursor_on_close then
-    resetcursor()
-  end
-  if settings.dynamic_binds then
-    unbind_keys(settings.key_moveup, 'moveup')
-    unbind_keys(settings.key_movedown, 'movedown')
-    unbind_keys(settings.key_movepageup, 'movepageup')
-    unbind_keys(settings.key_movepagedown, 'movepagedown')
-    unbind_keys(settings.key_movebegin, 'movebegin')
-    unbind_keys(settings.key_moveend, 'moveend')
-    unbind_keys(settings.key_selectfile, 'selectfile')
-    unbind_keys(settings.key_unselectfile, 'unselectfile')
-    unbind_keys(settings.key_playfile, 'playfile')
-    unbind_keys(settings.key_removefile, 'removefile')
-    unbind_keys(settings.key_closeplaylist, 'closeplaylist')
-  end
-end
-
-keybindstimer = mp.add_periodic_timer(settings.playlist_display_timeout, remove_keybinds)
-keybindstimer:kill()
-
-if not settings.dynamic_binds then
-  add_keybinds()
-end
-
-if settings.loadfiles_on_idle_start and mp.get_property_number('playlist-count', 0) == 0 then
-  playlist()
-end
-
-mp.observe_property('playlist-count', "number", function(_, plcount)
-  --if we promised to listen and sort on playlist size increase do it
-  if settings.sortplaylist_on_file_add and (plcount > plen) then
-    msg.info("Added files will be automatically sorted")
-    refresh_globals()
-    sortplaylist()
-  end
-  refresh_UI()
-  resolve_titles()
-end)
-mp.observe_property('osd-dimensions', 'native', refresh_UI)
-
-
-url_request_queue = {}
-function url_request_queue.push(item) table.insert(url_request_queue, item) end
-function url_request_queue.pop() return table.remove(url_request_queue, 1) end
-local url_titles_to_fetch = url_request_queue
-local ongoing_url_requests = {}
-
-function url_fetching_throttler()
-  if #url_titles_to_fetch == 0 then
-    url_title_fetch_timer:kill()
-  end
-
-  local ongoing_url_requests_count = 0
-  for _, ongoing in pairs(ongoing_url_requests) do
-    if ongoing then
-      ongoing_url_requests_count = ongoing_url_requests_count + 1
-    end
-  end
-
-  -- start resolving some url titles if there is available slots
-  local amount_to_fetch = math.max(0, settings.concurrent_title_resolve_limit - ongoing_url_requests_count)
-  for index=1,amount_to_fetch,1 do
-    local file = url_titles_to_fetch.pop()
-    if file then
-      ongoing_url_requests[file] = true
-      resolve_ytdl_title(file)
-    end
-  end
-end
-
-url_title_fetch_timer = mp.add_periodic_timer(0.1, url_fetching_throttler)
-url_title_fetch_timer:kill()
-
-local_request_queue = {}
-function local_request_queue.push(item) table.insert(local_request_queue, item) end
-function local_request_queue.pop() return table.remove(local_request_queue, 1) end
-local local_titles_to_fetch = local_request_queue
-local ongoing_local_request = false
-
--- this will only allow 1 concurrent local title resolve process
-function local_fetching_throttler()
-  if not ongoing_local_request then
-    local file = local_titles_to_fetch.pop()
-    if file then
-      ongoing_local_request = true
-      resolve_ffprobe_title(file)
-    end
-  end
-end
-
-function resolve_titles()
-  if settings.prefer_titles == 'none' then return end
-  if not settings.resolve_url_titles and not settings.resolve_local_titles then return end
-
-  local length = mp.get_property_number('playlist-count', 0)
-  if length < 2 then return end
-  -- loop all items in playlist because we can't predict how it has changed
-  local added_urls = false
-  local added_local = false
-  for i=0,length - 1,1 do
-    local filename = mp.get_property('playlist/'..i..'/filename')
-    local title = mp.get_property('playlist/'..i..'/title')
-    if i ~= pos
-      and filename
-      and not title
-      and not title_table[filename]
-      and not requested_titles[filename]
-    then
-      requested_titles[filename] = true
-      if filename:match('^https?://') and settings.resolve_url_titles then
-        url_titles_to_fetch.push(filename)
-        added_urls = true
-      elseif settings.prefer_titles == "all" and settings.resolve_local_titles then
-        local_titles_to_fetch.push(filename)
-        added_local = true
-      end
-    end
-  end
-  if added_urls then
-    url_title_fetch_timer:resume()
-  end
-  if added_local then
-    local_fetching_throttler()
-  end
-end
-
-function resolve_ytdl_title(filename)
-  local args = {
-    settings.youtube_dl_executable,
-    '--no-playlist',
-    '--flat-playlist',
-    '-sJ',
-    '--no-config',
-    filename,
-  }
-  local req = mp.command_native_async(
-    {
-      name = "subprocess",
-      args = args,
-      playback_only = false,
-      capture_stdout = true
-    },
-    function (success, res)
-      ongoing_url_requests[filename] = false
-      if res.killed_by_us then
-        msg.verbose('Request to resolve url title ' .. filename .. ' timed out')
-        return
-      end
-      if res.status == 0 then
-        local json, err = utils.parse_json(res.stdout)
-        if not err then
-          local is_playlist = json['_type'] and json['_type'] == 'playlist'
-          local title = (is_playlist and '[playlist]: ' or '') .. json['title']
-          msg.verbose(filename .. " resolved to '" .. title .. "'")
-          title_table[filename] = title
-          mp.set_property_native('user-data/playlistmanager/titles', title_table)
-          refresh_UI()
-        else
-          msg.error("Failed parsing json, reason: "..(err or "unknown"))
-        end
-      else
-        msg.error("Failed to resolve url title "..filename.." Error: "..(res.error or "unknown"))
-      end
-    end
-  )
-
-  mp.add_timeout(
-    settings.resolve_title_timeout,
-    function()
-      mp.abort_async_command(req)
-      ongoing_url_requests[filename] = false
-    end
-  )
-end
-
-function resolve_ffprobe_title(filename)
-  local args = { "ffprobe", "-show_format", "-show_entries", "format=tags", "-loglevel", "quiet", filename }
-  local req = mp.command_native_async(
-    {
-      name = "subprocess",
-      args = args,
-      playback_only = false,
-      capture_stdout = true
-    },
-    function (success, res)
-      ongoing_local_request = false
-      local_fetching_throttler()
-      if res.killed_by_us then
-        msg.verbose('Request to resolve local title ' .. filename .. ' timed out')
-        return
-      end
-      if res.status == 0 then
-        local title = string.match(res.stdout, "title=([^\n\r]+)")
-        if title then
-          msg.verbose(filename .. " resolved to '" .. title .. "'")
-          title_table[filename] = title
-          mp.set_property_native('user-data/playlistmanager/titles', title_table)
-          refresh_UI()
-        end
-      else
-        msg.error("Failed to resolve local title "..filename.." Error: "..(res.error or "unknown"))
-      end
-    end
-  )
-end
-
---script message handler
-function handlemessage(msg, value, value2)
-  if msg == "show" and value == "playlist" then
-    if value2 ~= "toggle" then
-      showplaylist(value2)
-      return
-    else
-      toggle_playlist(showplaylist)
-      return
-    end
-  end
-  if msg == "show" and value == "playlist-nokeys" then
-    if value2 ~= "toggle" then
-      showplaylist_non_interactive(value2)
-      return
-    else
-      toggle_playlist(showplaylist_non_interactive)
-      return
-    end
-  end
-  if msg == "show" and value == "filename" and strippedname and value2 then
-    mp.commandv('show-text', strippedname, tonumber(value2)*1000 ) ; return
-  end
-  if msg == "show" and value == "filename" and strippedname then
-    mp.commandv('show-text', strippedname ) ; return
-  end
-  if msg == "sort" then sortplaylist(value) ; return end
-  if msg == "shuffle" then shuffleplaylist() ; return end
-  if msg == "reverse" then reverseplaylist() ; return end
-  if msg == "loadfiles" then playlist(value) ; return end
-  if msg == "save" then save_playlist(value) ; return end
-  if msg == "playlist-next" then playlist_next() ; return end
-  if msg == "playlist-prev" then playlist_prev() ; return end
-  if msg == "playlist-next-random" then playlist_random() ; return end
-  if msg == "enable-interactive-save" then interactive_save = true end
-  if msg == "close" then remove_keybinds() end
-end
-
-mp.register_script_message("playlistmanager", handlemessage)
-
-bind_keys(settings.key_sortplaylist, "sortplaylist", function()
-  sortplaylist()
-  sort_mode = sort_mode + 1
-  if sort_mode > #sort_modes then sort_mode = 1 end
-end)
-bind_keys(settings.key_shuffleplaylist, "shuffleplaylist", shuffleplaylist)
-bind_keys(settings.key_reverseplaylist, "reverseplaylist", reverseplaylist)
-bind_keys(settings.key_loadfiles, "loadfiles", playlist)
-bind_keys(settings.key_saveplaylist, "saveplaylist", activate_playlist_save)
-bind_keys(settings.key_showplaylist, "showplaylist", showplaylist)
-bind_keys(
-  settings.key_peek_at_playlist,
-  "peek_at_playlist",
-  handle_complex_playlist_toggle,
-  { complex=true }
-)
-
-mp.register_event("start-file", on_start_file)
-mp.register_event("file-loaded", on_file_loaded)
-mp.register_event("end-file", on_end_file)
diff --git a/subdir_files/.config/sqlfluff/.sqlfluff b/subdir_files/.config/sqlfluff/.sqlfluff
new file mode 100644 (file)
index 0000000..a3c29ef
--- /dev/null
@@ -0,0 +1,14 @@
+[sqlfluff]
+dialect = postgres
+
+## Here are things the man page suggested might get it to indent despite parsing errors, but nothing worked.
+# fix_even_unparsable = True
+# exclude_rules = LT02
+#ignore = lexing,linting,parsing,templating
+
+
+[sqlfluff:indentation]
+tab_space_size = 2
+
+[sqlfluff:rules:capitalisation.keywords]
+capitalisation_policy = lower
diff --git a/subdir_files/.local/share/icc b/subdir_files/.local/share/icc
new file mode 120000 (symlink)
index 0000000..e3a5ff6
--- /dev/null
@@ -0,0 +1 @@
+../../../icc
\ No newline at end of file