dev.to

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

20년 넘은 친구를 위해 주말 동안 만든 마케팅 도구

개발자는 음악가 친구의 영상 게시 업무를 줄이려고 자체 호스팅 Gemma 4 기반 도구 Soundboard를 만들었습니다. 오디오·영상 분석으로 YouTube와 Bandcamp용 문안을 작성하고, 편집 기록을 다음 초안에 반영하며, 승인된 영상은 비공개 업로드한 뒤 YouTube API로 확인합니다.

AI 요약

Soundboard는 음악가 Nathan이 완성한 영상을 올릴 때 필요한 제목, 설명, 태그와 짧은 영상 제작을 돕는 도구입니다. 개발자는 20년 넘은 친구인 Nathan을 위해 Hacktoberfest의 ‘Build for a Friend’ 과제로 주말 동안 만들었습니다. 도구가 문안을 대신 확정하지는 않습니다. Nathan이 초안을 고치거나 승인하고, 수정 내용은 다음 초안에 반영됩니다.

채널의 말투와 편집을 기억하는 방식

초안을 만들기 전에 Gemma는 Nathan의 최근 YouTube 영상 30개와 썸네일 10개를 살펴보고, 유지·수정·제외할 브랜드 규칙을 제안합니다. Nathan이 승인한 가이드는 과거 채널의 반복 패턴보다 우선합니다. 예전 영상에서 이미 고치고 싶은 습관까지 학습하지 않도록 한 설계입니다.

메인 영상 승인 때는 바뀐 필드마다 EDITED 기록을 남기고, 승인된 결과에는 ACCEPTED 기록을 추가합니다. 다시 생성한 경우에는 SKIPPED로 표시합니다. 수정 기록과 승인은 강한 피드백으로, 다시 생성한 기록은 약한 피드백으로 가중합니다. 모델에는 수정 전후 문안을 전달해 무엇을 바꿨는지 보여줍니다. 피드백과 작업 상태는 Firestore 트랜잭션 하나로 저장하므로 승인 기록만 남고 작업 상태는 갱신되지 않는 상황을 막습니다. 데모 사용자는 Nathan의 기록을 읽을 수 있지만, 데모 작업의 수정 내용은 Nathan의 다음 초안에 반영되지 않습니다.

오디오 분석부터 YouTube 업로드 확인까지

Gemma 4 12B-it은 개발자의 Vertex AI 엔드포인트에서 llama.cpp의 llama-server로 실행합니다. 세 에이전트는 별도 에이전트 프레임워크 없이 TypeScript 프롬프트 빌더로 구현했고, 같은 /v1/chat/completions 엔드포인트를 엄격한 JSON 스키마로 호출합니다. 오디오 분석, YouTube와 Bandcamp용 문안 작성, 채널 가이드 제안이 각각 역할을 나눕니다.

Gemma의 오디오 입력 한도는 30초입니다. 영상은 29.5초 단위로 나누고, ffmpeg가 16kHz WAV와 영상 프레임 8장을 준비합니다. 음량, 피크, 클리핑, 무음 구간도 ffmpeg로 측정합니다. 모델이 음향 수치를 추측하지 않도록 수치 생성을 금지하고, 모델 출력에 수치가 섞이면 서버에서 제거합니다. 오디오 분석은 장르, 악기, 보컬 유형, 분위기, 템포 감각을 문장으로 정리하며 가사는 다루지 않습니다.

분석 결과에서 장르 관련 YouTube 검색을 진행하고, 후보 목록 안에서 해시태그와 태그를 고릅니다. 태그에는 아티스트 이름도 포함하며 분석에서 확인한 내용을 가리키도록 제한합니다. 설명의 구성은 청중 관련 근거를 따르고, 표현은 Nathan의 기존 설명에서 정리한 ARTIST_VOICE를 따릅니다. 제목·설명·태그에는 선택 이유도 붙입니다. 요청은 temperature 0.2, 토큰 예산 2,048로 실행하며 JSON 파싱에 실패하면 해당 단계에 시간이 남았을 때 한 번 재시도합니다.

