grafana-alloy: don't build the frontend twice
[NixPkgs.git] / nixos / modules / services / video / v4l2-relayd.nix
blob7d1d712fa4a3deef42c5a811235603530cc61165
1 { config, lib, pkgs, utils, ... }:
2 let
4   inherit (lib) attrValues concatStringsSep filterAttrs length listToAttrs literalExpression
5     makeSearchPathOutput mkEnableOption mkIf mkOption nameValuePair optionals types;
6   inherit (utils) escapeSystemdPath;
8   cfg = config.services.v4l2-relayd;
10   kernelPackages = config.boot.kernelPackages;
12   gst = (with pkgs.gst_all_1; [
13     gst-plugins-bad
14     gst-plugins-base
15     gst-plugins-good
16     gstreamer.out
17   ]);
19   instanceOpts = { name, ... }: {
20     options = {
21       enable = mkEnableOption "this v4l2-relayd instance";
23       name = mkOption {
24         type = types.str;
25         default = name;
26         description = ''
27           The name of the instance.
28         '';
29       };
31       cardLabel = mkOption {
32         type = types.str;
33         description = ''
34           The name the camera will show up as.
35         '';
36       };
38       extraPackages = mkOption {
39         type = with types; listOf package;
40         default = [ ];
41         description = ''
42           Extra packages to add to {env}`GST_PLUGIN_PATH` for the instance.
43         '';
44       };
46       input = {
47         pipeline = mkOption {
48           type = types.str;
49           description = ''
50             The gstreamer-pipeline to use for the input-stream.
51           '';
52         };
54         format = mkOption {
55           type = types.str;
56           default = "YUY2";
57           description = ''
58             The video-format to read from input-stream.
59           '';
60         };
62         width = mkOption {
63           type = types.ints.positive;
64           default = 1280;
65           description = ''
66             The width to read from input-stream.
67           '';
68         };
70         height = mkOption {
71           type = types.ints.positive;
72           default = 720;
73           description = ''
74             The height to read from input-stream.
75           '';
76         };
78         framerate = mkOption {
79           type = types.ints.positive;
80           default = 30;
81           description = ''
82             The framerate to read from input-stream.
83           '';
84         };
85       };
87       output = {
88         format = mkOption {
89           type = types.str;
90           default = "YUY2";
91           description = ''
92             The video-format to write to output-stream.
93           '';
94         };
95       };
97     };
98   };
103   options.services.v4l2-relayd = {
105     instances = mkOption {
106       type = with types; attrsOf (submodule instanceOpts);
107       default = { };
108       example = literalExpression ''
109         {
110           example = {
111             cardLabel = "Example card";
112             input.pipeline = "videotestsrc";
113           };
114         }
115       '';
116       description = ''
117         v4l2-relayd instances to be created.
118       '';
119     };
121   };
123   config =
124     let
126       mkInstanceService = instance: {
127         description = "Streaming relay for v4l2loopback using GStreamer";
129         after = [ "modprobe@v4l2loopback.service" "systemd-logind.service" ];
130         wantedBy = [ "multi-user.target" ];
132         serviceConfig = {
133           Type = "simple";
134           Restart = "always";
135           PrivateNetwork = true;
136           PrivateTmp = true;
137           LimitNPROC = 1;
138         };
140         environment = {
141           GST_PLUGIN_PATH = makeSearchPathOutput "lib" "lib/gstreamer-1.0" (gst ++ instance.extraPackages);
142           V4L2_DEVICE_FILE = "/run/v4l2-relayd-${instance.name}/device";
143         };
145         script =
146           let
147             appsrcOptions = concatStringsSep "," [
148               "caps=video/x-raw"
149               "format=${instance.input.format}"
150               "width=${toString instance.input.width}"
151               "height=${toString instance.input.height}"
152               "framerate=${toString instance.input.framerate}/1"
153             ];
155             outputPipeline = [
156               "appsrc name=appsrc ${appsrcOptions}"
157               "videoconvert"
158             ] ++ optionals (instance.input.format != instance.output.format) [
159               "video/x-raw,format=${instance.output.format}"
160               "queue"
161             ] ++ [ "v4l2sink name=v4l2sink device=$(cat $V4L2_DEVICE_FILE)" ];
162           in
163           ''
164             exec ${pkgs.v4l2-relayd}/bin/v4l2-relayd -i "${instance.input.pipeline}" -o "${concatStringsSep " ! " outputPipeline}"
165           '';
167         preStart = ''
168           mkdir -p $(dirname $V4L2_DEVICE_FILE)
169           ${kernelPackages.v4l2loopback.bin}/bin/v4l2loopback-ctl add -x 1 -n "${instance.cardLabel}" > $V4L2_DEVICE_FILE
170         '';
172         postStop = ''
173           ${kernelPackages.v4l2loopback.bin}/bin/v4l2loopback-ctl delete $(cat $V4L2_DEVICE_FILE)
174           rm -rf $(dirname $V4L2_DEVICE_FILE)
175         '';
176       };
178       mkInstanceServices = instances: listToAttrs (map
179         (instance:
180           nameValuePair "v4l2-relayd-${escapeSystemdPath instance.name}" (mkInstanceService instance)
181         )
182         instances);
184       enabledInstances = attrValues (filterAttrs (n: v: v.enable) cfg.instances);
186     in
187     {
189       boot = mkIf ((length enabledInstances) > 0) {
190         extraModulePackages = [ kernelPackages.v4l2loopback ];
191         kernelModules = [ "v4l2loopback" ];
192       };
194       systemd.services = mkInstanceServices enabledInstances;
196     };
198   meta.maintainers = with lib.maintainers; [ betaboon ];