피드 수집 필터를 쇼츠/롱폼 구분에서 최소 길이(초) 기준으로 교체.
기본 600초(10분) — FEED_MIN_DURATION_SEC 로 조정 가능.
화면 조회에도 같은 하한을 적용해 이미 담긴 10분 미만 영상도
소스·경쟁 탭에서 바로 사라진다. 길이 필터 라벨(공식클립 10~15분)도 정리.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
결과가 잘못 나왔을 때 지금은 작업을 통째로 삭제하고 URL을 다시 등록해야 했다.
등록 이력도 같이 날아간다.
큐 스킬이 status=PENDING 인 작업만 집어가므로, 결과만 비우고 PENDING 으로
되돌리면 그대로 재실행 대상이 된다.
- POST /api/shortform/jobs/{id}/reset — rawOutput·step1Output·clips·title·
completedAt 을 비우고 PENDING 으로. youtubeUrl·videoId·등록시각은 유지한다.
- 화면: PENDING 이 아닌 카드에만 되돌리기 버튼(확인창 포함). 결과가 삭제되므로
무엇이 지워지고 무엇이 남는지 문구에 명시했다.
- FAILED 재시도에 특히 필요했다 — 파싱 실패한 작업을 되살릴 방법이 아예 없었다.
fix: Mockito self-attach 실패로 목 기반 테스트 6건이 깨지던 문제
내 변경과 무관하게 환경에서 깨져 있었다(변경을 치운 깨끗한 트리에서도 동일 실패).
Mockito 가 JVM 에 self-attach 를 시도하다 실패하면 "Could not initialize plugin:
MockMaker" 로 DashboardServiceTest·YoutubeCommentServiceTest 가 전부 죽는다.
JDK 21+ 는 self-attach 를 경고하고 이후 버전에서 막으므로, 권장 방식대로
mockitoAgent 설정을 만들어 -javaagent 로 명시 지정했다. 116건 전부 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
썸네일·제목을 누르면 YouTube 새 탭으로 나가던 걸 레이어 팝업 재생으로 바꿨다.
쇼츠는 세로(9:16), 롱폼은 가로(16:9)로 비율을 맞추고, 팝업 안에 숏폼 큐 등록과
YouTube 열기를 함께 뒀다. Escape·배경 클릭으로 닫히고 닫을 때 재생이 멈춘다.
임베드를 막아둔 채널이 많다는 걸 실측했다 — 소재 원본 9건 중 4건이 차단이고
채널 단위로 갈린다(짠한형 전부 차단, 뜬뜬·스프 전부 가능). 그대로 두면 절반이
"동영상을 재생할 수 없음" 화면을 보게 되므로:
- 수집할 때 videos.list 에 status 파트를 붙여 embeddable 을 저장한다(쿼터 동일).
- 차단된 영상은 팝업 대신 바로 YouTube 새 탭으로 연다.
- 차단 영상 썸네일에 외부링크 아이콘을 띄워 눌렀을 때 나가는 걸 예고한다.
- embeddable 이 null(미수집)이면 일단 재생을 시도한다 — 팝업 안에 YouTube 링크가
있어 막혀도 빠져나갈 수 있다.
브라우저에서 확인: 재생 가능 영상은 팝업에서 실제 재생됐고(썸네일·제목 클릭 모두),
차단 채널 카드에만 외부링크 아이콘이 붙었다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
발굴한 롱폼을 숏폼 큐로 넘기는 동선이 없어 URL을 손으로 옮겨야 했다.
채널 등록·롱폼 수집·정보 표시는 이미 소재 피드에 있으므로 페이지를 새로
만들지 않고 카드에 버튼만 붙였다.
- 소재 원본/인물 탭 카드의 주 버튼을 '숏폼 큐로'로 바꿨다. 경쟁 쇼츠는 이미
잘린 결과물이라 큐 대상이 아니라 원본 보기만 남긴다.
- 피드를 열 때 큐 목록을 함께 받아 이미 담긴 영상은 '큐에 있음'으로 표시하고
숏폼 큐로 링크한다. 등록 API가 videoId 기준 멱등이라 중복 클릭도 안전하다.
- 기존 재가공 스튜디오 진입은 아이콘 버튼으로 남겼다.
- 큐 목록 조회가 실패해도 피드 자체는 정상 동작한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
JobDetail에 rawOutput 노출, 카드 펼침 상단에 전체 복사·영상 URL 복사 툴바 추가.
capcut2가 구분선·타이틀 후보 섞인 통짜 텍스트에서 JSON 5개를 알아서 뽑고
타이틀은 자체 선택 화면에서 고르므로 원문 그대로 복사한다.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
초안 없는 댓글을 한 번에 처리한다. 서버에서 통짜로 돌리지 않고 화면에서 한 건씩
순차 호출한다 — 실측 건당 26~32초라 26건이면 13분짜리 요청이 되어 프록시에서
끊기기 때문이다. 순차 방식이면 진행률이 보이고, 중단해도 앞서 만든 초안은 남는다.
- 진행 바 + 남은 건수, 중단 버튼(진행 중인 1건은 마저 끝냄)
- 완성되는 대로 카드에 반영되어 기다리는 동안에도 읽을 수 있다
- 실패한 건은 건너뛰고 개수만 보고한다(다시 눌러 재시도)
- 확인창에 예상 소요 시간을 알려준다 — 13분짜리 작업을 모르고 시작하지 않게
- 버튼에 남은 건수를 표시하고, 할 게 없으면 비활성화
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
내 영상에 달린 댓글 중 내가 답 안 한 것만 모아, 영상 내용까지 근거로
답글 초안을 만든다. 조회는 나오는데 구독이 안 붙는 문제(59영상 36만 조회
구독 38)에 직접 닿는 지점이라 여기부터 손댔다.
- 미응답 판정은 totalReplyCount 가 아니라 답글 작성자에 내 채널이 있는지로 본다.
남이 대댓글만 단 댓글은 여전히 내가 답해야 하기 때문이다.
- 수집 중 내가 단 답글을 발견하면 말투 샘플로 모아둔다. 실측 17개가 학습됐고
초안이 그 톤을 따른다. 8자 미만("ㅋㅋㅋ")은 말투 정보가 없어 제외.
- 영상 맥락은 Gemini 에 유튜브 URL 을 직접 준다. 내 영상은 30초 쇼츠라 토큰이
적고 화면에 박힌 자막까지 읽힌다. 영상당 1회만 만들어 캐시한다.
- 실제 게시는 OAuth 가 없어 불가 — 초안 복사까지가 끝이다.
실측에서 잡은 결함 두 개를 고쳤다.
1) 요약 생성 때 제목을 안 넘겨서 Gemini 가 출연자 이름을 추측했다. "이채영"
영상을 "이채연"으로 요약했고 그 오류가 답글까지 전파됐다. 제목을 함께 주고
"제목 표기를 글자 그대로" 규칙을 넣어 해결. 캐시가 오염된 경우를 위해
refreshContext 옵션을 두고, '다시 생성'은 요약도 다시 읽게 했다.
2) 영상 요약이 없는데도 규칙에 "[영상 내용]을 근거로"가 남아 없는 섹션을
가리켰다. 요약 유무에 따라 규칙을 갈랐다(테스트가 잡음).
쿼터: 영상당 1 unit — 59개 수집에 59 units.
테스트 11건 추가(미응답 판정·말투 샘플·프롬프트 조립·응답 파싱) — 총 106건 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
인물은 프로그램을 옮겨 다녀서 채널 시드로는 못 잡는다. 실측으로 확인했다:
이미 모은 롱폼 200건에서 제목에 인물명이 잡히는 건 4%뿐이다. 예능 클립 제목은
인물명 대신 상황을 쓰기 때문("혹시 젤리 가격 아세요"). 그래서 인물명으로
유튜브를 검색해 등장분을 찾는다.
- TrackedPerson 엔티티. 1명당 검색 101 units 라 하루 1회만 돌고 활성 인물
상한(기본 10명)을 둔다. 10명이면 1,010 units/일.
- 인물 탭은 source 가 아니라 matchedPerson 으로 조회한다. 소스 채널에서 이미
수집한 영상이 인물 검색에도 걸리면 소스 탭에서 사라지면 안 되기 때문이다.
- 새로 발견한 영상은 source=PERSON — 수집함/발굴 화이트리스트 밖이라 자동 격리.
- 시간당 조회수 하한(기본 20)으로 노이즈를 거른다. 인물명 검색에는 팬채널·
커버곡·개인 브이로그가 대량으로 딸려 오는데(실측 30회 미만), 생 조회수로
자르면 방금 올라온 영상까지 걸리므로 속도로 잰다. 적용 후 노이즈 6건이
전부 빠지고 29건 모두 실질 소재만 남았다.
실측: 윤경호 → KBS 청룡시리즈어워즈 남우조연상(31.6만), KBS 한국방송(18.2만),
SBS 스브스 Drama. 전부 채널 시드에 없는 곳이라 인물 추적으로만 잡힌다.
테스트 3건 추가(속도 필터 경계·신선한 업로드 통과·값 없을 때 미배제) — 총 92건 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
내 채널을 등록하면 그 영상이 수집함·칸반에 소재로 섞여 들어갔다. 이미 발행한
영상은 소재가 아니라 성과이므로 아예 분리한다.
- ChannelRole.OWN 추가. OWN 채널 영상은 source=OWN 으로 저장되고, 수집함·칸반·
발굴 쿼리는 CHANNEL·SEARCH 화이트리스트라 자동으로 격리된다(쿼리 수정 불필요).
/channels 목록에도 안 뜨고 피드에도 안 섞인다.
- /my-channel 신설: 성과 요약(구독·총조회·영상평균·최근30일), 성장 추이 차트,
내 영상 목록.
- 핵심은 소재별 성과 랭킹(TopicPerformance). 해시태그별 평균 조회수를 채널 평균
대비 배수로 보여준다. 실제 데이터로 유퀴즈 2.43x, 워크맨 0.30x 처럼 갈려서
피드에서 무엇을 자를지 판단하는 근거가 된다. 1편짜리 우연은 2편 미만 컷으로 제외.
- 자동 수집·스냅샷 대상에 OWN 포함(findTrackedChannels). 해시태그 역분석의
원천도 MY → OWN 으로 바꿨다.
테스트 8건 추가(TopicPerformance 6, ChannelRole 2) — 총 84건 통과.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
내 채널을 등록하면 그 영상이 수집함·칸반에 소재로 섞여 들어가서 등록을
취소했다. 그러면 해시태그 역분석의 원천이 사라지는데, 기존 코드는 키워드
0개로 검색 루프를 그냥 빠져나와 "발굴했지만 아무것도 못 찾음"처럼 보였다.
- 역분석할 해시태그가 없으면 이유를 명시한 오류를 던진다.
- 피드 화면의 '후보 자동 발굴'은 먼저 감지된 키워드를 보여주고 확인을 받는다.
키워드가 없으면 프로그램 이름을 직접 입력받아 검색한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
쇼츠 클립 채널의 소재를 최신순으로 발굴하는 /feed 화면과 수집 파이프라인.
- 소재 원본 탭: 웹예능 공식채널의 신규 롱폼(durationSec > 65)만 수집.
아직 아무도 안 자른 구간을 선점하는 용도라 공식계정 쇼츠는 제외한다.
- 경쟁 쇼츠 탭: 예능짤 채널의 신규 쇼츠. 제목·썸네일 벤치마킹용이라
재가공 대신 원본 열기 액션을 준다.
- Channel.role(MY/SOURCE/RIVAL) 컬럼 하나로 역할을 구분하고, 기존
uploads 플레이리스트 동기화를 그대로 재사용한다. 채널당 2 units라
search.list(100 units) 대비 쿼터가 거의 들지 않는다.
- 3시간 주기 수집(FeedCollectionService). 소재 선점은 업로드 직후가
승부라 기존 일 1회 채널 수집으로는 늦다.
- 수집함/발굴/떡상 후보 쿼리는 source 미지정 시 CHANNEL·SEARCH만 보도록
좁혀, 피드 영상이 기존 화면을 덮지 않게 격리했다.
- 시드는 자동 후보 → 수동 승인. 내 채널 해시태그를 역분석해(HashtagExtractor)
공식채널 후보를 추천 목록에 쌓고, 승인 시 SOURCE/RIVAL로 등록한다.
- 연속 3회 수집 실패한 시드는 자동 스킵해 쿼터 낭비를 막는다.
- role이 null인 기존 채널은 부팅 시 MY로 1회 백필(ddl-auto:update 특성).
UX: 기존 Editorial 디자인 시스템 유지. 골든타임(24h)·공식클립/풀에피·
떡상중·작업함 배지, 프로그램/길이/기간 필터, URL 상태 보존, 스켈레톤·
빈 상태·에러 복구 액션, 탭 키보드 이동, 이모지 대신 Lucide 아이콘.
테스트: HashtagExtractor·FeedBadges·ChannelRole 순수 로직 16건 추가(총 76건 통과).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
제외 단어 구분자를 검색어와 동일하게 쉼표만으로 통일했다. 공백이
구분자가 아니게 되어 구절 단위 제외가 가능하다. 제외 단어에 걸린
댓글은 검색·필터보다 먼저 걸러져 카드와 분석 모두에서 무조건 빠진다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
검색 입력을 쉼표로 구분하면 각 단어 중 하나라도 포함된 댓글을 표시한다.
공백은 구분자가 아니므로 여러 단어를 이어 쓴 구절 검색은 그대로 동작한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
발굴 대상이 SHORTS 하드코딩이었으나, 롱폼 콘텐츠 대응을 위해 포맷을 선택 가능하게 함.
- ChannelDiscoveryService: runDiscovery(String format) 오버로드. format(SHORTS|LONG_FORM),
null/미인식이면 설정 기본값(hlab.discovery.format, 기본 LONG_FORM) 사용. 검색 조건의
하드코딩 SHORTS를 fmt로 교체(키워드 없는 검색·키워드 검색 양쪽).
- application.yml: hlab.discovery.format 추가(기본 LONG_FORM, DISCOVERY_FORMAT env로 오버라이드).
- RecommendedChannelController /run: format 쿼리파라미터 수용.
- recommend.html: '지금 발굴' 옆 롱폼/쇼츠 선택 드롭다운(롱폼 기본), 소제목 KR·롱폼 반영.
동일 파일에 있던 진행 중 기능(추천채널 대표영상 '영상 재가공' 버튼 + /{id}/rework 엔드포인트)도 함께 포함.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
유통(내가 올려서 발행)에서 예능 소재 파이프라인으로 방향을 틀면서
발행 큐 기능을 제거한다. domain/publish 전체와 발행 준비 UI(재가공 화면),
사이드바 발행 큐 링크, 대시보드 발행 현황 위젯을 걷어냈다.
- domain/publish/* (Controller/Service/Repository/Entity) 삭제
- publish.html · PublishServiceTest 삭제
- rework.html 발행 준비 섹션·JS 제거
- dashboard.html · DashboardService 발행 집계 제거
- WebController /publish 라우트, sidebar 링크 제거
- ChannelVideo.publishedAt(원본 게시일)은 발행 기능과 무관하여 그대로 유지
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
카드 레이아웃은 유지한 채 댓글 내용 텍스트만 확대/축소하는 A−/A+ 스테퍼와,
배경을 기본→흰색→검정 순환하는 토글 추가. 검정 배경일 때 글자는 흰색으로 대비 확보.
배율은 카드 인라인 변수(--cc-text-scale)로 지정해 재렌더·PNG 캡처에도 그대로 반영됨.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- surface-2는 다크에서 배경과 거의 같아 color-mix로 텍스트색을 섞어 확실한 회색으로
- 왼쪽 accent 바 + 흐림(opacity)로 저장 여부를 한눈에 구분
- 렌더 루프에서 카드마다 lucide.createIcons()를 호출하던 것 제거(전체 스캔 반복 방지)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 저장 완료 카드에 회색 톤(.saved)과 '저장됨 ✓' 버튼 상태 부여
- 저장 표시는 화면 전용: 캡처 직전 .saved를 벗겨 PNG엔 남지 않음
- 댓글에 고유 id를 붙여 필터·정렬 재렌더 후에도 저장 표시 유지
- '저장 폴더' 버튼 재클릭 시 지정 해제 → 브라우저 다운로드 폴더로 저장
(해제 이후 '전체 저장'은 폴더를 다시 묻지 않음)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- '저장 폴더' 버튼(File System Access API)으로 폴더를 한 번 고르면
⬇ 저장·전체 저장이 그 폴더로 바로 저장(이 사이트 세션에만 적용)
- '전체 저장' 버튼: 현재 표시된 카드를 순서대로 일괄 저장(진행 표시)
- 미지원 브라우저(파이어폭스/사파리)는 폴더 버튼 숨김·기존 다운로드 폴백
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 토글(모자이크/모서리/단어가리기) ON 상태 accent로 시각화 + aria-pressed
- 검색·좋아요 필터 200ms 디바운스(대량 카드 렉 완화), 아바타 loading=lazy
- 수집 중 스피너, prefers-reduced-motion 대응
- 작은 회색 글자 대비 상향(text-3→text-2), 숫자 tabular-nums
- 아이콘 버튼/셀렉트 터치 타깃 확대, 입력 16px(iOS 확대 방지)
- 월별 댓글량 값 상시 노출(hover 의존 제거)
- 카드 키보드 접근(role/tabindex/Enter·Space 복사), focus-visible 링
- 섹션 아이콘 이모지→Lucide 통일(카드 내 👍💬는 export용 유지)
- 입력 aria-label, 결과 URL ?v= 딥링크(공유·새로고침 유지)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
기본은 댓글 단어 클릭 시 해당 단어만 블러(다시 클릭 해제).
'🖍 단어 가리기' 토글을 끄면 카드 클릭=이미지 복사로 전환.
블러는 캡처 PNG에도 그대로 반영됨.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 수집 상한 1,000→20,000개(안전 상한)로 상향해 전체 댓글 기준 분석
- 분석 패널: 많이 언급된 타임라인(클릭 시 해당 시점 유튜브 이동), 자주 나온 단어(조사 제거+불용어, 좋아요 가중 토글), 요약 통계(총/평균 좋아요·참여자·최다 좋아요 댓글·월별 댓글량)
- 모자이크 기본 ON, '모서리 둥글게' 토글 추가(기본 각진 모서리)
- 카드 렌더는 상위 2,000개로 제한(브라우저 보호), 분석은 전체 기준
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
필터바에 검색 입력창 추가 → 입력 즉시 댓글 내용에 그 단어가 포함된 카드만 노출
(작성자 제외, 내용만 매칭). 클라이언트 필터라 추가 API 호출 없음. JS 캐시버스터 갱신.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
캡컷은 클립보드 이미지 붙여넣기를 못 받아 파일 불러오기가 필요 → 카드별 '⬇ 저장' 버튼으로
PNG 다운로드 제공. 저장 버튼은 cc-noexport+modern-screenshot filter로 캡처 이미지에서 제외.
카드 클릭=클립보드 복사는 유지.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
복사 버튼이 캡처 이미지에 함께 찍히던 문제 해결. 버튼을 없애고 카드 자체 클릭으로
복사하도록 변경, 완료 피드백은 카드 밖 토스트로 표시(캡처에 미포함). JS 캐시버스터 갱신.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
grid가 행 단위로 최대 높이에 맞춰 늘어나던 것을 CSS 다단(column)으로 변경.
각 카드가 내용 높이에 맞고 빈틈없이 쌓이도록 break-inside:avoid + margin-bottom 적용.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HTML5 드래그가 터치에서 동작 안 하는 문제 — 카드마다 상태 드롭다운을 두어
터치로도 컬럼 이동 가능. 데스크톱은 드래그 유지(드롭다운은 모바일/터치에서만 노출).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>