Along with PVR software such as the arr family consisting in Sonarr, Lidarr, Readarr, Radarr or Whisparr, an additional component is usually added that is able to transcode downloaded videos using a desired codec or quality to match the requirements of things such as storage space, hardware capabilities, and so on.
The alternatives are either Tdarr or FileFlows and, unfortunately, these applications are not really within the arr family, given that they use different display technologies and run on different principles. While Tdarr sometimes seems counter-intuitive and not too stable, FileFlows seems more stable but unfortunately is really a subset of what Node-Red can accomplish.
The problem of both software packages is that they rely on hardcoded preset blocks and with some options, but the presets do not really cover nor reflect the whole capability range of the transcoding software.
Node-Red is a quick-to-deploy, low-requirements dataflow programming environment based on JavaScript and node.js. Node-Red can be installed using npm
directly:
npm -g install node-red
and then restarted using a SystemD service file:
[Unit] Description=Node Red After=network.target [Service] ExecStart=/usr/bin/node-red Restart=always RestartSec=10 StandardOutput=syslog StandardError=syslog SyslogIdentifier=node-red User=root Group=root Environment=PATH=/usr/bin/:/usr/local/bin/ WorkingDirectory=/opt/node-red [Install] WantedBy=multi-user.target
Upon loading the interface, node-red has a preset amount of nodes that can carry out various operations but the system is extensible by installing additional nodes from the node-red addon repository. However, for this walkthrough, no additional node-red nodes will be installed and the whole system will run using the nodes that are distributed with Node-Red by default.
In order to add some persistence, Node-Red can and will be configured to store variables persistently across runs. This can be done by editing the settings.json
file in the Node-Red configuration directory that is placed at the root of the user that is running Node-Red. If Node-Red is running as root, the path to the file will be /root/.node-red/settings.json
.
The context storage must be changed from memory to the local filesystem:
contextStorage: { default: { module:"localfilesystem" }, },
and then Node-Red must be restarted.
In order to transcode videos on demand, as well as pick up files added within a directory and transcode them, the following pipeline shall be established:
with the following remarks:
mediainfo
or ffprobe
will be used to check whether the video files already have the requested parameters in order to not transcode a video file twice if it already has the necessary settings,ffmpeg
will be used to transcode the files to the desired settings,The flow essentially relies on two queues that are filled dynamically as files are being detected or scanned:
whilst the scan path is monitored at the same time for new files that get added to the probe queue.
node.js is used just a little and mostly for the implementation of the queues, with some modules being necessary for the flow imported within the function nodes:
@root/walk
used for scanning the filesystem for files,chokidar
used as a filesystem watcher (inotify, etc)
The programming of the individual nodes is tricky because everything has to be implemented in an asynchronous, and preferably, event-driven way with even await
and promises not being suitable given their side-effect of blocking the main execution thread.
Some parameters are hard-coded, such as the path to the ffmpeg
, respectively ffprobe
binaries but they can be made configurable in any case. Instead, templates are used to generate the ffmpeg
resulting command elegantly, by interpolating settings variables and then reducing the template to one long string of parameters that is passed to ffmpeg
.
Similarly, ffmpeg
is used to extract the duration of the video clip and then the transcoding output of ffmpeg
is monitored in order to display the transcoding progress on the Node-Red user interface.
The following is the Node-Red flow that accomplishes all this:
[ { "id": "9f98f36f3385bcd8", "type": "tab", "label": "Transcoding", "disabled": false, "info": "", "env": [] }, { "id": "8c088cb2f7181ba2", "type": "group", "z": "9f98f36f3385bcd8", "name": "ffprobe", "style": { "label": true, "fill": "#bfdbef", "fill-opacity": "0.5" }, "nodes": [ "3e33e76d0e3c0e4d", "e42a58c30039d179", "c322cf0d03d623b1", "81fb8456c8a24bef", "01680d728b478aa5", "f0d454696f6e547f", "d666d15dbafe8395", "a4ebe3b65ec64062", "60bbd40c3f9d8c0e", "1b541b828612bc99", "3701809d5b290f02", "2098ad1b433ffa26", "bb0d09b469240dbb", "2d56998ff17d9e44", "49b44f05bfa9c6ac", "ceccd99cf2a40534", "191bead4c0d01b13", "914f0e31f40c7895", "ad7b379fe92408f3", "17b475a2e7ea9516", "5d78f655434a6555", "98be7c1856cb4c3f", "92f3af459ce90aa3", "c7abb9a6ba15539c", "8781d05a891a5bb1", "4b49fa8ec7c43636", "d40dc9475eb9ecba" ], "x": 64, "y": 539, "w": 1322, "h": 542 }, { "id": "77f59c55d0a9323f", "type": "group", "z": "9f98f36f3385bcd8", "name": "Constants", "style": { "fill": "#ffffbf", "label": true, "fill-opacity": "0.5" }, "nodes": [ "18c7efc706669366", "9786c5da6a945358" ], "x": 924, "y": 1719, "w": 392, "h": 82 }, { "id": "1a8449d0623e98a6", "type": "group", "z": "9f98f36f3385bcd8", "name": "ffmpeg", "style": { "label": true, "fill": "#bfdbef", "fill-opacity": "0.5" }, "nodes": [ "6314709e0f7c7b6b", "b4fd821e4032327a", "9c49830979494dbd", "07de187c55753134", "16c09cad03a240e1", "e939e3dc9ef4bb10", "b1a1e1fa961decd1", "8c2a3f510bba3f91", "82d819f90fa8e376", "d09426f17ccbd352", "a2ce74176e7bb93a", "66627cca970092b8", "a68ae4599eace14b", "8c9ef2a07a0e271b", "7a7af815b51f0fd1", "a78f6c483e1e2597", "1e743f463192bfd3", "a49ead80df7d8134", "f06ae7fe45bc8ee6", "0b74e59054355a3a", "db1094061707ac30", "5e5ac174da4067e4", "1102635d4c82203c", "f1897b1f7b56d49d", "54b14ea280430dea", "41accef666076be8", "1bde2da775a09b2b", "e45a9ec830c51602", "2023c957f816a653" ], "x": 64, "y": 39, "w": 1442, "h": 462 }, { "id": "118640a37c2f6bac", "type": "group", "z": "9f98f36f3385bcd8", "name": "User Interface", "style": { "label": true, "fill": "#dbcbe7", "fill-opacity": "0.5" }, "nodes": [ "312a5bcb53309fbd", "20c8e2ccfc16300d", "8c9fc57260072eb7", "a55079a596102a8d", "bb0455146846fb45", "b0e1d9038e08aad0", "e45801f0b7cc3cd4", "790c3950a63fa109", "68c70fe740f38a81", "54efb1e3b0611844", "ce11dc4527d4245e", "d497ac7322a54f69", "188c2940f78ada06", "c8c10f76c08795ac", "b6c06f50e19f051d", "3ea505b539e0b9ad", "8073655aeb3b7029", "6169a14dcff6f101", "dc1ccff0ec482caa", "dacd4902bd230787", "0e3a3381bc3841f3" ], "x": 54, "y": 1179, "w": 1432, "h": 462 }, { "id": "13e7445ef83fcae8", "type": "group", "z": "9f98f36f3385bcd8", "name": "Settings", "style": { "fill": "#e3f3d3", "fill-opacity": "0.5", "label": true }, "nodes": [ "81d7c71da8797959", "1e43c0ffb0d80603", "0556bead30d14d7a", "126582f943f52389", "f6172a25a7ac7bca", "879ac930e91a750f", "891e2e4363a18786", "b0bb9aa491c3bd80", "bfef60164daf91e3", "b23bafe27c4c5b95", "5de93c3d896716d6", "ae3859e24088c5f2", "188dc2f88112d7df", "b3b9dcb7075207b1", "1de5c48e2d62e5b0", "b6686ce43478776f", "c3f80d80e84d13ba", "436aa2571f967479", "8f42b24f5ba8481e", "95f8b028b62316d7", "8b09525b6f843634", "bf2116ae3cbe5b57", "90305768b3fc9d1c", "06a7100f39e362cc", "2a4044a67d1a5f60" ], "x": 64, "y": 1719, "w": 812, "h": 722 }, { "id": "6314709e0f7c7b6b", "type": "exec", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "command": "", "addpay": "payload", "append": "", "useSpawn": "true", "timer": "", "winHide": true, "oldrc": false, "name": "ffmpeg", "x": 810, "y": 280, "wires": [ [], [ "07de187c55753134", "16c09cad03a240e1" ], [ "b1a1e1fa961decd1" ] ] }, { "id": "b4fd821e4032327a", "type": "template", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "ffmpeg Template", "field": "payload", "fieldType": "msg", "format": "handlebars", "syntax": "mustache", "template": "{{{flow.ffmpegBinary}}}\n-i \"{{{payload.original}}}\"\n-y\n-map_metadata -1\n-c:v {{{flow._videoCodec}}} \n-filter:v scale={{{flow._videoWidth}}}:-1:flags=lanczos\n-crf 23 \n-c:a {{{flow._audioCodec}}}\n-ac:a 2 \n-b:a 64k \n-preset ultrafast \n-strict experimental \n-tune zerolatency \n-movflags +faststart\n\"{{{payload.temporary}}}\"\n", "output": "str", "x": 470, "y": 280, "wires": [ [ "9c49830979494dbd" ] ] }, { "id": "9c49830979494dbd", "type": "function", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "function 1", "func": "// normalize newlines\nvar regex = context.get('regex')\nmsg.payload = msg.payload.replace(regex, ' ')\n\n// return message\nreturn msg;\n", "outputs": 1, "noerr": 0, "initialize": "var reg = new RegExp(os.EOL, 'g')\ncontext.set('regex', reg)", "finalize": "", "libs": [ { "var": "os", "module": "os" } ], "x": 660, "y": 280, "wires": [ [ "6314709e0f7c7b6b" ] ] }, { "id": "07de187c55753134", "type": "function", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Extract Duration", "func": "var regex = context.get('regex')\nvar results = msg.payload.matchAll(regex)\nfor (let result of results) {\n let { duration } = result.groups\n var total = moment.duration(duration)\n flow.set('clipDuration', total)\n return\n}\n", "outputs": 0, "noerr": 0, "initialize": "var reg = new RegExp(/Duration: (?<duration>[^,]+),/, 'g')\ncontext.set('regex', reg)", "finalize": "", "libs": [ { "var": "os", "module": "os" }, { "var": "moment", "module": "moment" } ], "x": 1040, "y": 80, "wires": [] }, { "id": "16c09cad03a240e1", "type": "function", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Track Progress", "func": "var regex = context.get('regex')\nvar results = msg.payload.matchAll(regex)\nfor (let result of results) {\n let { time } = result.groups\n var clipTime = moment.duration(time).asMilliseconds()\n var clipDuration = flow.get('clipDuration').asMilliseconds()\n msg.payload = Math.round(clipTime / clipDuration * 100)\n return msg\n}", "outputs": 1, "noerr": 0, "initialize": "var reg = new RegExp(/time=(?<time>[^ ]+)/, 'g')\ncontext.set('regex', reg)", "finalize": "", "libs": [ { "var": "moment", "module": "moment" } ], "x": 1040, "y": 180, "wires": [ [ "e939e3dc9ef4bb10", "8c2a3f510bba3f91", "82d819f90fa8e376", "1bde2da775a09b2b" ] ] }, { "id": "e939e3dc9ef4bb10", "type": "ui_gauge", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "", "group": "7c5d8648955e9a1f", "order": 0, "width": 0, "height": 0, "gtype": "gage", "title": "Current File Transcoding Progress", "label": "%", "format": "{{value}}", "min": 0, "max": "100", "colors": [ "#ff0000", "#e6e600", "#80ff00" ], "seg1": "", "seg2": "", "diff": false, "className": "", "x": 1340, "y": 180, "wires": [] }, { "id": "b1a1e1fa961decd1", "type": "switch", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Success", "property": "payload.code", "propertyType": "msg", "rules": [ { "t": "eq", "v": "0", "vt": "num" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 960, "y": 300, "wires": [ [ "1e743f463192bfd3" ], [ "a49ead80df7d8134", "1102635d4c82203c" ] ] }, { "id": "8c2a3f510bba3f91", "type": "ui_text", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "group": "7c5d8648955e9a1f", "order": 2, "width": "0", "height": "0", "name": "", "label": "Current File", "format": "{{msg.transcode.original}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 1270, "y": 140, "wires": [] }, { "id": "82d819f90fa8e376", "type": "ui_text", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "group": "7c5d8648955e9a1f", "order": 3, "width": "0", "height": "0", "name": "", "label": "Temportary File", "format": "{{msg.transcode.temporary}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 1280, "y": 220, "wires": [] }, { "id": "312a5bcb53309fbd", "type": "function", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Get Folder Contents", "func": "var folder = flow.get('_controlFolder')\nif (!folder || !fs.statSync(folder).isDirectory()) {\n return [null, { \"payload\": folder }]\n}\n\nvar fileExtensionsWhitelist = flow.get('_fileExtensionWhitelist')\n\n/*var walker = walk.walk(folder, {})\n\nwalker.on('file', function (root, fileStats, next) {\n var run = flow.get('_run')\n var file = fileStats.name\n if (!fileExtensionsWhitelist.includes(file.split('.').pop())) {\n if (run) next()\n return\n }\n node.send([{ \"payload\": file }, null])\n if (run) next()\n})\n*/\n\nconst walkFunc = async (err, pathname, dirent) => {\n if(!flow.get('_run')) return\n if (err) throw err\n if (dirent.isDirectory()) return\n var file = path.join(path.dirname(pathname), dirent.name)\n if (!fileExtensionsWhitelist.includes(file.split('.').pop())) {\n return\n }\n node.send([{ \"payload\": file }, null])\n}\n\nawait walk.walk(folder, walkFunc)\nnode.send([null, {'payload': null}])", "outputs": 2, "noerr": 0, "initialize": "", "finalize": "", "libs": [ { "var": "path", "module": "path" }, { "var": "fs", "module": "fs" }, { "var": "walk", "module": "@root/walk" } ], "x": 740, "y": 1220, "wires": [ [ "b0e1d9038e08aad0" ], [ "0e3a3381bc3841f3" ] ] }, { "id": "20c8e2ccfc16300d", "type": "ui_text_input", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "", "label": "Folder", "tooltip": "", "group": "57b96149bd97f3cb", "order": 3, "width": 0, "height": 0, "passthru": true, "mode": "text", "delay": "150", "topic": "topic", "sendOnBlur": true, "className": "", "topicType": "msg", "x": 390, "y": 1280, "wires": [ [ "68c70fe740f38a81", "e45801f0b7cc3cd4", "188c2940f78ada06" ] ] }, { "id": "8c9fc57260072eb7", "type": "ui_button", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "", "group": "57b96149bd97f3cb", "order": 1, "width": 0, "height": 0, "passthru": false, "label": "Start", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "", "payloadType": "str", "topic": "topic", "topicType": "msg", "x": 390, "y": 1220, "wires": [ [ "e45801f0b7cc3cd4", "dacd4902bd230787" ] ] }, { "id": "a55079a596102a8d", "type": "inject", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Recall Control Folder", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "_controlFolder", "payloadType": "flow", "x": 200, "y": 1280, "wires": [ [ "20c8e2ccfc16300d", "188c2940f78ada06" ] ] }, { "id": "d09426f17ccbd352", "type": "inject", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "kill", "props": [ { "p": "kill", "v": "", "vt": "date" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 770, "y": 140, "wires": [ [ "6314709e0f7c7b6b" ] ] }, { "id": "bb0455146846fb45", "type": "ui_button", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "", "group": "57b96149bd97f3cb", "order": 2, "width": 0, "height": 0, "passthru": false, "label": "Stop", "tooltip": "", "color": "", "bgcolor": "", "className": "", "icon": "", "payload": "", "payloadType": "str", "topic": "topic", "topicType": "msg", "x": 390, "y": 1400, "wires": [ [ "e45801f0b7cc3cd4", "dc1ccff0ec482caa" ] ] }, { "id": "b0e1d9038e08aad0", "type": "function", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Add to Probe Queue", "func": "var queue = flow.get('_probeQueue')\nif(!Array.isArray(queue)) {\n queue = []\n}\nqueue.push(msg.payload)\nflow.set('_probeQueue', queue)\n\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [ { "var": "path", "module": "path" } ], "x": 980, "y": 1220, "wires": [ [ "6169a14dcff6f101" ] ] }, { "id": "18c7efc706669366", "type": "change", "z": "9f98f36f3385bcd8", "g": "77f59c55d0a9323f", "name": "CONSTANTS", "rules": [ { "t": "set", "p": "workFolder", "pt": "flow", "to": "/tmp/", "tot": "str" }, { "t": "set", "p": "ffmpegBinary", "pt": "flow", "to": "/usr/lib/jellyfin-ffmpeg/ffmpeg", "tot": "str" }, { "t": "set", "p": "ffprobeBinary", "pt": "flow", "to": "/usr/lib/jellyfin-ffmpeg/ffprobe", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1220, "y": 1760, "wires": [ [] ] }, { "id": "9786c5da6a945358", "type": "inject", "z": "9f98f36f3385bcd8", "g": "77f59c55d0a9323f", "name": "", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 1040, "y": 1760, "wires": [ [ "18c7efc706669366" ] ] }, { "id": "e45801f0b7cc3cd4", "type": "link out", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "link out 1", "mode": "link", "links": [ "66627cca970092b8", "d666d15dbafe8395", "9616785c60c44f70" ], "x": 495, "y": 1400, "wires": [] }, { "id": "3e33e76d0e3c0e4d", "type": "link out", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "link out 2", "mode": "link", "links": [ "a2ce74176e7bb93a" ], "x": 675, "y": 640, "wires": [] }, { "id": "a2ce74176e7bb93a", "type": "link in", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "link in 1", "links": [ "3e33e76d0e3c0e4d", "1102635d4c82203c", "a68ae4599eace14b" ], "x": 105, "y": 280, "wires": [ [ "f1897b1f7b56d49d" ] ] }, { "id": "66627cca970092b8", "type": "link in", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "link in 2", "links": [ "e45801f0b7cc3cd4" ], "x": 665, "y": 200, "wires": [ [ "7a7af815b51f0fd1", "db1094061707ac30" ] ] }, { "id": "d0f52fbe0d6d91e8", "type": "exec", "z": "9f98f36f3385bcd8", "command": "", "addpay": "payload", "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "name": "", "x": 2210, "y": 640, "wires": [ [], [ "17815b61e1ec70fb" ], [ "fa6c9e46602390b8" ] ] }, { "id": "4ae152e4f9923e5f", "type": "function", "z": "9f98f36f3385bcd8", "name": "Delete Original File", "func": "msg.payload = `rm \"${msg.transcode.path}\"`\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2030, "y": 640, "wires": [ [ "d0f52fbe0d6d91e8" ] ] }, { "id": "fa6c9e46602390b8", "type": "switch", "z": "9f98f36f3385bcd8", "name": "Success", "property": "payload.code", "propertyType": "msg", "rules": [ { "t": "eq", "v": "0", "vt": "num" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 2380, "y": 640, "wires": [ [ "dafe5c65a71f08a1" ], [] ] }, { "id": "dafe5c65a71f08a1", "type": "function", "z": "9f98f36f3385bcd8", "name": "Move Temporary File", "func": "msg.payload = `mv \"${msg.transcode.temporary}\" \"${msg.transcode.folder}\"`\nreturn msg\n", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 2020, "y": 700, "wires": [ [ "219a0d7c2b981228" ] ] }, { "id": "219a0d7c2b981228", "type": "exec", "z": "9f98f36f3385bcd8", "command": "", "addpay": "payload", "append": "", "useSpawn": "false", "timer": "", "winHide": false, "oldrc": false, "name": "", "x": 2210, "y": 700, "wires": [ [], [ "17815b61e1ec70fb" ], [ "73019a0f8f866a27" ] ] }, { "id": "73019a0f8f866a27", "type": "switch", "z": "9f98f36f3385bcd8", "name": "Success", "property": "payload.code", "propertyType": "msg", "rules": [ { "t": "eq", "v": "0", "vt": "num" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 2380, "y": 700, "wires": [ [], [] ] }, { "id": "a68ae4599eace14b", "type": "link out", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "link out 3", "mode": "link", "links": [ "e033200ab27e896d", "a2ce74176e7bb93a" ], "x": 1445, "y": 260, "wires": [] }, { "id": "17815b61e1ec70fb", "type": "debug", "z": "9f98f36f3385bcd8", "name": "debug 4", "active": true, "tosidebar": true, "console": false, "tostatus": false, "complete": "false", "statusVal": "", "statusType": "auto", "x": 2420, "y": 540, "wires": [] }, { "id": "8c9ef2a07a0e271b", "type": "ui_text", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "group": "7c5d8648955e9a1f", "order": 3, "width": 0, "height": 0, "name": "", "label": "Transcode Queue Size", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 690, "y": 340, "wires": [] }, { "id": "e42a58c30039d179", "type": "exec", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "command": "", "addpay": "payload", "append": "", "useSpawn": "false", "timer": "", "winHide": true, "oldrc": false, "name": "", "x": 210, "y": 720, "wires": [ [ "81fb8456c8a24bef" ], [ "ad7b379fe92408f3" ], [ "5d78f655434a6555" ] ] }, { "id": "c322cf0d03d623b1", "type": "template", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "ffprobe Template (Query)", "field": "payload", "fieldType": "msg", "format": "handlebars", "syntax": "mustache", "template": "\"{{{flow.ffprobeBinary}}}\"\n-v error \n-show_streams \n-show_entries stream=codec_type,codec_name,width\n-of json \n-v quiet\n-i \"{{{payload}}}\"\n", "output": "str", "x": 570, "y": 840, "wires": [ [ "01680d728b478aa5" ] ] }, { "id": "81fb8456c8a24bef", "type": "json", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "", "property": "payload", "action": "obj", "pretty": false, "x": 350, "y": 720, "wires": [ [ "f0d454696f6e547f" ] ] }, { "id": "01680d728b478aa5", "type": "function", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "function 6", "func": "// normalize newlines\nvar regex = context.get('regex')\nmsg.payload = msg.payload.replace(regex, ' ')\n\n// return message\nreturn msg;\n", "outputs": 1, "noerr": 0, "initialize": "var reg = new RegExp(os.EOL, 'g')\ncontext.set('regex', reg)", "finalize": "", "libs": [ { "var": "os", "module": "os" } ], "x": 780, "y": 840, "wires": [ [ "e42a58c30039d179" ] ] }, { "id": "f0d454696f6e547f", "type": "function", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "Check Probe matches Template", "func": "function traverse(target) {\n for (const key in target) {\n const value = target[key]\n if (typeof value === 'object' && value) {\n traverse(value)\n continue\n }\n switch (key) {\n case 'codec_type':\n switch (value) {\n case 'video':\n if (target['codec_name'] != flow.get('_videoCodec')) {\n node.send([msg, null])\n return\n }\n var width = parseInt(target['width'])\n if (width > flow.get('_videoWidth')) {\n node.send([msg, null])\n return\n }\n break;\n case 'audio':\n if (target['codec_name'] != flow.get('_audioCodec')) {\n node.send([msg, null])\n return\n }\n break;\n }\n break;\n }\n }\n}\n\ntraverse(msg.payload)\nnode.send([null, {'payload': null}])\n", "outputs": 2, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 580, "y": 720, "wires": [ [ "60bbd40c3f9d8c0e" ], [ "17b475a2e7ea9516" ] ] }, { "id": "790c3950a63fa109", "type": "ui_text", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "group": "57b96149bd97f3cb", "order": 3, "width": 0, "height": 0, "name": "", "label": "File Count", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 1390, "y": 1220, "wires": [] }, { "id": "68c70fe740f38a81", "type": "function", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Set Control Folder", "func": "flow.set('_controlFolder', msg.payload)\n", "outputs": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 570, "y": 1280, "wires": [] }, { "id": "d666d15dbafe8395", "type": "link in", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "link in 3", "links": [ "e45801f0b7cc3cd4" ], "x": 105, "y": 600, "wires": [ [ "2098ad1b433ffa26", "191bead4c0d01b13" ] ] }, { "id": "a4ebe3b65ec64062", "type": "function", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "Dequeue Probe Queue", "func": "flow.set('_probeQueueUnlocked', true)", "outputs": 2, "noerr": 0, "initialize": "setInterval(() => {\n if (!flow.get('_probeQueueUnlocked')) {\n return\n }\n\n var queue = flow.get('_probeQueue')\n if (queue.length == 0) {\n node.send([null, { \"payload\": null }])\n flow.set('_probeQueueUnlocked', false)\n return\n }\n var first = queue.pop()\n flow.set('_probeQueue', queue)\n\n flow.set('_probeQueueUnlocked', false)\n msg = {}\n msg.payload = first\n msg.transcode = first\n msg.queue = queue\n \n node.send([msg, null])\n}, 1000)\n", "finalize": "", "libs": [], "x": 270, "y": 840, "wires": [ [ "c322cf0d03d623b1", "8781d05a891a5bb1", "4b49fa8ec7c43636" ], [ "191bead4c0d01b13", "3e33e76d0e3c0e4d" ] ] }, { "id": "60bbd40c3f9d8c0e", "type": "function", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "Set Transcode Queue", "func": "var transcodeQueue = flow.get('_transcodeQueue')\nif(!Array.isArray(transcodeQueue)) {\n transcodeQueue = []\n}\nif (!transcodeQueue.find(element => element == msg.transcode)) {\n transcodeQueue.push(msg.transcode)\n}\nflow.set('_transcodeQueue', transcodeQueue)\nmsg.payload = transcodeQueue\nreturn msg", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 850, "y": 720, "wires": [ [ "1b541b828612bc99", "d40dc9475eb9ecba" ] ] }, { "id": "1b541b828612bc99", "type": "link out", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "link out 4", "mode": "link", "links": [ "3701809d5b290f02" ], "x": 1005, "y": 720, "wires": [] }, { "id": "3701809d5b290f02", "type": "link in", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "link in 4", "links": [ "1b541b828612bc99", "85a7ea277d73d0f1", "17b475a2e7ea9516", "98be7c1856cb4c3f", "0e3a3381bc3841f3" ], "x": 105, "y": 840, "wires": [ [ "a4ebe3b65ec64062", "ceccd99cf2a40534" ] ] }, { "id": "2098ad1b433ffa26", "type": "change", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "", "rules": [ { "t": "set", "p": "kill", "pt": "msg", "to": "", "tot": "date" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 230, "y": 640, "wires": [ [ "e42a58c30039d179" ] ] }, { "id": "7a7af815b51f0fd1", "type": "change", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "", "rules": [ { "t": "set", "p": "kill", "pt": "msg", "to": "", "tot": "date" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 790, "y": 200, "wires": [ [ "6314709e0f7c7b6b" ] ] }, { "id": "a78f6c483e1e2597", "type": "link out", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "link out 6", "mode": "link", "links": [ "9616785c60c44f70" ], "x": 615, "y": 460, "wires": [] }, { "id": "1e743f463192bfd3", "type": "function", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Replace Original File (Delete & Move)", "func": "var promise = new Promise((resolve, reject) => {\n fs.unlink(msg.transcode.original, (error) => {\n if(error) {\n reject(error)\n return\n }\n var target = path.join(path.dirname(msg.transcode.original), path.basename(msg.transcode.temporary))\n fs.copyFile(msg.transcode.temporary, target, (error) => {\n if(error) {\n reject(error)\n return\n }\n fs.unlink(msg.transcode.temporary, (error) => {\n if(error) {\n reject(error)\n return\n }\n resolve(true)\n })\n })\n })\n})\n\nvar result = await promise\nif(result) {\n return [ { payload: \"success\" }, null ]\n}\n\nreturn [null, { \"payload\": result }]", "outputs": 2, "noerr": 0, "initialize": "", "finalize": "", "libs": [ { "var": "fs", "module": "fs" }, { "var": "path", "module": "path" } ], "x": 1210, "y": 300, "wires": [ [ "a68ae4599eace14b" ], [ "a49ead80df7d8134", "a68ae4599eace14b" ] ] }, { "id": "a49ead80df7d8134", "type": "ui_text", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "group": "7c5d8648955e9a1f", "order": 0, "width": 0, "height": 0, "name": "", "label": "ffmpeg Errors", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 1140, "y": 400, "wires": [] }, { "id": "bb0d09b469240dbb", "type": "ui_text", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "group": "7c5d8648955e9a1f", "order": 0, "width": 0, "height": 0, "name": "", "label": "ffprobe Errors", "format": "{{msg.payload.message}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 780, "y": 980, "wires": [] }, { "id": "2d56998ff17d9e44", "type": "inject", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "kill", "props": [ { "p": "kill", "v": "", "vt": "date" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 210, "y": 580, "wires": [ [ "e42a58c30039d179" ] ] }, { "id": "f06ae7fe45bc8ee6", "type": "change", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Initialize ffmpeg Errors", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "", "tot": "str" }, { "t": "set", "p": "_ffmpegErrorsInitialized", "pt": "flow", "to": "true", "tot": "bool" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 920, "y": 400, "wires": [ [ "a49ead80df7d8134" ] ] }, { "id": "0b74e59054355a3a", "type": "switch", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Check ffmpeg Errors Initialized", "property": "_ffmpegErrorsInitialized", "propertyType": "flow", "rules": [ { "t": "false" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 650, "y": 400, "wires": [ [ "f06ae7fe45bc8ee6" ], [] ] }, { "id": "49b44f05bfa9c6ac", "type": "change", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "Initialize ffprobe Errors", "rules": [ { "t": "set", "p": "payload", "pt": "msg", "to": "", "tot": "str" }, { "t": "set", "p": "_ffprobeErrorsInitialized", "pt": "flow", "to": "true", "tot": "bool" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 560, "y": 980, "wires": [ [ "bb0d09b469240dbb" ] ] }, { "id": "ceccd99cf2a40534", "type": "switch", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "Check ffprobe Errors Initialized", "property": "_ffprobeErrorsInitialized", "propertyType": "flow", "rules": [ { "t": "false" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 290, "y": 980, "wires": [ [ "49b44f05bfa9c6ac" ], [] ] }, { "id": "191bead4c0d01b13", "type": "change", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "Reset Initializations", "rules": [ { "t": "set", "p": "_ffprobeErrorsInitialized", "pt": "flow", "to": "false", "tot": "bool" }, { "t": "set", "p": "_probeQueue", "pt": "flow", "to": "[]", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 530, "y": 640, "wires": [ [] ] }, { "id": "db1094061707ac30", "type": "change", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Reset Initializations", "rules": [ { "t": "set", "p": "_ffmpegErrorsInitialized", "pt": "flow", "to": "false", "tot": "bool" }, { "t": "set", "p": "_transcodeQueue", "pt": "flow", "to": "[]", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 470, "y": 460, "wires": [ [] ] }, { "id": "5e5ac174da4067e4", "type": "inject", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "init", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "str", "x": 870, "y": 460, "wires": [ [ "f06ae7fe45bc8ee6" ] ] }, { "id": "914f0e31f40c7895", "type": "inject", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "init", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "str", "x": 370, "y": 1040, "wires": [ [ "49b44f05bfa9c6ac" ] ] }, { "id": "54efb1e3b0611844", "type": "ui_switch", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "", "label": "Force Transcode", "tooltip": "", "group": "57b96149bd97f3cb", "order": 4, "width": 0, "height": 0, "passthru": true, "decouple": "false", "topic": "topic", "topicType": "msg", "style": "", "onvalue": "true", "onvalueType": "bool", "onicon": "", "oncolor": "", "offvalue": "false", "offvalueType": "bool", "officon": "", "offcolor": "", "animate": false, "className": "", "x": 530, "y": 1540, "wires": [ [ "ce11dc4527d4245e" ] ] }, { "id": "ce11dc4527d4245e", "type": "change", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Store Force Transcode", "rules": [ { "t": "set", "p": "_forceTranscode", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 770, "y": 1540, "wires": [ [] ] }, { "id": "d497ac7322a54f69", "type": "inject", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Recall Force Transcode", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "_forceTranscode", "payloadType": "flow", "x": 290, "y": 1540, "wires": [ [ "54efb1e3b0611844" ] ] }, { "id": "1e43c0ffb0d80603", "type": "ui_dropdown", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "label": "Video Codec", "tooltip": "", "place": "Select option", "group": "85a1a4cd106754fa", "order": 0, "width": 0, "height": 0, "passthru": true, "multiple": false, "options": [ { "label": "", "value": "h264", "type": "str" }, { "label": "", "value": "hvec", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "x": 480, "y": 1820, "wires": [ [ "0556bead30d14d7a" ] ] }, { "id": "f6172a25a7ac7bca", "type": "ui_dropdown", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "label": "Audio Codec", "tooltip": "", "place": "Select option", "group": "85a1a4cd106754fa", "order": 0, "width": 0, "height": 0, "passthru": true, "multiple": false, "options": [ { "label": "", "value": "aac", "type": "str" }, { "label": "", "value": "avc", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "x": 480, "y": 1960, "wires": [ [ "126582f943f52389" ] ] }, { "id": "b0bb9aa491c3bd80", "type": "ui_text_input", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "label": "Video Width", "tooltip": "", "group": "85a1a4cd106754fa", "order": 2, "width": 0, "height": 0, "passthru": true, "mode": "text", "delay": 300, "topic": "topic", "sendOnBlur": true, "className": "", "topicType": "msg", "x": 480, "y": 2100, "wires": [ [ "bfef60164daf91e3" ] ] }, { "id": "81d7c71da8797959", "type": "inject", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Recall Video Codec", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "_videoCodec", "payloadType": "flow", "x": 250, "y": 1820, "wires": [ [ "188dc2f88112d7df" ] ] }, { "id": "0556bead30d14d7a", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store Video Codec", "rules": [ { "t": "set", "p": "_videoCodec", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 680, "y": 1820, "wires": [ [] ] }, { "id": "879ac930e91a750f", "type": "inject", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Recall Audio Codec", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "_audioCodec", "payloadType": "flow", "x": 250, "y": 1960, "wires": [ [ "8f42b24f5ba8481e" ] ] }, { "id": "891e2e4363a18786", "type": "inject", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Recall Video Width", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "_videoWidth", "payloadType": "flow", "x": 240, "y": 2100, "wires": [ [ "1de5c48e2d62e5b0" ] ] }, { "id": "126582f943f52389", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store Audio Codec", "rules": [ { "t": "set", "p": "_audioCodec", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 680, "y": 1960, "wires": [ [] ] }, { "id": "bfef60164daf91e3", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store Video Width", "rules": [ { "t": "set", "p": "_videoWidth", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 680, "y": 2100, "wires": [ [] ] }, { "id": "5de93c3d896716d6", "type": "inject", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Recall Output Container", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "_videoContainer", "payloadType": "flow", "x": 260, "y": 2240, "wires": [ [ "c3f80d80e84d13ba" ] ] }, { "id": "b23bafe27c4c5b95", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store Output Container", "rules": [ { "t": "set", "p": "_videoContainer", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 740, "y": 2240, "wires": [ [] ] }, { "id": "ae3859e24088c5f2", "type": "ui_dropdown", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "label": "Output Container", "tooltip": "", "place": "Select option", "group": "85a1a4cd106754fa", "order": 6, "width": 0, "height": 0, "passthru": true, "multiple": false, "options": [ { "label": "", "value": "mp4", "type": "str" }, { "label": "", "value": "mkv", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "x": 500, "y": 2240, "wires": [ [ "b23bafe27c4c5b95" ] ] }, { "id": "188c2940f78ada06", "type": "function", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Folder Watcher", "func": "if (!flow.get('_enableWatching') || flow.get('_watchingBlocked')) {\n return\n}\n\nvar fileExtensionWhitelist = flow.get('_fileExtensionWhitelist')\n\nvar watcher = context.get('watcher')\nvar watcher = chokidar.watch(msg.payload, {\n ignored: /(^|[\\/\\\\])\\../, // ignore dotfiles\n persistent: true,\n ignoreInitial: true\n}).on('add', path => {\n if (!fileExtensionWhitelist.includes(path.split('.').pop())) {\n node.send([null, { \"payload\": path }])\n return\n }\n node.send([{ \"payload\": path }, null])\n})\n\ncontext.set('watcher', watcher)", "outputs": 2, "noerr": 0, "initialize": "", "finalize": "", "libs": [ { "var": "chokidar", "module": "chokidar" } ], "x": 560, "y": 1340, "wires": [ [ "c8c10f76c08795ac" ], [] ] }, { "id": "c8c10f76c08795ac", "type": "function", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Add to Probe Queue", "func": "var queue = flow.get('_probeQueue')\nif (!Array.isArray(queue)) {\n queue = []\n}\nqueue.push(msg.payload)\nflow.set('_probeQueue', queue)\n", "outputs": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [ { "var": "path", "module": "path" } ], "x": 780, "y": 1340, "wires": [] }, { "id": "188dc2f88112d7df", "type": "switch", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "empty" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 460, "y": 1760, "wires": [ [ "b3b9dcb7075207b1" ], [ "1e43c0ffb0d80603" ] ] }, { "id": "b3b9dcb7075207b1", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store Video Codec", "rules": [ { "t": "set", "p": "_videoCodec", "pt": "flow", "to": "h264", "tot": "str" }, { "t": "set", "p": "payload", "pt": "msg", "to": "h264", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 680, "y": 1760, "wires": [ [ "1e43c0ffb0d80603" ] ] }, { "id": "8f42b24f5ba8481e", "type": "switch", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "empty" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 460, "y": 1900, "wires": [ [ "95f8b028b62316d7" ], [ "f6172a25a7ac7bca" ] ] }, { "id": "95f8b028b62316d7", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store Audio Codec", "rules": [ { "t": "set", "p": "_audioCodec", "pt": "flow", "to": "aac", "tot": "str" }, { "t": "set", "p": "payload", "pt": "msg", "to": "aac", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 680, "y": 1900, "wires": [ [ "f6172a25a7ac7bca" ] ] }, { "id": "1de5c48e2d62e5b0", "type": "switch", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "empty" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 460, "y": 2040, "wires": [ [ "b6686ce43478776f" ], [ "b0bb9aa491c3bd80" ] ] }, { "id": "b6686ce43478776f", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store Video Width", "rules": [ { "t": "set", "p": "_videoWidth", "pt": "flow", "to": "640", "tot": "str" }, { "t": "set", "p": "payload", "pt": "msg", "to": "640", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 680, "y": 2040, "wires": [ [ "b0bb9aa491c3bd80" ] ] }, { "id": "c3f80d80e84d13ba", "type": "switch", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "empty" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 460, "y": 2180, "wires": [ [ "436aa2571f967479" ], [ "ae3859e24088c5f2" ] ] }, { "id": "436aa2571f967479", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store Video Container", "rules": [ { "t": "set", "p": "_videoContainer", "pt": "flow", "to": "mp4", "tot": "str" }, { "t": "set", "p": "payload", "pt": "msg", "to": "mp4", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 730, "y": 2180, "wires": [ [ "ae3859e24088c5f2" ] ] }, { "id": "b6c06f50e19f051d", "type": "inject", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Recall Enable Watching", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "_enableWatching", "payloadType": "flow", "x": 290, "y": 1600, "wires": [ [ "3ea505b539e0b9ad" ] ] }, { "id": "3ea505b539e0b9ad", "type": "ui_switch", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "", "label": "Enable Watching Folders for FIles", "tooltip": "", "group": "57b96149bd97f3cb", "order": 4, "width": 0, "height": 0, "passthru": true, "decouple": "false", "topic": "topic", "topicType": "msg", "style": "", "onvalue": "true", "onvalueType": "bool", "onicon": "", "oncolor": "", "offvalue": "false", "offvalueType": "bool", "officon": "", "offcolor": "", "animate": false, "className": "", "x": 580, "y": 1600, "wires": [ [ "8073655aeb3b7029" ] ] }, { "id": "8073655aeb3b7029", "type": "change", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Store Force Transcode", "rules": [ { "t": "set", "p": "_enableWatching", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 870, "y": 1600, "wires": [ [] ] }, { "id": "8b09525b6f843634", "type": "ui_dropdown", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "label": "Process Files", "tooltip": "", "place": "Select option", "group": "85a1a4cd106754fa", "order": 6, "width": 0, "height": 0, "passthru": true, "multiple": true, "options": [ { "label": "", "value": "mkv", "type": "str" }, { "label": "", "value": "mp4", "type": "str" } ], "payload": "", "topic": "topic", "topicType": "msg", "className": "", "x": 490, "y": 2400, "wires": [ [ "90305768b3fc9d1c" ] ] }, { "id": "bf2116ae3cbe5b57", "type": "inject", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Recall File Extension Whitelist", "props": [ { "p": "payload" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "_fileExtensionWhitelist", "payloadType": "flow", "x": 240, "y": 2400, "wires": [ [ "2a4044a67d1a5f60" ] ] }, { "id": "90305768b3fc9d1c", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store File Extension Whitelist", "rules": [ { "t": "set", "p": "_fileExtensionWhitelist", "pt": "flow", "to": "payload", "tot": "msg" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 730, "y": 2400, "wires": [ [] ] }, { "id": "2a4044a67d1a5f60", "type": "switch", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "", "property": "payload", "propertyType": "msg", "rules": [ { "t": "empty" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 460, "y": 2340, "wires": [ [ "06a7100f39e362cc" ], [ "8b09525b6f843634" ] ] }, { "id": "06a7100f39e362cc", "type": "change", "z": "9f98f36f3385bcd8", "g": "13e7445ef83fcae8", "name": "Store File Extension Whitelist", "rules": [ { "t": "set", "p": "_fileExtensionWhitelist", "pt": "flow", "to": "[ \"mp4\", \"mkv\" ]", "tot": "jsonata" }, { "t": "set", "p": "payload", "pt": "msg", "to": "[ \"mp4\", \"mkv\" ]", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 730, "y": 2340, "wires": [ [ "8b09525b6f843634" ] ] }, { "id": "ad7b379fe92408f3", "type": "debug", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "debug 11", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 390, "y": 580, "wires": [] }, { "id": "17b475a2e7ea9516", "type": "link out", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "link out 7", "mode": "link", "links": [ "3701809d5b290f02" ], "x": 765, "y": 780, "wires": [] }, { "id": "5d78f655434a6555", "type": "switch", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "Success", "property": "payload.code", "propertyType": "msg", "rules": [ { "t": "eq", "v": "0", "vt": "num" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 2, "x": 330, "y": 780, "wires": [ [], [ "bb0d09b469240dbb", "98be7c1856cb4c3f" ] ] }, { "id": "98be7c1856cb4c3f", "type": "link out", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "link out 8", "mode": "link", "links": [ "3701809d5b290f02" ], "x": 465, "y": 780, "wires": [] }, { "id": "1102635d4c82203c", "type": "link out", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "link out 9", "mode": "link", "links": [ "e033200ab27e896d", "a2ce74176e7bb93a" ], "x": 1075, "y": 340, "wires": [] }, { "id": "f1897b1f7b56d49d", "type": "function", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Transcode Queue", "func": "flow.set('_transcodeQueueUnlocked', true)\n", "outputs": 2, "noerr": 0, "initialize": "setInterval(() => {\n if(!flow.get('_transcodeQueueUnlocked')) {\n return\n }\n\n var queue = flow.get('_transcodeQueue')\n if(queue.length == 0) {\n node.send([ null, { \"payload\": null }])\n flow.set('_transcodeQueueUnlocked', false)\n return\n }\n var first = queue.pop()\n flow.set('_transcodeQueue', queue)\n\n flow.set('_transcodeQueueUnlocked', false)\n\n var name = path.basename(first)\n\n // replace file extension\n var segments = name.split('.')\n segments.pop()\n var videoContainer = flow.get('_videoContainer')\n segments.push(videoContainer)\n name = segments.join('.')\n\n var workFolder = flow.get('workFolder')\n var temp = path.join(workFolder, name)\n\n msg = {}\n var data = {\n 'original': first,\n 'temporary': temp\n }\n msg.payload = data\n msg.transcode = data\n msg.queue = queue\n \n node.send([ msg, null])\n}, 1000)\n", "finalize": "", "libs": [ { "var": "path", "module": "path" } ], "x": 250, "y": 280, "wires": [ [ "b4fd821e4032327a", "2023c957f816a653", "e45a9ec830c51602" ], [ "a78f6c483e1e2597", "db1094061707ac30" ] ] }, { "id": "54b14ea280430dea", "type": "change", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "Initialize Queue", "rules": [ { "t": "set", "p": "_transcodeQueueUnlocked", "pt": "flow", "to": "true", "tot": "bool" }, { "t": "set", "p": "_transcodeQueue", "pt": "flow", "to": "[]", "tot": "jsonata" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 240, "y": 220, "wires": [ [] ] }, { "id": "41accef666076be8", "type": "inject", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "init", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 210, "y": 160, "wires": [ [ "54b14ea280430dea" ] ] }, { "id": "1bde2da775a09b2b", "type": "debug", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "debug 12", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 1280, "y": 80, "wires": [] }, { "id": "e45a9ec830c51602", "type": "debug", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "debug 13", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 460, "y": 180, "wires": [] }, { "id": "c7abb9a6ba15539c", "type": "change", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "Initialize Queue", "rules": [ { "t": "set", "p": "_probeQueue", "pt": "flow", "to": "[]", "tot": "jsonata" }, { "t": "set", "p": "_probeQueueUnlocked", "pt": "flow", "to": "true", "tot": "bool" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 1280, "y": 900, "wires": [ [] ] }, { "id": "92f3af459ce90aa3", "type": "inject", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "init", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": true, "onceDelay": 0.1, "topic": "", "payload": "", "payloadType": "date", "x": 1110, "y": 900, "wires": [ [ "c7abb9a6ba15539c" ] ] }, { "id": "8781d05a891a5bb1", "type": "ui_text", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "group": "7c5d8648955e9a1f", "order": 6, "width": 0, "height": 0, "name": "", "label": "Scanning File", "format": "{{msg.payload}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 340, "y": 900, "wires": [] }, { "id": "4b49fa8ec7c43636", "type": "ui_text", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "group": "7c5d8648955e9a1f", "order": 7, "width": 0, "height": 0, "name": "", "label": "Probe Queue Size", "format": "{{msg.queue.length}}", "layout": "col-center", "className": "", "style": false, "font": "", "fontSize": 16, "color": "#000000", "x": 620, "y": 900, "wires": [] }, { "id": "6169a14dcff6f101", "type": "function", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Count Queue", "func": "var queue = flow.get('_probeQueue')\nif(!Array.isArray(queue)) {\n return\n}\nmsg.payload = queue.length\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 1200, "y": 1220, "wires": [ [ "790c3950a63fa109" ] ] }, { "id": "dc1ccff0ec482caa", "type": "function", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Set Run", "func": "flow.set('_run', false)", "outputs": 0, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 540, "y": 1460, "wires": [] }, { "id": "dacd4902bd230787", "type": "function", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "Set Run", "func": "flow.set('_run', true)\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 540, "y": 1220, "wires": [ [ "312a5bcb53309fbd" ] ] }, { "id": "0e3a3381bc3841f3", "type": "link out", "z": "9f98f36f3385bcd8", "g": "118640a37c2f6bac", "name": "link out 10", "mode": "link", "links": [ "3701809d5b290f02" ], "x": 925, "y": 1280, "wires": [] }, { "id": "2023c957f816a653", "type": "function", "z": "9f98f36f3385bcd8", "g": "1a8449d0623e98a6", "name": "function 7", "func": "msg.payload = msg.queue.length\nreturn msg;", "outputs": 1, "noerr": 0, "initialize": "", "finalize": "", "libs": [], "x": 460, "y": 340, "wires": [ [ "8c9ef2a07a0e271b" ] ] }, { "id": "d40dc9475eb9ecba", "type": "debug", "z": "9f98f36f3385bcd8", "g": "8c088cb2f7181ba2", "name": "debug 14", "active": false, "tosidebar": true, "console": false, "tostatus": false, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "auto", "x": 880, "y": 640, "wires": [] }, { "id": "7c5d8648955e9a1f", "type": "ui_group", "name": "Progress", "tab": "a5cec446271eb3ef", "order": 1, "disp": true, "width": "12", "collapse": false, "className": "" }, { "id": "57b96149bd97f3cb", "type": "ui_group", "name": "Control", "tab": "a5cec446271eb3ef", "order": 2, "disp": true, "width": "6", "collapse": false, "className": "" }, { "id": "85a1a4cd106754fa", "type": "ui_group", "name": "Settings", "tab": "a5cec446271eb3ef", "order": 5, "disp": true, "width": "6", "collapse": false, "className": "" }, { "id": "a5cec446271eb3ef", "type": "ui_tab", "name": "Transcoding", "icon": "dashboard", "disabled": false, "hidden": false } ]