{ config, lib, nixosConfig, pkgs, ... }: let mpv-prescalers = pkgs.fetchFromGitHub { owner = "bjin"; repo = "mpv-prescalers"; rev = "b37d0f12223720f5ce48e149678162ae0a984176"; sha256 = "1mmf5827qsyph0wqkyvi7zfng1cskkapngn5hf9073zc61n4398y"; meta.license = lib.licenses.lgpl3Plus; }; shaders = { fsrcnnx_x2 = pkgs.fetchurl { name = "FSRCNNX_x2_8-0-4-1.glsl"; url = "https://web.archive.org/web/20220609155212/https://objects.githubusercontent.com/github-production-release-asset-2e65be/99480330/aa89f8b1-35e1-49b5-8c04-9039d5263eb3?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIWNJYAX4CSVEH53A%2F20220609%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20220609T155212Z&X-Amz-Expires=300&X-Amz-Signature=8c60c1bbae7befb9a3f803f286a0471fdad6b77b83cfd87e3ff3b74f0a1bfb3f&X-Amz-SignedHeaders=host&actor_id=0&key_id=0&repo_id=99480330&response-content-disposition=attachment%3B%20filename%3DFSRCNNX_x2_8-0-4-1.glsl&response-content-type=application%2Foctet-stream"; sha256 = "sha256-6ADbxcHJUYXMgiFsWXckUz/18ogBefJW7vYA8D6Nwq4="; meta.license = lib.licenses.mit; }; SSimDownscaler = pkgs.fetchurl { url = "https://gist.github.com/igv/36508af3ffc84410fe39761d6969be10/raw/ac09db2c0664150863e85d5a4f9f0106b6443a12/SSimDownscaler.glsl"; sha256 = "1c3fp6llg0n8zdxadsg6g3kcvhqvikalbc9q61zgwvsx8q3d8rph"; meta.license = lib.licenses.lgpl3Plus; }; KrigBilateral = pkgs.fetchurl { url = "https://gist.github.com/igv/a015fc885d5c22e6891820ad89555637/raw/c471ef6dcbd3c4a977e1c95dc40944ee38fad08a/KrigBilateral.glsl"; sha256 = "11s0h6i0y3h2nrfp6lax6hzwvjbn93akjr6d61jrmz09zdf1iwmc"; meta.license = lib.licenses.lgpl3Plus; }; }; gallery-view = pkgs.fetchFromGitHub { owner = "occivink"; repo = "mpv-gallery-view"; rev = "eca93f9ff2b8716ac750824c9980f6eb766756cf"; sha256 = "01ys2nd166lvcppnrch8ls5n6jhrha2z0jcn9hnfip63gvv2c5gn"; meta.license = lib.licenses.gpl2; }; cycleShaders = shaders: "cycle-values glsl-shaders ${lib.concatStringsSep " " shaders}"; in { programs.mpv = { # mpv can also be useful without a display (e.g. for encoding) enable = nixosConfig.sbruder.gui.enable || nixosConfig.sbruder.full; package = pkgs.wrapMpv (pkgs.mpv-unwrapped.override ({ vapoursynthSupport = true; vapoursynth = pkgs.vapoursynth.withPlugins (with pkgs; [ vapoursynth-mvtools ]); } // (if (pkgs.lib.versionAtLeast (pkgs.lib.substring 0 5 pkgs.lib.version) "23.05") then { # TODO: Remove with 23.05 ffmpeg_5 = pkgs.ffmpeg_5-full; } else { ffmpeg = pkgs.ffmpeg-full; }))) { scripts = with pkgs.mpvScripts; [ pitchcontrol sponsorblock ]; }; defaultProfiles = [ "gpu-hq" # High quality by default ]; config = { # Main application font osd-font = "monospace"; # Subtitles (adapted from https://github.com/LightArrowsEXE/dotfiles/blob/75973b6c8493368bbdee6e8e632964045fcf22ec/mpv/.config/mpv/mpv.conf#L76) sub-bold = true; sub-border-color = "#ff000000"; sub-border-size = 2.4; sub-color = "#ffffff"; sub-font = "Gandhi Sans"; # see osd-font sub-font-size = 50; sub-shadow-color = "#a0000000"; sub-shadow-offset = 0.75; sub-use-margins = false; # Nicer looking vobsub/PGS subtitles sub-gauss = 0.5; sub-gray = true; # Allow higher volumes volume-max = 150; # Downmix to stereo by default audio-channels = "stereo"; # Allow seeking in streamed media force-seekable = true; # Enable hardware decoding when avaliable hwdec = "auto-safe"; # Do not show images only one second image-display-duration = "inf"; # Not too high quality by default (otherwise too high frame drop) deband = false; dither-depth = "auto"; # Lossless screenshots screenshot-format = "png"; # Use youtube-dl’s format configuration ytdl-format = "ytdl"; }; bindings = { # Audio filter bs2b (for headphones) "b" = "af toggle bs2b"; # Toggle debanding "D" = "cycle deband"; # Variable correction of audio pitch when changing speed "k" = "cycle-values audio-pitch-correction no yes"; # Reverse cycling for audio and video (shift modifier on german keyboard layout) "'" = "cycle audio down"; "-" = "cycle video down"; # Cycle between audio downmixes "c" = "cycle-values audio-channels stereo mono auto"; # Cycle between smart scalers (and /dev/null = disabled) "K" = cycleShaders ((map (radius: "${mpv-prescalers}/compute/ravu-r${toString radius}.hook") [ 4 3 2 ]) ++ [ "/dev/null" ]); "Alt+k" = cycleShaders ((map (neurons: "${mpv-prescalers}/compute/nnedi3-nns${toString neurons}-win8x4.hook") [ 256 128 64 32 16 ]) ++ [ "/dev/null" ]); # Toggle pitchcontrol "Ctrl+p" = "script-message-to pitchcontrol toggle"; # more granular panning "Alt+left" = "add video-pan-x 0.05"; "Alt+right" = "add video-pan-x -0.05"; "Alt+up" = "add video-pan-y 0.05"; "Alt+down" = "add video-pan-y -0.05"; # sub-delay for fixing 24 empty frames on blu-ray "Alt+z" = "add sub-delay -1"; "Alt+Z" = "add sub-delay +1"; # sub-delay for fixing exact frame offsets (for signs) "Ctrl+z" = "add sub-delay -0.04170833333"; "Ctrl+Z" = "add sub-delay +0.04170833333"; # sponsorblock: set segment boundaries "alt+g" = "script-binding sponsorblock/set_segment"; # Exits with error to not mark the video as watched in ytcc "Shift+q" = "quit-watch-later 1"; }; profiles = let mkVisualiser = width: height: rate: { lavfi-complex = lib.concatStringsSep ";" [ "[aid1]asplit=4[ao][a1][a2][a3]" "[a1]avectorscope=m=lissajous:s=${toString (width / 3)}x${toString (height / 2)}:draw=dot:r=${toString rate}[avectorscope]" "[a2]aresample=44100,showwaves=s=${toString (width / 3 * 2)}x${toString (height / 2)}:mode=p2p:colors=red|lightgreen:r=${toString rate}[showwaves]" "[a3]showcqt=s=${toString width}x${toString (height / 2)}:axis=0:axis_h=0:r=${toString rate}[showcqt]" "[showwaves][avectorscope]hstack=shortest=1[top]" "[top][showcqt]vstack=shortest=1,fps=${toString rate}[vo]" ]; alpha = false; }; in { visualiser = mkVisualiser 1920 1200 30; visualiser-60 = mkVisualiser 1920 1200 60; visualiser-1080 = mkVisualiser 1920 1080 30; visualiser-1080-60 = mkVisualiser 1920 1080 60; visualiser-2160 = mkVisualiser 3840 2160 30; visualiser-2160-60 = mkVisualiser 3840 2160 60; hqencode = { ovc = "libx264"; ovcopts-add = "crf=18"; oac = "flac"; audio-format = "s16"; }; lq = { scale = "bicubic_fast"; cscale = "bicubic_fast"; dscale = "bicubic_fast"; }; hq = { scale = "ewa_lanczossharp"; dscale = "mitchell"; linear-downscaling = "no"; # for KrigBilateral cscale = "ewa_lanczos"; glsl-shader = [ "${shaders.SSimDownscaler}" # dscale "${shaders.KrigBilateral}" # cscale "${shaders.fsrcnnx_x2}" # scale ]; }; interpolate = { blend-subtitles = true; video-sync = "display-resample"; interpolation = true; tscale = "box"; tscale-window = "sphinx"; tscale-radius = 1.0; tscale-clamp = 0.0; }; mvinterpolate.vf-add = "vapoursynth=${./mvinterpolate.py}"; # adapted from https://github.com/mpv-player/mpv/issues/4418#issuecomment-368272929 clear-speed.af-add = "scaletempo=stride=18:overlap=.6:search=10"; }; }; xdg.configFile = { # scripts that are incompatible with home-manager’s mechanism "mpv/scripts/lib.disable/gallery.lua".source = "${gallery-view}/scripts/lib.disable/gallery.lua"; "mpv/scripts/playlist-view.lua".source = "${gallery-view}/scripts/playlist-view.lua"; # script options "mpv/script-opts/playlist_view.conf".text = '' thumbs_dir=${config.xdg.cacheHome}/thumbnails/mpv ''; "mpv/script-opts/sponsorblock.conf".text = '' local_database=no ''; # make ytdl-hook use yt-dlp "mpv/script-opts/ytdl_hook.conf".text = '' ytdl_path=${pkgs.unstable.yt-dlp}/bin/yt-dlp ''; "mpv/script-opts/stats.conf".text = '' font=${config.programs.mpv.config.osd-font} font_mono=${config.programs.mpv.config.osd-font} font_size=10 redraw_delay=0.25 ''; # mpv-gallery-view’s thumbnail generation mechanism supports multithreading by linking it multiple times } // lib.listToAttrs (map (i: lib.nameValuePair "mpv/scripts/gallery-thumbgen-${toString i}.lua" { source = "${gallery-view}/scripts/gallery-thumbgen.lua"; }) (lib.range 1 16)); # dummy file home.file."${config.xdg.cacheHome}/thumbnails/mpv/.create".text = ""; }