From 3593e643797eb32b0d5f92d9e2d7e06aecb7dfa9 Mon Sep 17 00:00:00 2001 From: Hoang Nguyen Date: Sun, 3 Nov 2024 00:00:00 +0700 Subject: [PATCH] streamlink: init module Streamlink is a CLI utility which pipes video streams from various services into a video player. --- modules/lib/maintainers.nix | 6 + modules/misc/news.nix | 10 ++ modules/modules.nix | 1 + modules/programs/streamlink.nix | 143 ++++++++++++++++++ tests/default.nix | 1 + tests/modules/programs/streamlink/config | 5 + tests/modules/programs/streamlink/default.nix | 4 + tests/modules/programs/streamlink/dummy.py | 9 ++ .../streamlink/streamlink-custom-plugins.nix | 45 ++++++ .../streamlink/streamlink-settings.nix | 28 ++++ 10 files changed, 252 insertions(+) create mode 100644 modules/programs/streamlink.nix create mode 100644 tests/modules/programs/streamlink/config create mode 100644 tests/modules/programs/streamlink/default.nix create mode 100644 tests/modules/programs/streamlink/dummy.py create mode 100644 tests/modules/programs/streamlink/streamlink-custom-plugins.nix create mode 100644 tests/modules/programs/streamlink/streamlink-settings.nix diff --git a/modules/lib/maintainers.nix b/modules/lib/maintainers.nix index 7ee9e1df9c1b..03e5009a16a9 100644 --- a/modules/lib/maintainers.nix +++ b/modules/lib/maintainers.nix @@ -628,4 +628,10 @@ github = "ALameLlama"; githubId = 55490546; }; + folliehiyuki = { + name = "Hoang Nguyen"; + email = "folliekazetani@protonmail.com"; + github = "folliehiyuki"; + githubId = 67634026; + }; } diff --git a/modules/misc/news.nix b/modules/misc/news.nix index 699398dee44a..75aa5f62ca66 100644 --- a/modules/misc/news.nix +++ b/modules/misc/news.nix @@ -1947,6 +1947,16 @@ in { speed, features, or native UIs. Ghostty provides all three. ''; } + + { + time = "2025-01-04T04:16:57+00:00"; + message = '' + A new module is available: 'programs.streamlink'. + + Streamlink is a CLI utility which pipes video streams from various + services into a video player. + ''; + } ]; }; } diff --git a/modules/modules.nix b/modules/modules.nix index 3c422eb5ed97..33ddcb0a498c 100644 --- a/modules/modules.nix +++ b/modules/modules.nix @@ -231,6 +231,7 @@ let ./programs/sqls.nix ./programs/ssh.nix ./programs/starship.nix + ./programs/streamlink.nix ./programs/swaylock.nix ./programs/swayr.nix ./programs/taskwarrior.nix diff --git a/modules/programs/streamlink.nix b/modules/programs/streamlink.nix new file mode 100644 index 000000000000..d4accd633878 --- /dev/null +++ b/modules/programs/streamlink.nix @@ -0,0 +1,143 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.programs.streamlink; + + renderSettings = mapAttrsToList (name: value: + if (builtins.isBool value) then + if value then name else "" + else if (builtins.isList value) then + concatStringsSep "\n" + (builtins.map (item: "${name}=${builtins.toString item}") value) + else + "${name}=${builtins.toString value}"); + + pluginType = types.submodule { + options = { + src = mkOption { + type = with types; nullOr (either path lines); + default = null; + description = '' + Source of the custom plugin. The value should be a path to the + plugin file, or the text of the plugin code. Will be linked to + {file}`$XDG_DATA_HOME/streamlink/plugins/.py` (linux) or + {file}`Library/Application Support/streamlink/plugins/.py` (darwin). + ''; + example = literalExpression "./custom_plugin.py"; + }; + + settings = mkOption { + type = with types; + attrsOf (oneOf [ bool int str (listOf (either int str)) ]); + default = { }; + example = literalExpression '' + { + quiet = true; + } + ''; + description = '' + Configuration for the specific plugin, written to + {file}`$XDG_CONFIG_HOME/streamlink/config.` (linux) or + {file}`Library/Application Support/streamlink/config.` (darwin). + ''; + }; + }; + }; + +in { + meta.maintainers = [ hm.maintainers.folliehiyuki ]; + + options.programs.streamlink = { + enable = mkEnableOption "streamlink"; + + package = mkPackageOption pkgs "streamlink" { }; + + settings = mkOption { + type = with types; + attrsOf (oneOf [ bool int str (listOf (either int str)) ]); + default = { }; + example = literalExpression '' + { + player = "''${pkgs.mpv}/bin/mpv"; + player-args = "--cache 2048"; + player-no-close = true; + } + ''; + description = '' + Global configuration options for streamlink. It will be written to + {file}`$XDG_CONFIG_HOME/streamlink/config` (linux) or + {file}`Library/Application Support/streamlink/config` (darwin). + ''; + }; + + plugins = mkOption { + description = '' + Streamlink plugins. + + If a source is set, the custom plugin will be linked to the data directory. + + Additional configuration specific to the plugin, if defined, will be + written to the config directory, and override global settings. + ''; + type = types.attrsOf pluginType; + default = { }; + example = literalExpression '' + { + custom_plugin = { + src = ./custom_plugin.py; + settings = { + quiet = true; + }; + }; + } + ''; + }; + }; + + config = mkIf cfg.enable { + home.packages = [ cfg.package ]; + + home.file = mkIf pkgs.stdenv.hostPlatform.isDarwin { + "Library/Application Support/streamlink/config" = + mkIf (cfg.settings != { }) { + text = concatStringsSep "\n" (remove "" (renderSettings cfg.settings)) + + "\n"; + }; + } // (mapAttrs' (name: value: + nameValuePair "Library/Application Support/streamlink/config.${name}" + (mkIf (value.settings != { }) { + text = concatStringsSep "\n" (remove "" (renderSettings value.settings)) + + "\n"; + })) cfg.plugins) // (mapAttrs' (name: value: + nameValuePair + "Library/Application Support/streamlink/plugins/${name}.py" + (mkIf (value.src != null) (if (builtins.isPath value.src) then { + source = value.src; + } else { + text = value.src; + }))) cfg.plugins); + + xdg.configFile = mkIf pkgs.stdenv.hostPlatform.isLinux ({ + "streamlink/config" = mkIf (cfg.settings != { }) { + text = concatStringsSep "\n" (remove "" (renderSettings cfg.settings)) + + "\n"; + }; + } // (mapAttrs' (name: value: + nameValuePair "streamlink/config.${name}" (mkIf (value.settings != { }) { + text = concatStringsSep "\n" (remove "" (renderSettings value.settings)) + + "\n"; + })) cfg.plugins)); + + xdg.dataFile = mkIf pkgs.stdenv.hostPlatform.isLinux (mapAttrs' + (name: value: + nameValuePair "streamlink/plugins/${name}.py" (mkIf (value.src != null) + (if (builtins.isPath value.src) then { + source = value.src; + } else { + text = value.src; + }))) cfg.plugins); + }; +} diff --git a/tests/default.nix b/tests/default.nix index 3530138da0ef..eff3e5d571ef 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -154,6 +154,7 @@ in import nmtSrc { ./modules/programs/spotify-player ./modules/programs/ssh ./modules/programs/starship + ./modules/programs/streamlink ./modules/programs/taskwarrior ./modules/programs/tealdeer ./modules/programs/texlive diff --git a/tests/modules/programs/streamlink/config b/tests/modules/programs/streamlink/config new file mode 100644 index 000000000000..bfeececec083 --- /dev/null +++ b/tests/modules/programs/streamlink/config @@ -0,0 +1,5 @@ +http-header=User-Agent=Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0 +http-header=Accept-Language=en-US +player=mpv +player-args=--cache 2048 +player-no-close diff --git a/tests/modules/programs/streamlink/default.nix b/tests/modules/programs/streamlink/default.nix new file mode 100644 index 000000000000..4ce9bd645776 --- /dev/null +++ b/tests/modules/programs/streamlink/default.nix @@ -0,0 +1,4 @@ +{ + streamlink-settings = ./streamlink-settings.nix; + streamlink-custom-plugins = ./streamlink-custom-plugins.nix; +} diff --git a/tests/modules/programs/streamlink/dummy.py b/tests/modules/programs/streamlink/dummy.py new file mode 100644 index 000000000000..45149dd47d75 --- /dev/null +++ b/tests/modules/programs/streamlink/dummy.py @@ -0,0 +1,9 @@ +""" +$description Dummy plugin for testing +""" + +from streamlink.plugin import Plugin + +class DummyTV(Plugin): + +__plugin__ = DummyTV diff --git a/tests/modules/programs/streamlink/streamlink-custom-plugins.nix b/tests/modules/programs/streamlink/streamlink-custom-plugins.nix new file mode 100644 index 000000000000..49eb225cc28d --- /dev/null +++ b/tests/modules/programs/streamlink/streamlink-custom-plugins.nix @@ -0,0 +1,45 @@ +{ pkgs, ... }: + +{ + programs.streamlink = { + enable = true; + plugins = { + dummy.src = ./dummy.py; + + dummy2.src = builtins.readFile ./dummy.py; + + twitch.settings = { + player = "haruna"; + quiet = true; + }; + }; + }; + + test.stubs.streamlink = { }; + + nmt.script = let + configDir = if pkgs.stdenv.isDarwin then + "Library/Application Support/streamlink" + else + ".config/streamlink"; + + pluginDir = if pkgs.stdenv.isDarwin then + "Library/Application Support/streamlink/plugins" + else + ".local/share/streamlink/plugins"; + in '' + assertFileExists home-files/${configDir}/config.twitch + assertFileContent home-files/${configDir}/config.twitch ${ + pkgs.writeText "expected" '' + player=haruna + quiet + '' + } + + assertFileExists home-files/${pluginDir}/dummy.py + assertFileContent home-files/${pluginDir}/dummy.py ${./dummy.py} + + assertFileExists home-files/${pluginDir}/dummy2.py + assertFileContent home-files/${pluginDir}/dummy2.py ${./dummy.py} + ''; +} diff --git a/tests/modules/programs/streamlink/streamlink-settings.nix b/tests/modules/programs/streamlink/streamlink-settings.nix new file mode 100644 index 000000000000..df8a5e34c319 --- /dev/null +++ b/tests/modules/programs/streamlink/streamlink-settings.nix @@ -0,0 +1,28 @@ +{ pkgs, ... }: + +{ + programs.streamlink = { + enable = true; + settings = { + player = "mpv"; + player-args = "--cache 2048"; + player-no-close = true; + http-header = [ + "User-Agent=Mozilla/5.0 (X11; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0" + "Accept-Language=en-US" + ]; + }; + }; + + test.stubs.streamlink = { }; + + nmt.script = let + streamlinkConfig = if pkgs.stdenv.hostPlatform.isDarwin then + "Library/Application Support/streamlink/config" + else + ".config/streamlink/config"; + in '' + assertFileExists home-files/${streamlinkConfig} + assertFileContent home-files/${streamlinkConfig} ${./config} + ''; +}