{"id":15209103,"url":"https://github.com/tomhumphries/simple-nvr","last_synced_at":"2025-10-29T13:30:22.323Z","repository":{"id":50932751,"uuid":"307847760","full_name":"TomHumphries/simple-nvr","owner":"TomHumphries","description":"A simple Network Video Recorder written in Node.js and using ffmpeg","archived":false,"fork":false,"pushed_at":"2023-08-24T08:12:56.000Z","size":783,"stargazers_count":115,"open_issues_count":7,"forks_count":34,"subscribers_count":9,"default_branch":"master","last_synced_at":"2025-02-02T01:31:55.914Z","etag":null,"topics":["ejs","express","ffmpeg","ip-camera","ipcamera","javascript","network-video-recorder","nodejs","nvr","raspberry-pi","raspberrypi","rtsp"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TomHumphries.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-10-27T22:54:10.000Z","updated_at":"2025-01-08T12:49:37.000Z","dependencies_parsed_at":"2024-10-12T00:06:09.111Z","dependency_job_id":"1915aea6-d278-46ef-977f-43715e987f82","html_url":"https://github.com/TomHumphries/simple-nvr","commit_stats":{"total_commits":3,"total_committers":1,"mean_commits":3.0,"dds":0.0,"last_synced_commit":"c652a9564f389bb638c9be7b3492b6bfedc1a5a9"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomHumphries%2Fsimple-nvr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomHumphries%2Fsimple-nvr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomHumphries%2Fsimple-nvr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TomHumphries%2Fsimple-nvr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TomHumphries","download_url":"https://codeload.github.com/TomHumphries/simple-nvr/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238825790,"owners_count":19537127,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ejs","express","ffmpeg","ip-camera","ipcamera","javascript","network-video-recorder","nodejs","nvr","raspberry-pi","raspberrypi","rtsp"],"created_at":"2024-09-28T07:21:21.936Z","updated_at":"2025-10-29T13:30:16.980Z","avatar_url":"https://github.com/TomHumphries.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Simple Network Video Recorder  in Node.js\nThis is a simple Network Video Recorder (NVR) that is designed to run on cheap hardware, such as a Raspberry Pi with a hard drive. 24/7 video streams from network cameras are saved, and the recorded files are browsable from a basic web interface.\n\n![Camera locations](/images/camera-locations.png)\n\nThe project is deliberately bare-bones, and configuration is done through `.json` files.\n\nThe camera video streams are saved in 5 minute files (to prevent long periods of video loss should a file become corrupted). At 01:00 UTC, the video files for the previous day are concatinated into a single 24 hour file, and the 5 minute video files are deleted.\n\n`ffmpeg` is used to connect to the camera streams and save the video feeds.\n\n\n## Set up \u0026 configuration\nTo get started, the following steps must be taken:\n1. Install [ffmpeg](https://ffmpeg.org/).\n2. Choose where you want video files to be saved, and update the `rootpath` directory in the `/storage.json` configuration file.\n3. Add camera names and RTSP addresses to the `/cameras.json` configuation file.\n4. Run the `nvr.js` server. e.g. using [PM2](https://pm2.keymetrics.io/) with: \n```\npm2 start nvr.js --name nvr\n```\n\nThe `nvr.js` server will record the videos in 5 minute clips, and combine them at 01:00 UTC every day into a 24 hour video file.\nRunning `nvr-browser.js` will start a webserver at `http://localhost:3000` that will enable you to browser the folder structure and view video files (see example image below)\n\n![Video example](/images/video-example.png)\n\nIf you just want to record video without the browser, you can choose to only run `nvr.js`.\n\n---\n\n## Notes about the code and methods used\n**Extra details about the implementation and ffmpeg configuration**\n\n### MP4 vs MKV\n`mkv` files seem to be more resistent to corruption. When unplugging the camera while an `mp4` file is being written to, the file is un-openable. When recording to an `mkv` file and the camera is unplugged, the files can be played and data is available until nearly the point of unplugging. `mkv` files can be played in the browser in the latest version of Chrome (as of October 2021). \n\n### Connecting to camera streams\nUsing a wireless connection for the cameras appears to work well, and the video feeds very rarely drop connections (usually \u003c60 seconds a day). However using a wireless connection for the Raspberry Pi 3b+ causes many video connection drops, often several minutes a day. For this reason **it is recommended to use a wired network connection for the Raspberry Pi / base station**.\n\n#### TCP vs UDP\nUDP was tested for the `ffmpeg` streams, and although it resulted in fewer warning errors from `ffmpeg`, the video files were often corrupted with the video frames being incorrectly ordered when played back, and some files not opening at all. TCP connections do not seem to suffer from this problem. Many `ffmpeg` settings variations (e.g. the buffer size) were used to try to mitigate the UPD corruption problem, but none worked reliably.\n\n### Detecting stream errors\nSeveral methods of detecting when a video feed fails have been tried. Attempting to detect dropped streams by the error events raised by `ffmpeg` gave inconsistent results, and occasionally resulted in either: \n1. The feed not restarting\n2. Multiple streams from the same camera\n\nMultiple streams causes further problems, as one or more of the streams creates corrupted files that are difficult to detect programatically.\n\nThe best results are achieved with a filewatcher script. The filewatcher looks for constant changes to the raw file that is being streamed to, and when the file is not changed for a set period of time it is assumed that the stream connection has failed. The `ffmpeg` stream is then killed (if it still exists), and the stream is recreated.\n\n### Saving the streams\nThe streams are saved in 5 minute segments at \"regular\" 5 minute intervals (i.e. at 00:00:00, 00:05:00, 00:10:00, etc.). The naming configuration offered by `ffmpeg` allows for some customisation of the filenames, but we change the filenames to a \"friendlier\" UTC-like format of:\n```\nyyyy-mm-ddThh mm ss.mkv\n``` \nThis allows easy identification of the file time as a human, and the filename is also easily parsable back to a UTC time. \n\n#### Saving location\n\n![Camera locations](/images/folders.png)\n\nThe file that the stream is currently being written to is located in a `raw` folder. The `ffmpeg` configured datetime pattern does not seem to parse correctly according to `ISO8601` on Windows, with the lower case `z` parsing to a descriptor like `GMT Summer Time` instead of `+0100`. This causes problems with the default `Date()` parsing which the code automatically accounts for on Windows machines.\n```\n/camera-name/raw/%Y-%m-%dT%H %M %S%z.mkv\n```\nA file watcher looks for when multiple files exist in the `raw` directory, and moves all but the newest file to the camera's day directory (below), renaming it at the same time.\n```\n/camera-name/year/month/day/yyyy-mm-ddThh mm ss.mkv\n```\n\n### Detecting corrupted video files\nVery occasionally a video file becomes corrupted, and causes the concatination script to crash. To avoid this, each video file is scanned before the concatination script runs with `ffprobe`. Corrupted files are _not_ deleted in case they contain important (but corrupted) footage, and fixing the files may be possible.\n\n### Hardware \u0026 Cameras\nEach camera on a Raspberry Pi 3b+ writing to an external HDD seems to use ~9% CPU.\n\n![CPU use](/images/cpu-use.png)\n\nTwo _ieGeek_ cameras bought on Amazon run well when paired with a Raspberry Pi 3+. I suspect the Pi could easily handle more than 2 cameras given the CPU consumption.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomhumphries%2Fsimple-nvr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftomhumphries%2Fsimple-nvr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftomhumphries%2Fsimple-nvr/lists"}