avmosaic 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. avmosaic-0.1.0/LICENSE +21 -0
  2. avmosaic-0.1.0/NOTICE +26 -0
  3. avmosaic-0.1.0/PKG-INFO +331 -0
  4. avmosaic-0.1.0/README.md +293 -0
  5. avmosaic-0.1.0/pyproject.toml +64 -0
  6. avmosaic-0.1.0/setup.cfg +4 -0
  7. avmosaic-0.1.0/src/avmosaic/__init__.py +25 -0
  8. avmosaic-0.1.0/src/avmosaic/__main__.py +5 -0
  9. avmosaic-0.1.0/src/avmosaic/censor.py +208 -0
  10. avmosaic-0.1.0/src/avmosaic/cli.py +673 -0
  11. avmosaic-0.1.0/src/avmosaic/detector.py +237 -0
  12. avmosaic-0.1.0/src/avmosaic/evaluate.py +312 -0
  13. avmosaic-0.1.0/src/avmosaic/image.py +70 -0
  14. avmosaic-0.1.0/src/avmosaic/labeler.py +278 -0
  15. avmosaic-0.1.0/src/avmosaic/labels.py +78 -0
  16. avmosaic-0.1.0/src/avmosaic/models.py +165 -0
  17. avmosaic-0.1.0/src/avmosaic/tracking.py +391 -0
  18. avmosaic-0.1.0/src/avmosaic/video.py +296 -0
  19. avmosaic-0.1.0/src/avmosaic.egg-info/PKG-INFO +331 -0
  20. avmosaic-0.1.0/src/avmosaic.egg-info/SOURCES.txt +32 -0
  21. avmosaic-0.1.0/src/avmosaic.egg-info/dependency_links.txt +1 -0
  22. avmosaic-0.1.0/src/avmosaic.egg-info/entry_points.txt +2 -0
  23. avmosaic-0.1.0/src/avmosaic.egg-info/requires.txt +9 -0
  24. avmosaic-0.1.0/src/avmosaic.egg-info/top_level.txt +1 -0
  25. avmosaic-0.1.0/tests/test_censor.py +139 -0
  26. avmosaic-0.1.0/tests/test_cli.py +223 -0
  27. avmosaic-0.1.0/tests/test_evaluate.py +181 -0
  28. avmosaic-0.1.0/tests/test_image.py +47 -0
  29. avmosaic-0.1.0/tests/test_labeler.py +109 -0
  30. avmosaic-0.1.0/tests/test_labels.py +30 -0
  31. avmosaic-0.1.0/tests/test_model.py +145 -0
  32. avmosaic-0.1.0/tests/test_samples.py +60 -0
  33. avmosaic-0.1.0/tests/test_tracking.py +284 -0
  34. avmosaic-0.1.0/tests/test_video.py +172 -0
