RTOpacks Video Processing Pipeline — Standard Operating Procedure¶
Overview¶
This SOP governs the intake, gating, processing, naming, upload, and DB registration of all background video assets for rtopacks.com.au.
Source location: ~/Downloads/
Tim organises Envato downloads into numbered theme folders as he buys them:
~/Downloads/
_hold-discard/ ← rejects go here (create this first)
01 Transport and Logistics/
wide/ ← landscape Envato downloads drop here
tall/ ← portrait Envato downloads drop here
02 Education and Training/
wide/
tall/
... (03 through 10)
Run all scripts from ~/Downloads/ unless stated otherwise. Process one theme folder at a time — gate, review, transcode, rename, upload, DB insert — before moving to the next.
Step 1 — Duration gate (automated)¶
Run the gating script against the source folder. Any file under 15 seconds is moved to _hold-discard/ immediately. No manual review needed for sub-15s files.
# Install ffprobe if not present: brew install ffmpeg
# Run from the theme source folder
for f in *.mp4 *.mov; do
duration=$(ffprobe -v quiet -show_entries format=duration \
-of default=noprint_wrappers=1:nokey=1 "$f" 2>/dev/null)
if [ -z "$duration" ]; then continue; fi
# Round to integer
secs=$(printf "%.0f" "$duration")
if [ "$secs" -lt 15 ]; then
echo "DISCARD ($secs s): $f"
mv "$f" "../_hold-discard/$f"
else
echo "KEEP ($secs s): $f"
fi
done
Create _hold-discard/ folder at the same level as the theme folders before running.
Step 2 — Manual review of keepers¶
For files that pass the gate (≥15s), do a quick visual check:
Keep if: - Clear contextual relevance to the theme - Good exposure — not too dark, not washed out - No visible watermarks, logos, or text overlays - Landscape clips: wide composition with room for HUD overlay in lower 40% - Portrait clips: subject centred, vertical composition works at 9:16
Move to _hold-discard/ if:
- Generic stock footage feel with no sector context (e.g. "hands on keyboard" for tech)
- Heavy camera shake or strobing effects
- Faces too prominent / closeup — background should be atmosphere, not portrait
- Duplicate theme — if you already have 5 strong clips for a theme, excess go to hold
Target per theme: 3–5 landscape clips + 3–5 portrait clips. Not more than 8 of either.
Step 3 — Transcoding (standardise format)¶
All keepers must be transcoded to a consistent spec before upload. Run ffmpeg:
Landscape (desktop):
ffmpeg -i "input.mp4" \
-vf "scale=1920:1080:force_original_aspect_ratio=increase,crop=1920:1080" \
-c:v libx264 -crf 23 -preset slow \
-c:a none \
-movflags +faststart \
"output-landscape.mp4"
Portrait (mobile):
ffmpeg -i "input.mp4" \
-vf "scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920" \
-c:v libx264 -crf 23 -preset slow \
-c:a none \
-movflags +faststart \
"output-portrait.mp4"
Notes:
- -c:a none — strips audio (videos play muted, no point carrying audio track)
- -movflags +faststart — moves metadata to front of file for faster browser streaming
- crf 23 — good quality/size balance. Lower = better quality, larger file. Don't go below 20.
- For .mov source files, same ffmpeg command works — ffmpeg handles the container conversion
Step 4 — Naming convention¶
Use this exact format:
Where:
- {theme-slug} = the slug from the video_themes table (see list below)
- {orientation} = wide or tall
- {sequence} = zero-padded 2-digit number: 01, 02, 03 etc.
Examples:
rtopacks-transport-logistics-wide-01.mp4
rtopacks-transport-logistics-wide-02.mp4
rtopacks-transport-logistics-tall-01.mp4
rtopacks-transport-logistics-tall-02.mp4
rtopacks-health-care-wide-01.mp4
rtopacks-health-care-tall-01.mp4
Theme slugs (from video_themes table):
transport-logistics
education-training
agriculture-environment
health-care
tourism-hospitality
construction-trades
technology-automotive
creative-arts
resources-infrastructure
business-finance
Step 5 — Upload to R2¶
Upload all processed files to the R2 bucket at:
Use wrangler or the Cloudflare dashboard. Files are served directly from R2 via the media subdomain.
# Via wrangler
wrangler r2 object put media-rtopacks/videos/rtopacks-transport-logistics-wide-01.mp4 \
--file ./rtopacks-transport-logistics-wide-01.mp4 \
--content-type video/mp4
Step 6 — Update the DB¶
The existing video_themes table maps training package codes to theme slugs. A new video_clips table handles the many-clips-per-theme model with separate wide and tall URLs.
Create video_clips table (one-time migration — run once):
CREATE TABLE IF NOT EXISTS video_clips (
id INTEGER PRIMARY KEY AUTOINCREMENT,
theme_slug TEXT NOT NULL,
video_url_wide TEXT NOT NULL,
video_url_tall TEXT,
duration_seconds INTEGER,
active INTEGER NOT NULL DEFAULT 1,
added_at TEXT NOT NULL DEFAULT (datetime('now'))
);
CREATE INDEX IF NOT EXISTS idx_video_clips_theme ON video_clips(theme_slug);
Insert new clips after each batch upload:
INSERT INTO video_clips (theme_slug, video_url_wide, video_url_tall, duration_seconds)
VALUES
('transport-logistics',
'https://media.rtopacks.com.au/videos/rtopacks-transport-logistics-wide-01.mp4',
'https://media.rtopacks.com.au/videos/rtopacks-transport-logistics-tall-01.mp4',
28),
('transport-logistics',
'https://media.rtopacks.com.au/videos/rtopacks-transport-logistics-wide-02.mp4',
'https://media.rtopacks.com.au/videos/rtopacks-transport-logistics-tall-02.mp4',
35);
The qual page video selection query:
SELECT video_url_wide, video_url_tall
FROM video_clips
WHERE theme_slug = ? AND active = 1
ORDER BY RANDOM()
LIMIT 1;
video_themes table stays unchanged — it still maps training package codes to theme slugs. The qual page joins video_themes (to get theme_slug from training_package_code) then video_clips (to get a random clip URL for that theme_slug).
Note: video_url_tall can be NULL if no portrait clip exists for that entry yet — the front-end falls back to video_url_wide in that case.
Step 7 — Dissolve logic (front-end)¶
The dissolve is handled in the qual page HUD component. Standard:
- Min duration gate: 15s (enforced at ingest — see Step 1)
- Dissolve trigger: at
currentTime >= duration - 2.5 - Transition: CSS
opacitycrossfade, 2.5s duration, ease-in-out - Next clip selection: random pick from pool for current theme_slug, exclude currently playing clip
- Reduced motion: if
prefers-reduced-motion, skip crossfade — hard cut or static image fallback - On mobile: use
video_url_tallfrom video_clips if present, fall back tovideo_url_wideif no tall clip exists
// Pseudocode for dissolve trigger
video.addEventListener('timeupdate', () => {
if (video.duration - video.currentTime <= 2.5 && !transitioning) {
transitioning = true;
fadeOut(video); // opacity 1 → 0 over 2.5s
const next = pickRandom(themeClips.filter(c => c !== current));
loadAndFadeIn(next); // preload + opacity 0 → 1 over 2.5s
}
});
Summary checklist per batch¶
- Gate script run — sub-15s files in
_hold-discard/ - Manual review complete — keepers confirmed
- All files transcoded to 1920×1080 (wide) and 1080×1920 (tall) where applicable
- Audio stripped from all files
- Files named to convention:
rtopacks-{theme-slug}-{orientation}-{sequence}.mp4 - All files uploaded to
media.rtopacks.com.au/videos/ -
video_themesrows inserted for all new clips (both wide and tall) - video_clips table created in rtopacks-db
- All new clips inserted into video_clips with both wide and tall URLs where available
- Dissolve logic updated in HUD component if not already implemented
- Ops note logged: X landscape + Y portrait clips added for {theme}
Legacy files¶
The existing hero-1.mp4 through hero-13.mp4 at media.rtopacks.com.au/videos/ can remain until replaced. The DB rows referencing them stay active until updated. No destructive action on legacy files — just add new rows pointing to new filenames.