diff --git a/src/main/java/com/hlab/yanalyst/domain/channel/PersonPicks.java b/src/main/java/com/hlab/yanalyst/domain/channel/PersonPicks.java index 31b1fd1..d27a651 100644 --- a/src/main/java/com/hlab/yanalyst/domain/channel/PersonPicks.java +++ b/src/main/java/com/hlab/yanalyst/domain/channel/PersonPicks.java @@ -13,8 +13,29 @@ import java.util.Set; */ public final class PersonPicks { + /** + * 제목에 이게 들어가면 버린다. 아이돌 이름으로 검색하면 음악방송 직캠·무대·교차편집이 + * 결과를 뒤덮어 정작 예능 출연분이 묻힌다. 말로 터지는 순간이 소재이므로 무대 영상은 소용없다. + */ + private static final List STAGE_MARKERS = List.of( + "직캠", "페이스캠", "풀캠", "fancam", "focus cam", + "교차편집", "stage mix", "stagemix", "무대교차", + "음중", "쇼챔", "엠카", "뮤직뱅크", "인기가요", "music bank", "show champion", + "performance video", "mv", "m/v", "lyric video", "cover", "커버" + ); + private PersonPicks() {} + /** 음악방송 무대·직캠·커버 영상인가(제목 기준). */ + public static boolean isStageVideo(String title) { + if (title == null || title.isBlank()) return false; + String t = title.toLowerCase(java.util.Locale.ROOT); + for (String marker : STAGE_MARKERS) { + if (t.contains(marker)) return true; + } + return false; + } + /** * 검색 결과 1건. * @@ -32,7 +53,7 @@ public final class PersonPicks { * @param minViewsPerHour 시간당 조회수 하한. 인물명 검색에는 팬채널·커버곡·개인 브이로그가 * 대량으로 딸려 오는데(실측 조회수 30회 미만), 생 조회수로 자르면 방금 올라온 * 영상까지 걸리므로 시간당 조회수로 거른다. 0 이하면 미적용 - * @return 롱폼이면서 제외 대상이 아닌 것들 (입력 순서 유지) + * @return 롱폼이면서 무대 영상이 아니고 제외 대상도 아닌 것들 (입력 순서 유지) */ public static List keep(List found, Set excludeChannels, LocalDateTime publishedAfter, double minViewsPerHour) { @@ -47,6 +68,7 @@ public final class PersonPicks { && excludeChannels.contains(f.ytChannelId())) continue; if (publishedAfter != null && f.publishedAt() != null && f.publishedAt().isBefore(publishedAfter)) continue; + if (isStageVideo(f.title())) continue; if (minViewsPerHour > 0 && !meetsVelocity(f, minViewsPerHour)) continue; out.add(f); } diff --git a/src/test/java/com/hlab/yanalyst/domain/channel/PersonPicksTest.java b/src/test/java/com/hlab/yanalyst/domain/channel/PersonPicksTest.java index c6d3d05..dbe0d76 100644 --- a/src/test/java/com/hlab/yanalyst/domain/channel/PersonPicksTest.java +++ b/src/test/java/com/hlab/yanalyst/domain/channel/PersonPicksTest.java @@ -90,6 +90,32 @@ class PersonPicksTest { assertThat(PersonPicks.keep(List.of(unknown), Set.of(), null, 20)).hasSize(1); } + @Test + void 음악방송_무대_직캠_커버는_버린다() { + // 아이돌 이름으로 검색하면 직캠·무대가 결과를 뒤덮는다. 말로 터지는 순간이 소재라 무대는 소용없다 + assertThat(PersonPicks.isStageVideo("[#음중직캠] fromis_9 LEE CHAEYOUNG – Vitamin ME")).isTrue(); + assertThat(PersonPicks.isStageVideo("[페이스캠4K] 프로미스나인 이채영")).isTrue(); + assertThat(PersonPicks.isStageVideo("fromis_9 'Vitamin ME' PERFORMANCE VIDEO")).isTrue(); + assertThat(PersonPicks.isStageVideo("[cover] 딘딘 - 태양은 가득히")).isTrue(); + assertThat(PersonPicks.isStageVideo("교차편집 stage mix")).isTrue(); + + // 예능·드라마 클립은 남아야 한다 + assertThat(PersonPicks.isStageVideo("이채영 임슬옹 공포체험 촬영 중단!?")).isFalse(); + assertThat(PersonPicks.isStageVideo("남우조연상 - 윤경호 [제5회 청룡시리즈어워즈]")).isFalse(); + assertThat(PersonPicks.isStageVideo(null)).isFalse(); + } + + @Test + void keep은_무대영상을_걸러낸다() { + PersonPicks.Found stage = new PersonPicks.Found( + "s", "[#음중직캠] 프로미스나인 이채영", "C1", "MBCkpop", 200, NOW, 50_000L); + PersonPicks.Found variety = new PersonPicks.Found( + "v", "이채영 임슬옹 공포체험 촬영 중단!?", "C2", "OngStyle", 200, NOW, 50_000L); + + assertThat(PersonPicks.keep(List.of(stage, variety), Set.of(), null, 0)) + .extracting(PersonPicks.Found::videoId).containsExactly("v"); + } + @Test void null_입력과_빈_videoId_방어() { List found = new java.util.ArrayList<>();