DEV Community

Cover image for 20+ Years of Friendship. One Weekend to Build His Marketing Department.
Ashley Childress
Ashley Childress Subscriber

Posted on AI-assisted

20+ Years of Friendship. One Weekend to Build His Marketing Department.

Hacktoberfest Weekend Challenge: Build for a Friend Submission 🤝

This is a submission for the Hacktoberfest Weekend Challenge: Build for a Friend

TL;DR

  • Built for Nathan: best friend of 20+ years, musician behind Flies Like Robots, not a marketing guy.
  • Self-hosted Gemma 4 watches and listens to his video, then drafts the YouTube metadata and Bandcamp copy in his voice, and picks a hook for a vertical Short.
  • He edits or approves. Soundboard remembers the edit, uploads as private, and reads it back from YouTube before calling it verified.
  • Code • 60 s demo • Deployed site

What I Built

Nathan makes the music and the videos. He's been one of my best friends for more than 20 years, so I can say this with some authority: he is a musician, and asking him to also be in marketing is seriously pushing it.

Before it drafts anything, Gemma reads Nathan's 30 latest videos and 10 thumbnails, then proposes a brand guide with keep, fix, and drop lists for him to edit or approve. That approved guide takes priority over patterns in his older uploads, because reading his channel shouldn't mean repeating everything he's trying to improve. I want the copy to sound like Nathan, and “write me a YouTube title” gives the model nothing to work with.

Brand guidelines for approval

Soundboard covers the gap between a finished video and somebody finding it. He uploads the video and types the song title. Gemma drafts the YouTube title, description, and tags, grounded in what's reaching listeners in that genre and written the way Nathan writes. It drafts the Bandcamp About text and credits too, with a Copy button for each, because Bandcamp has no upload API and won't be framed. And it picks the hook for a vertical Short, which ffmpeg cuts to 9:16. He edits, re-runs, or approves. Every edit becomes evidence for the next draft.

I can't promise an algorithm will get his music heard. I can take the busywork off him.

His music: YouTube • Bandcamp.

Demo

One sample, start to finish: upload, Gemma's draft, an edit, approval, and the private upload verified against YouTube. To run it locally, follow the README's GCP setup, pull the Gemma 4 GGUF for llama-server, then make dev.

