spen studio

Case study

Content System

A native macOS ops console: SSH JSON-line jobs, no stored AWS secrets, and thirteen guided workspaces instead of Terminal tabs.

Project 03
Content System website
Client
Streaming content ops
Services
Automation · macOS app · Internal tools
  1. 01 / Challenge

    Content operators lived in Terminal, SSH sessions, and memorized AWS CLI flags. Uploads, metadata checks, subtitle pulls, live episode review, promo staging, artwork resize, and weekly reporting were separate rituals with no shared history — fine for one engineer, fragile for a team that had to move a slate without dropping a file. Secrets sat in shells. Progress was scrollback. Failures were “did it finish?” in Slack.

    Content System starting frame
  2. 02 / Insight

    The bottleneck was not upload speed — it was cognitive load across uploads, validation, subtitles, live review, promos, artwork, and reporting with no shared state, plus secrets living in shells.

    Content System in use
  3. 03 / System

    We shipped a SwiftUI glass console that wraps remote bash behind SSH with JSON-line progress, plus local Python for validators and subtitle cleanup. Authenticate once per session: host, key, and AWS profile name — never a long-lived AWS secret in the app. Every workflow is preview-then-apply, with Activity as the shared log. Marketing copy, sanitize, live review, promos, artwork, and weekly Mail drafts all sit in the same sidebar instead of a wiki of scripts.

    AUTHSSH / JSONUPLOADVALIDATESUBTITLESREPORTACTIVITY LOG

    Process reconstruction

    Content System architecture

    How the pieces hand off without another dashboard nobody opens.

    • Auth
    • SSH / JSON
    • Workflows
    • Activity
  4. 04 / Execution

    We shipped a SwiftUI glass console that wraps remote bash behind SSH with JSON-line progress, plus local Python for validators and subtitle cleanup. Authenticate once per session: host, key, and AWS profile name — never a long-lived AWS secret in the app. Every workflow is preview-then-apply, with Activity as the shared log. Marketing copy, sanitize, live review, promos, artwork, and weekly Mail drafts all sit in the same sidebar instead of a wiki of scripts.

    Content System in motion
  5. 05 / Outcome

    Ops got a cockpit they could hand to someone who should not be trusted with raw IAM. Multi-file S3 uploads show live speed; CSV leaves a cleaned file; captions can be pulled and normalized; live IDs get checked against mapping paths; marketing leaves a folder instead of a scavenger hunt. The Mac is a console. The host still does the privileged work.

    Content System after launch

Toolkit

Every surface, featured fully.

Each piece below is almost its own product — designed, shipped, and used as a first-class workflow rather than a buried submenu.

01

Upload to S3

The upload workspace is a packaging console, not a file picker glued to AWS CLI. Operators drop or browse local masters, pick a destination prefix, and run a dry-run that checks names, extensions, and size before anything leaves the machine. When they commit, each file streams independently with live MB/s, remaining time, and a per-file status so a 40 GB episode does not hide a failed sidecar. Under the hood the Mac never talks to S3 with an embedded IAM key. A JSON-line SSH session to the ops host drives the existing remote scripts, and progress events hydrate the UI as they arrive. Sessions are resumable: if the laptop sleeps mid-batch, the operator reopens the same job instead of guessing which objects already landed. Sanitize-before-publish is the default so a space, emoji, or uppercase extension never becomes a 403 at the CDN. The problem this solves is cognitive, not bandwidth. The old ritual was Terminal tabs, remembered flags, and a Slack “did it finish?” The new one is preview, confirm, watch, and a row in Activity.

Upload to S3 screenshot

02

Marketing Request

Marketing did not need access to the master upload bucket — they needed a ready folder on their SFTP share. This workspace takes campaign paths, copies the matching objects, and writes a destination the editor can open without a scavenger hunt. The copy still happens on the ops host (same SSH JSON-line channel as upload). On success, a follow-on Asana call completes the content-ops sub-task, comments the video editor with the destination, and unassigns the operator from the parent so they stop getting edit/review spam. Failure stays in Activity with the remote stderr, not a silent AWS console error. This is the native face of the Marketing Requests pipeline: Slack form and Asana still start the work; this screen is where the files actually move.

Marketing Request screenshot

03

Activity

Every workflow used to leave its own log — Terminal scrollback, a CSV on a desktop, a Slack screenshot. Activity is the shared history: uploads, marketing copies, subtitle pulls, CSV validation, artwork resize, and promo jobs in one reverse-chronological feed with success, warning, and failure. Rows expand to the same structured events the SSH stream emitted, so an operator can answer “what happened at 14:12?” without SSHing in. Filters keep the feed usable on a busy week. Nothing here is a second source of truth; it is a readable index over jobs the app already ran.

