at 22.05-pre 14 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 gid = config.ids.gids.mediatomb; 8 cfg = config.services.mediatomb; 9 name = cfg.package.pname; 10 pkg = cfg.package; 11 optionYesNo = option: if option then "yes" else "no"; 12 # configuration on media directory 13 mediaDirectory = { 14 options = { 15 path = mkOption { 16 type = types.str; 17 description = '' 18 Absolute directory path to the media directory to index. 19 ''; 20 }; 21 recursive = mkOption { 22 type = types.bool; 23 default = false; 24 description = "Whether the indexation must take place recursively or not."; 25 }; 26 hidden-files = mkOption { 27 type = types.bool; 28 default = true; 29 description = "Whether to index the hidden files or not."; 30 }; 31 }; 32 }; 33 toMediaDirectory = d: "<directory location=\"${d.path}\" mode=\"inotify\" recursive=\"${optionYesNo d.recursive}\" hidden-files=\"${optionYesNo d.hidden-files}\" />\n"; 34 35 transcodingConfig = if cfg.transcoding then with pkgs; '' 36 <transcoding enabled="yes"> 37 <mimetype-profile-mappings> 38 <transcode mimetype="video/x-flv" using="vlcmpeg" /> 39 <transcode mimetype="application/ogg" using="vlcmpeg" /> 40 <transcode mimetype="audio/ogg" using="ogg2mp3" /> 41 <transcode mimetype="audio/x-flac" using="oggflac2raw"/> 42 </mimetype-profile-mappings> 43 <profiles> 44 <profile name="ogg2mp3" enabled="no" type="external"> 45 <mimetype>audio/mpeg</mimetype> 46 <accept-url>no</accept-url> 47 <first-resource>yes</first-resource> 48 <accept-ogg-theora>no</accept-ogg-theora> 49 <agent command="${ffmpeg}/bin/ffmpeg" arguments="-y -i %in -f mp3 %out" /> 50 <buffer size="1048576" chunk-size="131072" fill-size="262144" /> 51 </profile> 52 <profile name="vlcmpeg" enabled="no" type="external"> 53 <mimetype>video/mpeg</mimetype> 54 <accept-url>yes</accept-url> 55 <first-resource>yes</first-resource> 56 <accept-ogg-theora>yes</accept-ogg-theora> 57 <agent command="${libsForQt5.vlc}/bin/vlc" 58 arguments="-I dummy %in --sout #transcode{venc=ffmpeg,vcodec=mp2v,vb=4096,fps=25,aenc=ffmpeg,acodec=mpga,ab=192,samplerate=44100,channels=2}:standard{access=file,mux=ps,dst=%out} vlc:quit" /> 59 <buffer size="14400000" chunk-size="512000" fill-size="120000" /> 60 </profile> 61 </profiles> 62 </transcoding> 63'' else '' 64 <transcoding enabled="no"> 65 </transcoding> 66''; 67 68 configText = optionalString (! cfg.customCfg) '' 69<?xml version="1.0" encoding="UTF-8"?> 70<config version="2" xmlns="http://mediatomb.cc/config/2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://mediatomb.cc/config/2 http://mediatomb.cc/config/2.xsd"> 71 <server> 72 <ui enabled="yes" show-tooltips="yes"> 73 <accounts enabled="no" session-timeout="30"> 74 <account user="${name}" password="${name}"/> 75 </accounts> 76 </ui> 77 <name>${cfg.serverName}</name> 78 <udn>uuid:${cfg.uuid}</udn> 79 <home>${cfg.dataDir}</home> 80 <interface>${cfg.interface}</interface> 81 <webroot>${pkg}/share/${name}/web</webroot> 82 <pc-directory upnp-hide="${optionYesNo cfg.pcDirectoryHide}"/> 83 <storage> 84 <sqlite3 enabled="yes"> 85 <database-file>${name}.db</database-file> 86 </sqlite3> 87 </storage> 88 <protocolInfo extend="${optionYesNo cfg.ps3Support}"/> 89 ${optionalString cfg.dsmSupport '' 90 <custom-http-headers> 91 <add header="X-User-Agent: redsonic"/> 92 </custom-http-headers> 93 94 <manufacturerURL>redsonic.com</manufacturerURL> 95 <modelNumber>105</modelNumber> 96 ''} 97 ${optionalString cfg.tg100Support '' 98 <upnp-string-limit>101</upnp-string-limit> 99 ''} 100 <extended-runtime-options> 101 <mark-played-items enabled="yes" suppress-cds-updates="yes"> 102 <string mode="prepend">*</string> 103 <mark> 104 <content>video</content> 105 </mark> 106 </mark-played-items> 107 </extended-runtime-options> 108 </server> 109 <import hidden-files="no"> 110 <autoscan use-inotify="auto"> 111 ${concatMapStrings toMediaDirectory cfg.mediaDirectories} 112 </autoscan> 113 <scripting script-charset="UTF-8"> 114 <common-script>${pkg}/share/${name}/js/common.js</common-script> 115 <playlist-script>${pkg}/share/${name}/js/playlists.js</playlist-script> 116 <virtual-layout type="builtin"> 117 <import-script>${pkg}/share/${name}/js/import.js</import-script> 118 </virtual-layout> 119 </scripting> 120 <mappings> 121 <extension-mimetype ignore-unknown="no"> 122 <map from="mp3" to="audio/mpeg"/> 123 <map from="ogx" to="application/ogg"/> 124 <map from="ogv" to="video/ogg"/> 125 <map from="oga" to="audio/ogg"/> 126 <map from="ogg" to="audio/ogg"/> 127 <map from="ogm" to="video/ogg"/> 128 <map from="asf" to="video/x-ms-asf"/> 129 <map from="asx" to="video/x-ms-asf"/> 130 <map from="wma" to="audio/x-ms-wma"/> 131 <map from="wax" to="audio/x-ms-wax"/> 132 <map from="wmv" to="video/x-ms-wmv"/> 133 <map from="wvx" to="video/x-ms-wvx"/> 134 <map from="wm" to="video/x-ms-wm"/> 135 <map from="wmx" to="video/x-ms-wmx"/> 136 <map from="m3u" to="audio/x-mpegurl"/> 137 <map from="pls" to="audio/x-scpls"/> 138 <map from="flv" to="video/x-flv"/> 139 <map from="mkv" to="video/x-matroska"/> 140 <map from="mka" to="audio/x-matroska"/> 141 ${optionalString cfg.ps3Support '' 142 <map from="avi" to="video/divx"/> 143 ''} 144 ${optionalString cfg.dsmSupport '' 145 <map from="avi" to="video/avi"/> 146 ''} 147 </extension-mimetype> 148 <mimetype-upnpclass> 149 <map from="audio/*" to="object.item.audioItem.musicTrack"/> 150 <map from="video/*" to="object.item.videoItem"/> 151 <map from="image/*" to="object.item.imageItem"/> 152 </mimetype-upnpclass> 153 <mimetype-contenttype> 154 <treat mimetype="audio/mpeg" as="mp3"/> 155 <treat mimetype="application/ogg" as="ogg"/> 156 <treat mimetype="audio/ogg" as="ogg"/> 157 <treat mimetype="audio/x-flac" as="flac"/> 158 <treat mimetype="audio/x-ms-wma" as="wma"/> 159 <treat mimetype="audio/x-wavpack" as="wv"/> 160 <treat mimetype="image/jpeg" as="jpg"/> 161 <treat mimetype="audio/x-mpegurl" as="playlist"/> 162 <treat mimetype="audio/x-scpls" as="playlist"/> 163 <treat mimetype="audio/x-wav" as="pcm"/> 164 <treat mimetype="audio/L16" as="pcm"/> 165 <treat mimetype="video/x-msvideo" as="avi"/> 166 <treat mimetype="video/mp4" as="mp4"/> 167 <treat mimetype="audio/mp4" as="mp4"/> 168 <treat mimetype="application/x-iso9660" as="dvd"/> 169 <treat mimetype="application/x-iso9660-image" as="dvd"/> 170 </mimetype-contenttype> 171 </mappings> 172 <online-content> 173 <YouTube enabled="no" refresh="28800" update-at-start="no" purge-after="604800" racy-content="exclude" format="mp4" hd="no"> 174 <favorites user="${name}"/> 175 <standardfeed feed="most_viewed" time-range="today"/> 176 <playlists user="${name}"/> 177 <uploads user="${name}"/> 178 <standardfeed feed="recently_featured" time-range="today"/> 179 </YouTube> 180 </online-content> 181 </import> 182 ${transcodingConfig} 183 </config> 184''; 185 defaultFirewallRules = { 186 # udp 1900 port needs to be opened for SSDP (not configurable within 187 # mediatomb/gerbera) cf. 188 # http://docs.gerbera.io/en/latest/run.html?highlight=udp%20port#network-setup 189 allowedUDPPorts = [ 1900 cfg.port ]; 190 allowedTCPPorts = [ cfg.port ]; 191 }; 192 193in { 194 195 ###### interface 196 197 options = { 198 199 services.mediatomb = { 200 201 enable = mkOption { 202 type = types.bool; 203 default = false; 204 description = '' 205 Whether to enable the Gerbera/Mediatomb DLNA server. 206 ''; 207 }; 208 209 serverName = mkOption { 210 type = types.str; 211 default = "Gerbera (Mediatomb)"; 212 description = '' 213 How to identify the server on the network. 214 ''; 215 }; 216 217 package = mkOption { 218 type = types.package; 219 example = literalExpression "pkgs.mediatomb"; 220 default = pkgs.gerbera; 221 defaultText = literalExpression "pkgs.gerbera"; 222 description = '' 223 Underlying package to be used with the module. 224 ''; 225 }; 226 227 ps3Support = mkOption { 228 type = types.bool; 229 default = false; 230 description = '' 231 Whether to enable ps3 specific tweaks. 232 WARNING: incompatible with DSM 320 support. 233 ''; 234 }; 235 236 dsmSupport = mkOption { 237 type = types.bool; 238 default = false; 239 description = '' 240 Whether to enable D-Link DSM 320 specific tweaks. 241 WARNING: incompatible with ps3 support. 242 ''; 243 }; 244 245 tg100Support = mkOption { 246 type = types.bool; 247 default = false; 248 description = '' 249 Whether to enable Telegent TG100 specific tweaks. 250 ''; 251 }; 252 253 transcoding = mkOption { 254 type = types.bool; 255 default = false; 256 description = '' 257 Whether to enable transcoding. 258 ''; 259 }; 260 261 dataDir = mkOption { 262 type = types.path; 263 default = "/var/lib/${name}"; 264 description = '' 265 The directory where Gerbera/Mediatomb stores its state, data, etc. 266 ''; 267 }; 268 269 pcDirectoryHide = mkOption { 270 type = types.bool; 271 default = true; 272 description = '' 273 Whether to list the top-level directory or not (from upnp client standpoint). 274 ''; 275 }; 276 277 user = mkOption { 278 type = types.str; 279 default = "mediatomb"; 280 description = "User account under which ${name} runs."; 281 }; 282 283 group = mkOption { 284 type = types.str; 285 default = "mediatomb"; 286 description = "Group account under which ${name} runs."; 287 }; 288 289 port = mkOption { 290 type = types.int; 291 default = 49152; 292 description = '' 293 The network port to listen on. 294 ''; 295 }; 296 297 interface = mkOption { 298 type = types.str; 299 default = ""; 300 description = '' 301 A specific interface to bind to. 302 ''; 303 }; 304 305 openFirewall = mkOption { 306 type = types.bool; 307 default = false; 308 description = '' 309 If false (the default), this is up to the user to declare the firewall rules. 310 If true, this opens port 1900 (tcp and udp) and the port specified by 311 <option>sercvices.mediatomb.port</option>. 312 313 If the option <option>services.mediatomb.interface</option> is set, 314 the firewall rules opened are dedicated to that interface. Otherwise, 315 those rules are opened globally. 316 ''; 317 }; 318 319 uuid = mkOption { 320 type = types.str; 321 default = "fdfc8a4e-a3ad-4c1d-b43d-a2eedb03a687"; 322 description = '' 323 A unique (on your network) to identify the server by. 324 ''; 325 }; 326 327 mediaDirectories = mkOption { 328 type = with types; listOf (submodule mediaDirectory); 329 default = []; 330 description = '' 331 Declare media directories to index. 332 ''; 333 example = [ 334 { path = "/data/pictures"; recursive = false; hidden-files = false; } 335 { path = "/data/audio"; recursive = true; hidden-files = false; } 336 ]; 337 }; 338 339 customCfg = mkOption { 340 type = types.bool; 341 default = false; 342 description = '' 343 Allow ${name} to create and use its own config file inside the <literal>dataDir</literal> as 344 configured by <option>services.mediatomb.dataDir</option>. 345 Deactivated by default, the service then runs with the configuration generated from this module. 346 Otherwise, when enabled, no service configuration is generated. Gerbera/Mediatomb then starts using 347 config.xml within the configured <literal>dataDir</literal>. It's up to the user to make a correct 348 configuration file. 349 ''; 350 }; 351 352 }; 353 }; 354 355 356 ###### implementation 357 358 config = let binaryCommand = "${pkg}/bin/${name}"; 359 interfaceFlag = optionalString ( cfg.interface != "") "--interface ${cfg.interface}"; 360 configFlag = optionalString (! cfg.customCfg) "--config ${pkgs.writeText "config.xml" configText}"; 361 in mkIf cfg.enable { 362 systemd.services.mediatomb = { 363 description = "${cfg.serverName} media Server"; 364 after = [ "network.target" ]; 365 wantedBy = [ "multi-user.target" ]; 366 serviceConfig.ExecStart = "${binaryCommand} --port ${toString cfg.port} ${interfaceFlag} ${configFlag} --home ${cfg.dataDir}"; 367 serviceConfig.User = cfg.user; 368 }; 369 370 users.groups = optionalAttrs (cfg.group == "mediatomb") { 371 mediatomb.gid = gid; 372 }; 373 374 users.users = optionalAttrs (cfg.user == "mediatomb") { 375 mediatomb = { 376 isSystemUser = true; 377 group = cfg.group; 378 home = cfg.dataDir; 379 createHome = true; 380 description = "${name} DLNA Server User"; 381 }; 382 }; 383 384 # Open firewall only if users enable it 385 networking.firewall = mkMerge [ 386 (mkIf (cfg.openFirewall && cfg.interface != "") { 387 interfaces."${cfg.interface}" = defaultFirewallRules; 388 }) 389 (mkIf (cfg.openFirewall && cfg.interface == "") defaultFirewallRules) 390 ]; 391 }; 392}