Sign in with username anchildress1demo@gmail.com and password xAf(nH*2Qf1T to access the YouTube sandbox. You can also sign into YouTube directly to validate your upload.

Deployed at https://soundboard.anchildress1.dev.

Code

GitHub logo anchildress1 / soundboard

Release agent for one musician: self-hosted Gemma 4 watches and listens to a music video, then drafts and verifies the YouTube upload.

🎛️ Soundboard

Soundboard analyzes a finished music video and drafts its YouTube title, description, hashtags, and tags. The artist approves, edits, or re-runs the draft; on approval, Soundboard uploads the video as private and confirms the upload through the YouTube API.

Live app DEV submission CI CodeQL Quality Gate Coverage License: MIT

Node 24 SvelteKit 2 Gemma 4 Vertex AI Cloud Run Sentry

Built for Flies Like Robots as an entry in the DEV Hacktoberfest Weekend Challenge: Build for a Friend. Try it at soundboard.anchildress1.dev, and read the submission.

Soundboard


Table of Contents


About

Nathan writes, records, and films his own music. Publishing each video still takes metadata work: a title, a description, tags, hashtags, consistency with the rest of the channel, and a check that the audio isn't clipping or dropping out.

Soundboard produces one complete draft of that metadata for Nathan to review, instead of a set of options to choose from.

The…




⚖️ MIT. Gemma's weights ship separately under Apache 2.0.

The requirement: if Nathan fixes a title once, he shouldn't fix it the same way twice. For this build, I pass his saved edits into the next prompt instead of maintaining a fine-tuning pipeline or vector database.

Review card with the proposed metadata, measured flags, and an edit before approval

A main-video approval writes an EDITED row per changed field plus an ACCEPTED row; a re-run writes SKIPPED. Short approvals record edits only, since their metadata starts from the video's draft. Feedback and the job's state change commit in one Firestore transaction, so the app can't remember an approval that never happened.

export function approvalFeedback(
  proposed: PickFields,
  final: PickFields,
  meta: { jobId: string; songTitle: string; pickVersion: number; at: number },
): Feedback[] {
  const rows: Feedback[] = [];
  const asText = (v: string | string[]) => (Array.isArray(v) ? v.join(', ') : v);
  for (const field of ['title', 'description', 'tags'] as const) {
    const before = asText(proposed[field]);
    const after = asText(final[field]);
    if (before !== after) rows.push({ kind: 'EDITED', field, before, after, ...meta });
  }
  rows.push({ kind: 'ACCEPTED', ...meta, after: final.title });
  return rows;
}

export function weighFeedback(feedback: Feedback[]) {
  return feedback.map((f) => ({
    weight: f.kind === 'SKIPPED' ? 'weak' : 'strong',
    kind: f.kind,
    song: f.songTitle,
    field: f.field,
    before: f.before?.slice(0, 300),
    after: f.after?.slice(0, 300),
  }));
}
✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด

Edits and approvals outweigh a re-run, since "try again" doesn't say what he disliked. Gemma gets the before/after pairs so it can see what he actually changed. Nathan can change his mind. He's allowed; it's his music.

Demo runs read his memory but write to their own jobs. Letting strangers teach the app what Nathan likes would be a spectacularly stupid feature.

Successful YouTube upload

How I Built It

I'm running Gemma 4 12B-it through llama.cpp's llama-server, with Google's open weights under Apache 2.0. The three “agents” are TypeScript prompt builders calling the same /v1/chat/completions endpoint with strict JSON schemas, which didn't need an agent framework. Gemma's 30-second audio limit sets the window size at 29.5 seconds, and the page advances one step per request because a 12B model on one L4 takes a few seconds to answer. I can work with that; pretending it's instant doesn't make it faster.

Piece Choice
Model Gemma 4 12B-it, Google's Q4_0 GGUF + vision/audio projector, on llama-server
Hosting One Cloud Run app instance + one Vertex AI model endpoint with one NVIDIA L4; both scale to zero
Media ffmpeg: 29.5 s windows (under Gemma's 30 s audio cap), 16 kHz WAV + 8 frames + loudness/peak/clipping/silence
Agents chunk-analyst analyzes each window; smart-pick writes one draft for YouTube + Bandcamp; a separate brand-guide call proposes keep/fix/drop rules from his channel
Short Gemma picks the hook window and length; ffmpeg's loudness places the cut start and renders 1080×1920; no generated frames
Upload Private upload, then videos.list read-back before it's marked verified—video and Short both
Stack SvelteKit 2, Svelte 5, TypeScript, Node 24, Firestore, Cloud Storage, Secret Manager, YouTube Data API v3, Sentry

The page runs one pipeline step per request. A scaled-down, loading, or busy model returns a wait reason; reloading resumes the job.

Why Does Open Innovation Matter?

Nathan's unreleased music is the input. Gemma runs on a Vertex AI endpoint in my GCP project, so I don't send that audio to a separate inference API. Storage and uploads still touch GCP and YouTube, and Sentry gets traces with the media stripped, but I decide where the model runs and what reaches it.

Open weights also leave the door open to fine-tune on his music and audience data later, without replacing what I built this weekend.

Prize Categories

Best Use of Gemma 🪙

Gemma gets the audio and frames, not a description of them. chunk-analyst reports genre terms, instrumentation, vocal type, mood, and tempo feel in words. No lyrics.

Those terms drive a YouTube search of the genre's top videos. Hashtags and tags are picked from the job's candidate lists, with the artist name included in the tag pool, and tags must name something the analysis heard. The description's structure follows the audience evidence; its wording follows ARTIST_VOICE, distilled from Nathan's own YouTube descriptions: short, literal, his "hacked and slashed" credit line, the dry aside about what genre this even is. The title, description, and tags return a why.

Calls use a strict JSON schema, temperature 0.2, and a 2048-token budget. A bad parse gets one retry if the step has time left.

Audio numbers come from ffmpeg. The model is told not to produce them, and the server strips any it does. I don't need Gemma's opinion on what the meter probably said.

export function measuredFlags(m: Measurements | null): string[] {
  if (!m) return [];
  const flags: string[] = [];
  if (m.truePeakDbtp !== null && m.truePeakDbtp > -1) {
    flags.push(`True peak ${m.truePeakDbtp.toFixed(1)} dBTP, above the -1 dBTP ceiling`);
  }
  if (m.clippedSamples > 0) flags.push(`${m.clippedSamples} samples at full scale (clipping)`);
  for (const s of m.silences.filter((s) => s.end - s.start >= 2)) {
    flags.push(`Silence ${s.start.toFixed(1)}s to ${s.end.toFixed(1)}s`);
  }
  return flags;
}
✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด ✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด

Sentry Agent Tracing 🩻

Nathan is a fantastic musician and terrible with technology, so I'm also the support team. I already know the bug report: "it's broken," some unspecified time after it broke.

One Sentry trace per job: agent calls, duration, tokens, messages. Each invoke_agent span wraps the model's chat span. Audio and images become {type, mime, bytes} placeholders first; his unreleased track doesn't belong in my error tracker.

A completed job trace showing the agent and chat spans, duration, and token counts

Two things the traces caught before Nathan did. First, every invoke_agent span was empty: Sentry only shows an agent's input and output from its own span, and I'd put them on the chat span underneath. Second, smart-pick was spending about 4,200 of its 8,192-token context on the prompt alone—each of his five previous uploads cost a description excerpt and a thumbnail. Three uploads still carry his credit and contact lines. A live pick dropped to 3,119 prompt tokens.

GitHub Copilot 🪛

Every PR gets a Copilot review before I merge it. GitHub Actions runs format, lint, typecheck, 1,050+ unit tests with coverage, and Playwright E2E on code PRs. CodeQL runs on code PRs, code pushes to main, and a weekly schedule; release-please handles versioning and changelogs. For a one-person build that has to outlive the weekend, the bot reviewer and the pipeline are the team.

A PR with the Copilot review and the green CI checks

Now He Has to Use It 🎬

YouTube Studio showing the private upload Soundboard verified

Social posts and analytics can come later. First Nathan has to ship a real release with it and tell me what still annoys him. If I've done my job, the YouTube description stops being the unfinished task next to his music.

He's already plotting out the next set of features for me to build. He also says this version of AI is a lot easier to manage than the way he was doing it before.


🛡️ Apparently I Also Need an Editor

Claude helped draft this post, and Codex helped remove the parts that sounded like Claude helped draft this post—including writing this footer. I'm responsible for the claims and the app; Nathan is responsible for texting me when it breaks.

Top comments (9)

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
learn2027 profile image
meow.hair •

Hi Ashley,

I read your Soundboard submission,
then went through the repo and README.
I'm not an engineer — just someone
with scientific curiosity who loves exploring.
That's exactly why a few things stayed with me:

  • Running Gemma on your own GPU
    so unreleased audio never leaves your project.
    A principled choice, not just a technical one.

  • Using ffmpeg to measure loudness
    and clipping instead of letting the model guess.
    That distinction between measuring and guessing
    reflects deep thinking.

  • The honesty about cost and
    about what the tool can't promise.
    That kind of transparency is rare.

It's a great example of what
"Build for a Friend" should look like.

Starred the repo as a small
token of appreciation. Thanks for
sharing this with the community.

Keep building 🙏😁

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
anchildress1 profile image
Ashley Childress •

Thank you so much! There's a ton of things to build out here, starting with analytics. So I imagine this project will keep me busy for a while 😆

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
learn2027 profile image
meow.hair •

Thanks for sharing your work 🙌 Excited to see the analytics side 🙂 Keep it up!

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
pixquill profile image
PixQuill •

Wow, building a whole marketing workflow for your musician friend in one weekend is impressive! The idea of auto‑generating short clip hooks from source video with ffmpeg is super clever, I want to try this demo out.

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
anchildress1 profile image
Ashley Childress • • Edited

Thanks! You can sign in with username anchildress1demo@gmail.com and password xAf(nH*2Qf1T. It's deployed at soundboard.anchildress1.dev.

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
techgirl007 profile image
TechGirl007 •

What a GREAT idea. LOVE this. Well done. 🤩

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
anchildress1 profile image
Ashley Childress •

Thank you!

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
abhisekroy169 profile image
Abhisek Roy •

Nice !! Did you get sticker in hacktoberfest for this challenge .

✨ อัปเดตล่าสุด: anchildress/ years of friendship one weekend to build his marketing department bd - สรุปดราม่าล่าสุด
 
anchildress1 profile image
Ashley Childress •

You know... I don't remember 😅😆 Thanks, though! I'll have to go look.