Nationwide UK evidence project — public documentation, compliance standards, and ingest tooling for the @Reckless-Rides-UK YouTube channel. Timestamped evidence of dangerous and illegal cycling wherever incidents are captured: pavement, footpath, and road.
Nationwide UK · Awareness · Evidence · Change
| Operator / controller | Dynamic Devices Ltd (England & Wales) |
| ICO registration | Dynamic Devices Limited · ZC180994 (Tier 1 micro; fee paid 23 Jun 2026) |
| Site | recklessrides.uk (incident map) |
| Repo | github.com/DynamicDevices/reckless-rides-uk |
| Shared core | evidence-core (git submodule at core/) |
| Compliance | COMPLIANCE-STATEMENT.md |
| Privacy / takedown | ajlennon@dynamicdevices.co.uk |
Video evidence is never stored in this repository (see .gitignore). *_UPLOAD.json metadata (titles, descriptions, tags, YouTube URLs — no faces or video) is tracked for transparency and powers the public map.
Incident IDs use the legacy DEB- prefix (unchanged after rebrand).
Link this section from your YouTube channel About box.
| Document | Audience | URL |
|---|---|---|
| Compliance & standards statement | Complainants, YouTube, police | https://github.com/DynamicDevices/reckless-rides-uk/blob/main/COMPLIANCE-STATEMENT.md |
| Incident map | Public map of uploaded incidents | recklessrides.uk (GitHub Pages + custom domain) |
| UK compliance record | Full GDPR/legal operating detail | https://github.com/DynamicDevices/reckless-rides-uk/blob/main/UK-COMPLIANCE.md |
| Publication workflow | How clips are anonymised before going public | This README |
Privacy / takedown / feedback: ajlennon@dynamicdevices.co.uk
We welcome constructive feedback to ensure we meet all legal and platform obligations.
Mandatory path from capture to upload. Never skip the manual gates. Full legal context: UK-COMPLIANCE.md.
flowchart TD
subgraph capture ["1 · Capture"]
A["Record in public<br/>(Ray-Ban Meta glasses)"]
B["LocalSend → rides-imports/<br/>or manual path"]
end
subgraph auto ["Optional: import watcher"]
B2["process-import-inbox.sh"]
end
subgraph ingest ["2 · ingest-incident.sh"]
C["Assign DEB incident ID<br/>from GPS + timestamp"]
D["ORIGINAL.mov · evidence/originals/<br/>FULL metadata + faces"]
E["PROCESSED.mp4 · deface face blur"]
F["PUBLISH.mp4 · 16:9 letterbox<br/>metadata stripped"]
G["UPLOAD.json · title / description / tags"]
H["MANIFEST.json + incidents.csv<br/>SHA-256 chain of custody"]
end
subgraph gates ["3 · Manual compliance gates"]
I{"Faces adequately<br/>blurred?"}
J{"Children visible or<br/>anonymisation failed?"}
K{"Title & description<br/>factual · no names?"}
Q["DO NOT PUBLISH<br/>re-blur · mute · or withhold"]
end
subgraph publish ["4 · YouTube (private auto-upload)"]
L["upload-pending-incidents.sh<br/>PRIVATE + privacy footer"]
M["YouTube PRIVATE<br/>awaiting your review"]
S{"Final review<br/>on YouTube?"}
T["Set PUBLIC manually<br/>in YouTube Studio"]
U["YouTube PUBLIC"]
end
subgraph police ["Police only (never YouTube)"]
N["101 report<br/>ORIGINAL + manifest"]
O["evidence/export/<br/>optional zip bundle"]
end
subgraph ongoing ["5 · Ongoing obligations"]
P["Privacy complaint / SAR / objection"]
R["Take down YouTube clip<br/>delete or retain originals per policy"]
end
A --> B
B --> B2
B --> C
B2 --> C
C --> D
C --> E --> F
C --> G
C --> H
E --> I
I -->|No| Q
I -->|Yes| J
J -->|Yes| Q
J -->|No| K
K -->|No| Q
K -->|Yes| L --> M
M --> S
S -->|Fail| R
S -->|Pass| T --> U
D -.->|optional| N
N -.-> O
U --> P --> R
style D fill:#2d3748,stroke:#e53e3e,color:#fff
style Q fill:#742a2a,stroke:#e53e3e,color:#fff
style M fill:#4a5568,stroke:#a0aec0,color:#fff
style U fill:#1a365d,stroke:#63b3ed,color:#fff
style N fill:#22543d,stroke:#68d391,color:#fff
| Stage | Privacy / legal control |
|---|---|
| ORIGINAL | Never uploaded; gitignored; identifiable data retained only for police / defence |
| PROCESSED | Face blur (deface); human review before any publish decision |
| PUBLISH | No embedded GPS/device metadata; 1920×1080 letterbox; only this file goes to YouTube |
| UPLOAD.json | Factual text from templates; default private; set public in Studio after review |
| MANIFEST | Integrity hashes; documents what was shared with police |
| Gates | See UK-COMPLIANCE.md §12 checklist |
| Complaints | See UK-COMPLIANCE.md §9 |
reckless-rides-uk/
core/ evidence-core git submodule (metadata probe, manifest, geojson)
branding/ Channel art and watermark (YouTube-sized PNGs)
channel/ Copy-paste text for YouTube Studio + upload templates
config/ import-inbox.conf, OAuth secrets (gitignored; see .example)
docs/ GitHub Pages site + incident map (Leaflet + GeoJSON)
.github/workflows/ Pages CI (build map, deploy docs/)
evidence/
originals/ Full metadata, identifiable faces — POLICE ONLY
processed/ Face-blurred review copies (gitignored) + *_UPLOAD.json (tracked)
publish/ Metadata-stripped — upload these to YouTube
export/ Optional zip bundles for 101 handover
register/
incidents.csv Master log (gitignored — copy from .example on first run)
manifests/ Per-incident JSON with SHA-256 hashes
scripts/
ingest-incident.sh Full ingest pipeline
republish-incident.sh Re-letterbox + metadata (fix Shorts / wrong aspect)
process-import-inbox.sh One-shot scan of glasses import inbox
watch-import-inbox.sh Poll inbox (used by systemd)
upload-pending-incidents.sh Upload ingested clips missing a YouTube URL
install-import-watcher.sh Enable import watcher service
regenerate-upload-metadata.sh Rebuild *_UPLOAD.json from manifest
upload_metadata.py Channel templates → YouTube metadata builder
update-youtube-metadata.py Update title/description/tags on uploaded videos
upload-incident.sh Upload *_PUBLISH.mp4 via YouTube API
youtube-upload.py Upload implementation
build-map-data.py *_UPLOAD.json → docs/data/incidents.geojson
install-pre-commit.sh Install git pre-commit hooks
pre-commit-check.sh Run all hooks manually
.pre-commit-config.yaml Hooks: map build, JSON, shell, YAML, path checks
Metadata probing, chain-of-custody manifests, and the public incident map are built on the shared evidence-core library (git submodule at core/).
| Script | Uses core for |
|---|---|
ingest-incident.sh |
Probe source MOV (GPS, time, device); write manifest |
build-map-data.py |
*_UPLOAD.json → docs/data/incidents.geojson |
Existing clone — initialise the submodule after pull:
git submodule update --init --recursive
pip install -e "./core[dev]"Probe any file (glasses clip, test phone media):
probe-media /path/to/video.MOVEach incident gets a sequential ID and consistent prefix:
DEB-{UTC}_{LAT}_{LON}_{NNN}_{ROLE}.{ext}
Example:
DEB-20260623T080303Z_53.4092N_2.9778W_001_ORIGINAL.mov ← police evidence
DEB-20260623T080303Z_53.4092N_2.9778W_001_PROCESSED.mp4 ← review (blurred)
DEB-20260623T080303Z_53.4092N_2.9778W_001_UPLOAD.json ← YouTube title/description/tags
DEB-20260623T080303Z_53.4092N_2.9778W_001_PUBLISH.mp4 ← YouTube upload
DEB-20260623T080303Z_53.4092N_2.9778W_001_MANIFEST.json ← register/manifests/
- DEB — dossier prefix for handover
- UTC —
com.apple.quicktime.creationdatefrom glasses - LAT/LON — from ISO6709 GPS tag
- NNN — incident sequence (
001,002, …) fromregister/incidents.csv - ROLE —
ORIGINAL|PROCESSED|PUBLISH|UPLOAD(JSON metadata, lives with processed)
git clone --recurse-submodules git@github.com:DynamicDevices/reckless-rides-uk.git
cd reckless-rides-uk
pip3 install --user deface
pip3 install --user -r requirements-youtube.txt # for automated YouTube upload
pip3 install -e "./core[dev]" # optional: probe-media CLI
# ffmpeg/ffprobe already on systemCopy or transfer from glasses/LocalSend, then:
./scripts/ingest-incident.sh /path/to/video.MOV "optional notes"This will:
- Copy the source to
evidence/originals/with the controlled name - Blur faces (
deface) →evidence/processed/ - Strip metadata and letterbox to 16:9 (1920×1080) →
evidence/publish/— avoids YouTube Shorts classification - Write
evidence/processed/*_UPLOAD.json(YouTube title, description, tags) - Write
register/manifests/*_MANIFEST.json(SHA-256 per file) - Append a row to
register/incidents.csv
Upload *_PUBLISH.mp4 to YouTube as private. After review on YouTube, set public manually in Studio.
Give police *_ORIGINAL + manifest if you report via 101.
Copy clips from Ray-Ban Meta glasses via LocalSend into:
/home/ajlennon/LocalSend/rides-imports/
New .MOV / .mp4 files are auto-ingested (face blur, register, manifest). After ingest the source moves to rides-imports/done/ and PUBLISH.mp4 is uploaded to YouTube as private automatically.
You still review on YouTube Studio and set public manually when ready. Nothing uploads as public without --confirm-public-bypass.
One-time setup:
./scripts/install-import-watcher.shThis enables a user systemd service that polls the inbox every 10 seconds. Processed sources move to rides-imports/done/; failures go to rides-imports/failed/.
Manual one-shot (without the watcher):
./scripts/process-import-inbox.shStatus / logs:
systemctl --user status reckless-rides-import-watcher.service
tail -f register/import-inbox.logConfig: copy config/import-inbox.conf.example → config/import-inbox.conf to change inbox path, poll interval, or set AUTO_YOUTUBE_UPLOAD=false to disable YouTube automation.
Upload backlog manually:
./scripts/upload-pending-incidents.shOne-time setup (Google Cloud):
- Google Cloud Console → create project → enable YouTube Data API v3
- OAuth consent screen → External → add scopes
youtube.uploadandyoutube→ add your Google account as test user - Credentials → Create OAuth client ID → Desktop app → download JSON
- Save as
config/client_secret.json(seeconfig/client_secret.json.example) - First upload opens a browser to authorise; token saved to
config/youtube-token.json(gitignored)
Create playlist Reckless Rides UK 2026 in YouTube Studio once (script adds videos to it by name; name from channel/upload-playlist.txt).
After ingest + review of *_PROCESSED.mp4:
# Dry run
./scripts/upload-incident.sh DEB-20260623T080303Z_53.4092N_2.9778W_001 --dry-run
# Upload private (default)
./scripts/upload-incident.sh DEB-20260623T080303Z_53.4092N_2.9778W_001Updates *_UPLOAD.json (top-level youtube_url plus youtube.video_id, youtube.url, youtube.studio_url, youtube.uploaded_utc), register/incidents.csv, and manifest. Default quota ~6 uploads/day.
Manual alternative: YouTube Studio → upload *_PUBLISH.mp4 using text from *_UPLOAD.json.
Regenerate upload metadata after editing channel templates:
./scripts/regenerate-upload-metadata.sh DEB-20260623T080303Z_53.4092N_2.9778W_001Update existing YouTube videos (title, description, tags from *_UPLOAD.json — no re-upload):
./scripts/update-youtube-metadata.py --all
# or one incident:
./scripts/update-youtube-metadata.py DEB-20260623T080303Z_53.4092N_2.9778W_001 --dry-runTemplates live in channel/ — contact email, compliance URL, title/description templates, tags, playlist.
Portrait clips from the glasses may be classified as YouTube Shorts (limited description visibility). The ingest pipeline letterboxes to 1920×1080. If you uploaded before that fix:
- Delete the Short in YouTube Studio (or leave private and ignore)
- Rebuild publish copy and metadata:
./scripts/republish-incident.sh DEB-20260623T080303Z_53.4092N_2.9778W_001
./scripts/upload-incident.sh DEB-20260623T080303Z_53.4092N_2.9778W_001- Confirm the new upload appears under Content → Videos, not Shorts
After upload, add police_ref to incidents.csv and the manifest when reported via 101.
This project is designed to stay within UK GDPR and YouTube Community Guidelines. Key controls:
| Area | What we do |
|---|---|
| GDPR | Legitimate interests + minimisation; face blur; private-first upload; takedown process |
| Harassment | No naming, no vigilante language, no repeated targeting of one rider |
| Privacy | Originals never published; metadata stripped; contact on every video |
| YouTube CGT | Factual titles; comments disabled (2026-06-23); engage with platform notices |
| Shorts risk | 16:9 letterbox so descriptions and compliance footer are searchable |
Full detail: COMPLIANCE-STATEMENT.md (external) and UK-COMPLIANCE.md (operating record).
Compliance baseline (2026-06-24): ICO register ZC180994 (Dynamic Devices Limited, Tier 1 micro); Appendix A LIA signed in UK-COMPLIANCE.md.
Public map of uploaded incidents: https://recklessrides.uk/ (canonical) · also https://dynamicdevices.github.io/reckless-rides-uk/
Built from *_UPLOAD.json (YouTube URL required). Pins show incident metadata and link to YouTube — no video embedded on the map site.
On each push to main, GitHub Actions checks out core/ recursively, runs scripts/build-map-data.py, and deploys docs/. If Pages CI fails with No module named 'evidence_core', run git submodule update --init --recursive locally and ensure .github/workflows/pages.yml uses submodules: recursive.
First-time setup: repo Settings → Pages → Build and deployment → Source: GitHub Actions.
Status (June 2026): DNS on Cloudflare (grey cloud / DNS only). https://recklessrides.uk is live with GitHub Pages TLS (https_enforced: true). www redirects to the apex.
docs/CNAME contains recklessrides.uk. Nameservers: Cloudflare (brit.ns.cloudflare.com, jihoon.ns.cloudflare.com).
Cloudflare DNS (Dashboard → DNS → Records). Set every record below to DNS only (grey cloud, not proxied) — Cloudflare proxy breaks GitHub Pages TLS.
| Type | Name | Content | Proxy |
|---|---|---|---|
| A | @ |
185.199.108.153 |
DNS only |
| A | @ |
185.199.109.153 |
DNS only |
| A | @ |
185.199.110.153 |
DNS only |
| A | @ |
185.199.111.153 |
DNS only |
| CNAME | www |
dynamicdevices.github.io |
DNS only |
Optional IPv6: four AAAA @ records to 2606:50c0:8000::153 … 2606:50c0:8003::153 (also DNS only). Apex CNAME to dynamicdevices.github.io works on Cloudflare via CNAME flattening, but four A records match GitHub’s documented setup.
Verify DNS (after propagation, up to 24 h):
dig recklessrides.uk +noall +answer -t A
dig www.recklessrides.uk +noall +answer -t CNAMEGitHub Pages (custom domain recklessrides.uk, build via Actions):
- DNS checks pass in repo Settings → Pages → Custom domain.
- Enforce HTTPS is enabled — share https://recklessrides.uk/ (not
http://). - https://www.recklessrides.uk/ redirects to the apex.
Local preview:
python3 scripts/build-map-data.py
python3 -m http.server 8765 --directory docs
# open http://localhost:8765Hooks catch map-data, JSON, shell, and workflow YAML issues before push (mirrors the Pages CI build step).
Install once:
./scripts/install-pre-commit.sh
# or: pip3 install --user pre-commit && pre-commit installManual run (all files, no commit required):
./scripts/pre-commit-check.sh
# or: pre-commit run --all-files| Hook | What it checks |
|---|---|
build-map-data.py |
*_UPLOAD.json → docs/data/incidents.geojson; fails if geojson is out of sync |
check-json |
Valid JSON in staged *_UPLOAD.json and incidents.geojson |
bash -n |
Shell syntax for scripts/*.sh |
check-yaml |
.github/workflows/*.yml |
py_compile |
Python syntax for scripts/*.py |
no /home/ paths |
Staged *_UPLOAD.json must use repo-relative paths |
For each reported incident, provide:
evidence/originals/DEB-…_ORIGINAL.movregister/manifests/DEB-…_MANIFEST.jsonregister/incidents.csv(or a printout of the matching row)
Manifest includes SHA-256 hashes so integrity can be checked.
Optional export:
INC=DEB-20260623T080303Z_53.4092N_2.9778W_001
zip -j "evidence/export/${INC}_police_bundle.zip" \
"evidence/originals/${INC}_ORIGINAL.mov" \
"register/manifests/${INC}_MANIFEST.json"Studio text lives in channel/ — description.txt, guidelines.txt, upload-tags.txt, etc.
Upload metadata templates: upload-title-template.txt, video-description-header.txt, video-description-footer.txt, upload-playlist.txt.
| Asset | File |
|---|---|
| Profile picture (800×800) | branding/reckless-rides-channel-icon-800x800.png |
| Banner (2560×1440) | branding/reckless-rides-channel-banner-2560x1440.png |
| Video watermark (150×150) | branding/reckless-rides-watermark-150x150.png |
Evidence video files and the incident register CSV are gitignored. *_UPLOAD.json files are committed — they contain publication metadata only (no video, no faces, repo-relative paths).
Legal & GDPR approach: UK-COMPLIANCE.md — public operating record (review every six months).
External statement (complainants, YouTube, police): COMPLIANCE-STATEMENT.md — share as PDF, link, or paste into correspondence to demonstrate standards and openness to feedback.
Tagged releases document stable ingest/map tooling. Current: v1.1.0 — evidence-core submodule integration (releases).
After checkout of a release tag:
git submodule update --init --recursive