Gemma가 짧은 영상에 쓸 구간과 길이를 고르면 ffmpeg가 9:16, 1080×1920 영상으로 자릅니다. 컷 시작점은 음량 분석값으로 정하며 새 프레임을 생성하지 않습니다. 승인된 영상과 Short는 비공개로 업로드하고, YouTube Data API의 videos.list로 업로드 결과를 다시 읽은 뒤 검증 완료로 표시합니다. Bandcamp에는 업로드 API가 없어 문안을 복사하는 버튼을 제공합니다.

운영과 배포

Cloud Run 앱 한 인스턴스와 NVIDIA L4를 쓰는 Vertex AI 모델 엔드포인트 하나로 구성하며 두 서비스 모두 0개까지 축소됩니다. 앱은 요청마다 파이프라인 한 단계씩 진행합니다. 모델이 축소 상태이거나 바쁘면 대기 이유를 반환하고, 페이지를 다시 열면 작업을 이어갑니다. 스택은 SvelteKit 2, Svelte 5, TypeScript, Node 24, Firestore, Cloud Storage, Secret Manager, YouTube Data API v3, Sentry입니다.

Sentry 추적에는 모델 호출, 소요 시간, 토큰, 메시지를 기록하지만 오디오와 이미지는 형식·MIME 유형·바이트 수를 나타내는 자리표시자로 바꿉니다. 추적을 점검하며 에이전트 입력과 출력이 하위 채팅 스팬에만 들어가 화면에서 비어 보이던 문제를 고쳤습니다. 또 smart-pick 프롬프트가 컨텍스트 8,192토큰 중 약 4,200토큰을 차지하던 원인을 찾아 이전 업로드 다섯 개의 설명과 썸네일을 세 개로 줄였습니다. 실시간 작업에서 프롬프트는 3,119토큰으로 낮아졌습니다.

개발자는 GitHub Actions에서 포맷, 린트, 타입 검사, 1,050개가 넘는 단위 테스트와 커버리지, Playwright E2E 테스트를 실행합니다. CodeQL도 코드 PR, main 브랜치 푸시, 주간 일정에 맞춰 수행합니다. 글 작성에는 Claude가 초안을 도왔고 Codex가 일부 표현을 다듬었다고 밝힙니다. 개발자는 서비스가 음악을 더 많은 사람에게 들려준다고 약속하지는 않으며, 게시 과정의 반복 작업을 덜어주는 데 초점을 둡니다.

dev.to 반응

  • @techgirl007 — 정말 좋은 아이디어네요. 정말 마음에 들어요. 잘 만들었습니다. 🤩
    • @anchildress1 — 고맙습니다!
  • @learn2027 — 안녕하세요 Ashley. Soundboard 제출 글을 읽고 저장소와 README도 살펴봤습니다. 저는 엔지니어는 아니고, 과학적 호기심을 품고 탐구하기를 좋아하는 사람입니다. 그래서 몇 가지가 기억에 남았습니다. Gemma를 직접 관리하는 GPU에서 실행해 공개되지 않은 오디오가 프로젝트 밖으로 나가지 않게 한 점은 단순한 기술 선택을 넘어 원칙 있는 결정으로 보였습니다. 모델이 음량과 클리핑을 추측하게 하지 않고 ffmpeg로 측정한 점에서도 측정과 추측을 구분하는 깊은 고민이 느껴졌습니다. 비용과 도구가 약속하지 못하는 부분을 솔직하게 밝힌 점도 좋았습니다. 그런 투명성은 드뭅니다. ‘Build for a Friend’가 어떤 모습인지 잘 보여주는 사례입니다. 작은 감사의 뜻으로 저장소에 별을 눌렀습니다. 작업을 커뮤니티와 나눠주셔서 감사합니다. 계속 만들어주세요 🙏😁
    • @anchildress1 — 정말 고맙습니다! 여기서 더 만들 것이 많고, 분석 기능부터 시작하려고 합니다. 그러니 이 프로젝트가 한동안 저를 바쁘게 해줄 것 같네요 😆
    • @learn2027 — 작업을 공유해주셔서 감사합니다 🙌 분석 기능도 기대하겠습니다 🙂 계속 만들어주세요!

원문: dev.to / 번역·요약: Trawling