avmosaic-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Donghwan Lee and avmosaic contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
avmosaic-0.1.0/NOTICE ADDED
@@ -0,0 +1,26 @@
1
+ avmosaic — third-party components
2
+ ===================================
3
+
4
+ This repository contains only avmosaic's own source code (MIT, see LICENSE),
5
+ one public-domain test image, and a demo image derived from it. The components
6
+ below are installed separately as Python packages or downloaded at runtime; they
7
+ are not vendored here and keep their own licenses.
8
+
9
+ NudeNet (detector models: 320n.onnx, 640m.onnx)
10
+ https://github.com/notAI-tech/NudeNet
11
+ IMPORTANT: the PyPI package "nudenet" 3.4.x declares the MIT license, but the
12
+ GitHub repository (which also hosts the 640m weights) is licensed AGPL-3.0.
13
+ If you redistribute avmosaic together with NudeNet or its weights, or offer
14
+ it as a network service, check which license applies to you. A detector of
15
+ your own can be plugged in through the Python API instead.
16
+
17
+ ONNX Runtime — MIT — https://github.com/microsoft/onnxruntime
18
+ OpenCV (opencv-python-headless) — Apache-2.0 — https://github.com/opencv/opencv-python
19
+ NumPy — BSD-3-Clause — https://numpy.org
20
+ imageio-ffmpeg — BSD-2-Clause — https://github.com/imageio/imageio-ffmpeg
21
+ Its wheels include an ffmpeg binary, which is a separate program under the
22
+ (L)GPL and is run as a subprocess.
23
+
24
+ tests/data/astronaut.jpg, docs/demo.jpg
25
+ Portrait of astronaut Eileen Collins, NASA, public domain (the same image as
26
+ scikit-image's data.astronaut()).
@@ -0,0 +1,331 @@
1
+ Metadata-Version: 2.4
2
+ Name: avmosaic
3
+ Version: 0.1.0
4
+ Summary: Detect and mosaic explicit regions in images and videos (CLI + Python API).
5
+ Author: avmosaic contributors
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/fattaillabs/avmosaic
8
+ Project-URL: Repository, https://github.com/fattaillabs/avmosaic
9
+ Project-URL: Issues, https://github.com/fattaillabs/avmosaic/issues
10
+ Project-URL: Korean README, https://github.com/fattaillabs/avmosaic/blob/main/README.ko.md
11
+ Project-URL: Japanese README, https://github.com/fattaillabs/avmosaic/blob/main/README.ja.md
12
+ Keywords: censorship,mosaic,nudity-detection,pixelate,video,onnx
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: End Users/Desktop
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: Multimedia :: Graphics
19
+ Classifier: Topic :: Multimedia :: Video
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Programming Language :: Python :: 3.14
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ License-File: NOTICE
29
+ Requires-Dist: numpy>=1.23
30
+ Requires-Dist: opencv-python-headless>=4.8
31
+ Requires-Dist: onnxruntime>=1.16
32
+ Requires-Dist: nudenet<4,>=3.4
33
+ Requires-Dist: imageio-ffmpeg>=0.4.9
34
+ Provides-Extra: dev
35
+ Requires-Dist: pytest>=7; extra == "dev"
36
+ Requires-Dist: ruff>=0.5; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # avmosaic
40
+
41
+ Detect explicit regions in images and videos and cover them with a mosaic — from the
42
+ command line or from Python, on Windows, macOS and Linux.
43
+
44
+ ![original · detection boxes (--draw-boxes) · mosaic](https://raw.githubusercontent.com/fattaillabs/avmosaic/main/docs/demo.jpg)
45
+
46
+ *Demo uses the `faces` preset on a public-domain NASA portrait; the default preset
47
+ censors genitalia and the anus.*
48
+
49
+ **English** · [한국어](https://github.com/fattaillabs/avmosaic/blob/main/README.ko.md) · [日本語](https://github.com/fattaillabs/avmosaic/blob/main/README.ja.md)
50
+
51
+ > **Automated detection is an aid, not a guarantee.** The detector will miss regions
52
+ > (unusual angles, close-ups, occlusion, lighting) and will sometimes censor the wrong
53
+ > thing. Review every output before publishing it. You are responsible for complying
54
+ > with the laws that apply to your content. Only process material you have the right to
55
+ > process.
56
+
57
+ ## Features
58
+
59
+ - Detection with the open-source [NudeNet](https://github.com/notAI-tech/NudeNet) v3
60
+ ONNX models (`320n` ships with the package; `640m` is more accurate and downloaded on
61
+ demand). Runs on CPU, or with CUDA / DirectML / CoreML through ONNX Runtime.
62
+ - Mosaic tiles aligned to a fixed grid so they don't shimmer between frames. The default
63
+ tile size follows a commonly cited guideline: at least 1/100 of the image's long side
64
+ and never under 4 px. Blur and solid fill are available too.
65
+ - Video tracking: each region is followed with optical flow, so the mosaic moves with it
66
+ instead of smearing along its path. This covers the frames between detector runs, short
67
+ stretches where the detector loses the region, and a few frames before it is first
68
+ detected. Tracks end at scene cuts, and audio is kept.
69
+ - Every key parameter is a flag: classes, threshold, model, padding, tile size, shape,
70
+ hold, codec and quality.
71
+ - Batch processing of folders. JSON reports of every detection. A `--draw-boxes` mode
72
+ for tuning.
73
+ - Handles Unicode file names (Korean, Japanese, …), alpha channels, odd video sizes, and
74
+ NTSC frame rates.
75
+
76
+ ## Install
77
+
78
+ Python 3.10+ is required. ffmpeg is bundled through `imageio-ffmpeg`, so there is
79
+ nothing else to install. On Intel Macs, note that recent ONNX Runtime releases
80
+ ship no Intel-macOS wheels, so `pip` can only use an older ONNX Runtime, and only if
81
+ one exists for your Python version (Python 3.14 on Intel macOS has none).
82
+
83
+ ```bash
84
+ git clone https://github.com/fattaillabs/avmosaic && cd avmosaic
85
+ pip install . # or: pip install -e ".[dev]" for development
86
+ ```
87
+
88
+ GPU (optional): replace `onnxruntime` with `onnxruntime-gpu` (NVIDIA CUDA) or
89
+ `onnxruntime-directml` (Windows, any GPU), then pass `--device cuda` / `--device directml`.
90
+ The default `--device auto` picks CUDA, then DirectML, then CoreML (Apple), then CPU.
91
+
92
+ ## Quick start
93
+
94
+ ```bash
95
+ avmosaic video.mp4 # -> video_censored.mp4
96
+ avmosaic photo.jpg -o out.jpg
97
+ avmosaic ./scenes -o ./censored # whole folder, structure mirrored
98
+ avmosaic video.mp4 --model 640m --detect-every 2 --hold 8
99
+ avmosaic photo.jpg --draw-boxes # see what was detected and how confident it was
100
+ avmosaic detect video.mp4 -o detections.json # detect only, no output media
101
+ avmosaic classes # list classes and presets
102
+ avmosaic models download 640m # fetch the bigger model ahead of time
103
+ avmosaic frames sample.mp4 -o ./frames --every 2 # video -> images to label
104
+ avmosaic label ./frames # mark where the mosaic must go, in your browser
105
+ avmosaic eval regions ./frames # is the mosaic where you marked it?
106
+ avmosaic eval run ./NPDI -o npdi.jsonl # benchmark on a labelled dataset (see below)
107
+ avmosaic eval summary npdi.jsonl
108
+ ```
109
+
110
+ `avmosaic FILE …` is shorthand for `avmosaic censor FILE …`.
111
+
112
+ ## Parameters
113
+
114
+ | Flag | Default | Meaning |
115
+ |---|---|---|
116
+ | `--classes` | `genitals` | Presets and/or class names, comma separated. Presets: `genitals` (female/male genitalia, anus), `breasts`, `buttocks`, `nudity`, `faces`, `all`. |
117
+ | `--threshold` | `0.25` | Minimum detection score. Lower it to censor more aggressively. |
118
+ | `--model` | `320n` | `320n`, `640m`, or a path to an `.onnx` file. |
119
+ | `--resolution` | model's own | Model input size (a multiple of 32). |
120
+ | `--device` | `auto` | `auto`, `cpu`, `cuda`, `tensorrt`, `directml`, `coreml`, `openvino`. |
121
+ | `--channels` | `rgb` | Channel order fed to the model (`bgr` reproduces upstream NudeNet exactly). |
122
+ | `--method` | `mosaic` | `mosaic`, `blur` or `fill`. |
123
+ | `--block-size` | `auto` | Mosaic tile: `auto`, a pixel count (`24`), or a share of the long side (`2%`). |
124
+ | `--shape` | `rect` | `rect`, or `ellipse` (still covers the whole box). |
125
+ | `--padding` | `0.15` | Grow each box by this fraction of its size on every side. |
126
+ | `--fill-color` | `black` | For `--method fill`: `black`, `white`, `#rrggbb`, `r,g,b`. |
127
+ | `--detect-every` | `1` | Video: run the detector on every Nth frame (and on every scene cut). Tracking fills the frames in between. |
128
+ | `--tracking` | `on` | Video: follow regions with optical flow. `off` censors the union of boxes found within `--hold` frames instead (simpler, but smears fast motion). |
129
+ | `--hold` | `5` | Tracking on: follow a new region back N frames before its first detection. Off: censor boxes found up to N frames before and after. |
130
+ | `--max-lost` | `15` | Tracking on: keep following a region N frames after the detector lost it. |
131
+ | `--smooth` | `2` | Tracking on: average box positions over ±N frames (the raw box is always covered too). |
132
+ | `--codec` / `--crf` / `--preset` | `libx264` / `18` / `medium` | Video encoder settings. `--ffmpeg-args "…"` passes extra encoder flags. |
133
+ | `--no-audio` | off | Drop the audio track. Audio is otherwise copied, and converted to AAC when the source codec doesn't belong in MP4 (e.g. PCM). |
134
+ | `--report FILE` | — | Write every detection (per frame for video) as JSON. |
135
+ | `--draw-boxes` | off | Draw boxes, classes and scores on the output. |
136
+ | `--progress` | `auto` | Progress: a bar in a terminal (`auto`); `always` also logs a line every 10 s when output goes to a file. |
137
+ | `-o`, `--suffix`, `-y` | — | Output file or folder, suffix used when `-o` is absent, overwrite. |
138
+
139
+ ### Tuning
140
+
141
+ 1. Run `avmosaic sample.mp4 --draw-boxes --report r.json` and look at the result.
142
+ 2. Missed regions → lower `--threshold` (e.g. `0.15`), try `--model 640m`, raise `--hold`
143
+ and `--padding`.
144
+ 3. Too much censored → raise `--threshold`, and check which classes the report says
145
+ were found.
146
+
147
+ ## Recommended settings
148
+
149
+ These are starting points, not guarantees. Check the result with `--draw-boxes` and adjust.
150
+
151
+ | Goal | Flags | Notes |
152
+ |---|---|---|
153
+ | Balanced (default) | none | `320n`, `--threshold 0.25`, tracking on |
154
+ | Fewer false alarms | `--threshold 0.32` (or `0.35`) | On 7,783 swimwear photos, false alarms fell from 0.8% at 0.25 to 0.4% at 0.35. On a 12-minute test clip, 0.32 cut the mosaic segments from 59 to 33 and still caught every long segment. |
155
+ | Fewer misses (safety first) | `--threshold 0.15 --model 640m --max-lost 30 --hold 8 --padding 0.25` | Lower bar, bigger model, longer tracking, larger boxes. Slower, and censors more. |
156
+ | Faster | `--detect-every 2` (30 fps video) or `--detect-every 3` (60 fps) | Tracking fills the frames in between. A 1080p clip went from 22.0 s to 16.1 s with `--detect-every 2`. |
157
+ | Coarser mosaic on small videos | `--block-size 2%` | `auto` is max(4 px, 1/100 of the long side), so a 640×360 video gets 7 px tiles. |
158
+ | Also cover breasts | `--classes genitals,breasts` | |
159
+ | Anonymize faces | `--classes faces` | |
160
+
161
+ ## How tracking works
162
+
163
+ For each region the detector finds, avmosaic keeps a track and follows it frame by frame
164
+ with median-flow optical flow (Lucas–Kanade on a grid of points, with a forward–backward
165
+ check). The tracker is built to cover too much rather than too little:
166
+
167
+ - **Between detector runs** (`--detect-every N`): when the next detection arrives, the
168
+ accumulated flow drift is spread back over the frames in between. Both the flow path and
169
+ the corrected path are covered.
170
+ - **Detector misses**: the track continues on optical flow for up to `--max-lost` frames.
171
+ If flow cannot follow (occlusion, blur, a featureless region), the box covers both "it
172
+ stopped" and "it kept its recent speed". When the detector finds the region again within
173
+ `--max-lost` frames, the frames of the gap are still buffered, so they also get the
174
+ straight path between the two detections. The box also grows 1% per frame without a
175
+ detection, up to +50%.
176
+ - **Before the first detection**: a new track is followed backwards over `--hold` frames.
177
+ - **Scene cuts**: these end all tracks and force a detection. Nothing is carried across
178
+ the cut.
179
+
180
+ Frames are rendered with a look-ahead of `max(--max-lost, --hold, --smooth,
181
+ --detect-every − 1)` frames. At the defaults that is 15 frames, or about 100 MB at 1080p.
182
+
183
+ ## Checking that the mosaic is in the right place
184
+
185
+ Whether a file is explicit is the wrong question for a censoring tool. What matters is
186
+ whether the mosaic lands where it should. A person marks that once, on a few samples, and
187
+ then it can be checked automatically every time the code or settings change:
188
+
189
+ 1. `avmosaic frames sample.mp4 -o ./frames --every 2` saves a frame every 2 seconds.
190
+ 2. `avmosaic label ./frames` opens a local page (served on 127.0.0.1 only). Drag a box
191
+ around every region that must be censored. Press **C** for an image that must stay
192
+ clean. Boxes are saved next to each image as `<name>.json`. The page starts empty;
193
+ `--suggest` pre-fills the detector's boxes, which is faster but biases the labels
194
+ towards what the detector already finds.
195
+ 3. `avmosaic eval regions ./frames` prints a text report:
196
+ - how much of each marked region the mosaic covers, with the worst cases listed
197
+ - how much of the mosaic falls outside the marked regions
198
+ - whether any clean image was censored
199
+
200
+ It exits with status 1 if a region is covered less than 90% (`--min-coverage`) or a
201
+ clean image loses more than 2% (`--max-false-area`). It takes the same detection and
202
+ censoring flags as `censor`, so settings can be compared directly.
203
+ `AVMOSAIC_SAMPLES=./frames pytest tests/test_samples.py` runs the same check as tests.
204
+
205
+ ## Trying it on real footage
206
+
207
+ avmosaic does not include, host or distribute any explicit material, and its tests use
208
+ none. To try it on real footage, you can use a public research dataset such as
209
+ [The Image and video dataset for adult content detection in image and video](https://figshare.com/articles/dataset/The_Image_and_video_dataset_for_adult_content_detection_in_image_and_video/14495367?file=27771210)
210
+ (figshare).
211
+
212
+ > **18+, at your own risk.** Such datasets contain explicit adult content. Before
213
+ > downloading, read the dataset page, including its license and disclaimer. Use the data
214
+ > at your own risk and only where that is lawful. Never commit files from it to this
215
+ > repository or attach them to issues.
216
+
217
+ ```bash
218
+ avmosaic "some video.mp4" --draw-boxes --report report.json # then watch the result
219
+ avmosaic frames "some video.mp4" -o ./frames --every 2 # label + eval regions
220
+ ```
221
+
222
+ More about test data and private samples: [tests/data/README.md](https://github.com/fattaillabs/avmosaic/blob/main/tests/data/README.md).
223
+
224
+ ## Benchmarking on NPDI / Pornography-2k
225
+
226
+ These research datasets are not public downloads. They contain copyrighted adult material,
227
+ so their maintainers share them only after a formal request and a signed responsibility
228
+ agreement:
229
+
230
+ - **NPDI** (800 videos, ~80 h; half clean, including 200 "difficult" clips such as beaches
231
+ and wrestling): Prof. Sandra Avila, UNICAMP.
232
+ - **Pornography-2k** (2,000 videos, ~140 h): Prof. Anderson Rocha, UNICAMP.
233
+
234
+ Both have per-video labels only, without region boxes, so `avmosaic eval` measures what
235
+ those labels allow. It also accepts folders of images, where each image counts as one
236
+ sample, e.g. explicit images vs. swimwear photos as hard negatives. A file's label comes
237
+ from its path below (and including) the folder you pass: anything matching
238
+ `no[nt][-_ ]?porn` (`nonporn`, `non_porn`, `not_porn`, …) is clean, then anything matching
239
+ `porn` is explicit. Change this with `--positive` and `--negative`.
240
+
241
+ ```bash
242
+ avmosaic eval run ./NPDI -o npdi-320n.jsonl --sample-fps 1 # resumable
243
+ avmosaic eval run ./NPDI -o npdi-640m.jsonl --model 640m
244
+ avmosaic eval summary npdi-320n.jsonl npdi-640m.jsonl --thresholds 0.15,0.25,0.35
245
+ ```
246
+
247
+ The summary reports the following, as text only:
248
+
249
+ - the file-level AUC
250
+ - for each threshold, the share of explicit and clean videos that get censored at all,
251
+ and the mean share of sampled frames censored (split into easy and difficult clean
252
+ videos when the path says so)
253
+ - the clean files that scored highest, with timestamps, so you can check them for false
254
+ alarms
255
+ - the explicit files where nothing was found
256
+
257
+ ## Speed
258
+
259
+ Measured on an Apple M1 (CoreML) with a 1080p, 30 fps, 20-second clip (600 frames):
260
+
261
+ | Settings | Time | Throughput |
262
+ |---|---|---|
263
+ | default (`320n`) | 22.0 s | 27 fps |
264
+ | `--detect-every 2` | 16.1 s | 37 fps |
265
+ | `--model 640m --detect-every 3` | 75.6 s | 7.9 fps |
266
+
267
+ Detection alone takes 15.7 ms per 1080p frame for `320n` (33.1 ms on CPU) and 346 ms for
268
+ `640m` (469 ms on CPU). Re-encoding the same clip with ffmpeg alone takes 8.3 s.
269
+
270
+ Tracking added about 10% in a separate run on the same clip (31.8 s → 34.8 s). The clip
271
+ contained no regions to follow, so this is the scene-cut check. With tracking,
272
+ `--detect-every 3` keeps boxes on target and took 20.0 s.
273
+
274
+ ## Python API
275
+
276
+ ```python
277
+ from avmosaic import CensorOptions, VideoOptions, censor_image, censor_video, create_detector
278
+
279
+ detector = create_detector("320n", classes=["FEMALE_GENITALIA_EXPOSED", "MALE_GENITALIA_EXPOSED"],
280
+ threshold=0.2)
281
+ censor_image("in.jpg", "out.jpg", detector, CensorOptions(block_size="2%"))
282
+ censor_video("in.mp4", "out.mp4", detector, CensorOptions(), VideoOptions(hold=8))
283
+ ```
284
+
285
+ Any object with a `detect(bgr_image) -> list[Detection]` method can replace the
286
+ detector, for example a model you trained yourself.
287
+
288
+ ## Testing
289
+
290
+ ```bash
291
+ pip install -e ".[dev]"
292
+ pytest # everything (about 10 s)
293
+ pytest -m "not model" # skip the tests that run the real model
294
+ ```
295
+
296
+ The tests use no explicit material.
297
+
298
+ - **Pipeline tests** replace the model with a colour detector that treats a synthetic
299
+ red/black checkerboard as the region to censor. They check mosaic geometry, frame count,
300
+ audio and duration, the frame-hold logic (a deliberately missed frame leaks with
301
+ `hold=0` and is covered with `hold=2`), odd sizes, NTSC rates and Unicode paths.
302
+ - **Tracking tests** run the planner on in-memory frames and measure exactly how much of
303
+ the true object box is covered in every frame. The scenarios are fast motion, 15-frame
304
+ detector misses, curved paths with `--detect-every 6`, backfill, scene cuts, two objects
305
+ moving in opposite directions, and forced optical-flow failure. In the fast-motion test,
306
+ a 40 px object stays fully covered either way. Tracking censors 1.03× the object's area
307
+ at 8 px/frame and 1.14× at 25 px/frame. `--tracking off` censors 2.70× and 5.48×.
308
+ - **Model tests** run the real NudeNet model. They check that its output matches upstream
309
+ `nudenet` exactly in BGR mode, and use the model's *face* class on a public-domain photo
310
+ as an end-to-end check.
311
+ - **Your own accuracy check:** put labelled images in a private folder (never commit them)
312
+ and run `AVMOSAIC_SAMPLES=/path/to/samples pytest tests/test_samples.py -v`. See
313
+ [tests/test_samples.py](https://github.com/fattaillabs/avmosaic/blob/main/tests/test_samples.py) for the label format.
314
+
315
+ CI runs the suite on Linux, macOS and Windows with Python 3.10, 3.12 and 3.14.
316
+
317
+ ## Limitations
318
+
319
+ - The detector is the main source of error. NudeNet was trained on general nudity
320
+ images. Small regions in large frames, extreme close-ups and partial occlusion are
321
+ hard for it.
322
+ - Variable-frame-rate video is converted to a constant rate (this keeps audio in sync).
323
+ - Subtitle and data streams are not copied. Odd video sizes are padded by one pixel for
324
+ H.264.
325
+ - Image metadata (EXIF) is not copied to the output. The EXIF orientation is applied
326
+ first.
327
+
328
+ ## License
329
+
330
+ MIT for this project's code — see [LICENSE](https://github.com/fattaillabs/avmosaic/blob/main/LICENSE). Third-party components, including
331
+ an important note about NudeNet's license, are listed in [NOTICE](https://github.com/fattaillabs/avmosaic/blob/main/NOTICE).