# Cctv recording and playback (work in progress)

**URL:** https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478
**Category:** Share Your Projects
**Created:** [12 November 2022 04:22 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478 "2022-11-12T04:22:33Z")
**Posts on this page:** 20
**Page:** 2

<div class="post-metadata">

### Author: ![kevinGodell](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/kevingodell/32/27040_2.png) [@kevinGodell](https://discourse.nodered.org/u/kevinGodell)
#### Post date: [27 February 2023 21:49 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/21 "2023-02-27T21:49:33Z")

</div>

> [@krambriw](#):
>
> Does the ui\_mp4frag node know when the playlist is playing - paused - finished? If so, could the node not send messages when that happen?

The node is unaware of what is happening on the client side. It simply receives the playlist and sends it to the client side where there is an attempt to play it.

If you wanted to handle the client side event on the video element, you can add the event listener to detect `ended` and send a message back using scope.send. If you added this little piece of code on the controls, you could detect the event and send a message and somehow load the next video from your list. Of course, this would make it change for all viewers since the dashboard is designed to be single user.

```auto
const video = document.getElementById(msg.videoId);

            if (video) {

                video.addEventListener('ended', () => {
                    scope.send('it ended');
                });

```

This successfully sent a message to my debug panel.

 ![Screen Shot 2023-02-27 at 3.44.19 PM](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/8/5/8562205f4fe8f6778a0620b779a8233c9a4ffa15.png)

It would need more work so that to only detects the ended event when you want it to, maybe with some checkbox control. Just a proof of concept at this point...

---

<div class="post-metadata">

### Author: ![krambriw](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/krambriw/32/5429_2.png) [@krambriw](https://discourse.nodered.org/u/krambriw)
#### Post date: [28 February 2023 08:45 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/22 "2023-02-28T08:45:36Z")

</div>

> [@kevinGodell](#):
>
> ```auto
> video.addEventListener('ended', () => {
> scope.send('it ended');
> });
> 
> ```

Works great, really useful. Understood, it is events from the HTMLMediaElement you have to watch. Many useful and interesting:

> **[HTMLMediaElement: ended event - Web APIs | MDN](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/ended_event)**
>
> The ended event is fired when playback or streaming has stopped because the end of the media was reached or because no further data is available.

---

<div class="post-metadata">

### Author: ![kevinGodell](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/kevingodell/32/27040_2.png) [@kevinGodell](https://discourse.nodered.org/u/kevinGodell)
#### Post date: [28 February 2023 11:35 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/23 "2023-02-28T11:35:43Z")

</div>

In this situation, it might be better to set the `onended` property with the callback function vs adding the event listener. This will help to ensure that you have only added the callback a single time. And instead of removing the event listener, you can just delete the onended property (or maybe set to null or call removeAttribute, can't remember right now at the top of my head).

---

<div class="post-metadata">

### Author: ![krambriw](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/krambriw/32/5429_2.png) [@krambriw](https://discourse.nodered.org/u/krambriw)
#### Post date: [28 February 2023 18:27 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/24 "2023-02-28T18:27:09Z")

</div>

Well, both works fine, why is one better than the other?  
Best regards, Walter

```auto
            if (video) {

                video.onended = function() {myEnd()};

                function myEnd() {
                    scope.send('the end');
                }

                video.addEventListener('ended', () => {
                scope.send('it ended');
                });

                video.addEventListener('playing', () => {
                scope.send('it plays');
                });

                video.addEventListener('pause', () => {
                scope.send('it paused');
                });

```

---

<div class="post-metadata">

### Author: ![kevinGodell](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/kevingodell/32/27040_2.png) [@kevinGodell](https://discourse.nodered.org/u/kevinGodell)
#### Post date: [28 February 2023 22:43 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/25 "2023-02-28T22:43:50Z")

</div>

> [@krambriw](#):
>
> why is one better than the other?

Assigning it as a property only allows 1 handler, whereas adding the event listener will allow more than 1 handler. If you are only need a single callback, which is most likely the case for this particular situation, then using the property might be simpler and you won't accidentally add too many event listeners. Other than that, I don't know if one is better or just a matter of preference.

---

<div class="post-metadata">

### Author: ![kevinGodell](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/kevingodell/32/27040_2.png) [@kevinGodell](https://discourse.nodered.org/u/kevinGodell)
#### Post date: [18 March 2023 12:07 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/26 "2023-03-18T12:07:48Z")

</div>

Is anybody here using nginx along with the cctv stuff? I have been deep diving nginx over the last month and found some interesting techniques to offload some burden from the node-red instance, in particular when playing back recordings and using nginx's slice module. Hopefully I can get a small change to node-red core to allow this to work. If anybody is interested in nginx integration with the cctv things, then maybe we can start a new thread for that. Let me know.

---

<div class="post-metadata">

### Author: ![BartButenaers](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/bartbutenaers/32/10476_2.png) [@BartButenaers](https://discourse.nodered.org/u/BartButenaers)
#### Post date: [18 March 2023 14:50 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/27 "2023-03-18T14:50:48Z")

</div>

> [@kevinGodell](#):
>
> using nginx's slice module. Hopefully I can get a small change to node-red core to allow this to work. If anybody is interested in nginx integration with the cctv things, then maybe we can start a new thread for that.

If you found something interesting, please yes explain it a new thread 😉

---

<div class="post-metadata">

### Author: ![allacmc](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/allacmc/32/73549_2.png) [@allacmc](https://discourse.nodered.org/u/allacmc)
#### Post date: [19 June 2023 19:03 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/28 "2023-06-19T19:03:18Z")

</div>

How is this project that involves the recordings?

I'm going to fall into those possibilities now that I've managed to make it work. The RTSP camera on the system.

---

<div class="post-metadata">

### Author: ![krambriw](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/krambriw/32/5429_2.png) [@krambriw](https://discourse.nodered.org/u/krambriw)
#### Post date: [19 June 2023 19:13 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/29 "2023-06-19T19:13:29Z")

</div>

To my knowledge, this worked when I added & worked with a sample to my flow (using that RedBull stream, but it should work with your camera(s) as well in the same way

---

<div class="post-metadata">

### Author: ![allacmc](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/allacmc/32/73549_2.png) [@allacmc](https://discourse.nodered.org/u/allacmc)
#### Post date: [20 June 2023 01:17 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/30 "2023-06-20T01:17:30Z")

</div>

Who is the user who creates these folders so I can give this mkdir permission?

I'm a little lost on this item of code ??

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/a/9/a9210646d1f4c68c10927198609bf2f6a26a055c.png)

```auto
[{"id":"f3874cf4a963908d","type":"subflow","name":"recording","info":"","category":"","in":[{"x":80,"y":160,"wires":[{"id":"d0615cb16ccf58fb"}]}],"out":[{"x":320,"y":120,"wires":[{"id":"d0615cb16ccf58fb","port":0}]}],"env":[{"name":"STATIC_PATH","type":"str","value":"/run/media/system/surveillance1/cctv/recordings"},{"name":"STATIC_ROOT","type":"str","value":"/static/recordings"}],"meta":{},"color":"#DDAA99","status":{"x":320,"y":200,"wires":[{"id":"d0615cb16ccf58fb","port":1}]}},{"id":"d0615cb16ccf58fb","type":"function","z":"f3874cf4a963908d","name":"recording","func":"const { payload, payload: { length }, duration, topic } = msg;\n\nconst [, id, , type] = topic.split('/');\n\nconst recording = context.get('recording');\n\nif (type === 'seg' || type === 'pre') {\n\n const { mp4WriteStream = {}, m3u8WriteStream = {}, mp4Name, bytesWritten, segments, playlist } = recording;\n\n if (mp4WriteStream.writable && m3u8WriteStream.writable) {\n\n // write live (seg) or previous (pre) segments to existing writeStreams\n\n mp4WriteStream.write(payload);\n\n m3u8WriteStream.write(`#EXTINF:${duration}\\n#EXT-X-BYTERANGE:${length}@${bytesWritten}\\n${mp4Name}\\n`);\n\n recording.bytesWritten += length;\n\n recording.segments++;\n\n // only send playlist after the first segment is written so that hls.js does not throw Hls.Events.ERROR, Hls.ErrorTypes.NETWORK_ERROR, Hls.ErrorDetails.LEVEL_EMPTY_ERROR\n\n if (segments === 0) {\n\n return { payload: playlist };\n\n }\n\n } else {\n\n node.error(`mp4WriteStream and m3u8WriteStream must be writable`);\n\n }\n\n return;\n\n}\n\nif (type === 'init') {\n\n // create new or end existing write streams based on buffer length\n\n if (length) {\n\n const date = new Date();\n\n const year = date.getFullYear();\n\n const month = (date.getMonth() + 1).toString().padStart(2, '0');\n\n const day = date.getDate().toString().padStart(2, '0');\n\n const hours = date.getHours().toString().padStart(2, '0');\n\n const minutes = date.getMinutes().toString().padStart(2, '0');\n\n const seconds = date.getSeconds().toString().padStart(2, '0');\n\n // create name based on timestamp\n\n const baseName = `${hours}h.${minutes}m.${seconds}s`; // 00h.00m.00s\n\n const mp4Name = `${baseName}.mp4`; // 00h.00m.00s.mp4\n\n const m3u8Name = `${baseName}.m3u8`; // 00h.00m.00s.m3u8\n\n const baseRoot = `${env.get('STATIC_ROOT')}/${id}`;\n\n const basePath = `${env.get('STATIC_PATH')}/${id}`;\n\n // create dir base on timestamp\n\n const fullDir = `${basePath}/${year}/${month}/${day}`; // basePath/yyyy/mm/dd\n\n const { mkdirSync, createWriteStream, writeFile } = fs;\n\n const newDir = mkdirSync(fullDir, { recursive: true });\n\n newDir && writeFile(`${basePath}/listing.txt`, `${year}/${month}/${day}\\n`, { encoding: 'utf8', flag: 'a' }, (err) => { err && node.error(err) });\n\n writeFile(`${fullDir}/listing.txt`, `${m3u8Name}\\n`, { encoding: 'utf8', flag: 'a' }, (err) => { err && node.error(err) });\n\n // create new write streams for mp4 and playlist\n \n const mp4WriteStream = createWriteStream(`${fullDir}/${mp4Name}`)\n .on('error', (err) => {\n node.error(err);\n });\n\n const m3u8WriteStream = createWriteStream(`${fullDir}/${m3u8Name}`)\n .on('error', (err) => {\n node.error(err);\n });\n\n mp4WriteStream.write(payload);\n\n m3u8WriteStream.write(`#EXTM3U\\n#EXT-X-VERSION:7\\n#EXT-X-PLAYLIST-TYPE:EVENT\\n#EXT-X-TARGETDURATION:${Math.round(duration) || 1}\\n#EXT-X-MEDIA-SEQUENCE:0\\n#EXT-X-MAP:URI=\"${mp4Name}\",BYTERANGE=\"${length}@0\"\\n`);\n\n recording.mp4WriteStream = mp4WriteStream;\n\n recording.m3u8WriteStream = m3u8WriteStream;\n\n recording.mp4Name = mp4Name;\n\n recording.bytesWritten = length;\n\n recording.segments = 0;\n\n // save playlist\n\n recording.playlist = `${baseRoot}/${year}/${month}/${day}/${m3u8Name}`;\n\n // send status\n\n return [null, { payload: { fill: 'green', shape: \"dot\", text: `${id}/${year}/${month}/${day}/${baseName}` } }];\n\n } else {\n\n // end existing writeStreams\n\n const { mp4WriteStream = {}, m3u8WriteStream = {} } = recording;\n\n if (mp4WriteStream.writable) {\n\n mp4WriteStream.end();\n\n }\n\n if (m3u8WriteStream.writable) {\n\n m3u8WriteStream.end('#EXT-X-ENDLIST\\n');\n\n }\n\n return;\n\n }\n\n}","outputs":2,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\n\ncontext.set('recording', {});","finalize":"// Code added here will be run when the\n// node is being stopped or re-deployed.\n\nconst { mp4WriteStream = {}, m3u8WriteStream = {} } = context.get('recording');\n\nif (mp4WriteStream.writable) {\n\n mp4WriteStream.end();\n\n}\n\nif (m3u8WriteStream.writable) {\n\n m3u8WriteStream.end('#EXT-X-ENDLIST\\n');\n\n}\n\ncontext.set('recording', undefined);","libs":[{"var":"fs","module":"fs"}],"x":200,"y":160,"wires":[[],[]]},{"id":"32d61e0f.0490b2","type":"subflow","name":"stderr","info":"","category":"","in":[{"x":60,"y":80,"wires":[{"id":"211d46bf.a79c7a"}]}],"out":[],"env":[],"color":"#DDAA99","status":{"x":280,"y":120,"wires":[{"id":"211d46bf.a79c7a","port":1}]}},{"id":"211d46bf.a79c7a","type":"function","z":"32d61e0f.0490b2","name":"stderr","func":"// convert payload for Buffer to String and split by newlines\n\nconst stderr = msg.payload.toString().split('\\n');\n\nnode.send([{ stderr }, { payload: { fill: 'red', text: `${new Date().toString()}` } }]);","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":170,"y":80,"wires":[["c6e5408.f19c4c"],[]]},{"id":"c6e5408.f19c4c","type":"debug","z":"32d61e0f.0490b2","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"stderr","targetType":"msg","statusVal":"","statusType":"auto","x":330,"y":40,"wires":[]},{"id":"9b59c1a2222210b8","type":"inject","z":"5273a6de.b59318","name":"start","props":[{"p":"action","v":"{\"command\":\"start\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":"5","topic":"","x":450,"y":340,"wires":[["7a8c3f95b486b8f9"]]},{"id":"b4a8c0bc44499078","type":"inject","z":"5273a6de.b59318","name":"restart","props":[{"p":"action","v":"{\"command\":\"restart\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","x":450,"y":380,"wires":[["7a8c3f95b486b8f9"]]},{"id":"7dde9f15d2aa5933","type":"inject","z":"5273a6de.b59318","name":"stop","props":[{"p":"action","v":"{\"command\":\"stop\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":450,"y":420,"wires":[["7a8c3f95b486b8f9"]]},{"id":"58b77ee9a2e29092","type":"mp4frag","z":"5273a6de.b59318","name":"","outputs":2,"basePath":"telemetria","serveHttp":"false","serveIo":"false","hlsPlaylistSize":4,"hlsPlaylistExtra":0,"autoStart":"true","preBuffer":"0","timeLimit":"-1","repeated":"false","statusData":"playlist","x":1010,"y":100,"wires":[[],["571c8af49dfc40ae"]]},{"id":"a8741b7c88d88102","type":"subflow:32d61e0f.0490b2","z":"5273a6de.b59318","name":"","x":910,"y":340,"wires":[]},{"id":"ef6008f4d7425c22","type":"inject","z":"5273a6de.b59318","name":"write restart","props":[{"p":"action","v":"{\"command\":\"restart\",\"subject\":\"write\",\"preBuffer\":1,\"timeLimit\":-1,\"repeated\":false}","vt":"json"}],"repeat":"","crontab":"*/15 0-23 * * *","once":false,"onceDelay":"5","topic":"","x":750,"y":60,"wires":[["58b77ee9a2e29092"]]},{"id":"e23111864612f2c5","type":"inject","z":"5273a6de.b59318","name":"write stop","props":[{"p":"action","v":"{\"command\":\"stop\",\"subject\":\"write\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":760,"y":100,"wires":[["58b77ee9a2e29092"]]},{"id":"571c8af49dfc40ae","type":"subflow:f3874cf4a963908d","z":"5273a6de.b59318","name":"","x":1320,"y":180,"wires":[["e2da3c3cee315665"]]},{"id":"e2da3c3cee315665","type":"ui_mp4frag","z":"5273a6de.b59318","name":"","group":"9a076144e0b8c687","order":1,"width":"20","height":"14","readyPoster":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAMAAACtqHJCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODIyQUIzREQ3RTA0MDAyNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRkU5MkM4RDJCNTkxMUVCQjJCOTg0MEQ0QThCQ0YyQyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRkU5MkM4QzJCNTkxMUVCQjJCOTg0MEQ0QThCQ0YyQyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6Rjc3RjExNzQwNzIwNjgxMTgyMkFDQTkzQjdGQkY1MzAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4MDExNzQwNzIwNjgxMTgyMkFCM0REN0UwNDAwMjQiLz4gPC9yZGY6RGVzY3Jp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","errorPoster":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAMAAACtqHJCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODIyQUIzREQ3RTA0MDAyNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRkU5MkM5MTJCNTkxMUVCQjJCOTg0MEQ0QThCQ0YyQyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRkU5MkM5MDJCNTkxMUVCQjJCOTg0MEQ0QThCQ0YyQyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6Rjc3RjExNzQwNzIwNjgxMTgyMkFDQTkzQjdGQkY1MzAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4MDExNzQwNzIwNjgxMTgyMkFCM0REN0UwNDAwMjQiLz4gPC9yZGY6RGVzY3Jp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","hlsJsConfig":"{\"startPosition\":0,\"liveDurationInfinity\":false,\"progressive\":false}","autoplay":"true","unload":"false","threshold":0.1,"controls":"true","muted":"true","players":["hls.js","hls","socket.io","mp4"],"x":1330,"y":240,"wires":[["9ec4f3ad80a3ec74"]]},{"id":"9ec4f3ad80a3ec74","type":"ui_template","z":"5273a6de.b59318","group":"9a076144e0b8c687","name":"video controls","order":15,"width":"20","height":"1","format":"<div layout-fill layout=\"row\">\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"toggleControls()\">\n <md-icon class=\"material-icons\">smart_button</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"playPause()\">\n <md-icon class=\"material-icons\">play_arrow</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"forwardFive()\">\n <md-icon class=\"material-icons\">forward_5</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"replayFive()\">\n <md-icon class=\"material-icons\">replay_5</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"decreaseRate()\">\n <md-icon class=\"material-icons\">slow_motion_video</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"increaseRate()\">\n <md-icon class=\"material-icons\">speed</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"toggleMuted()\">\n <md-icon class=\"material-icons\">volume_mute</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"videoFullscreen()\">\n <md-icon class=\"material-icons\">fullscreen</md-icon>\n </md-button>\n </div>\n\n</div>\n\n<script>\n ((scope) => {\n\n const unwatchMsg = scope.$watch('msg', (msg) => {\n\n if (msg && msg.videoId) {\n\n const video = document.getElementById(msg.videoId);\n \n if (video) {\n\n const rates = [0.1, 0.2, 0.5, 1, 2, 5, 10];\n\n let rateIndex = 3;\n\n const requestFullscreen = video.webkitRequestFullscreen || video.webkitEnterFullScreen || video.mozRequestFullScreen || video.requestFullscreen;\n\n scope.toggleControls = () => {\n\n video.controls = !video.controls;\n\n }\n\n scope.playPause = () => {\n\n video.paused ? video.play() : video.pause();\n\n }\n\n scope.toggleMuted = () => {\n\n video.muted = !video.muted;\n\n }\n\n scope.videoFullscreen = () => {\n\n requestFullscreen.call(video);\n\n }\n\n scope.videoDisplay = () => {\n\n video.style.display = video.style.display === 'none' ? 'initial' : 'none';\n\n }\n\n scope.increaseRate = () => {\n\n rateIndex++;\n\n if (rateIndex > 6) {\n\n rateIndex = 6;\n\n }\n\n video.playbackRate = rates[rateIndex];\n\n }\n\n scope.decreaseRate = () => {\n \n rateIndex--;\n \n if (rateIndex < 0) {\n\n rateIndex = 0;\n \n }\n \n video.playbackRate = rates[rateIndex];\n \n }\n\n scope.forwardFive = () => {\n \n video.currentTime += 5;\n \n }\n\n scope.replayFive = () => {\n \n video.currentTime -= 5;\n \n }\n\n unwatchMsg();\n\n }\n\n }\n\n });\n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1340,"y":340,"wires":[[]]},{"id":"cfa37a1fd41f4570","type":"group","z":"5273a6de.b59318","name":"Camera IP - Protocolo RTSP","style":{"stroke":"#ffC000","fill":"#ffefbf","label":true,"color":"#6f2fa0"},"nodes":["7a8c3f95b486b8f9","4cc7afbbcc802243","652bde80360a1fe1","d1112dfcbfef615b","2858e68578995d73","379c223a6ef2f8ea","adffeae04f85fed5","96e79d1eccb9df9d"],"x":74,"y":139,"w":1132,"h":122},{"id":"7a8c3f95b486b8f9","type":"ffmpeg","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","outputs":3,"cmdPath":"ffmpeg","cmdArgs":"[]","cmdOutputs":2,"killSignal":"SIGTERM","x":750,"y":200,"wires":[["4cc7afbbcc802243","58b77ee9a2e29092"],["4cc7afbbcc802243","58b77ee9a2e29092"],["a8741b7c88d88102"]]},{"id":"4cc7afbbcc802243","type":"mp4frag","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","outputs":2,"basePath":"teste","serveHttp":"true","serveIo":"true","hlsPlaylistSize":"6","hlsPlaylistExtra":"2","autoStart":"false","preBuffer":1,"timeLimit":10000,"repeated":"false","statusData":"playlist","x":920,"y":200,"wires":[["652bde80360a1fe1"],[]]},{"id":"652bde80360a1fe1","type":"ui_mp4frag","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","group":"46d6d60673638810","order":1,"width":22,"height":13,"readyPoster":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHNoYXBlLXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB2aWV3Qm94PSIwIDAgODAwMCA2MDAwIj48dGV4dCBmaWxsPSIjMGYwIiBzdHJva2Utd2lkdGg9IjAiIGR4PSIwIiBkeT0iMCIgZm9udC1mYW1pbHk9IiZxdW90O2UyY3B1d3p4YldsMTo6OlJvYm90byZxdW90OyIgZm9udC1zaXplPSI2MDAiIGZvbnQtd2VpZ2h0PSI3MDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKCAxMDExLjI0IDMyMDQuODgpIj48dHNwYW4geT0iMCIgc3Ryb2tlLXdpZHRoPSIwIiBmb250LXdlaWdodD0iNzAwIj48IVtDREFUQVsKVmlkZW8gUGxheWJhY2sgUmVhZHkKXV0+PC90c3Bhbj48L3RleHQ+PC9zdmc+Cg==","errorPoster":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHNoYXBlLXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB2aWV3Qm94PSIwIDAgODAwMCA2MDAwIj48dGV4dCBmaWxsPSJyZWQiIHN0cm9rZS13aWR0aD0iMCIgZHg9IjAiIGR5PSIwIiBmb250LWZhbWlseT0iJnF1b3Q7ZW42OXdhSUtnN0MxOjo6Um9ib3RvJnF1b3Q7IiBmb250LXNpemU9IjYwMCIgZm9udC13ZWlnaHQ9IjcwMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoIDExODMuOTYgMzIwNC44OCkiPjx0c3BhbiB5PSIwIiBzdHJva2Utd2lkdGg9IjAiIGZvbnQtd2VpZ2h0PSI3MDAiPjwhW0NEQVRBWwpWaWRlbyBQbGF5YmFjayBFcnJvcgpdXT48L3RzcGFuPjwvdGV4dD48L3N2Zz4K","hlsJsConfig":"{\"liveDurationInfinity\":true,\"liveBackBufferLength\":5,\"maxBufferLength\":10,\"manifestLoadingTimeOut\":1000,\"manifestLoadingMaxRetry\":10,\"manifestLoadingRetryDelay\":500}","autoplay":"true","unload":"true","threshold":0.1,"controls":"true","muted":"true","players":["socket.io","hls.js","hls","mp4"],"x":1110,"y":200,"wires":[[]]},{"id":"d1112dfcbfef615b","type":"ui_button","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","group":"46d6d60673638810","order":1,"width":0,"height":0,"passthru":false,"label":"Start","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":150,"y":180,"wires":[["adffeae04f85fed5"]]},{"id":"2858e68578995d73","type":"ui_button","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","group":"46d6d60673638810","order":1,"width":0,"height":0,"passthru":false,"label":"Stop","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":150,"y":220,"wires":[["379c223a6ef2f8ea"]]},{"id":"379c223a6ef2f8ea","type":"change","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","rules":[{"t":"set","p":"action","pt":"msg","to":"{\"command\":\"stop\",\"args\":[\"-loglevel\",\"quiet\",\"-rtsp_transport\",\"tcp\",\"-i\",\"rtsp://belov:belov12345678@192.168.1.129:554/cam/realmonitor?channel=1&subtype=0\",\"-an\",\"-c:v\",\"copy\",\"-f\",\"mp4\",\"-movflags\",\"+frag_every_frame+empty_moov+default_base_moof\",\"-min_frag_duration\",\"1000000\",\"pipe:1\"]}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":220,"wires":[["7a8c3f95b486b8f9"]]},{"id":"adffeae04f85fed5","type":"change","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","rules":[{"t":"set","p":"URLCAM","pt":"msg","to":"#:(persistent)::URLCAM_conf","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":180,"wires":[["96e79d1eccb9df9d"]]},{"id":"96e79d1eccb9df9d","type":"change","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","rules":[{"t":"set","p":"action","pt":"msg","to":"{\"command\":\"start\",\"args\":[\"-loglevel\",\"quiet\",\"-rtsp_transport\",\"tcp\",\"-i\", $.URLCAM,\"-an\",\"-c:v\",\"copy\",\"-f\",\"mp4\",\"-movflags\",\"+frag_every_frame+empty_moov+default_base_moof\",\"-min_frag_duration\",\"1000000\",\"pipe:1\"]}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":180,"wires":[["7a8c3f95b486b8f9"]]},{"id":"46d6d60673638810","type":"ui_group","name":"","tab":"9eb9a9fcd56859ba","order":1,"disp":true,"width":"22","collapse":false,"className":""},{"id":"9eb9a9fcd56859ba","type":"ui_tab","name":"Camera Mergulhador","icon":"dashboard","order":2,"disabled":false,"hidden":false},{"id":"9a076144e0b8c687","type":"ui_group","name":"shared","tab":"e671ec4ca9656cab","order":1,"disp":true,"width":"20","collapse":false,"className":""},{"id":"e671ec4ca9656cab","type":"ui_tab","name":"shared","icon":"dashboard","disabled":false,"hidden":false}]

```

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/1/d/1dd47484222b1c8b7b3a76eb2c8f385cc4f8f3b8.png)

---

<div class="post-metadata">

### Author: ![allacmc](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/allacmc/32/73549_2.png) [@allacmc](https://discourse.nodered.org/u/allacmc)
#### Post date: [20 June 2023 01:40 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/31 "2023-06-20T01:40:41Z")

</div>

I solved with chmod 777 in dir.

---

<div class="post-metadata">

### Author: ![allacmc](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/allacmc/32/73549_2.png) [@allacmc](https://discourse.nodered.org/u/allacmc)
#### Post date: [20 June 2023 01:41 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/32 "2023-06-20T01:41:44Z")

</div>

Is possible send me link or here, with code the sample with updated ?

I try is not sucess

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/8/5/851d1b5e4eb8828b226fbcd171fd0a10bb731bfa.png)

```auto
[{"id":"f3874cf4a963908d","type":"subflow","name":"recording","info":"","category":"","in":[{"x":80,"y":160,"wires":[{"id":"d0615cb16ccf58fb"}]}],"out":[{"x":320,"y":120,"wires":[{"id":"d0615cb16ccf58fb","port":0}]}],"env":[{"name":"STATIC_PATH","type":"str","value":"/run/media/system/surveillance1/cctv/recordings"},{"name":"STATIC_ROOT","type":"str","value":"/static/recordings"}],"meta":{},"color":"#DDAA99","status":{"x":320,"y":200,"wires":[{"id":"d0615cb16ccf58fb","port":1}]}},{"id":"d0615cb16ccf58fb","type":"function","z":"f3874cf4a963908d","name":"recording","func":"const { payload, payload: { length }, duration, topic } = msg;\n\nconst [, id, , type] = topic.split('/');\n\nconst recording = context.get('recording');\n\nif (type === 'seg' || type === 'pre') {\n\n const { mp4WriteStream = {}, m3u8WriteStream = {}, mp4Name, bytesWritten, segments, playlist } = recording;\n\n if (mp4WriteStream.writable && m3u8WriteStream.writable) {\n\n // write live (seg) or previous (pre) segments to existing writeStreams\n\n mp4WriteStream.write(payload);\n\n m3u8WriteStream.write(`#EXTINF:${duration}\\n#EXT-X-BYTERANGE:${length}@${bytesWritten}\\n${mp4Name}\\n`);\n\n recording.bytesWritten += length;\n\n recording.segments++;\n\n // only send playlist after the first segment is written so that hls.js does not throw Hls.Events.ERROR, Hls.ErrorTypes.NETWORK_ERROR, Hls.ErrorDetails.LEVEL_EMPTY_ERROR\n\n if (segments === 0) {\n\n return { payload: playlist };\n\n }\n\n } else {\n\n node.error(`mp4WriteStream and m3u8WriteStream must be writable`);\n\n }\n\n return;\n\n}\n\nif (type === 'init') {\n\n // create new or end existing write streams based on buffer length\n\n if (length) {\n\n const date = new Date();\n\n const year = date.getFullYear();\n\n const month = (date.getMonth() + 1).toString().padStart(2, '0');\n\n const day = date.getDate().toString().padStart(2, '0');\n\n const hours = date.getHours().toString().padStart(2, '0');\n\n const minutes = date.getMinutes().toString().padStart(2, '0');\n\n const seconds = date.getSeconds().toString().padStart(2, '0');\n\n // create name based on timestamp\n\n const baseName = `${hours}h.${minutes}m.${seconds}s`; // 00h.00m.00s\n\n const mp4Name = `${baseName}.mp4`; // 00h.00m.00s.mp4\n\n const m3u8Name = `${baseName}.m3u8`; // 00h.00m.00s.m3u8\n\n const baseRoot = `${env.get('STATIC_ROOT')}/${id}`;\n\n const basePath = `${env.get('STATIC_PATH')}/${id}`;\n\n // create dir base on timestamp\n\n const fullDir = `${basePath}/${year}/${month}/${day}`; // basePath/yyyy/mm/dd\n\n const { mkdirSync, createWriteStream, writeFile } = fs;\n\n const newDir = mkdirSync(fullDir, { recursive: true });\n\n newDir && writeFile(`${basePath}/listing.txt`, `${year}/${month}/${day}\\n`, { encoding: 'utf8', flag: 'a' }, (err) => { err && node.error(err) });\n\n writeFile(`${fullDir}/listing.txt`, `${m3u8Name}\\n`, { encoding: 'utf8', flag: 'a' }, (err) => { err && node.error(err) });\n\n // create new write streams for mp4 and playlist\n \n const mp4WriteStream = createWriteStream(`${fullDir}/${mp4Name}`)\n .on('error', (err) => {\n node.error(err);\n });\n\n const m3u8WriteStream = createWriteStream(`${fullDir}/${m3u8Name}`)\n .on('error', (err) => {\n node.error(err);\n });\n\n mp4WriteStream.write(payload);\n\n m3u8WriteStream.write(`#EXTM3U\\n#EXT-X-VERSION:7\\n#EXT-X-PLAYLIST-TYPE:EVENT\\n#EXT-X-TARGETDURATION:${Math.round(duration) || 1}\\n#EXT-X-MEDIA-SEQUENCE:0\\n#EXT-X-MAP:URI=\"${mp4Name}\",BYTERANGE=\"${length}@0\"\\n`);\n\n recording.mp4WriteStream = mp4WriteStream;\n\n recording.m3u8WriteStream = m3u8WriteStream;\n\n recording.mp4Name = mp4Name;\n\n recording.bytesWritten = length;\n\n recording.segments = 0;\n\n // save playlist\n\n recording.playlist = `${baseRoot}/${year}/${month}/${day}/${m3u8Name}`;\n\n // send status\n\n return [null, { payload: { fill: 'green', shape: \"dot\", text: `${id}/${year}/${month}/${day}/${baseName}` } }];\n\n } else {\n\n // end existing writeStreams\n\n const { mp4WriteStream = {}, m3u8WriteStream = {} } = recording;\n\n if (mp4WriteStream.writable) {\n\n mp4WriteStream.end();\n\n }\n\n if (m3u8WriteStream.writable) {\n\n m3u8WriteStream.end('#EXT-X-ENDLIST\\n');\n\n }\n\n return;\n\n }\n\n}","outputs":2,"noerr":0,"initialize":"// Code added here will be run once\n// whenever the node is deployed.\n\ncontext.set('recording', {});","finalize":"// Code added here will be run when the\n// node is being stopped or re-deployed.\n\nconst { mp4WriteStream = {}, m3u8WriteStream = {} } = context.get('recording');\n\nif (mp4WriteStream.writable) {\n\n mp4WriteStream.end();\n\n}\n\nif (m3u8WriteStream.writable) {\n\n m3u8WriteStream.end('#EXT-X-ENDLIST\\n');\n\n}\n\ncontext.set('recording', undefined);","libs":[{"var":"fs","module":"fs"}],"x":200,"y":160,"wires":[[],[]]},{"id":"32d61e0f.0490b2","type":"subflow","name":"stderr","info":"","category":"","in":[{"x":60,"y":80,"wires":[{"id":"211d46bf.a79c7a"}]}],"out":[],"env":[],"color":"#DDAA99","status":{"x":280,"y":120,"wires":[{"id":"211d46bf.a79c7a","port":1}]}},{"id":"211d46bf.a79c7a","type":"function","z":"32d61e0f.0490b2","name":"stderr","func":"// convert payload for Buffer to String and split by newlines\n\nconst stderr = msg.payload.toString().split('\\n');\n\nnode.send([{ stderr }, { payload: { fill: 'red', text: `${new Date().toString()}` } }]);","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":170,"y":80,"wires":[["c6e5408.f19c4c"],[]]},{"id":"c6e5408.f19c4c","type":"debug","z":"32d61e0f.0490b2","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"stderr","targetType":"msg","statusVal":"","statusType":"auto","x":330,"y":40,"wires":[]},{"id":"9b59c1a2222210b8","type":"inject","z":"5273a6de.b59318","name":"start","props":[{"p":"action","v":"{\"command\":\"start\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":"5","topic":"","x":150,"y":320,"wires":[["adffeae04f85fed5"]]},{"id":"b4a8c0bc44499078","type":"inject","z":"5273a6de.b59318","name":"restart","props":[{"p":"action","v":"{\"command\":\"restart\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":"1","topic":"","x":150,"y":360,"wires":[["7a8c3f95b486b8f9"]]},{"id":"7dde9f15d2aa5933","type":"inject","z":"5273a6de.b59318","name":"stop","props":[{"p":"action","v":"{\"command\":\"stop\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":400,"wires":[["7a8c3f95b486b8f9"]]},{"id":"58b77ee9a2e29092","type":"mp4frag","z":"5273a6de.b59318","name":"","outputs":2,"basePath":"telemetria","serveHttp":"false","serveIo":"false","hlsPlaylistSize":4,"hlsPlaylistExtra":0,"autoStart":"true","preBuffer":"0","timeLimit":"-1","repeated":"false","statusData":"playlist","x":1010,"y":100,"wires":[[],["571c8af49dfc40ae"]]},{"id":"a8741b7c88d88102","type":"subflow:32d61e0f.0490b2","z":"5273a6de.b59318","name":"","x":910,"y":340,"wires":[]},{"id":"ef6008f4d7425c22","type":"inject","z":"5273a6de.b59318","name":"write restart","props":[{"p":"action","v":"{\"command\":\"restart\",\"subject\":\"write\",\"preBuffer\":1,\"timeLimit\":-1,\"repeated\":false}","vt":"json"}],"repeat":"","crontab":"*/15 0-23 * * *","once":false,"onceDelay":"5","topic":"","x":750,"y":60,"wires":[["58b77ee9a2e29092"]]},{"id":"e23111864612f2c5","type":"inject","z":"5273a6de.b59318","name":"write stop","props":[{"p":"action","v":"{\"command\":\"stop\",\"subject\":\"write\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":760,"y":100,"wires":[["58b77ee9a2e29092"]]},{"id":"571c8af49dfc40ae","type":"subflow:f3874cf4a963908d","z":"5273a6de.b59318","name":"","env":[{"name":"STATIC_PATH","value":"/var/www/arq","type":"str"},{"name":"STATIC_ROOT","value":"/var/www/arq","type":"str"}],"x":1320,"y":180,"wires":[["e2da3c3cee315665"]]},{"id":"e2da3c3cee315665","type":"ui_mp4frag","z":"5273a6de.b59318","name":"","group":"9a076144e0b8c687","order":1,"width":"20","height":"14","readyPoster":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAMAAACtqHJCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODIyQUIzREQ3RTA0MDAyNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRkU5MkM4RDJCNTkxMUVCQjJCOTg0MEQ0QThCQ0YyQyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRkU5MkM4QzJCNTkxMUVCQjJCOTg0MEQ0QThCQ0YyQyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6Rjc3RjExNzQwNzIwNjgxMTgyMkFDQTkzQjdGQkY1MzAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4MDExNzQwNzIwNjgxMTgyMkFCM0REN0UwNDAwMjQiLz4gPC9yZGY6RGVzY3Jp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","errorPoster":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAAJYCAMAAACtqHJCAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2hpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowMzgwMTE3NDA3MjA2ODExODIyQUIzREQ3RTA0MDAyNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDpGRkU5MkM5MTJCNTkxMUVCQjJCOTg0MEQ0QThCQ0YyQyIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpGRkU5MkM5MDJCNTkxMUVCQjJCOTg0MEQ0QThCQ0YyQyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M2IChNYWNpbnRvc2gpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6Rjc3RjExNzQwNzIwNjgxMTgyMkFDQTkzQjdGQkY1MzAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDM4MDExNzQwNzIwNjgxMTgyMkFCM0REN0UwNDAwMjQiLz4gPC9yZGY6RGVzY3Jp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","hlsJsConfig":"{\"startPosition\":0,\"liveDurationInfinity\":false,\"progressive\":false}","autoplay":"true","unload":"false","threshold":0.1,"controls":"true","muted":"true","players":["hls.js","hls","socket.io","mp4"],"x":1330,"y":340,"wires":[["9ec4f3ad80a3ec74"]]},{"id":"9ec4f3ad80a3ec74","type":"ui_template","z":"5273a6de.b59318","group":"9a076144e0b8c687","name":"video controls","order":15,"width":"20","height":"1","format":"<div layout-fill layout=\"row\">\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"toggleControls()\">\n <md-icon class=\"material-icons\">smart_button</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"playPause()\">\n <md-icon class=\"material-icons\">play_arrow</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"forwardFive()\">\n <md-icon class=\"material-icons\">forward_5</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"replayFive()\">\n <md-icon class=\"material-icons\">replay_5</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"decreaseRate()\">\n <md-icon class=\"material-icons\">slow_motion_video</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"increaseRate()\">\n <md-icon class=\"material-icons\">speed</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"toggleMuted()\">\n <md-icon class=\"material-icons\">volume_mute</md-icon>\n </md-button>\n </div>\n\n <div layout-fill>\n <md-button layout-fill class=\"md-raised\" ng-click=\"videoFullscreen()\">\n <md-icon class=\"material-icons\">fullscreen</md-icon>\n </md-button>\n </div>\n\n</div>\n\n<script>\n ((scope) => {\n\n const unwatchMsg = scope.$watch('msg', (msg) => {\n\n if (msg && msg.videoId) {\n\n const video = document.getElementById(msg.videoId);\n \n if (video) {\n\n const rates = [0.1, 0.2, 0.5, 1, 2, 5, 10];\n\n let rateIndex = 3;\n\n const requestFullscreen = video.webkitRequestFullscreen || video.webkitEnterFullScreen || video.mozRequestFullScreen || video.requestFullscreen;\n\n scope.toggleControls = () => {\n\n video.controls = !video.controls;\n\n }\n\n scope.playPause = () => {\n\n video.paused ? video.play() : video.pause();\n\n }\n\n scope.toggleMuted = () => {\n\n video.muted = !video.muted;\n\n }\n\n scope.videoFullscreen = () => {\n\n requestFullscreen.call(video);\n\n }\n\n scope.videoDisplay = () => {\n\n video.style.display = video.style.display === 'none' ? 'initial' : 'none';\n\n }\n\n scope.increaseRate = () => {\n\n rateIndex++;\n\n if (rateIndex > 6) {\n\n rateIndex = 6;\n\n }\n\n video.playbackRate = rates[rateIndex];\n\n }\n\n scope.decreaseRate = () => {\n \n rateIndex--;\n \n if (rateIndex < 0) {\n\n rateIndex = 0;\n \n }\n \n video.playbackRate = rates[rateIndex];\n \n }\n\n scope.forwardFive = () => {\n \n video.currentTime += 5;\n \n }\n\n scope.replayFive = () => {\n \n video.currentTime -= 5;\n \n }\n\n unwatchMsg();\n\n }\n\n }\n\n });\n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":1340,"y":440,"wires":[[]]},{"id":"cfa37a1fd41f4570","type":"group","z":"5273a6de.b59318","name":"Camera IP - Protocolo RTSP","style":{"stroke":"#ffC000","fill":"#ffefbf","label":true,"color":"#6f2fa0"},"nodes":["7a8c3f95b486b8f9","4cc7afbbcc802243","652bde80360a1fe1","d1112dfcbfef615b","2858e68578995d73","379c223a6ef2f8ea","adffeae04f85fed5","96e79d1eccb9df9d"],"x":74,"y":139,"w":1132,"h":122},{"id":"7a8c3f95b486b8f9","type":"ffmpeg","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","outputs":3,"cmdPath":"ffmpeg","cmdArgs":"[]","cmdOutputs":2,"killSignal":"SIGTERM","x":750,"y":200,"wires":[["4cc7afbbcc802243","58b77ee9a2e29092"],["4cc7afbbcc802243","58b77ee9a2e29092"],["a8741b7c88d88102"]]},{"id":"4cc7afbbcc802243","type":"mp4frag","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","outputs":2,"basePath":"teste","serveHttp":"true","serveIo":"true","hlsPlaylistSize":"6","hlsPlaylistExtra":"2","autoStart":"false","preBuffer":1,"timeLimit":10000,"repeated":"false","statusData":"playlist","x":920,"y":200,"wires":[["652bde80360a1fe1"],[]]},{"id":"652bde80360a1fe1","type":"ui_mp4frag","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","group":"46d6d60673638810","order":1,"width":22,"height":13,"readyPoster":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHNoYXBlLXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB2aWV3Qm94PSIwIDAgODAwMCA2MDAwIj48dGV4dCBmaWxsPSIjMGYwIiBzdHJva2Utd2lkdGg9IjAiIGR4PSIwIiBkeT0iMCIgZm9udC1mYW1pbHk9IiZxdW90O2UyY3B1d3p4YldsMTo6OlJvYm90byZxdW90OyIgZm9udC1zaXplPSI2MDAiIGZvbnQtd2VpZ2h0PSI3MDAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKCAxMDExLjI0IDMyMDQuODgpIj48dHNwYW4geT0iMCIgc3Ryb2tlLXdpZHRoPSIwIiBmb250LXdlaWdodD0iNzAwIj48IVtDREFUQVsKVmlkZW8gUGxheWJhY2sgUmVhZHkKXV0+PC90c3Bhbj48L3RleHQ+PC9zdmc+Cg==","errorPoster":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHNoYXBlLXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB0ZXh0LXJlbmRlcmluZz0iZ2VvbWV0cmljUHJlY2lzaW9uIiB2aWV3Qm94PSIwIDAgODAwMCA2MDAwIj48dGV4dCBmaWxsPSJyZWQiIHN0cm9rZS13aWR0aD0iMCIgZHg9IjAiIGR5PSIwIiBmb250LWZhbWlseT0iJnF1b3Q7ZW42OXdhSUtnN0MxOjo6Um9ib3RvJnF1b3Q7IiBmb250LXNpemU9IjYwMCIgZm9udC13ZWlnaHQ9IjcwMCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoIDExODMuOTYgMzIwNC44OCkiPjx0c3BhbiB5PSIwIiBzdHJva2Utd2lkdGg9IjAiIGZvbnQtd2VpZ2h0PSI3MDAiPjwhW0NEQVRBWwpWaWRlbyBQbGF5YmFjayBFcnJvcgpdXT48L3RzcGFuPjwvdGV4dD48L3N2Zz4K","hlsJsConfig":"{\"liveDurationInfinity\":true,\"liveBackBufferLength\":5,\"maxBufferLength\":10,\"manifestLoadingTimeOut\":1000,\"manifestLoadingMaxRetry\":10,\"manifestLoadingRetryDelay\":500}","autoplay":"true","unload":"true","threshold":0.1,"controls":"true","muted":"true","players":["socket.io","hls.js","hls","mp4"],"x":1110,"y":200,"wires":[[]]},{"id":"d1112dfcbfef615b","type":"ui_button","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","group":"46d6d60673638810","order":1,"width":0,"height":0,"passthru":false,"label":"Start","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":150,"y":180,"wires":[["adffeae04f85fed5"]]},{"id":"2858e68578995d73","type":"ui_button","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","group":"46d6d60673638810","order":1,"width":0,"height":0,"passthru":false,"label":"Stop","tooltip":"","color":"","bgcolor":"","className":"","icon":"","payload":"","payloadType":"str","topic":"topic","topicType":"msg","x":150,"y":220,"wires":[["379c223a6ef2f8ea"]]},{"id":"379c223a6ef2f8ea","type":"change","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","rules":[{"t":"set","p":"action","pt":"msg","to":"{\"command\":\"stop\",\"args\":[\"-loglevel\",\"quiet\",\"-rtsp_transport\",\"tcp\",\"-i\",\"rtsp://belov:belov12345678@192.168.1.129:554/cam/realmonitor?channel=1&subtype=0\",\"-an\",\"-c:v\",\"copy\",\"-f\",\"mp4\",\"-movflags\",\"+frag_every_frame+empty_moov+default_base_moof\",\"-min_frag_duration\",\"1000000\",\"pipe:1\"]}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":220,"wires":[["7a8c3f95b486b8f9"]]},{"id":"adffeae04f85fed5","type":"change","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","rules":[{"t":"set","p":"URLCAM","pt":"msg","to":"#:(persistent)::URLCAM_conf","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":330,"y":180,"wires":[["96e79d1eccb9df9d"]]},{"id":"96e79d1eccb9df9d","type":"change","z":"5273a6de.b59318","g":"cfa37a1fd41f4570","name":"","rules":[{"t":"set","p":"action","pt":"msg","to":"{\"command\":\"start\",\"args\":[\"-loglevel\",\"quiet\",\"-rtsp_transport\",\"tcp\",\"-i\", $.URLCAM,\"-an\",\"-c:v\",\"copy\",\"-f\",\"mp4\",\"-movflags\",\"+frag_every_frame+empty_moov+default_base_moof\",\"-min_frag_duration\",\"1000000\",\"pipe:1\"]}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":180,"wires":[["7a8c3f95b486b8f9"]]},{"id":"46d6d60673638810","type":"ui_group","name":"","tab":"9eb9a9fcd56859ba","order":1,"disp":true,"width":"22","collapse":false,"className":""},{"id":"9eb9a9fcd56859ba","type":"ui_tab","name":"Camera Mergulhador","icon":"dashboard","order":2,"disabled":false,"hidden":false},{"id":"9a076144e0b8c687","type":"ui_group","name":"shared","tab":"e671ec4ca9656cab","order":1,"disp":true,"width":"20","collapse":false,"className":""},{"id":"e671ec4ca9656cab","type":"ui_tab","name":"shared","icon":"dashboard","disabled":false,"hidden":false}]

```

---

<div class="post-metadata">

### Author: ![kevinGodell](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/kevingodell/32/27040_2.png) [@kevinGodell](https://discourse.nodered.org/u/kevinGodell)
#### Post date: [20 June 2023 02:07 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/33 "2023-06-20T02:07:34Z")

</div>

I had to re-read some of the previous posts from last year to remember what we did.

- Firstly, you have to setup static hosting from node-red by configuring httpStatic in the setting.js file.
- Then you have to set the values of static path/root in the subflow to match those values.

The current problem is that your system is setup differently than mine and you probably do not have an external usb 6tb hard drive mounted at /run/media/system/surveillance1 as I do. The values are configurable so that you can make it match your specific setup of where you would want to write the data.

To clarify, I have a hard drive mounted at a location that I am using to write large quantities of video.

```auto
Filesystem Size Used Avail Use% Mounted on
/dev/root 30G 9.3G 19G 34% /
devtmpfs 3.5G 0 3.5G 0% /dev
tmpfs 3.7G 904K 3.7G 1% /dev/shm
tmpfs 1.5G 932K 1.5G 1% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
/dev/mmcblk0p1 253M 31M 222M 13% /boot
/dev/sda1 5.5T 4.2T 1015G 81% /run/media/system/surveillance1
tmpfs 739M 0 739M 0% /run/user/1000

```

So, in the settings.js file, I added some configuration to tell node-red to map a location there and host files from it in the httpStatic sections.

```auto
httpStaticRoot: '/static/',
httpStatic: [
        { path: '/run/media/system/surveillance1/cctv/recordings/', root: '/recordings/' }
    ],

```

Using those values, I added that to the subflow node so that I can write to the correct location.

 ![Screen Shot 2023-06-19 at 9.00.44 PM](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/e/f/ef2cdb1941ce4b4b813145c49d55540b9952f50f.png)

Currently, I am writing video 24/7 for 11 main streams and it seems to work ok.

---

<div class="post-metadata">

### Author: ![krambriw](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/krambriw/32/5429_2.png) [@krambriw](https://discourse.nodered.org/u/krambriw)
#### Post date: [20 June 2023 11:17 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/34 "2023-06-20T11:17:12Z")

</div>

@allacmc  
What is not yet working??

---

<div class="post-metadata">

### Author: ![craigcurtin](https://avatars.discourse-cdn.com/v4/letter/c/94ad74/32.png) [@craigcurtin](https://discourse.nodered.org/u/craigcurtin)
#### Post date: [21 June 2023 02:11 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/35 "2023-06-21T02:11:52Z")

</div>

Hey Kevin,

Is this a turn key solution now or you have provided just the (excellent) building blocks ?

We are just deploying a number of security cameras around our place and would like to integrate them into Node Red.

I run my Node Red in a Virtual machine under ESXI so could easily spawn a dedicated machine for this if required

All of my storage is centralised and will be made available to the Node Red host through NFS shares

Craig

---

<div class="post-metadata">

### Author: ![krambriw](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/krambriw/32/5429_2.png) [@krambriw](https://discourse.nodered.org/u/krambriw)
#### Post date: [21 June 2023 05:13 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/36 "2023-06-21T05:13:56Z")

</div>

Sorry jumpin in

Turn-Key and Turn-Key, well you have to follow the instructions given and import the flow sample (one per camera) and make the modifications to match their url's etc. (In addition I did on my own some experiments how to handle and play the recordings in a convenient way from the browser. So far that worked excellent bur unfortunately all my cameras are usb types so I just archived my setup for future, next house or camera upgrading, whatever happens first)

---

<div class="post-metadata">

### Author: ![krambriw](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/krambriw/32/5429_2.png) [@krambriw](https://discourse.nodered.org/u/krambriw)
#### Post date: [21 June 2023 05:18 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/37 "2023-06-21T05:18:45Z")

</div>

> [@kevinGodell](#):
>
> `/dev/sda1 5.5T 4.2T 1015G 81% /run/media/system/surveillance1`

Myself I do have a house-keeping script that erases older recordings. Since your drive is reaching the upper limit, do you have something similar as well?

---

<div class="post-metadata">

### Author: ![kevinGodell](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/kevingodell/32/27040_2.png) [@kevinGodell](https://discourse.nodered.org/u/kevinGodell)
#### Post date: [21 June 2023 10:28 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/38 "2023-06-21T10:28:33Z")

</div>

> [@craigcurtin](#):
>
> building blocks

It's definitely closer to building blocks than it is to turn-key. Although, I have ensured that any library I have published is designed to be cross platform for linux, macos, and windows.

Dealing with the local police department yesterday reminded of how turn-key it is NOT when I was trying to extract video footage that caught the [getaway driver's vehicle of someone that stole a car from the dealership on the other side of the apartment complex](https://www.dropbox.com/scl/fi/js5zjfc7bug3wmc92cboy/police1.mp4?dl=0&rlkey=kbjsewlu4htwik1sjqmqgc9z2). And that reminds me, I only setup the 24/7 recording after the previous car theft occurred at the same location and I had no recordings available. I guess necessity is the mother of all invention. In fact, I only ever set up cameras many years ago due to an ex girlfriend who was threatening my property while I had to be away at work. And then I wasn't satisfied with the "off the shelf" camera systems and started working on my own stuff. It's funny life works.

> [@krambriw](#):
>
> Sorry jumpin in

Thanks for jumping in.

> [@krambriw](#):
>
> do you have something similar as well?

I do, but it's not perfect. When I updated it a while back I broke something and my empty folders no longer get deleted. I still have time to fix it since they don't take up much space.

 ![Screen Shot 2023-06-21 at 5.22.12 AM](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/6/6/6660920d20d345a74452e9edd1738bc82ec908ea.png)  
The use percent gets run once per hour, while the removal script run once per night to delete anything older than 2 weeks. I used the standard `fs` module but my hunch is that your cleanup script is made in python?

---

<div class="post-metadata">

### Author: ![allacmc](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/allacmc/32/73549_2.png) [@allacmc](https://discourse.nodered.org/u/allacmc)
#### Post date: [21 June 2023 13:16 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/40 "2023-06-21T13:16:57Z")

</div>

([Cctv recording and playback (work in progress) - #39 by allacmc](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/39))

Is it possible for you to add a sample code? of the nodes with the recording feature?

The last ones I got don't have the node updated, it's missing.

---

<div class="post-metadata">

### Author: ![krambriw](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/krambriw/32/5429_2.png) [@krambriw](https://discourse.nodered.org/u/krambriw)
#### Post date: [21 June 2023 15:41 UTC](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478/41 "2023-06-21T15:41:48Z")

</div>

> [@kevinGodell](#):
>
> my hunch is that your cleanup script is made in python?

That's correct, 😄 but the idea and concept is more or less the same, I keep the last 10 days

[Previous page](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478.md?page=1)

[Next page](https://discourse.nodered.org/t/cctv-recording-and-playback-work-in-progress/70478.md?page=3)