Activity screenshot

04

Sanitize Files

CDN and player stacks are unforgiving about filenames. Sanitize scans a server directory, proposes a rename for every illegal character, collision, or extension mismatch, and waits. Apply is explicit. There is no “fix it as we upload and hope.” The preview table is the safety valve: operators can uncheck a row, keep a house exception, or abort. Remote apply uses the same SSH channel; the Mac never mounts the volume. This exists because a single bad name used to fail a whole batch after minutes of transfer.

Sanitize Files screenshot

05

Download Subtitles

Subtitle packages live next to the masters in S3, but pulling them used to mean composing a prefix, an AWS profile, and a local destination by hand. This workspace lists recent uploads or accepts an explicit path, then fetches VTT/SRT (and related sidecars) with a completion summary. Auth is the session AWS profile already chosen in Settings — still not a secret stored in the app. Operators get a folder they can hand to Clean Subtitles or to an editor, instead of a half-downloaded prefix they have to inspect in the console.

Download Subtitles screenshot

06

Clean Subtitles

Broadcast and web captions arrive as SCC, SRT, and VTT with overlapping cues, wild speaker tags, and timecodes that drift. Clean Subtitles runs local Python against the files on disk: normalize, de-overlap, strip junk, preview the diff, then apply with an on-disk backup. Preview-first is non-negotiable. Caption QC is legal and accessibility work; a silent rewrite would be worse than leaving the file alone. The Mac can do this without SSH because the files are already local — a deliberate split from the remote S3 tools.

Clean Subtitles screenshot

07

Validate CSV

Episode and shorts metadata still travels in spreadsheets. Validate CSV checks required columns, ID formats, titles, and window fields, then writes a cleaned file instead of asking someone to hunt red cells in Excel. Excel format guidance is one click away because the failure mode is almost always “saved as the wrong sheet” or “wrapped the ID in a formula.” The cleaned output is what downstream tools (live review, weekly report) actually consume — so the validator is a gate, not a suggestion.

Validate CSV screenshot

08

Review Live Episodes

Live slates fail in public. This workspace cross-checks episode IDs against the schedule CSV and the stream-mapping paths configured in Settings: missing files, window mismatches, and IDs that exist in the sheet but not on the server. It does not try to be a scheduler. It is a pre-air gate so the person who will get the Slack ping has already seen the miss. Mapping paths are session configuration, not hardcoded brands, so the same binary can point at whichever streams the desk is running today.

Review Live Episodes screenshot

09

Vidaa Monthly Promos

Monthly promo packages are a checklist that used to live in someone’s head: path tokens, ownership, file presence, then a copy. This workspace validates the package first — pass/fail with the reason on the row — and only then stages the copy. The workaround it replaced was a shared doc plus a hope that last month’s folder naming still applied. Tokens and ownership checks are boring on purpose. Promo misses are public.

Vidaa Monthly Promos screenshot

10

Resize Images

Artwork has a dozen required sizes (banners, keyart, boxart, CTV, hub icons) and a habit of being exported once, wrong. Resize Images runs named presets against the original in place, then shows an outcome strip so you can see which rendition was written. It is local Python, not Photoshop scripting and not a trip through a DAM. The point is repeatability: the same preset list every week, not a designer reconstructing export settings from Slack.

Resize Images screenshot

11

Weekly Report

Ops still owes a written week. Weekly Report is a structured composer: paste section updates, preview HTML, and hand a Mail draft to the operator instead of another spreadsheet that nobody formats the same way twice. It is not analytics. It is the human summary that used to take Friday afternoon and a fight with Outlook. Preview is HTML so what you send is what you saw.

Weekly Report screenshot

12

Settings & auth

Settings is the only place host, user, SSH key path, AWS profile name, and stream-mapping directories are entered. Authenticate once per session. The app stores connection shape (which profile, which key file) — not the AWS secret, not a long-lived token, not a pasted password. Jobs then stream progress as JSON lines over that SSH session. If auth fails, every workspace fails in the same obvious way instead of each script prompting differently. Mapping paths are data: point them at the streams this desk is running; do not fork the app. Demo captures on this site use a placeholder host. Production operators bring their own bastion.

Settings & auth screenshot
Thirteen first-class workspaces in one native shell.
Preview-first safety on sanitize, subtitles, CSV, and promos.
Auth once — JSON-line progress, no secrets stored in-app.
Content System — spen studio