feat: 숏폼 자막 완료 텔레그램 알림 추가
This commit is contained in:
parent
504d0f09d5
commit
f51cab471e
@ -1,5 +1,6 @@
|
|||||||
package com.hlab.yanalyst.domain.shortform;
|
package com.hlab.yanalyst.domain.shortform;
|
||||||
|
|
||||||
|
import com.hlab.yanalyst.global.notify.TelegramNotifier;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
@ -29,6 +30,7 @@ public class ShortformSubtitleService {
|
|||||||
|
|
||||||
private final ShortformJobRepository jobRepository;
|
private final ShortformJobRepository jobRepository;
|
||||||
private final ShortformWhisperService whisperService;
|
private final ShortformWhisperService whisperService;
|
||||||
|
private final TelegramNotifier telegramNotifier;
|
||||||
|
|
||||||
@Value("${ytdlp.bin:yt-dlp}")
|
@Value("${ytdlp.bin:yt-dlp}")
|
||||||
private String ytdlpBin;
|
private String ytdlpBin;
|
||||||
@ -52,18 +54,47 @@ public class ShortformSubtitleService {
|
|||||||
String vtt = collectOrTranscribe(jobId, job.getVideoId());
|
String vtt = collectOrTranscribe(jobId, job.getVideoId());
|
||||||
ShortformJob current = jobRepository.findById(jobId).orElse(null);
|
ShortformJob current = jobRepository.findById(jobId).orElse(null);
|
||||||
if (current == null) return;
|
if (current == null) return;
|
||||||
if (vtt == null) current.markSubtitleUnavailable("한국어 공식/자동 자막이 없습니다");
|
if (vtt == null) current.markSubtitleUnavailable("YouTube/Whisper 자막을 생성하지 못했습니다");
|
||||||
else current.storeSubtitle(vtt);
|
else current.storeSubtitle(vtt);
|
||||||
jobRepository.save(current);
|
jobRepository.save(current);
|
||||||
|
if (vtt == null) notifyFailure(current, current.getSubtitleError());
|
||||||
|
else notifyReady(current);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("숏폼 큐 자막 수집 실패: jobId={}, videoId={}", jobId, job.getVideoId(), e);
|
log.warn("숏폼 큐 자막 수집 실패: jobId={}, videoId={}", jobId, job.getVideoId(), e);
|
||||||
jobRepository.findById(jobId).ifPresent(current -> {
|
jobRepository.findById(jobId).ifPresent(current -> {
|
||||||
current.markSubtitleFailed(e.getMessage());
|
current.markSubtitleFailed(e.getMessage());
|
||||||
jobRepository.save(current);
|
jobRepository.save(current);
|
||||||
|
notifyFailure(current, current.getSubtitleError());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void notifyReady(ShortformJob job) {
|
||||||
|
telegramNotifier.sendMessage("✅ <b>숏폼 자막 생성 완료</b>\n"
|
||||||
|
+ "분류: " + categoryLabel(job) + "\n"
|
||||||
|
+ "영상: <code>" + job.getVideoId() + "</code>\n"
|
||||||
|
+ "<a href=\"https://h-lab.tolag.shop/shortform?category="
|
||||||
|
+ job.categoryOrDefault().name() + "\">숏폼 큐에서 확인</a>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyFailure(ShortformJob job, String reason) {
|
||||||
|
telegramNotifier.sendMessage("❌ <b>숏폼 자막 생성 실패</b>\n"
|
||||||
|
+ "분류: " + categoryLabel(job) + "\n"
|
||||||
|
+ "영상: <code>" + job.getVideoId() + "</code>\n"
|
||||||
|
+ "사유: " + escapeHtml(reason) + "\n"
|
||||||
|
+ "<a href=\"https://h-lab.tolag.shop/shortform?category="
|
||||||
|
+ job.categoryOrDefault().name() + "\">숏폼 큐에서 확인</a>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String categoryLabel(ShortformJob job) {
|
||||||
|
return job.categoryOrDefault() == ShortformCategory.POLITICS ? "정치" : "예능";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String escapeHtml(String value) {
|
||||||
|
if (value == null || value.isBlank()) return "알 수 없는 오류";
|
||||||
|
return value.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||||
|
}
|
||||||
|
|
||||||
String collectOrTranscribe(Long jobId, String videoId) throws IOException, InterruptedException {
|
String collectOrTranscribe(Long jobId, String videoId) throws IOException, InterruptedException {
|
||||||
try {
|
try {
|
||||||
String vtt = collectWithRetry(videoId);
|
String vtt = collectWithRetry(videoId);
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.hlab.yanalyst.domain.shortform;
|
package com.hlab.yanalyst.domain.shortform;
|
||||||
|
|
||||||
|
import com.hlab.yanalyst.global.notify.TelegramNotifier;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -37,7 +38,8 @@ class ShortformSubtitleServiceTest {
|
|||||||
@Test
|
@Test
|
||||||
void retriesOnceWhenFirstCollectionHasNoSubtitle() throws Exception {
|
void retriesOnceWhenFirstCollectionHasNoSubtitle() throws Exception {
|
||||||
ShortformSubtitleService service = spy(new ShortformSubtitleService(
|
ShortformSubtitleService service = spy(new ShortformSubtitleService(
|
||||||
mock(ShortformJobRepository.class), mock(ShortformWhisperService.class)));
|
mock(ShortformJobRepository.class), mock(ShortformWhisperService.class),
|
||||||
|
mock(TelegramNotifier.class)));
|
||||||
doReturn(null, "WEBVTT\n").when(service).collect("gDfGqRDubCg");
|
doReturn(null, "WEBVTT\n").when(service).collect("gDfGqRDubCg");
|
||||||
|
|
||||||
assertThat(service.collectWithRetry("gDfGqRDubCg")).isEqualTo("WEBVTT\n");
|
assertThat(service.collectWithRetry("gDfGqRDubCg")).isEqualTo("WEBVTT\n");
|
||||||
@ -48,7 +50,8 @@ class ShortformSubtitleServiceTest {
|
|||||||
void fallsBackToWhisperWhenYoutubeHasNoSubtitle() throws Exception {
|
void fallsBackToWhisperWhenYoutubeHasNoSubtitle() throws Exception {
|
||||||
ShortformJobRepository repository = mock(ShortformJobRepository.class);
|
ShortformJobRepository repository = mock(ShortformJobRepository.class);
|
||||||
ShortformWhisperService whisperService = mock(ShortformWhisperService.class);
|
ShortformWhisperService whisperService = mock(ShortformWhisperService.class);
|
||||||
ShortformSubtitleService service = spy(new ShortformSubtitleService(repository, whisperService));
|
ShortformSubtitleService service = spy(new ShortformSubtitleService(
|
||||||
|
repository, whisperService, mock(TelegramNotifier.class)));
|
||||||
doReturn(null).when(service).collectWithRetry("gDfGqRDubCg");
|
doReturn(null).when(service).collectWithRetry("gDfGqRDubCg");
|
||||||
when(whisperService.transcribe("gDfGqRDubCg")).thenReturn("WEBVTT\n");
|
when(whisperService.transcribe("gDfGqRDubCg")).thenReturn("WEBVTT\n");
|
||||||
|
|
||||||
@ -64,13 +67,15 @@ class ShortformSubtitleServiceTest {
|
|||||||
"gDfGqRDubCg",
|
"gDfGqRDubCg",
|
||||||
ShortformCategory.ENTERTAINMENT);
|
ShortformCategory.ENTERTAINMENT);
|
||||||
when(repository.findById(1L)).thenReturn(Optional.of(job));
|
when(repository.findById(1L)).thenReturn(Optional.of(job));
|
||||||
|
TelegramNotifier telegramNotifier = mock(TelegramNotifier.class);
|
||||||
ShortformSubtitleService service = spy(new ShortformSubtitleService(
|
ShortformSubtitleService service = spy(new ShortformSubtitleService(
|
||||||
repository, mock(ShortformWhisperService.class)));
|
repository, mock(ShortformWhisperService.class), telegramNotifier));
|
||||||
doReturn("WEBVTT\n").when(service).collectOrTranscribe(1L, "gDfGqRDubCg");
|
doReturn("WEBVTT\n").when(service).collectOrTranscribe(1L, "gDfGqRDubCg");
|
||||||
|
|
||||||
service.collectAsync(1L);
|
service.collectAsync(1L);
|
||||||
|
|
||||||
assertThat(job.getSubtitleStatus()).isEqualTo(SubtitleStatus.READY);
|
assertThat(job.getSubtitleStatus()).isEqualTo(SubtitleStatus.READY);
|
||||||
assertThat(job.getSubtitleVtt()).isEqualTo("WEBVTT\n");
|
assertThat(job.getSubtitleVtt()).isEqualTo("WEBVTT\n");
|
||||||
|
verify(telegramNotifier).sendMessage(org.mockito.ArgumentMatchers.contains("자막 생성 완료"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user