Repository navigation
Conversation
Wisenet WAVE (Hanwha's OEM of Nx Witness) exports a layout file: named streams and an index of where each starts, at the end of the file (version 2) or at its start (version 1), as Network Optix's open source client (nx_open, MPL-2.0) writes them. Each camera's recording is a Matroska stream inside; Filee reads the index and gives FFmpeg the largest recording through the subfile protocol, without copying it. Password-protected exports are encrypted and refused with their own message, as are exports without a recording; a damaged index throws InvalidDataException. Settings schema 87 teaches existing video donuts .nov. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
KnifeLemon
left a comment
There was a problem hiding this comment.
Thanks, @Ugoyo. The index reader is careful: the entry count is checked against the file size before anything is
allocated, every offset is range-checked, and the structures match layout_file_info.h upstream. Reading the
recording in place through subfile is a nice touch.
One thing needs fixing before merge, plus a small one:
-
Other video-donut actions still hand FFmpeg the raw
.nov.NovExport.InputForis only applied inside
FfmpegConverter.ConvertAsync, after the Live Photo branch. These go around it and fail with FFmpeg's "Invalid data":- Caption (
caption-video, on the video donut's front):WhisperConverterruns-i step.InputPath. - Live Photo / Motion Photo of a clip:
step.LivePhotoreturns toLivePhotoWriterbefore the rewrite. - Merge videos:
JoinAsyncprobesPath.GetFullPath(inputPaths[i]). - The Cut a clip editor:
VideoFramesandClipPreviewprobe and preview the raw path.
Could you resolve the input in one shared helper (path + format id → FFmpeg input) and call it from those places?
Then every slot that reaches.novworks the same way. #1462 (Hikvision) is merged now and changed the same
var input = ...line, so please rebase on main and let the helper cover its header rewrite too. - Caption (
-
Say when recordings are left out. For a multi-camera export, or one camera with gaps, only the largest
.mkvis
converted and the user isn't told. Astep.Notice(new key in all 22 language files) when more than one recording
is present would cover it.
Small things, no action needed:
- The FFmpeg-backed tests (
A_wisenet_wave_export_converts_its_largest_recording_to_mp4) are skipped on every CI job,
so CI covers the parser only. That's fine, just noting it. - Schema 87 has just gone to #1444; we'll renumber yours at merge time.
Hikvision SD-card recordings (KnifeLemon#1462) and .nov exports now share the line where FfmpegConverter picks FFmpeg's input: a .nov goes to NovExport.InputFor with the plain path, every other file to HikvisionStream.InputFor. Schema 87 went to the Arduino sketches on main, so .nov takes 88. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
Review of KnifeLemon#1463: only FfmpegConverter.ConvertAsync rewrote the input, so Caption, Live / Motion Photo, Merge videos, the Cut a clip editor and size estimates still handed FFmpeg the raw .nov (and the header-less Hikvision recording). MediaInput.For(path, format) now gives each of them the same input: a .nov's recording through subfile, a Hikvision SD-card recording with mu-law sound through concat, any other file as it is. Merge videos' concat list allows those two protocols, which the concat demuxer refuses by default. Only video files are looked into for the Hikvision header. A .nov export with more than one recording now says that only the largest was converted (notice.nov_recordings_left_out, in all 22 languages). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
From the review of KnifeLemon#1462: HikvisionStream read the first 256 KB of every FFmpeg input through a FileStream. It now reads with RandomAccess, and only the first four bytes unless they are a pack header, so an MP4 or a Matroska file costs one small read. The shared header of the Cut a clip editor is written once, under another name first, so a reader never sees half of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
|
Done, thanks for the review. New Media/MediaInput.cs: one For(path, format) gives FFmpeg the input for every slot: a .nov’s recording through subfile, a header-less Hikvision recording with mu-law sound through concat (now looked for in video files only), anything else as is. Convert, Caption (Whisper), Live / Motion Photo, Merge videos, the Cut a clip editor (VideoFrames, ClipPreview) and CompressionEstimator go through it. Merge videos’ concat list adds -protocol_whitelist file,crypto,data,subfile,concat, since the concat demuxer refuses both protocols by default. Also folded in the notes from #1462: the Hikvision rewrite goes through the same MediaInput helper, so a header-less mu-law recording now works in Caption, Live / Motion Photo, Merge videos and the Cut a clip editor too. HikvisionStream reads with RandomAccess, only 4 bytes of a file that doesn’t start with a pack header, and only for video formats. The editor’s shared header file is written once, atomically. |
Schema 88 went to the NuGet / APK containers (KnifeLemon#1464, KnifeLemon#1465), so .nov takes 89; the specialist count is 124 with them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
KnifeLemon
left a comment
There was a problem hiding this comment.
스키마 89: #1461(G-code), #1458(Gerber), #1463 셋이 모두 89를 써요. AppSettings.cs와 FormatFieldsTests(Specialist
124)는 세 PR이 같은 줄을 같은 값으로 바꿔서 서로 병합해도 텍스트 충돌이 안 나요. 두 번째부터는 숫자(90, 91)와
Specialist 개수(125, 126)를 직접 올려야 해요. 안 그러면 FormatFieldsTests가 깨지고, from < 89 블록이 두 개 생겨요.
UserDataStore.cs는 실제로 충돌해요(#1463 vs #1461, #1463 vs #1458 모두).
- N1을 작성자에게 맡길지, 병합 때 직접 한 줄 고칠지.
5. 리뷰 본문 초안 (English)
Thanks, @Ugoyo, this is exactly what I hoped for. MediaInput.For is now the one place every slot asks, and I checked
each one from the last round: Convert, Caption (WhisperConverter), Live / Motion Photo (LivePhotoWriter), Merge
videos (per clip, with the -protocol_whitelist on the concat list), the Cut a clip editor (VideoFrames,
ClipPreview) and CompressionEstimator. The left-out recordings notice reads well too, and folding the #1462
follow-ups in (video formats only, a 4-byte read with RandomAccess, the staged header write) was a nice extra.
One small change before merge:
- The shared header folder should be Filee's work folder, not the temp folder.
MediaInput.SharedFolderis
Path.Combine(Path.GetTempPath(), "filee-media"). On Linux that's/tmp/filee-media, which another user can create
first;WriteHeaderthen reuses that folder and an existinghikvision-imkh.bin, and the folder's owner can swap the
file before FFmpeg reads it. That's the caseWorkFolders(inFilee.Core.Platform) exists for: on Linux and macOS
it gives a per-user folder with mode 700.Path.Combine(WorkFolders.Root, "media")fixes it (and
ClipPreview.ReadLoudnessAsynccould pass itsworkFolder). The docs line about "Filee's folder in the temp folder"
would follow.
A nit, take it or leave it:
- The new
ko.jsonstrings use 합니다체 ("…있습니다", "없습니다", "변환했습니다"), while the file uses 해요체. For example:
"…암호화되어 있어요. … 다시 내보내 주세요", "…카메라 녹화가 없어요", "…있어요. 가장 큰 녹화만 변환했어요".
The schema number: #1456 has taken 89 meanwhile, and we'll renumber yours when merging, so no need to change it.
Review of KnifeLemon#1463: the folder used when an action has no work folder of its own was /tmp/filee-media, which another user can create first and then swap the header before FFmpeg reads it. It is now a "media" folder in WorkFolders.Root, private to the user on Linux and macOS. The Cut a clip editor's loudness reading passes its own work folder. The Korean strings use the file's 해요체. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
|
Done: when a slot has no work folder of its own, the header now goes to Path.Combine(WorkFolders.Root, "media") instead of /tmp/filee-media, and ClipPreview.ReadLoudnessAsync passes its workFolder. The docs line follows, and MediaInputTests checks the new place. The Korean strings now use 해요체 as suggested. Schema left at 89 for you to renumber, thanks! |
Schema 89 went to the KuGou lyrics (KnifeLemon#1296), so .nov takes 90. The Cut a clip editor's probe keeps main's sound-only summary (KnifeLemon#1430) and reads through MediaInput. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BPxh5pgy6HfVPDtxbszvh3
What does this change?
Adds
.novas a read-only video format..novis the export of Wisenet WAVE (Hanwha's OEM of Network Optix Nx Witness, the VMS used with Wisenet cameras) and of Nx Witness itself. A.novis a layout file. Network Optix's open-source client (nx_open, MPL-2.0:vms/libs/nx_vms_common/src/nx/core/layout/layout_file_info.h,layout_storage_resource.cpp) writes it as a series of named streams: a NUL-terminated UTF-8 name, then the data. An index records where each stream starts:0xdea5489c3f44ca16);Each camera's recording is a Matroska stream named
<camera id>_<start ms>.mkv.Media/NovExport.csreads the index and gives FFmpeg the largest recording through thesubfileprotocol, so nothing is copied. It then goes to every video and audio target.error.nov_password), which says to export again without a password. An export with no recording is refused witherror.nov_no_video. Both keys are in all 22 i18n files.InvalidDataExceptionwith a clear message. The entry count is checked against the file's size before anything is allocated (version 2 must fill exactly the room between header and footer; version 1 allows at most 256 entries). Every stream must lie within the data, and names are read up to 1024 bytes..exeexports (the client's player with the same streams appended) are not touched..exestays a Windows program (read for its icon, [Format]: .exe #1188) and is never run..nov; custom claims stay put. It may need renumbering if another migration lands first..novis graded Specialist, in thevideo-profield next to.dav; theFormatFieldsTestscounts are updated.No code was copied from
nx_open: the reader is written from the structures its header documents, so nothing is added toTHIRD-PARTY-NOTICES.md.Type of change
How was it tested?
dotnet format --verify-no-changes(anddotnet build -c Release)dotnet test: new tests pass;LocalizationTestspass. On Linux with the system's FFmpeg 6.1. The remaining failures fail onmaintoo (Core tests that need a non-root user, 5MediaTeststhat need codecs missing from this FFmpeg).New tests:
NovFormatTests(Core):.novis detected as read-only video on the video donut; schema 86 → 87 adds it once.NovExportTests(no FFmpeg): streams found by name with their data in both versions; protected exports refused (both versions); the largest.mkvis given as asubfileinput; an export without a recording is refused; damaged indexes (not an export, cut short, wrong entry count, offset past the data, version 1 count over 256) throwInvalidDataException.MediaTests.Nov_exports_are_read_but_never_writtenandA_wisenet_wave_export_converts_its_largest_recording_to_mp4(versions 1 and 2):NovFilewrites an export the way the client does, with a 1 s and a 2 s Matroska recording among layout streams. The 2 s one converts to MP4 (50 frames of 160x120 H.264, AAC, ~2 s).No real sample was used. I found no public
.novsample. The test files follow the client's source code. A short unprotected export from Wisenet WAVE would be the best confirmation, if anyone has one.Screenshots (UI changes)
None, no UI changes (two new error messages).
Checklist
src/Filee.App/Assets/i18n/*.jsonfilesTHIRD-PARTY-NOTICES.md(none added)🤖 Generated with Claude Code