|
Bogdan Timofte
authored
a month ago
|
1
|
# Development Log
|
|
|
2
|
|
|
|
3
|
Decisions, trade-offs, and rationale recorded during development of `garmin_varia_transcode.sh`.
|
|
|
4
|
|
|
|
5
|
---
|
|
|
6
|
|
|
|
7
|
## 2026-05-04 — Initial design and implementation
|
|
|
8
|
|
|
|
9
|
### Language: Python → Bash
|
|
|
10
|
|
|
|
11
|
Started with Python 3 for portability (macOS/Linux/Windows). Switched to Bash after evaluating
|
|
|
12
|
supply-chain risk: a Python script tends to acquire `pip` dependencies over time, each one a
|
|
|
13
|
potential attack surface. Bash with only `ffmpeg`/`ffprobe` as external deps has a minimal,
|
|
|
14
|
auditable dependency surface.
|
|
|
15
|
|
|
|
16
|
Windows support was dropped as a result (Bash is not a first-class environment there).
|
|
|
17
|
Trade-off accepted: target platforms are macOS and Linux.
|
|
|
18
|
|
|
|
19
|
### Encoding strategy
|
|
|
20
|
|
|
|
21
|
Garmin Varia source clips are H.264, ~1080p, ~29.97fps, ~22 Mbps, ~30s duration, ~80-85 MB each.
|
|
|
22
|
The high bitrate is due to inefficient compression, not high visual complexity. Re-encoding with
|
|
|
23
|
CRF-based HEVC yields ~60-70% size reduction with no perceptible quality loss for this content type.
|
|
|
24
|
|
|
|
25
|
Bitrate is never derived mechanically from source file size. CRF (software) and constrained VBR
|
|
|
26
|
(hardware) are used instead, so the encoder allocates bits based on actual scene complexity.
|
|
|
27
|
|
|
|
28
|
### Default mode evolution
|
|
|
29
|
|
|
|
30
|
1. `auto` — initial default; cross-platform safe
|
|
|
31
|
2. `quality` (libx265) — changed to maximize archival quality
|
|
|
32
|
3. `hardware` (hevc_videotoolbox) — **final default**, after measuring on Apple Silicon MacBook Pro:
|
|
|
33
|
- hardware: ~4-5s per clip, ~35W (~0.049 Wh/clip)
|
|
|
34
|
- software: ~50s per clip, ~80W (~1.18 Wh/clip)
|
|
|
35
|
- Energy difference: ~96% less energy per clip with hardware encoding
|
|
|
36
|
- Quality difference for dashcam footage: negligible
|
|
|
37
|
|
|
|
38
|
`auto` is still available as a cross-platform fallback.
|
|
|
39
|
|
|
|
40
|
### Audio handling
|
|
|
41
|
|
|
|
42
|
ffprobe is called per-file to detect audio streams before building the ffmpeg command.
|
|
|
43
|
Source Garmin Varia clips contain PCM audio. Output is AAC 128k when audio is present;
|
|
|
44
|
no audio track is added if none is detected.
|
|
|
45
|
|
|
|
46
|
### Apple compatibility tag
|
|
|
47
|
|
|
|
48
|
HEVC outputs use `-tag:v hvc1` (not the default `hev1`). This is required for reliable
|
|
|
49
|
import and playback in Apple Photos and QuickTime Player.
|
|
|
50
|
|
|
|
51
|
### `--move-source` validation
|
|
|
52
|
|
|
|
53
|
Deleting originals is guarded by a three-step post-encode check:
|
|
|
54
|
1. Output file exists on disk
|
|
|
55
|
2. Codec matches expected (hevc or h264)
|
|
|
56
|
3. Duration within 1.0s of source
|
|
|
57
|
|
|
|
58
|
Source is only deleted if all three pass.
|
|
|
59
|
|
|
|
60
|
### Destination safety guard
|
|
|
61
|
|
|
|
62
|
If destination is inside source, the script exits with an error. This prevents `find` from
|
|
|
63
|
recursing into partially-written output files during a run.
|
|
|
64
|
|
|
|
65
|
### Bash 3.2 compatibility
|
|
|
66
|
|
|
|
67
|
macOS ships with Bash 3.2 (GPL v2). The script avoids:
|
|
|
68
|
- `local -n` (nameref, requires Bash 4.3+)
|
|
|
69
|
- `${var,,}` (lowercase expansion, requires Bash 4+)
|
|
|
70
|
- `extglob` patterns
|
|
|
71
|
|
|
|
72
|
### CLI defaults (final state)
|
|
|
73
|
|
|
|
74
|
| Flag | Default | Rationale |
|
|
|
75
|
|------|---------|-----------|
|
|
|
76
|
| `--recursive` | on | Tool is designed for large libraries with subdirectories |
|
|
|
77
|
| `--overwrite` | on | Filenames are timestamp-based (unique); no accidental overwrite risk |
|
|
|
78
|
| `--mode` | hardware | 96% less energy vs software on Apple Silicon |
|
|
|
79
|
| `--quiet` | on (default) | One progress line per file; verbose available via `--verbose` |
|
|
|
80
|
|
|
|
81
|
### Output verbosity
|
|
|
82
|
|
|
|
83
|
Default output is one line per file:
|
|
|
84
|
```
|
|
|
85
|
2026-05-04 12:08:00 : Transcoding SampleFootage/Day/clip.mp4 ... done in 5s
|
|
|
86
|
```
|
|
|
87
|
Paths are displayed relative to the working directory, not absolute, to keep lines short.
|
|
|
88
|
`--verbose` restores full per-operation logs and exposes ffmpeg/ffprobe output.
|
|
|
89
|
|
|
|
90
|
### Sidecar and manifest
|
|
|
91
|
|
|
|
92
|
JSON sidecars (Garmin activity metadata) are copied 1:1 alongside transcoded files.
|
|
|
93
|
`telemetry_manifest.json` is written as a placeholder contract for a future pipeline that
|
|
|
94
|
will parse Garmin FIT files and correlate telemetry (power, speed, HR, GPS) with video timestamps.
|
|
|
95
|
FIT parsing is not implemented in this release.
|