shuttlecheck 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 (37) hide show
  1. shuttlecheck-0.1.0/LICENSE +43 -0
  2. shuttlecheck-0.1.0/PKG-INFO +145 -0
  3. shuttlecheck-0.1.0/PYPI.md +112 -0
  4. shuttlecheck-0.1.0/README.md +392 -0
  5. shuttlecheck-0.1.0/pyproject.toml +57 -0
  6. shuttlecheck-0.1.0/setup.cfg +4 -0
  7. shuttlecheck-0.1.0/shuttlecheck/__init__.py +39 -0
  8. shuttlecheck-0.1.0/shuttlecheck/checks.py +156 -0
  9. shuttlecheck-0.1.0/shuttlecheck/cli.py +240 -0
  10. shuttlecheck-0.1.0/shuttlecheck/delivery.py +505 -0
  11. shuttlecheck-0.1.0/shuttlecheck/extensions.py +53 -0
  12. shuttlecheck-0.1.0/shuttlecheck/infer.py +730 -0
  13. shuttlecheck-0.1.0/shuttlecheck/patterns.py +350 -0
  14. shuttlecheck-0.1.0/shuttlecheck/report.py +146 -0
  15. shuttlecheck-0.1.0/shuttlecheck/schedule.py +215 -0
  16. shuttlecheck-0.1.0/shuttlecheck/schema.json +1274 -0
  17. shuttlecheck-0.1.0/shuttlecheck/sidecar.py +330 -0
  18. shuttlecheck-0.1.0/shuttlecheck/spec.py +241 -0
  19. shuttlecheck-0.1.0/shuttlecheck/ui.py +332 -0
  20. shuttlecheck-0.1.0/shuttlecheck/ui_assets/app.css +186 -0
  21. shuttlecheck-0.1.0/shuttlecheck/ui_assets/app.html +145 -0
  22. shuttlecheck-0.1.0/shuttlecheck/ui_assets/app.js +396 -0
  23. shuttlecheck-0.1.0/shuttlecheck/validate.py +335 -0
  24. shuttlecheck-0.1.0/shuttlecheck/version.py +1 -0
  25. shuttlecheck-0.1.0/shuttlecheck/walkthrough.py +321 -0
  26. shuttlecheck-0.1.0/shuttlecheck.egg-info/PKG-INFO +145 -0
  27. shuttlecheck-0.1.0/shuttlecheck.egg-info/SOURCES.txt +35 -0
  28. shuttlecheck-0.1.0/shuttlecheck.egg-info/dependency_links.txt +1 -0
  29. shuttlecheck-0.1.0/shuttlecheck.egg-info/entry_points.txt +2 -0
  30. shuttlecheck-0.1.0/shuttlecheck.egg-info/requires.txt +8 -0
  31. shuttlecheck-0.1.0/shuttlecheck.egg-info/top_level.txt +1 -0
  32. shuttlecheck-0.1.0/tests/test_cli.py +89 -0
  33. shuttlecheck-0.1.0/tests/test_delivery.py +341 -0
  34. shuttlecheck-0.1.0/tests/test_infer.py +259 -0
  35. shuttlecheck-0.1.0/tests/test_ui.py +251 -0
  36. shuttlecheck-0.1.0/tests/test_validate.py +320 -0
  37. shuttlecheck-0.1.0/tests/test_verify.py +132 -0
