feat: 인물 추적에서 음악방송 무대·직캠·커버 제외
아이돌을 인물로 추적해봤더니 안 먹힌다는 걸 실측으로 확인했다. - 박지원 → 상위 3건이 전부 정치인 박지원(위대한 이슈 12.9만, KBC/JTV 뉴스). 동명이인 때문에 이름만으로는 못 쓴다. - 송하영·오해원·프로미스나인 → 잡히는 게 전부 음악방송 직캠·무대·팬캠. 예능 출연분이 직캠에 묻힌다. 배우·코미디언(윤경호·소지섭·딘딘)은 예능/드라마 클립이 상위에 오는데 아이돌은 검색 자체가 직캠 판이라 구조가 다르다. 그래서 두 가지를 했다. 1) 제목에 직캠·페이스캠·풀캠·교차편집·음중·PERFORMANCE VIDEO·커버 등이 들어가면 버린다. 말로 터지는 순간이 소재라 무대 영상은 소용없다. 적용 후 이채영 결과가 예능만 남았다(공포체험 4.9만, 고부갈등, 아이돌 인간극장). 2) 아이돌 소재는 인물이 아니라 채널로 잡는 게 맞다. fromis_9 공식채널을 SOURCE 시드로 추가 — 자체 예능 롱폼이 나온다([FM_1.24] 31분, Answer ME! 고민상담소 15분). 테스트 2건 추가 — 총 94건 통과. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7a954907d7
commit
c0dbd3b23d
@ -13,8 +13,29 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
public final class PersonPicks {
|
public final class PersonPicks {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 제목에 이게 들어가면 버린다. 아이돌 이름으로 검색하면 음악방송 직캠·무대·교차편집이
|
||||||
|
* 결과를 뒤덮어 정작 예능 출연분이 묻힌다. 말로 터지는 순간이 소재이므로 무대 영상은 소용없다.
|
||||||
|
*/
|
||||||
|
private static final List<String> STAGE_MARKERS = List.of(
|
||||||
|
"직캠", "페이스캠", "풀캠", "fancam", "focus cam",
|
||||||
|
"교차편집", "stage mix", "stagemix", "무대교차",
|
||||||
|
"음중", "쇼챔", "엠카", "뮤직뱅크", "인기가요", "music bank", "show champion",
|
||||||
|
"performance video", "mv", "m/v", "lyric video", "cover", "커버"
|
||||||
|
);
|
||||||
|
|
||||||
private PersonPicks() {}
|
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건.
|
* 검색 결과 1건.
|
||||||
*
|
*
|
||||||
@ -32,7 +53,7 @@ public final class PersonPicks {
|
|||||||
* @param minViewsPerHour 시간당 조회수 하한. 인물명 검색에는 팬채널·커버곡·개인 브이로그가
|
* @param minViewsPerHour 시간당 조회수 하한. 인물명 검색에는 팬채널·커버곡·개인 브이로그가
|
||||||
* 대량으로 딸려 오는데(실측 조회수 30회 미만), 생 조회수로 자르면 방금 올라온
|
* 대량으로 딸려 오는데(실측 조회수 30회 미만), 생 조회수로 자르면 방금 올라온
|
||||||
* 영상까지 걸리므로 시간당 조회수로 거른다. 0 이하면 미적용
|
* 영상까지 걸리므로 시간당 조회수로 거른다. 0 이하면 미적용
|
||||||
* @return 롱폼이면서 제외 대상이 아닌 것들 (입력 순서 유지)
|
* @return 롱폼이면서 무대 영상이 아니고 제외 대상도 아닌 것들 (입력 순서 유지)
|
||||||
*/
|
*/
|
||||||
public static List<Found> keep(List<Found> found, Set<String> excludeChannels,
|
public static List<Found> keep(List<Found> found, Set<String> excludeChannels,
|
||||||
LocalDateTime publishedAfter, double minViewsPerHour) {
|
LocalDateTime publishedAfter, double minViewsPerHour) {
|
||||||
@ -47,6 +68,7 @@ public final class PersonPicks {
|
|||||||
&& excludeChannels.contains(f.ytChannelId())) continue;
|
&& excludeChannels.contains(f.ytChannelId())) continue;
|
||||||
if (publishedAfter != null && f.publishedAt() != null
|
if (publishedAfter != null && f.publishedAt() != null
|
||||||
&& f.publishedAt().isBefore(publishedAfter)) continue;
|
&& f.publishedAt().isBefore(publishedAfter)) continue;
|
||||||
|
if (isStageVideo(f.title())) continue;
|
||||||
if (minViewsPerHour > 0 && !meetsVelocity(f, minViewsPerHour)) continue;
|
if (minViewsPerHour > 0 && !meetsVelocity(f, minViewsPerHour)) continue;
|
||||||
out.add(f);
|
out.add(f);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -90,6 +90,32 @@ class PersonPicksTest {
|
|||||||
assertThat(PersonPicks.keep(List.of(unknown), Set.of(), null, 20)).hasSize(1);
|
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
|
@Test
|
||||||
void null_입력과_빈_videoId_방어() {
|
void null_입력과_빈_videoId_방어() {
|
||||||
List<PersonPicks.Found> found = new java.util.ArrayList<>();
|
List<PersonPicks.Found> found = new java.util.ArrayList<>();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user