@@ -0,0 +1,43 @@
1
+ ShuttleCheck — Licence
2
+ Copyright (c) 2026 MVRK Systems. All rights reserved.
3
+
4
+ This software is made available free of charge. It is not open source.
5
+
6
+ PERMITTED
7
+
8
+ You may install this software and use it, without charge, for any purpose
9
+ including commercial work, on any number of machines, for as long as you
10
+ like. No account, licence key or registration is required.
11
+
12
+ You may run it against any material you are lawfully in possession of, and
13
+ you may share the reports it produces with anyone.
14
+
15
+ NOT PERMITTED
16
+
17
+ You may not redistribute this software, in whole or in part, in original or
18
+ modified form, whether or not for a fee.
19
+
20
+ You may not modify, adapt, translate, reverse engineer, decompile or create
21
+ derivative works from it, except to the extent that this restriction is
22
+ prohibited by applicable law.
23
+
24
+ You may not remove or alter this notice, or any attribution contained in the
25
+ software.
26
+
27
+ You may not sublicense, rent, lease or sell it, or offer it as a hosted or
28
+ managed service.
29
+
30
+ NO WARRANTY
31
+
32
+ This software is provided "as is", without warranty of any kind, express or
33
+ implied, including but not limited to the warranties of merchantability,
34
+ fitness for a particular purpose and non-infringement.
35
+
36
+ In no event shall MVRK Systems be liable for any claim, damages or other
37
+ liability, whether in an action of contract, tort or otherwise, arising from,
38
+ out of or in connection with the software or its use.
39
+
40
+ This software reports on files. It does not modify them. You remain
41
+ responsible for verifying your own deliverables.
42
+
43
+ Questions, and licences permitting anything above: hello@mvrk.systems
@@ -0,0 +1,145 @@
1
+ Metadata-Version: 2.4
2
+ Name: shuttlecheck
3
+ Version: 0.1.0
4
+ Summary: Check media filenames, folder structure and deliverables against a written convention. Read-only.
5
+ Author-email: MVRK Systems <hello@mvrk.systems>
6
+ License: Proprietary — free to use, no redistribution
7
+ Project-URL: Homepage, https://mvrk.systems/shuttlecheck
8
+ Project-URL: Documentation, https://mvrk.systems/shuttlecheck#checks
9
+ Keywords: broadcast,post-production,dailies,media,naming-convention,file-naming,delivery,conformance,validation,vfx,video
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: Environment :: Web Environment
13
+ Classifier: Intended Audience :: End Users/Desktop
14
+ Classifier: License :: Other/Proprietary License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Multimedia :: Video
22
+ Classifier: Topic :: System :: Archiving
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: PyYAML>=6.0
28
+ Requires-Dist: jsonschema>=4.20
29
+ Requires-Dist: tzdata>=2024.1; sys_platform == "win32"
30
+ Provides-Extra: dev
31
+ Requires-Dist: pytest>=8.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # ShuttleCheck
35
+
36
+ **Check media filenames, folder structure and deliverables against a written
37
+ convention.** Nothing in this package renames, moves or deletes anything.
38
+
39
+ On a production the naming convention is a PDF or a message in a chat, and
40
+ every vendor on the job breaks it. Nobody finds out until a conform fails in
41
+ post, or a lab rejects a delivery at four in the morning.
42
+
43
+ ```bash
44
+ pip install shuttlecheck
45
+ shuttlecheck ui
46
+ ```
47
+
48
+ Point it at a job you have already finished. It works out the convention you
49
+ are already using, then shows you every file that does not follow it.
50
+
51
+ ---
52
+
53
+ ## It reads the convention off your own files
54
+
55
+ Nobody can describe their naming convention in the abstract, and nobody should
56
+ have to. Point `init` at a finished job and it recovers the fields, tells you
57
+ how confident it is in each one, and flags anything it had to guess at.
58
+
59
+ ```
60
+ FIELDS RECOVERED
61
+ {project} constant 100% NRD24
62
+ {day} integer 90% DAY1, DAY2
63
+ {stage} enum 80% HALLA, HALLB
64
+ {session} integer 90% SES001, SES002
65
+ {title} slug 85% opening-remarks, keynote
66
+ ```
67
+
68
+ It handles conventions that disagree with themselves — several accepted forms
69
+ per field, optional elements, free-text titles containing the separator —
70
+ because real ones do.
71
+
72
+ ## Then it tells you what breaks it
73
+
74
+ ```
75
+ SHUTTLECHECK CONFORMANCE REPORT
76
+ files scanned 412
77
+ conforming 398
78
+ errors 9 ingest will fail
79
+ warnings 5 inconsistent, will work
80
+
81
+ ERROR filename-mismatch (6)
82
+ D02/MAIN/MASTERS/AUT26_D02_MAINSTAGE_S114_v01.mov
83
+ the stage field does not match what the spec allows
84
+ ```
85
+
86
+ Findings name the field at fault, not just the file. Errors mean an ingest will
87
+ reject it; warnings mean it is inconsistent but will work. The two are never
88
+ conflated, because a noisy report does not get read.
89
+
90
+ ## What it checks
91
+
92
+ | | |
93
+ |---|---|
94
+ | Names and folders | Against a pattern, with the offending field identified |
95
+ | Codec and container | Read from the file, not from the filename's claim |
96
+ | Resolution, frame rate, scan | Including 29.97 handled as `30000/1001` |
97
+ | Audio | Channel count, layout, sample rate, bit depth |
98
+ | Sidecars | Captions, checksums, metadata a deliverable must carry |
99
+ | Packages | Whether a session is short a master, a proxy, a cutdown |
100
+ | Portability | Illegal characters, reserved names, path length, case collisions |
101
+ | Unicode | Names that compare equal on one platform and not another |
102
+
103
+ Media inspection uses `ffprobe` when it is on your PATH. When it is not, those
104
+ checks are reported as **skipped** — never as passed. A silent skip is how a
105
+ conformance tool starts lying.
106
+
107
+ ## For live events, the running order does the work
108
+
109
+ An event agenda is structured data that exists weeks before anyone turns a
110
+ camera on. Joined against time-of-day timecode, every recording names itself —
111
+ stage, session, speaker, sponsor — with no inference of any kind.
112
+
113
+ It will also tell you when a filename is well-formed and still wrong: `S007`
114
+ recorded during `S011` is a valid shape carrying a false claim, and only the
115
+ running order knows.
116
+
117
+ ## Checking a drive that arrived
118
+
119
+ If a drive was sealed with a convention on it, one command with no arguments
120
+ checks both that it arrived intact and that the names conform:
121
+
122
+ ```bash
123
+ shuttlecheck verify /Volumes/SHUTTLE01
124
+ ```
125
+
126
+ No spec to be told about, no account, no connection.
127
+
128
+ ---
129
+
130
+ ## What this package will not do
131
+
132
+ It will not rename, move or delete anything. That boundary is the point: it is
133
+ the command a stranger will run on a live job, because running it cannot cost
134
+ them anything.
135
+
136
+ Conforming files, the reversible journal, sealing drives and per-vendor specs
137
+ are licensed separately as ShuttleCheck Pro. Asking for one of those commands
138
+ here prints an explanation rather than an error.
139
+
140
+ ## Licence
141
+
142
+ Free to install and use. Not open source: no right to redistribute or modify.
143
+ See <https://mvrk.systems/shuttlecheck>.
144
+
145
+ Made by [MVRK Systems](https://mvrk.systems).
@@ -0,0 +1,112 @@
1
+ # ShuttleCheck
2
+
3
+ **Check media filenames, folder structure and deliverables against a written
4
+ convention.** Nothing in this package renames, moves or deletes anything.
5
+
6
+ On a production the naming convention is a PDF or a message in a chat, and
7
+ every vendor on the job breaks it. Nobody finds out until a conform fails in
8
+ post, or a lab rejects a delivery at four in the morning.
9
+
10
+ ```bash
11
+ pip install shuttlecheck
12
+ shuttlecheck ui
13
+ ```
14
+
15
+ Point it at a job you have already finished. It works out the convention you
16
+ are already using, then shows you every file that does not follow it.
17
+
18
+ ---
19
+
20
+ ## It reads the convention off your own files
21
+
22
+ Nobody can describe their naming convention in the abstract, and nobody should
23
+ have to. Point `init` at a finished job and it recovers the fields, tells you
24
+ how confident it is in each one, and flags anything it had to guess at.
25
+
26
+ ```
27
+ FIELDS RECOVERED
28
+ {project} constant 100% NRD24
29
+ {day} integer 90% DAY1, DAY2
30
+ {stage} enum 80% HALLA, HALLB
31
+ {session} integer 90% SES001, SES002
32
+ {title} slug 85% opening-remarks, keynote
33
+ ```
34
+
35
+ It handles conventions that disagree with themselves — several accepted forms
36
+ per field, optional elements, free-text titles containing the separator —
37
+ because real ones do.
38
+
39
+ ## Then it tells you what breaks it
40
+
41
+ ```
42
+ SHUTTLECHECK CONFORMANCE REPORT
43
+ files scanned 412
44
+ conforming 398
45
+ errors 9 ingest will fail
46
+ warnings 5 inconsistent, will work
47
+
48
+ ERROR filename-mismatch (6)
49
+ D02/MAIN/MASTERS/AUT26_D02_MAINSTAGE_S114_v01.mov
50
+ the stage field does not match what the spec allows
51
+ ```
52
+
53
+ Findings name the field at fault, not just the file. Errors mean an ingest will
54
+ reject it; warnings mean it is inconsistent but will work. The two are never
55
+ conflated, because a noisy report does not get read.
56
+
57
+ ## What it checks
58
+
59
+ | | |
60
+ |---|---|
61
+ | Names and folders | Against a pattern, with the offending field identified |
62
+ | Codec and container | Read from the file, not from the filename's claim |
63
+ | Resolution, frame rate, scan | Including 29.97 handled as `30000/1001` |
64
+ | Audio | Channel count, layout, sample rate, bit depth |
65
+ | Sidecars | Captions, checksums, metadata a deliverable must carry |
66
+ | Packages | Whether a session is short a master, a proxy, a cutdown |
67
+ | Portability | Illegal characters, reserved names, path length, case collisions |
68
+ | Unicode | Names that compare equal on one platform and not another |
69
+
70
+ Media inspection uses `ffprobe` when it is on your PATH. When it is not, those
71
+ checks are reported as **skipped** — never as passed. A silent skip is how a
72
+ conformance tool starts lying.
73
+
74
+ ## For live events, the running order does the work
75
+
76
+ An event agenda is structured data that exists weeks before anyone turns a
77
+ camera on. Joined against time-of-day timecode, every recording names itself —
78
+ stage, session, speaker, sponsor — with no inference of any kind.
79
+
80
+ It will also tell you when a filename is well-formed and still wrong: `S007`
81
+ recorded during `S011` is a valid shape carrying a false claim, and only the
82
+ running order knows.
83
+
84
+ ## Checking a drive that arrived
85
+
86
+ If a drive was sealed with a convention on it, one command with no arguments
87
+ checks both that it arrived intact and that the names conform:
88
+
89
+ ```bash
90
+ shuttlecheck verify /Volumes/SHUTTLE01
91
+ ```
92
+
93
+ No spec to be told about, no account, no connection.
94
+
95
+ ---
96
+
97
+ ## What this package will not do
98
+
99
+ It will not rename, move or delete anything. That boundary is the point: it is
100
+ the command a stranger will run on a live job, because running it cannot cost
101
+ them anything.
102
+
103
+ Conforming files, the reversible journal, sealing drives and per-vendor specs
104
+ are licensed separately as ShuttleCheck Pro. Asking for one of those commands
105
+ here prints an explanation rather than an error.
106
+
107
+ ## Licence
108
+
109
+ Free to install and use. Not open source: no right to redistribute or modify.
110
+ See <https://mvrk.systems/shuttlecheck>.
111
+
112
+ Made by [MVRK Systems](https://mvrk.systems).
@@ -0,0 +1,392 @@
1
+ # ShuttleCheck — validator
2
+
3
+ Naming conformance for a folder tree: infer a spec from it, check it, fix it,
4
+ undo the fix, and send the convention with the media. Installs as `shuttlecheck`;
5
+ the Python package is `shuttlecheck`.
6
+
7
+ `init`, `validate`, `verify` and `conform` (without `--apply`) never write to
8
+ the tree; `seal` writes only inside `_SPEC/`. Only `conform --apply` and
9
+ `revert --apply` move media, and both are journalled with a working undo. The
10
+ validator staying read-only is the point: it is the command a stranger can be
11
+ talked into running on a live job, because running it cannot cost them anything.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ python -m pip install -e .
17
+ ```
18
+
19
+ `tzdata` is a real runtime dependency on Windows — the platform ships no system
20
+ timezone database, so `zoneinfo` cannot resolve the agenda timezone without it.
21
+
22
+ ## Use
23
+
24
+ The app is the front door:
25
+
26
+ ```bash
27
+ shuttlecheck ui
28
+ ```
29
+
30
+ That serves a page from the package itself, opens a browser at it, and walks
31
+ you through pointing at a folder, reading the convention off it, seeing what
32
+ does not conform, and fixing what can be derived. No network is used at any
33
+ point. `shuttlecheck ui /Volumes/LAST_JOB` opens straight onto a folder.
34
+
35
+ Everything the app does is also a command, which is what you want in a cron
36
+ job or a delivery hook. Start by reading the convention off a job that already
37
+ exists:
38
+
39
+ ```bash
40
+ shuttlecheck init /Volumes/LAST_JOB
41
+ ```
42
+
43
+ That infers the spec, validates the tree against what it inferred, prints both,
44
+ asks only about what it could not settle, and writes `_SPEC/spec.yaml`. Add
45
+ `--yes` to skip the questions, `--project-code` to override the detected code,
46
+ `--vocabulary film` for a shoot rather than an event.
47
+
48
+ Then check any tree against it:
49
+
50
+ ```bash
51
+ shuttlecheck validate /Volumes/SHUTTLE01 --spec EXC26/_SPEC/spec.yaml
52
+ ```
53
+
54
+ ```bash
55
+ shuttlecheck validate /Volumes/SHUTTLE01 --spec spec.yaml --json --out report.json
56
+ ```
57
+
58
+ ```bash
59
+ shuttlecheck check-spec --spec examples/event-conference.yaml
60
+ ```
61
+
62
+ Seal the drive before it ships, and verify it when one arrives:
63
+
64
+ ```bash
65
+ shuttlecheck seal /Volumes/SHUTTLE01 --spec spec.yaml --hash
66
+ shuttlecheck verify /Volumes/SHUTTLE01 # no --spec needed
67
+ ```
68
+
69
+ Fix what can be fixed:
70
+
71
+ ```bash
72
+ shuttlecheck conform /Volumes/SHUTTLE01 --spec spec.yaml # plan only
73
+ shuttlecheck conform /Volumes/SHUTTLE01 --spec spec.yaml --apply # execute
74
+ shuttlecheck history /Volumes/SHUTTLE01
75
+ shuttlecheck revert /Volumes/SHUTTLE01 --apply # undo the last run
76
+ ```
77
+
78
+ Exit codes: `0` clean, `1` findings at error level (or any level with
79
+ `--strict`), `2` the spec or the path could not be read.
80
+
81
+ ## What it checks
82
+
83
+ | Rule | Level | What it means |
84
+ |---|---|---|
85
+ | `filename-mismatch` | error | Name does not parse against the context's pattern |
86
+ | `folder-mismatch` | error | File is not in the folder its context requires |
87
+ | `token-conflict` | error | The same token disagrees between folder and filename |
88
+ | `schedule-mismatch` | error | Named session is not the session the agenda had running |
89
+ | `forbidden-char`, `reserved-name`, `trailing-char` | error | Will not survive a Windows ingest |
90
+ | `path-too-long`, `filename-too-long` | error | Over the platform limit |
91
+ | `case-collision` | error | Siblings differ only by case |
92
+ | `unscheduled-media` | warning | No session was running at that clock time |
93
+ | `loose-file`, `missing-folder` | warning | Structure does not match the spec |
94
+ | `unicode-normalisation` | warning | NFD name; compares unequal across platforms |
95
+ | `unclassified` | info | No context matched; the file was not checked |
96
+ | `spec-*` | error/warning | Problems in the spec itself, not the tree |
97
+
98
+ Delivery rules, for contexts that declare a `deliverable:` profile:
99
+
100
+ | Rule | Level | What it means |
101
+ |---|---|---|
102
+ | `container-mismatch` | error | Wrapper is not what the spec asks for |
103
+ | `codec-mismatch` | error | Wrong codec or ProRes/DNx variant |
104
+ | `resolution-mismatch`, `framerate-mismatch`, `scan-mismatch` | error | Wrong picture |
105
+ | `video-bitdepth-mismatch` | error | 8-bit where the spec says 10 |
106
+ | `audio-channels-mismatch`, `audio-samplerate-mismatch`, `audio-bitdepth-mismatch`, `audio-track-count-mismatch`, `audio-layout-mismatch` | error | Wrong sound |
107
+ | `duration-out-of-range` | error | Too short or too long |
108
+ | `timecode-missing`, `timecode-start-mismatch` | error | Start timecode wrong or absent |
109
+ | `missing-sidecar` | error | Captions or checksum did not arrive with the file |
110
+ | `no-video-stream`, `no-audio-stream`, `unreadable-media` | error | Not the kind of file it claims to be |
111
+ | `color-*-mismatch` | error | Tagged with the wrong colour space |
112
+ | `color-*-unknown` | warning | Not tagged at all — absence is not proof of wrongness |
113
+ | `incomplete-package` | error | A session or episode is short a deliverable |
114
+ | `spec-unknown-deliverable` | error | A context points at a profile that is not defined |
115
+
116
+ Severities are overridable per rule in the spec's `rules:` block.
117
+
118
+ ## The app
119
+
120
+ `shuttlecheck ui` is a local web UI, not Electron and not Qt. The page ships
121
+ inside the wheel and is served by the standard library's HTTP server, so the
122
+ install stays `pip install shuttlecheck`, nothing is downloaded at runtime, and
123
+ it works with no connection at all — which matters, because the machine this
124
+ runs on is often in a truck. No web fonts either; the stylesheet falls back to
125
+ whatever the OS has.
126
+
127
+ Two safety properties are worth knowing, because a local server that renames
128
+ files is a different proposition from one that serves a blog:
129
+
130
+ **Every API call carries a session token.** Any page open in any tab can issue
131
+ requests to `127.0.0.1`. Without a token, a site you happened to visit could
132
+ drive a tool that renames your media. The token is minted at startup, travels
133
+ in the URL the browser is opened with, and never leaves the machine.
134
+
135
+ **Applying a plan requires echoing back the move count.** The page states how
136
+ many moves it believes it is approving. If the folder changed since the plan
137
+ was drawn, the numbers disagree and nothing runs — a stale tab cannot apply a
138
+ plan that no longer matches reality.
139
+
140
+ ## How inference works
141
+
142
+ `init` reads the tree rather than asking you to describe it, because nobody can
143
+ describe their own convention in the abstract and most people should not have
144
+ to try.
145
+
146
+ 1. **Group** files by the nearest role folder above them (`MASTERS`, `ISO`,
147
+ `PROXY`, `DELIVERABLES`, ...) and by media kind.
148
+ 2. **Split** each group's filenames on the separator that divides them most
149
+ consistently. Where a free-text title contains that separator and every file
150
+ has a different field count, anchor from the left and the right while the
151
+ columns still look like fixed codes and treat the remainder as one variable
152
+ field. Names structurally unrelated to the rest are set aside first, and
153
+ reported — they are usually the existing violations.
154
+ 3. **Profile** each column: constant, prefixed counter, enum, date, slug, or
155
+ free text, each with a confidence and a plain-English reason.
156
+ 4. **Reconcile** value sets across groups, so a column that reads as the
157
+ constant `HALLA` in one group and the enum `{HALLA, HALLB}` in another
158
+ becomes one token rather than two.
159
+ 5. **Name** the fields from prefix semantics rather than exact widths — `D01`
160
+ and `DAY1` are both `{day}` — falling back to `{field3}` and flagging it.
161
+ 6. **Self-validate**: run the validator over the same tree with the spec just
162
+ inferred, and show the result. That is the thirty-second demonstration.
163
+
164
+ Constants other than the project code become literals in the pattern, so you
165
+ get `{project}-{day}-{stage}-{session}-proxy` rather than a token bound to the
166
+ constant `proxy`.
167
+
168
+ Everything is conservative on purpose. A spec that over-constrains flags every
169
+ file and gets deleted; one that under-constrains still finds the real outliers
170
+ and can be tightened by hand. Low-confidence guesses are listed in the header
171
+ comments of the file it writes.
172
+
173
+ ## How the conformer works
174
+
175
+ It renames only what it can *derive*. For each non-conforming file:
176
+
177
+ 1. Parse the folder and the filename against the context's patterns.
178
+ 2. If both parse but disagree on a token, **skip** — there is no safe way to
179
+ tell which one is the mistake, so it is a question for a human.
180
+ 3. If the name fails on a single field, recover that one field: from the folder
181
+ path, by matching a known alias, by re-applying the token's own transforms
182
+ (which fixes casing, spaces and punctuation), or from its declared source.
183
+ 4. If a context sets `schedule_check`, cross-check the schedule-bound tokens
184
+ against the agenda and correct them. A name can parse perfectly and still be
185
+ wrong — `S001` recorded during `S005` is a valid shape carrying a false
186
+ claim. An ambiguous clock window corrects nothing rather than guessing.
187
+ 5. If the correct name still cannot be derived, **skip and say why**. Inventing
188
+ a session number is worse than leaving a bad filename.
189
+
190
+ Safety, none of it optional:
191
+
192
+ | | |
193
+ |---|---|
194
+ | Dry run | The default. `--apply` is a separate, explicit act. |
195
+ | Originals | `rename: never` contexts are never planned, moved, or renamed. |
196
+ | Overwrite | Never. A destination that exists aborts the whole plan. |
197
+ | Collisions | Two files resolving to one name abort everything — a half-renamed job is worse than an unrenamed one. |
198
+ | Stale plans | Every source is re-checked for size and mtime before the first move. |
199
+ | Case-only renames | Routed through a temporary name, as are swaps and cycles. |
200
+ | Mid-run failure | Every completed move is rolled back, and the run is marked `rolled_back` in the journal. |
201
+
202
+ ## The delivery spec
203
+
204
+ Naming is the wedge; this is the business. A file can be named perfectly and
205
+ still be rejected at ingest for being 8-bit instead of 10, 29.97 instead of 25,
206
+ six audio channels where the spec says two, or arriving without its captions.
207
+
208
+ `deliverables:` profiles say what a file must *be*; a context points at one with
209
+ `deliverable:`. `packages:` says what a session or episode owes in total, which
210
+ is the only way to catch the master that was never exported at all.
211
+
212
+ Three things worth knowing:
213
+
214
+ **Frame rates compare with a tolerance.** A spec says `29.97`, a file says
215
+ `30000/1001`. Exact comparison fails every NTSC delivery ever made.
216
+
217
+ **MOV and MP4 cannot be told apart by `format_name`.** They share an ffprobe
218
+ demuxer and both report `mov,mp4,m4a,3gp,3g2,mj2`, so taking the first entry
219
+ marks every MP4 as a MOV and fails every MP4 delivery. The ftyp major brand is
220
+ the real discriminator, with the file extension as a fallback.
221
+
222
+ **Without a media inspector, every media check reports as skipped.** Not passed.
223
+ The report prints a `SOURCES NOT AVAILABLE` block naming exactly what was not
224
+ checked. A delivery report that silently checked nothing is worse than no report
225
+ at all.
226
+
227
+ `ffprobe` is the default inspector and is found on PATH. `Prober` is the seam if
228
+ you ever want a different one.
229
+
230
+ ## Vendor packs
231
+
232
+ A convention only works if everyone on the job follows the same one, and a job
233
+ has a captions vendor, a social edit shop and a sponsor agency, none of whom
234
+ share a network with the production or with each other.
235
+
236
+ ```bash
237
+ shuttlecheck pack /Volumes/JOB --spec spec.yaml --vendor captions
238
+ shuttlecheck vendors /Volumes/JOB --spec spec.yaml
239
+ ```
240
+
241
+ `pack` writes a folder holding a scoped `spec.yaml`, anything it references,
242
+ and a README that states the obligation in plain words:
243
+
244
+ ```
245
+ WHAT YOU ARE DELIVERING
246
+ Speaker copy
247
+ folder {project}/DELIVERABLES/SPEAKERS/{day}
248
+ filename {project}_{day}_{session}_{speaker}_{title}_{version}
249
+ must be container mp4, codec h264, resolution 1920x1080 or 1280x720,
250
+ 25 fps, 2 audio channel(s)
251
+ with Subtitles ({stem}.srt)
252
+ ```
253
+
254
+ The pack is **derived**, never authored separately — a vendor cannot drift from
255
+ a document they did not write. `vendors` is the other direction: point it at the
256
+ incoming deliveries and it reports who is clear, who has errors, and who has
257
+ sent nothing at all.
258
+
259
+ Two things worth knowing:
260
+
261
+ **Out-of-scope contexts are kept as inert stubs, not deleted.** Contexts are
262
+ first-match-wins, so removing an early one silently re-routes its files into a
263
+ later catch-all — the ISO rushes would come back reported as malformed masters.
264
+ The stubs preserve ordering and say plainly: not yours.
265
+
266
+ **A vendor is never shown a rule they cannot act on.** Packages narrow to the
267
+ requirements they own; a package that ends up empty is dropped rather than
268
+ shown as trivially satisfied. Noise is what stops a report being read.
269
+
270
+ ## The journal
271
+
272
+ `_SPEC/journal/`, one append-only JSON Lines file per run, flushed after each
273
+ move so an interrupted run records what actually happened rather than what was
274
+ intended.
275
+
276
+ Its second job matters more than undo. Every file carries an `original_path`,
277
+ recorded the first time it is touched and never rewritten. When a spec is
278
+ edited and the tree re-conformed, names are computed from that identity — never
279
+ from the current name. Without it, tokens compound on every pass: the second
280
+ run re-prefixes what the first already prefixed, and by the third edit the
281
+ names are unrecoverable. That failure is silent and looks like it worked.
282
+
283
+ ## The drive sidecar
284
+
285
+ A convention is only worth anything if everyone downstream uses the same one,
286
+ and downstream is a different company on a different network reading a shuttle
287
+ drive out of a Peli case. `seal` writes `_SPEC/` onto the drive itself:
288
+
289
+ | File | What it is |
290
+ |---|---|
291
+ | `spec.yaml` | the convention, plus every file it references (the agenda travels too) |
292
+ | `manifest.jsonl` | one JSON object per line: path, size, mtime, optional hash |
293
+ | `REPORT.txt` | the conformance report as it stood when the drive was sealed |
294
+ | `README.txt` | plain-language instructions for someone who has never heard of this tool |
295
+ | `journal/` | what has been renamed, and by which spec revision |
296
+
297
+ The receiving end runs `shuttlecheck verify .` — no spec to be told about, no
298
+ account, no connectivity. Sealing never touches the media; it only writes
299
+ inside `_SPEC/`.
300
+
301
+ What `verify` reports:
302
+
303
+ | | |
304
+ |---|---|
305
+ | `missing` | error — on the manifest, not on the drive; the copy is incomplete |
306
+ | `size-mismatch` | error — damaged or replaced in transit |
307
+ | `hash-mismatch` | error — same size, different contents (needs `--hash` at both ends) |
308
+ | `added` | warning — on the drive but not the manifest |
309
+ | `mtime-drift` | info — most copy tools do not preserve timestamps |
310
+ | `clock-did-not-survive-copy` | warning — see below |
311
+
312
+ Two judgement calls worth knowing about.
313
+
314
+ **Timestamp drift is not damage.** Most copy tools do not preserve mtime, so
315
+ treating that as corruption would make every genuine transfer look broken.
316
+ *But* if the spec joins the agenda on `file_mtime`, drift silently moves every
317
+ recording to the wrong session and the naming findings become fiction — so that
318
+ specific combination is raised as a warning rather than left to be discovered.
319
+ A spec using embedded timecode does not have the problem.
320
+
321
+ **Hashing is opt-in.** Re-hashing terabytes to restate what an existing MHL
322
+ already asserts wastes hours, so where checksum sidecars are already on the
323
+ drive they are noted and left to do their job. Without `--hash`, sizes and
324
+ timestamps are all that can be checked, and the sidecar says so in writing.
325
+
326
+ ## Design notes
327
+
328
+ **It parses, it does not render.** The validator compiles each naming pattern
329
+ into a regex whose groups are constrained by each token's own type, enum values,
330
+ and transforms, then matches the existing name against it. That is why it needs
331
+ no prompting, no schedule, and no media parser to do its core job — and why
332
+ `prompt`-sourced tokens validate fine.
333
+
334
+ **Mismatches name the field.** On a failure it recompiles the pattern with each
335
+ token loosened in turn; whichever loosening makes the name match is the field at
336
+ fault. "The stage field does not match what the spec allows" is a finding
337
+ someone fixes. "Does not match the pattern" is a finding someone ignores.
338
+
339
+ **Missing sources are reported as skipped, never as passed.** Timecode and
340
+ embedded start times need a media parser. Without `ffprobe` on PATH the report
341
+ prints a `SOURCES NOT AVAILABLE` block naming what could not be checked. A
342
+ silent skip is how a conformance tool starts lying.
343
+
344
+ **Original recorder output is never name-checked.** Contexts declaring
345
+ `rename: never` have their folder structure validated and their filenames left
346
+ alone, and the schema will not let such a context propose a name at all.
347
+
348
+ ## Tests
349
+
350
+ ```bash
351
+ python -m pytest -q
352
+ ```
353
+
354
+ 213 tests, across three fixture trees.
355
+
356
+ `tests/make_fixture.py` writes seven conforming files and six deliberate
357
+ violations, plus an agenda whose slots are stamped onto file mtimes so the
358
+ schedule join is exercisable without a media parser:
359
+
360
+ ```bash
361
+ python -m tests.make_fixture ./fixture
362
+ shuttlecheck validate ./fixture --spec ./fixture/EXC26/_SPEC/spec.yaml
363
+ ```
364
+
365
+ `tests/make_infer_fixture.py` writes a tree using a convention the code has
366
+ never seen — dashes instead of underscores, `DAY1` instead of `D01`, `SES001`
367
+ instead of `S001`, variable-length titles containing the separator, and
368
+ camera-native ISO names. If inference recovers that, it is reading the tree
369
+ rather than recognising something baked into the source:
370
+
371
+ ```bash
372
+ python -m tests.make_infer_fixture ./infer-fixture
373
+ shuttlecheck init ./infer-fixture --yes
374
+ ```
375
+
376
+ The load-bearing test is the round trip: infer a spec from a tree, then run the
377
+ validator over that same tree with that spec, and assert it finds no naming
378
+ errors in the files it learned from.
379
+
380
+ Portability rules are unit-tested against the check functions rather than real
381
+ files, because Windows cannot create a file named `NUL.mov`, `bad<name>.mov`, or
382
+ `trailing..mov.` — which is the point of the check.
383
+
384
+ ## Not built yet
385
+
386
+ - Embedded metadata tokens beyond a start time (reel, camera ID, lens, BWF
387
+ scene/take). `MediaProbe` is the seam; it currently reads timecode and
388
+ creation time via `ffprobe` and degrades honestly without it.
389
+ - Agenda formats other than CSV. `ics`, `sched`, `swapcard`, and `cvent` are in
390
+ the schema and raise `ScheduleUnavailable` rather than failing the run.
391
+ - The conformer, its journal, and revert. Deliberately absent — see the
392
+ read-only boundary above.