생성 시 내부적으로 분석 서비스와 플랫폼별 분석기를 초기화합니다.
- */
- public VideoAnalyzer() {
- this.analyzerService = new VideoAnalyzerService();
- this.platformFactory = new PlatformAnalyzerFactory(analyzerService);
- }
-
- /**
- * 비디오 URL을 분석하여 재생 가능 여부를 판단합니다.
- *
- *
특정 플랫폼(네이버TV 등)의 URL인 경우 해당 플랫폼에 맞는 분석을 수행하며,
- * 일반 URL인 경우 표준 비디오 분석을 수행합니다.
- *
- *
분석 항목:
- *
- *
보호된 URL 여부 (DRM, 서명된 URL 등)
- *
3D 비디오 여부
- *
AVPlayer 재생 가능 여부 (포맷 체크)
- *
- *
- *
성공 조건: 모든 항목이 재생 가능한 경우
- * 실패 조건: 하나라도 재생 불가능한 경우 (상세 이유 제공)
- *
- * @param url 분석할 비디오 URL (http:// 또는 https:// 형식)
- * @return 분석 결과를 담은 {@link VideoAnalysisResult} 객체
- * @see VideoAnalysisResult#isSuccess()
- * @see VideoAnalysisResult#getReason()
- *
- * @example
- *
{@code
- * // 네이버TV URL 분석
- * VideoAnalysisResult result1 = analyzer.analyzeUrl("https://tv.naver.com/v/84373511");
- *
- * // 일반 비디오 URL 분석
- * VideoAnalysisResult result2 = analyzer.analyzeUrl("https://example.com/video.mp4");
- * }
- */
- public VideoAnalysisResult analyzeUrl(String url) {
- // 플랫폼별 분석기 찾기
- PlatformAnalyzer analyzer = platformFactory.findAnalyzer(url);
-
- if (analyzer != null) {
- return analyzer.analyze(url);
- } else {
- // 일반 비디오 URL로 처리
- return analyzerService.analyzeVideo(url);
- }
- }
-
- /**
- * 현재 지원하는 플랫폼 목록을 반환합니다.
- *
- *
각 플랫폼별로 특화된 URL 분석 로직이 제공됩니다.
- *
- * @return 지원하는 플랫폼 이름 배열 (예: ["네이버TV"])
- *
모든 검사 항목이 통과했을 때 사용되며, reason은 "모든 검사 통과"로 설정됩니다.
- *
- * @return 성공 결과 객체
- */
- public static VideoAnalysisResult success() {
- return new VideoAnalysisResult(true, Messages.SUCCESS_ALL_CHECKS_PASSED);
- }
-
- /**
- * 실패 결과 객체를 생성합니다.
- *
- *
하나 이상의 검사 항목이 실패했을 때 사용되며,
- * 상세한 실패 이유를 reason에 포함합니다.
- *
- * @param reason 실패 이유 (개행 문자로 구분된 상세 설명 가능)
- * @return 실패 결과 객체
- */
- public static VideoAnalysisResult failure(String reason) {
- return new VideoAnalysisResult(false, reason);
- }
-
- /**
- * 분석 결과가 성공인지 여부를 반환합니다.
- *
- *
성공 조건:
- *
- *
보호되지 않은 URL
- *
2D 비디오
- *
AVPlayer에서 재생 가능
- *
- *
- * @return 모든 검사가 통과하면 {@code true}, 하나라도 실패하면 {@code false}
- */
- public boolean isSuccess() {
- return success;
- }
-
- /**
- * 분석 결과의 이유를 반환합니다.
- *
- *
성공한 경우: "모든 검사 통과"
- * 실패한 경우: 상세한 실패 이유 (각 항목별로 구분)
- *
- *
실패 이유 예시:
- *
- * 재생 불가 사유:
- * - 보호된 URL: DRM 보호 키워드 감지: widevine
- * - 재생 불가: 지원되지 않는 포맷 또는 Content-Type: video/x-flv
- *
- *
- * @return 분석 결과 이유 (null이 아닌 문자열)
- */
- public String getReason() {
- return reason;
- }
-
- @Override
- public String toString() {
- if (success) {
- return "VideoAnalysisResult{ 성공: " + reason + " }";
- } else {
- return "VideoAnalysisResult{ 실패: " + reason + " }";
- }
- }
-}
diff --git a/backup/src/main/java/com/caliverse/video/analyzer/platform/PlatformAnalyzer.java b/backup/src/main/java/com/caliverse/video/analyzer/platform/PlatformAnalyzer.java
deleted file mode 100644
index 15e2a42..0000000
--- a/backup/src/main/java/com/caliverse/video/analyzer/platform/PlatformAnalyzer.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.caliverse.video.analyzer.platform;
-
-import com.caliverse.video.analyzer.model.VideoAnalysisResult;
-
-public interface PlatformAnalyzer {
- /**
- * 해당 플랫폼의 URL인지 확인합니다.
- *
- * @param url 확인할 URL
- * @return 해당 플랫폼 URL이면 true, 아니면 false
- */
- boolean canHandle(String url);
-
- /**
- * 플랫폼 이름을 반환합니다.
- *
- * @return 플랫폼 이름
- */
- String getPlatformName();
-
- /**
- * 플랫폼별 URL 분석을 수행합니다.
- *
- * @param url 분석할 URL
- * @return 분석 결과
- */
- VideoAnalysisResult analyze(String url);
-
-}
diff --git a/backup/src/main/java/com/caliverse/video/analyzer/platform/PlatformAnalyzerFactory.java b/backup/src/main/java/com/caliverse/video/analyzer/platform/PlatformAnalyzerFactory.java
deleted file mode 100644
index a6cd0b7..0000000
--- a/backup/src/main/java/com/caliverse/video/analyzer/platform/PlatformAnalyzerFactory.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package com.caliverse.video.analyzer.platform;
-
-import com.caliverse.video.analyzer.platform.impl.NaverTvPlatformAnalyzer;
-import com.caliverse.video.analyzer.service.VideoAnalyzerService;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class PlatformAnalyzerFactory {
- private final List analyzers;
-
- public PlatformAnalyzerFactory(VideoAnalyzerService videoAnalyzerService) {
- this.analyzers = new ArrayList<>();
-
- // 플랫폼 분석기들을 등록
- analyzers.add(new NaverTvPlatformAnalyzer(videoAnalyzerService));
-
- // 향후 다른 플랫폼들도 여기에 추가
- // analyzers.add(new VimeoPlatformAnalyzer(videoAnalyzerService));
- // analyzers.add(new DaumTvPlatformAnalyzer(videoAnalyzerService));
- }
-
- /**
- * URL에 맞는 플랫폼 분석기를 찾습니다.
- *
- * @param url 분석할 URL
- * @return 해당 플랫폼 분석기, 없으면 null
- */
- public PlatformAnalyzer findAnalyzer(String url) {
- for (PlatformAnalyzer analyzer : analyzers) {
- if (analyzer.canHandle(url)) {
- return analyzer;
- }
- }
- return null;
- }
-
- /**
- * 등록된 모든 플랫폼 분석기 목록을 반환합니다.
- *
- * @return 플랫폼 분석기 목록
- */
- public List getAllAnalyzers() {
- return new ArrayList<>(analyzers);
- }
-
- /**
- * 새로운 플랫폼 분석기를 추가합니다.
- *
- * @param analyzer 추가할 분석기
- */
- public void addAnalyzer(PlatformAnalyzer analyzer) {
- analyzers.add(analyzer);
- }
-
-}
diff --git a/backup/src/main/java/com/caliverse/video/analyzer/platform/impl/NaverTvPlatformAnalyzer.java b/backup/src/main/java/com/caliverse/video/analyzer/platform/impl/NaverTvPlatformAnalyzer.java
deleted file mode 100644
index 89af613..0000000
--- a/backup/src/main/java/com/caliverse/video/analyzer/platform/impl/NaverTvPlatformAnalyzer.java
+++ /dev/null
@@ -1,525 +0,0 @@
-package com.caliverse.video.analyzer.platform.impl;
-
-import com.caliverse.video.analyzer.platform.PlatformAnalyzer;
-import com.caliverse.video.analyzer.service.VideoAnalyzerService;
-import com.caliverse.video.analyzer.model.VideoAnalysisResult;
-import com.caliverse.video.global.common.CommonConstants;
-import com.caliverse.video.global.common.Messages;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.http.HttpEntity;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.util.EntityUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.regex.Matcher;
-
-public class NaverTvPlatformAnalyzer implements PlatformAnalyzer {
-
- private static final Logger logger = LoggerFactory.getLogger(NaverTvPlatformAnalyzer.class);
-
- private final ObjectMapper objectMapper;
- private final VideoAnalyzerService videoAnalyzerService;
-
- public NaverTvPlatformAnalyzer(VideoAnalyzerService videoAnalyzerService) {
- this.objectMapper = new ObjectMapper();
- this.videoAnalyzerService = videoAnalyzerService;
- }
-
- @Override
- public boolean canHandle(String url) {
- if (url == null) return false;
- String lowerUrl = url.toLowerCase();
- return lowerUrl.contains(CommonConstants.DOMAIN_NAVER_TV) || lowerUrl.contains(CommonConstants.DOMAIN_NAVER);
- }
-
- @Override
- public String getPlatformName() {
- return CommonConstants.FLATFORM_NAME_NAVER_TV;
- }
-
- @Override
- public VideoAnalysisResult analyze(String url) {
- try {
- // 1. HTML 콘텐츠 가져오기
- String htmlContent = fetchHtmlContent(url);
-
- // 2. __NEXT_DATA__ 파싱
- JsonNode nextDataNode = extractNextData(htmlContent);
- if (nextDataNode == null) {
- return VideoAnalysisResult.failure(Messages.NAVER_TV_PARSING_FAILED);
- }
-
- // 3. props.pageProps 접근
- JsonNode pageProps = nextDataNode.path("props").path("pageProps");
- if (pageProps.isMissingNode()) {
- return VideoAnalysisResult.failure(Messages.NAVER_TV_PARSING_FAILED);
- }
-
- // 4. 콘텐츠 타입별 처리
- String videoUrl = null;
-
- // 4-1. vodInfo 확인
- JsonNode vodInfo = pageProps.path("vodInfo");
- if (!vodInfo.isMissingNode()) {
- videoUrl = handleVodInfo(vodInfo);
- }
-
- // 4-2. clipInfo 확인
- if (videoUrl == null) {
- JsonNode clipInfo = pageProps.path("clipInfo");
- if (!clipInfo.isMissingNode()) {
- videoUrl = handleClipInfo(clipInfo);
- }
- }
-
- // 4-3. liveInfo 확인
- if (videoUrl == null) {
- JsonNode liveInfo = pageProps.path("liveInfo");
- if (!liveInfo.isMissingNode()) {
- videoUrl = handleLiveInfo(liveInfo);
- }
- }
-
- if (videoUrl == null) {
- return VideoAnalysisResult.failure(Messages.NAVER_TV_API_PARSING_FAILED);
- }
-
- // 5. 제한사항 확인
- String restrictionReason = checkRestrictions(videoUrl);
- if (restrictionReason != null) {
- return VideoAnalysisResult.failure(Messages.format(Messages.NAVER_TV_RESTRICTION, restrictionReason));
- }
-
- // 6. 비디오 분석
- return videoAnalyzerService.analyzeVideo(videoUrl);
-
- } catch (Exception e) {
- return VideoAnalysisResult.failure(Messages.format(Messages.NAVER_TV_ANALYSIS_ERROR, e.getMessage()));
- }
- }
-
- private String fetchHtmlContent(String url) throws IOException {
- try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
- HttpGet request = new HttpGet(url);
- request.setHeader(CommonConstants.REQUEST_HEADER_NAME_USER_AGENT, CommonConstants.REQUEST_HEADER_USER_AGENT);
-
- try (CloseableHttpResponse response = httpClient.execute(request)) {
- HttpEntity entity = response.getEntity();
- return EntityUtils.toString(entity, StandardCharsets.UTF_8);
- }
- }
- }
-
- /**
- * HTML에서 __NEXT_DATA__ 스크립트를 파싱합니다.
- */
- private JsonNode extractNextData(String htmlContent) {
- try {
- Matcher matcher = CommonConstants.NAVER_NEXT_DATA_PATTERN.matcher(htmlContent);
- if (matcher.find()) {
- String jsonData = matcher.group(1);
- return objectMapper.readTree(jsonData);
- }
- return null;
- } catch (Exception e) {
- logger.warn("__NEXT_DATA__ 파싱 실패: {}", e.getMessage());
- return null;
- }
- }
-
- /**
- * vodInfo 처리: videoId + inKey로 VOD API 호출
- * 구조: vodInfo.clip.videoId, vodInfo.play.inKey
- */
- private String handleVodInfo(JsonNode vodInfo) throws IOException {
- String videoId = vodInfo.path("clip").path("videoId").asText(null);
- String inKey = vodInfo.path("play").path("inKey").asText(null);
-
- if (videoId != null && inKey != null) {
- String apiUrl = String.format(CommonConstants.NAVER_VOD_API_URL,
- videoId, URLEncoder.encode(inKey, StandardCharsets.UTF_8));
- String apiResponse = fetchApiResponse(apiUrl);
- return extractVideoUrl(apiResponse);
- }
- return null;
- }
-
- /**
- * clipInfo 처리: videoId만으로 Short API 호출
- */
- private String handleClipInfo(JsonNode clipInfo) throws IOException {
- String videoId = clipInfo.path("videoId").asText(null);
-
- if (videoId != null) {
- String apiUrl = String.format(CommonConstants.NAVER_SHORT_API_URL, videoId);
- String apiResponse = fetchApiResponse(apiUrl);
- return extractVideoUrlFromShortApi(apiResponse);
- }
- return null;
- }
-
- /**
- * liveInfo 처리: playbackBody에서 직접 m3u8 URL 추출
- */
- private String handleLiveInfo(JsonNode liveInfo) {
- try {
- String playbackBody = liveInfo.path("playbackBody").asText(null);
- if (playbackBody == null) {
- return null;
- }
-
- // playbackBody는 JSON 문자열이므로 다시 파싱
- JsonNode playbackNode = objectMapper.readTree(playbackBody);
-
- // media 배열에서 HLS 프로토콜의 path 찾기
- JsonNode mediaArray = playbackNode.path("media");
- if (mediaArray.isArray()) {
- for (JsonNode media : mediaArray) {
- String protocol = media.path("protocol").asText("");
- if ("HLS".equals(protocol)) {
- String path = media.path("path").asText(null);
- if (path != null && !path.isEmpty()) {
- // \\u 이스케이프 처리
- path = unescapeUnicode(path);
- logger.debug("liveInfo에서 m3u8 URL 추출 성공");
- return path;
- }
- }
- }
- }
-
- return null;
- } catch (Exception e) {
- logger.warn("liveInfo playbackBody 파싱 실패: {}", e.getMessage());
- return null;
- }
- }
-
- /**
- * 유니코드 이스케이프 시퀀스 처리
- */
- private String unescapeUnicode(String text) {
- if (text == null) {
- return null;
- }
- return text.replace("\\u003d", "=")
- .replace("\\u0026", "&")
- .replace("\\u003c", "<")
- .replace("\\u003e", ">");
- }
-
- private String fetchApiResponse(String apiUrl) throws IOException {
- try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
- HttpGet request = new HttpGet(apiUrl);
- request.setHeader(CommonConstants.REQUEST_HEADER_NAME_USER_AGENT, CommonConstants.REQUEST_HEADER_USER_AGENT);
- request.setHeader(CommonConstants.REQUEST_HEADER_NAME_REFERER, CommonConstants.REQUEST_HEADER_NAVER_REFERER);
-
- try (CloseableHttpResponse response = httpClient.execute(request)) {
- if (response.getStatusLine().getStatusCode() != 200) {
- throw new IOException("API 호출 실패");
- }
- HttpEntity entity = response.getEntity();
- return EntityUtils.toString(entity, StandardCharsets.UTF_8);
- }
- }
- }
-
- private String extractVideoUrl(String apiResponse) throws IOException {
- try {
- JsonNode rootNode = objectMapper.readTree(apiResponse);
-
- // 1. MPD 구조 탐색: MPD -> Period -> AdaptationSet -> Representation
- JsonNode mpdNode = rootNode.path("MPD");
- if (mpdNode.isArray() && !mpdNode.isEmpty()) {
- JsonNode firstMpd = mpdNode.get(0);
- JsonNode periodNode = firstMpd.path("Period");
-
- if (periodNode.isArray() && !periodNode.isEmpty()) {
- JsonNode firstPeriod = periodNode.get(0);
- JsonNode adaptationSetNode = firstPeriod.path("AdaptationSet");
-
- if (adaptationSetNode.isArray() && !adaptationSetNode.isEmpty()) {
- for (int i = 0; i < adaptationSetNode.size(); i++) {
- JsonNode adaptationSet = adaptationSetNode.get(i);
- JsonNode representationNode = adaptationSet.path("Representation");
-
- if (representationNode.isArray() && !representationNode.isEmpty()) {
- for (int j = 0; j < representationNode.size(); j++) {
- JsonNode representation = representationNode.get(j);
- String videoUrl = extractBaseUrlFromRepresentation(representation);
-
- if (videoUrl != null) {
-// logger.debug("비디오 URL 추출 성공 (AdaptationSet[{}], Representation[{}])", i, j);
- return videoUrl;
- }
- }
- }
-
- String adaptationSetUrl = extractUrlFromAdaptationSet(adaptationSet);
- if (adaptationSetUrl != null) {
-// logger.debug("비디오 URL 추출 성공 (AdaptationSet[{}] 레벨)", i);
- return adaptationSetUrl;
- }
- }
- }
- }
- }
-
- // 2. 레거시 구조 확인 (이전 API 버전 호환성)
- JsonNode legacyRepresentations = rootNode.path("Representation");
- if (legacyRepresentations.isArray() && legacyRepresentations.size() > 0) {
- for (int i = 0; i < legacyRepresentations.size(); i++) {
- JsonNode representation = legacyRepresentations.get(i);
- String videoUrl = extractBaseUrlFromRepresentation(representation);
-
- if (videoUrl != null) {
- logger.debug("비디오 URL 추출 성공 (레거시 Representation[{}])", i);
- return videoUrl;
- }
- }
- }
-
- // 3. 다른 가능한 경로들 확인
- String alternativeUrl = findAlternativeVideoUrl(rootNode);
- if (alternativeUrl != null) {
- logger.debug("비디오 URL 추출 성공 (대안 경로)");
- return alternativeUrl;
- }
-
- logger.warn("비디오 URL을 찾을 수 없습니다. JSON 구조를 확인해주세요.");
- return null;
-
- } catch (Exception e) {
- throw new IOException("JSON 파싱 오류: " + e.getMessage(), e);
- }
-
- }
-
- private String checkRestrictions(String videoUrl) {
- try {
- try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
- HttpGet request = new HttpGet(videoUrl);
- request.setHeader(CommonConstants.REQUEST_HEADER_NAME_USER_AGENT, CommonConstants.REQUEST_HEADER_USER_AGENT);
- request.setHeader(CommonConstants.REQUEST_HEADER_NAME_REFERER, CommonConstants.REQUEST_HEADER_NAVER_REFERER);
-
- try (CloseableHttpResponse response = httpClient.execute(request)) {
- int statusCode = response.getStatusLine().getStatusCode();
-
- if (statusCode == 403) return "접근 금지 (403) - CORS 또는 권한 제한";
- if (statusCode == 401) return "인 필요 (401)";
- if (statusCode >= 400) return "HTTP 오류 (" + statusCode + ")";
-
- // CORS 헤더 확인
- if (response.containsHeader("Access-Control-Allow-Origin")) {
- String allowOrigin = response.getFirstHeader("Access-Control-Allow-Origin").getValue();
- if (!allowOrigin.equals("*") && !allowOrigin.contains("tv.naver.com")) {
- return "CORS 제한 - 특정 도메인에서만 접근 가능";
- }
- }
-
- return null; // 제한 없음
- }
- }
- } catch (IOException e) {
- return "네트워크 연결 오류: " + e.getMessage();
- }
- }
-
- /**
- * Representation 노드에서 BaseURL을 추출합니다.
- */
- private String extractBaseUrlFromRepresentation(JsonNode representation) {
- if (representation == null) return null;
-
- // BaseURL 배열 형태
- JsonNode baseUrls = representation.path("BaseURL");
- if (baseUrls.isArray() && !baseUrls.isEmpty()) {
- String url = baseUrls.get(0).asText();
- if (url != null && !url.trim().isEmpty() && isValidVideoUrl(url)) {
- return url;
- }
- }
-
- // BaseURL 단일 문자열 형태
- if (!baseUrls.isMissingNode() && baseUrls.isTextual()) {
- String url = baseUrls.asText();
- if (url != null && !url.trim().isEmpty() && isValidVideoUrl(url)) {
- return url;
- }
- }
-
- return null;
- }
-
- /**
- * AdaptationSet 레벨에서 URL을 찾습니다.
- */
- private String extractUrlFromAdaptationSet(JsonNode adaptationSet) {
- if (adaptationSet == null) return null;
-
- // AdaptationSet의 다양한 속성들 확인
- JsonNode baseUrl = adaptationSet.path("BaseURL");
- if (!baseUrl.isMissingNode()) {
- if (baseUrl.isArray() && !baseUrl.isEmpty()) {
- String url = baseUrl.get(0).asText();
- if (isValidVideoUrl(url)) return url;
- } else if (baseUrl.isTextual()) {
- String url = baseUrl.asText();
- if (isValidVideoUrl(url)) return url;
- }
- }
-
- // SegmentTemplate이나 다른 방식으로 URL 구성이 필요한 경우
- JsonNode segmentTemplate = adaptationSet.path("SegmentTemplate");
- if (!segmentTemplate.isMissingNode()) {
- // SegmentTemplate 기반 URL 구성 로직
- return buildUrlFromSegmentTemplate(segmentTemplate);
- }
-
- return null;
- }
-
- /**
- * 대안적인 비디오 URL 경로를 찾습니다.
- */
- private String findAlternativeVideoUrl(JsonNode rootNode) {
- // 1. 직접적인 URL 필드들 확인
- String[] possibleUrlFields = {"videoUrl", "streamUrl", "playbackUrl", "mediaUrl", "url"};
-
- for (String field : possibleUrlFields) {
- JsonNode urlNode = rootNode.path(field);
- if (!urlNode.isMissingNode() && urlNode.isTextual()) {
- String url = urlNode.asText();
- if (isValidVideoUrl(url)) return url;
- }
- }
-
- // 2. 중첩된 구조에서 URL 찾기
- JsonNode sources = rootNode.path("sources");
- if (sources.isArray() && !sources.isEmpty()) {
- for (JsonNode source : sources) {
- String url = source.path("src").asText();
- if (isValidVideoUrl(url)) return url;
- }
- }
-
- // 3. 다른 가능한 구조들...
- // 필요시 추가 경로들을 여기에 구현
-
- return null;
- }
-
- /**
- * SegmentTemplate에서 URL을 구성합니다.
- */
- private String buildUrlFromSegmentTemplate(JsonNode segmentTemplate) {
- // SegmentTemplate 방식은 복잡하므로 현재는 기본 구현만
- JsonNode media = segmentTemplate.path("@media");
-// JsonNode initialization = segmentTemplate.path("@initialization");
-
- if (!media.isMissingNode() && media.isTextual()) {
- String mediaTemplate = media.asText();
- // 템플릿 변수들을 실제 값으로 치환하는 로직 필요
- // 현재는 간단히 템플릿 문자열만 반환
- if (mediaTemplate.contains("http")) {
- return mediaTemplate;
- }
- }
-
- return null;
- }
-
- /**
- * 유효한 비디오 URL인지 확인합니다.
- */
- private boolean isValidVideoUrl(String url) {
- if (url == null || url.trim().isEmpty()) {
- return false;
- }
-
- // 기본적인 URL 형식 확인
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
- return false;
- }
-
- // 비디오 파일 확장자나 스트리밍 패턴 확인
- String lowerUrl = url.toLowerCase();
- return lowerUrl.contains(".mp4") ||
- lowerUrl.contains(".m3u8") ||
- lowerUrl.contains(".mov") ||
- lowerUrl.contains(".m4v") ||
- lowerUrl.contains("stream") ||
- lowerUrl.contains("video");
- }
-
- /**
- * 짧은 URL API 응답에서 비디오 URL을 추출합니다.
- * 응답 구조: {"card":{"content":{"vod":{"playback":{"videos":{"list":[{"source":"URL"}]}}}}}}
- */
- private String extractVideoUrlFromShortApi(String apiResponse) throws IOException {
- try {
- JsonNode rootNode = objectMapper.readTree(apiResponse);
-
- // card.content.vod.playback.videos.list[0].source 경로로 접근
- JsonNode cardNode = rootNode.path("card");
- if (cardNode.isMissingNode()) {
- logger.warn("짧은 URL API 응답에 'card' 노드가 없습니다.");
- return null;
- }
-
- JsonNode contentNode = cardNode.path("content");
- if (contentNode.isMissingNode()) {
- logger.warn("짧은 URL API 응답에 'content' 노드가 없습니다.");
- return null;
- }
-
- JsonNode vodNode = contentNode.path("vod");
- if (vodNode.isMissingNode()) {
- logger.warn("짧은 URL API 응답에 'vod' 노드가 없습니다.");
- return null;
- }
-
- JsonNode playbackNode = vodNode.path("playback");
- if (playbackNode.isMissingNode()) {
- logger.warn("짧은 URL API 응답에 'playback' 노드가 없습니다.");
- return null;
- }
-
- JsonNode videosNode = playbackNode.path("videos");
- if (videosNode.isMissingNode()) {
- logger.warn("짧은 URL API 응답에 'videos' 노드가 없습니다.");
- return null;
- }
-
- JsonNode listNode = videosNode.path("list");
- if (listNode.isArray() && !listNode.isEmpty()) {
- JsonNode firstVideo = listNode.get(0);
- JsonNode sourceNode = firstVideo.path("source");
-
- if (!sourceNode.isMissingNode() && sourceNode.isTextual()) {
- String videoUrl = sourceNode.asText();
- if (isValidVideoUrl(videoUrl)) {
- logger.debug("짧은 URL API에서 비디오 URL 추출 성공");
- return videoUrl;
- }
- }
- }
-
- logger.warn("짧은 URL API 응답에서 비디오 URL을 찾을 수 없습니다.");
- return null;
-
- } catch (Exception e) {
- throw new IOException("짧은 URL API JSON 파싱 오류: " + e.getMessage(), e);
- }
- }
-
-
-}
diff --git a/backup/src/main/java/com/caliverse/video/analyzer/service/VideoAnalyzerService.java b/backup/src/main/java/com/caliverse/video/analyzer/service/VideoAnalyzerService.java
deleted file mode 100644
index 41bda1c..0000000
--- a/backup/src/main/java/com/caliverse/video/analyzer/service/VideoAnalyzerService.java
+++ /dev/null
@@ -1,293 +0,0 @@
-package com.caliverse.video.analyzer.service;
-
-import com.caliverse.video.analyzer.entity.UrlType;
-import com.caliverse.video.analyzer.model.VideoAnalysisResult;
-import com.caliverse.video.global.common.Messages;
-import com.caliverse.video.global.util.HttpUtils;
-import com.caliverse.video.global.util.UrlParser;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
-
-/**
- * 비디오 URL을 분석하는 핵심 서비스 클래스입니다.
- */
-public class VideoAnalyzerService {
-
- private final HttpUtils httpUtils;
- private final UrlParser urlParser;
-
- /**
- * 체크 결과를 담는 내부 클래스
- */
- private static class CheckResult {
- final boolean result;
- final String reason;
-
- CheckResult(boolean result, String reason) {
- this.result = result;
- this.reason = reason;
- }
- }
-
- // AVPlayer에서 지원하는 비디오 포맷 목록
- private static final List SUPPORTED_FORMATS = Arrays.asList(
- "mp4", "mov", "m4v", "m3u8", "hls"
- );
-
- // DRM 관련 키워드
- private static final List DRM_KEYWORDS = Arrays.asList(
- "drm", "widevine", "playready", "fairplay", "encrypted", "protection"
- );
-
- public VideoAnalyzerService() {
- this.httpUtils = new HttpUtils();
- this.urlParser = new UrlParser();
- }
-
- /**
- * URL이 보호된 URL인지 분석합니다 (DRM, CORS, Signed URL).
- */
- private CheckResult checkProtection(String videoUrl) {
- if (videoUrl == null || videoUrl.trim().isEmpty()) {
- return new CheckResult(false, Messages.ERROR_INVALID_URL);
- }
-
- // URL 유형별로 분기 처리
- UrlType urlType = determineUrlType(videoUrl);
-
- switch (urlType) {
- case YOUTUBE: // 서명 정보가 계속 필요하기때문에 불가능
- return new CheckResult(false, Messages.PROTECTION_YOUTUBE_URL);
- case NAVER_TV:
- return new CheckResult(false, "");
- case BLOB:
- return checkBlobProtection(videoUrl);
- case STREAMING:
- return checkStreamingProtection(videoUrl);
- case DIRECT_FILE:
- return checkDirectFileProtection(videoUrl);
- default:
- return checkGenericProtection(videoUrl);
- }
- }
-
- /**
- * URL이 보호된 URL인지 분석합니다 (하위 호환성을 위한 메소드).
- * @deprecated checkProtection 사용을 권장합니다
- */
- @Deprecated
- public boolean isProtectedUrl(String videoUrl) {
- return checkProtection(videoUrl).result;
- }
-
- /**
- * URL 유형을 판별합니다.
- */
- private UrlType determineUrlType(String videoUrl) {
- String lowerUrl = videoUrl.toLowerCase();
-
- if (lowerUrl.contains("youtube.com") || lowerUrl.contains("youtu.be")) {
- return UrlType.YOUTUBE;
- } else if (lowerUrl.contains("naver.com") || lowerUrl.contains("naver")) {
- return UrlType.NAVER_TV;
- } else if (lowerUrl.startsWith("blob:")) {
- return UrlType.BLOB;
- } else if (lowerUrl.contains(".m3u8") || lowerUrl.contains("hls") ||
- lowerUrl.contains("dash") || lowerUrl.contains("stream")) {
- return UrlType.STREAMING;
- } else if (lowerUrl.matches(".*\\.(mp4|mov|m4v|avi|mkv|webm)($|\\?.*)")) {
- return UrlType.DIRECT_FILE;
- } else {
- return UrlType.UNKNOWN;
- }
- }
-
- /**
- * Blob URL의 보호 상태를 확인합니다.
- */
- private CheckResult checkBlobProtection(String videoUrl) {
- // Blob URL은 브라우저에서 생성되므로 일반적으로 보호되지 않음
- // 단, 원본 소스가 보호된 경우일 수 있음
- return new CheckResult(false, Messages.PROTECTION_BLOB_URL);
- }
-
- /**
- * 스트리밍 URL의 보호 상태를 확인합니다.
- */
- private CheckResult checkStreamingProtection(String videoUrl) {
- String lowerUrl = videoUrl.toLowerCase();
-
- // 1. DRM 관련 키워드 확인
- for (String keyword : DRM_KEYWORDS) {
- if (lowerUrl.contains(keyword)) {
- return new CheckResult(true, Messages.format(Messages.PROTECTION_DRM_KEYWORD_DETECTED, keyword));
- }
- }
-
- // 2. 토큰 기반 인증 확인
- if (urlParser.hasSignatureParameters(videoUrl)) {
- return new CheckResult(true, Messages.PROTECTION_SIGNED_URL);
- }
-
- // 3. HTTP 헤더 확인
- try {
- if (httpUtils.hasSecurityHeaders(videoUrl)) {
- return new CheckResult(true, Messages.PROTECTION_SECURITY_HEADER_DETECTED);
- }
- return new CheckResult(false, Messages.PROTECTION_NOT_PROTECTED_STREAMING);
- } catch (IOException e) {
- return new CheckResult(true, Messages.format(Messages.PROTECTION_CONNECTION_FAILED, e.getMessage()));
- }
- }
-
- /**
- * 직접 파일 URL의 보호 상태를 확인합니다.
- */
- private CheckResult checkDirectFileProtection(String videoUrl) {
- String lowerUrl = videoUrl.toLowerCase();
-
- // 1. DRM 관련 키워드 확인
- for (String keyword : DRM_KEYWORDS) {
- if (lowerUrl.contains(keyword)) {
- return new CheckResult(true, Messages.format(Messages.PROTECTION_DRM_KEYWORD_DETECTED, keyword));
- }
- }
-
- // 2. Signed URL 검사
- if (urlParser.hasSignatureParameters(videoUrl)) {
- return new CheckResult(true, Messages.PROTECTION_SIGNED_URL);
- }
-
- // 3. HTTP 헤더를 통한 보안 검사
- try {
- if (httpUtils.hasSecurityHeaders(videoUrl)) {
- return new CheckResult(true, Messages.PROTECTION_SECURITY_HEADER_DETECTED);
- }
- return new CheckResult(false, Messages.PROTECTION_NOT_PROTECTED_DIRECT_FILE);
- } catch (IOException e) {
- return new CheckResult(true, Messages.format(Messages.PROTECTION_CONNECTION_FAILED, e.getMessage()));
- }
- }
-
- /**
- * 일반적인 URL의 보호 상태를 확인합니다.
- */
- private CheckResult checkGenericProtection(String videoUrl) {
- // 기존의 일반적인 보호 URL 검사 로직
- return checkDirectFileProtection(videoUrl);
- }
-
- /**
- * URL이 3D 비디오인지 분석합니다.
- */
- private CheckResult check3DVideo(String videoUrl) {
- if (videoUrl == null || videoUrl.trim().isEmpty()) {
- return new CheckResult(false, Messages.ERROR_INVALID_URL);
- }
-
- // 필요시 헤더나 메타데이터 확인
- try {
- if (httpUtils.checkFor3DHeaders(videoUrl)) {
- return new CheckResult(true, Messages.THREE_D_HEADER_DETECTED);
- }
- return new CheckResult(false, Messages.THREE_D_NOT_DETECTED);
- } catch (IOException e) {
- return new CheckResult(false, Messages.format(Messages.THREE_D_CONNECTION_FAILED, e.getMessage()));
- }
- }
-
- /**
- * URL이 3D 비디오인지 분석합니다 (하위 호환성을 위한 메소드).
- * @deprecated check3DVideo 사용을 권장합니다
- */
- @Deprecated
- public boolean is3DVideo(String videoUrl) {
- return check3DVideo(videoUrl).result;
- }
-
- /**
- * URL이 AVPlayer에서 재생 가능한지 분석합니다.
- */
- private CheckResult checkPlayability(String videoUrl) {
- if (videoUrl == null || videoUrl.trim().isEmpty()) {
- return new CheckResult(false, Messages.ERROR_INVALID_URL);
- }
-
- // 1. 포맷 검사 (파일 확장자)
- String extension = urlParser.getExtension(videoUrl);
- if (extension != null && SUPPORTED_FORMATS.contains(extension.toLowerCase())) {
- return new CheckResult(true, Messages.format(Messages.PLAYABILITY_SUPPORTED_FORMAT, extension));
- }
-
- // 2. 스트리밍 URL 패턴 검사
- if (urlParser.isStreamingUrl(videoUrl)) {
- return new CheckResult(true, Messages.PLAYABILITY_SUPPORTED_STREAMING);
- }
-
- // 3. 필요시 콘텐츠 타입 헤더 확인
- try {
- String contentType = httpUtils.getContentType(videoUrl);
- if (contentType != null && (
- contentType.contains("video/") ||
- contentType.contains("application/x-mpegURL") ||
- contentType.contains("application/vnd.apple.mpegurl")
- )) {
- return new CheckResult(true, Messages.format(Messages.PLAYABILITY_SUPPORTED_CONTENT_TYPE, contentType));
- }
- return new CheckResult(false, Messages.format(Messages.PLAYABILITY_UNSUPPORTED_FORMAT, contentType));
- } catch (IOException e) {
- return new CheckResult(false, Messages.format(Messages.PLAYABILITY_CONNECTION_FAILED, e.getMessage()));
- }
- }
-
- /**
- * URL이 AVPlayer에서 재생 가능한지 분석합니다 (하위 호환성을 위한 메소드).
- * @deprecated checkPlayability 사용을 권장합니다
- */
- @Deprecated
- public boolean isPlayableOnAVPlayer(String videoUrl) {
- return checkPlayability(videoUrl).result;
- }
-
- /**
- * 모든 분석을 한번에 수행하고 결과를 반환합니다.
- * 보호되지 않고, 2D 비디오이고, AVPlayer에서 재생 가능하면 성공입니다.
- */
- public VideoAnalysisResult analyzeVideo(String videoUrl) {
- CheckResult protectionResult = checkProtection(videoUrl);
- CheckResult threeDResult = check3DVideo(videoUrl);
- CheckResult playabilityResult = checkPlayability(videoUrl);
-
- // 실패 이유 수집
- StringBuilder failureReasons = new StringBuilder();
-
- // 보호된 URL이면 실패
- if (protectionResult.result) {
- failureReasons.append(Messages.ANALYSIS_PROTECTED_URL_PREFIX)
- .append(protectionResult.reason).append("\n");
- }
-
- // 3D 비디오이면 실패
-// if (threeDResult.result) {
-// failureReasons.append(Messages.ANALYSIS_THREE_D_VIDEO_PREFIX)
-// .append(threeDResult.reason).append("\n");
-// }
-
- // AVPlayer에서 재생 불가능하면 실패
- if (!playabilityResult.result) {
- failureReasons.append(Messages.ANALYSIS_NOT_PLAYABLE_PREFIX)
- .append(playabilityResult.reason).append("\n");
- }
-
- // 실패 이유가 있으면 실패, 없으면 성공
- if (!failureReasons.isEmpty()) {
- return VideoAnalysisResult.failure(
- Messages.ANALYSIS_FAILURE_HEADER + "\n" + failureReasons.toString().trim()
- );
- } else {
- return VideoAnalysisResult.success();
- }
- }
-}
diff --git a/backup/src/main/java/com/caliverse/video/global/common/CommonConstants.java b/backup/src/main/java/com/caliverse/video/global/common/CommonConstants.java
deleted file mode 100644
index bd9c171..0000000
--- a/backup/src/main/java/com/caliverse/video/global/common/CommonConstants.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.caliverse.video.global.common;
-
-import java.util.regex.Pattern;
-
-public class CommonConstants {
- // 도메인
- public static final String DOMAIN_NAVER_TV = "naver.com";
- public static final String DOMAIN_NAVER = "naver.me";
- public static final String FLATFORM_NAME_NAVER_TV = "네이버TV";
-
- // 정규표현식
- public static final Pattern NAVER_NEXT_DATA_PATTERN = Pattern.compile("");
- public static final Pattern NAVER_VIDEO_ID_PATTERN = Pattern.compile("\"videoId\"\\s*:\\s*\"([^\"]+)\"");
- public static final Pattern NAVER_IN_KEY_PATTERN = Pattern.compile("\"inKey\"\\s*:\\s*\"([^\"]+)\"");
-
- // API URL
- public static final String NAVER_VOD_API_URL = "https://apis.naver.com/neonplayer/vodplay/v3/playback/%s?key=%s";
- public static final String NAVER_SHORT_API_URL = "https://api-videohub.naver.com/shortformhub/feeds/v7/card?serviceType=NTV&seedMediaId=%s&mediaType=VOD";
-
- //헤더
- public static final String REQUEST_HEADER_NAME_USER_AGENT = "User-Agent";
- public static final String REQUEST_HEADER_NAME_REFERER = "Referer";
-
- public static final String REQUEST_HEADER_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36";
- public static final String REQUEST_HEADER_NAVER_REFERER = "https://tv.naver.com/";
-}
diff --git a/backup/src/main/java/com/caliverse/video/global/common/Messages.java b/backup/src/main/java/com/caliverse/video/global/common/Messages.java
deleted file mode 100644
index b89f330..0000000
--- a/backup/src/main/java/com/caliverse/video/global/common/Messages.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.caliverse.video.global.common;
-
-/**
- * 분석 결과 메시지를 관리하는 상수 클래스입니다.
- */
-public class Messages {
-
- // ========== 일반 메시지 ==========
- public static final String SUCCESS_ALL_CHECKS_PASSED = "모든 검사 통과";
- public static final String ERROR_INVALID_URL = "유효하지 않은 URL";
-
- // ========== 보호 URL 체크 메시지 ==========
- public static final String PROTECTION_YOUTUBE_URL = "YouTube URL (서명 정보 필요)";
- public static final String PROTECTION_BLOB_URL = "Blob URL (브라우저에서 생성된 URL)";
- public static final String PROTECTION_DRM_KEYWORD_DETECTED = "DRM 보호 키워드 감지: %s";
- public static final String PROTECTION_SIGNED_URL = "서명된 URL (토큰 기반 인증)";
- public static final String PROTECTION_SECURITY_HEADER_DETECTED = "보안 헤더 감지";
- public static final String PROTECTION_NOT_PROTECTED_STREAMING = "보호되지 않은 스트리밍 URL";
- public static final String PROTECTION_NOT_PROTECTED_DIRECT_FILE = "보호되지 않은 직접 파일 URL";
- public static final String PROTECTION_CONNECTION_FAILED = "연결 실패 (보호된 것으로 간주): %s";
-
- // ========== 3D 비디오 체크 메시지 ==========
- public static final String THREE_D_HEADER_DETECTED = "3D 비디오 헤더 감지";
- public static final String THREE_D_NOT_DETECTED = "일반 2D 비디오";
- public static final String THREE_D_CONNECTION_FAILED = "연결 실패 (2D로 간주): %s";
-
- // ========== 재생 가능성 체크 메시지 ==========
- public static final String PLAYABILITY_SUPPORTED_FORMAT = "지원되는 포맷: %s";
- public static final String PLAYABILITY_SUPPORTED_STREAMING = "지원되는 스트리밍 URL";
- public static final String PLAYABILITY_SUPPORTED_CONTENT_TYPE = "지원되는 Content-Type: %s";
- public static final String PLAYABILITY_UNSUPPORTED_FORMAT = "지원되지 않는 포맷 또는 Content-Type: %s";
- public static final String PLAYABILITY_CONNECTION_FAILED = "연결 실패 (재생 불가능으로 간주): %s";
-
- // ========== 전체 분석 결과 메시지 ==========
- public static final String ANALYSIS_FAILURE_HEADER = "재생 불가 사유:";
- public static final String ANALYSIS_PROTECTED_URL_PREFIX = "- 보호된 URL: ";
- public static final String ANALYSIS_THREE_D_VIDEO_PREFIX = "- 3D 비디오: ";
- public static final String ANALYSIS_NOT_PLAYABLE_PREFIX = "- 재생 불가: ";
-
- // ========== 네이버TV 관련 메시지 ==========
- public static final String NAVER_TV_PARSING_FAILED = "네이버TV URL 파싱 실패: videoId 또는 inKey를 찾을 수 없습니다.";
- public static final String NAVER_TV_API_PARSING_FAILED = "네이버TV API 응답 파싱 실패: 비디오 URL을 찾을 수 없습니다.";
- public static final String NAVER_TV_RESTRICTION = "네이버TV 제한사항: %s";
- public static final String NAVER_TV_ANALYSIS_ERROR = "네이버TV 분석 중 오류 발생: %s";
-
- private Messages() {
- // 인스턴스화 방지
- }
-
- /**
- * 포맷 문자열을 사용하는 메시지를 생성합니다.
- *
- * @param format 포맷 문자열
- * @param args 포맷 인자
- * @return 포맷팅된 메시지
- */
- public static String format(String format, Object... args) {
- return String.format(format, args);
- }
-}
diff --git a/backup/src/main/java/com/caliverse/video/global/util/HttpUtils.java b/backup/src/main/java/com/caliverse/video/global/util/HttpUtils.java
deleted file mode 100644
index 0be0b2b..0000000
--- a/backup/src/main/java/com/caliverse/video/global/util/HttpUtils.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package com.caliverse.video.global.util;
-
-import org.apache.http.Header;
-import org.apache.http.HttpHeaders;
-import org.apache.http.client.config.RequestConfig;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpHead;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-
-/**
- * HTTP 요청 관련 유틸리티 클래스입니다.
- */
-public class HttpUtils {
-
- private static final Logger logger = LoggerFactory.getLogger(HttpUtils.class);
- private static final int CONNECTION_TIMEOUT = 5000; // 5초
-
- /**
- * URL의 HTTP 헤더에서 보안 관련 헤더가 있는지 확인합니다.
- *
- * @param url 확인할 URL
- * @return 보안 헤더가 있으면 true, 없으면 false
- * @throws IOException HTTP 요청 실패 시
- */
- public boolean hasSecurityHeaders(String url) throws IOException {
- HttpHead request = new HttpHead(url);
- RequestConfig config = RequestConfig.custom()
- .setConnectTimeout(CONNECTION_TIMEOUT)
- .setSocketTimeout(CONNECTION_TIMEOUT)
- .build();
- request.setConfig(config);
-
- try (CloseableHttpClient httpClient = HttpClients.createDefault();
- CloseableHttpResponse response = httpClient.execute(request)) {
-
- // CORS 헤더 확인
- if (response.containsHeader("Access-Control-Allow-Origin")) {
- return true;
- }
-
- // 인증 관련 헤더 확인
- if (response.containsHeader("WWW-Authenticate") ||
- response.containsHeader("Authorization")) {
- return true;
- }
-
- // DRM 관련 헤더 확인
- if (response.containsHeader("X-DRM-Type") ||
- response.containsHeader("X-Content-Protection")) {
- return true;
- }
-
- // 기타 보안 헤더 확인
- for (Header header : response.getAllHeaders()) {
- String headerName = header.getName().toLowerCase();
- String headerValue = header.getValue().toLowerCase();
-
- if (headerName.contains("token") ||
- headerName.contains("auth") ||
- headerName.contains("drm") ||
- headerValue.contains("token") ||
- headerValue.contains("protection")) {
- return true;
- }
- }
-
- return false;
- }
- }
-
- /**
- * URL의 HTTP 헤더에서 3D 비디오 관련 정보가 있는지 확인합니다.
- *
- * @param url 확인할 URL
- * @return 3D 비디오 헤더가 있으면 true, 없으면 false
- * @throws IOException HTTP 요청 실패 시
- */
- public boolean checkFor3DHeaders(String url) throws IOException {
- HttpHead request = new HttpHead(url);
- // User-Agent 헤더 추가 (일반적인 브라우저로 위장)
- request.setHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36");
-
- // 기타 일반적인 브라우저 헤더들 추가
- request.setHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/json");
- request.setHeader("Accept-Language", "ko-KR,ko;q=0.9,en;q=0.8");
- request.setHeader("Accept-Encoding", "gzip, deflate, br");
- request.setHeader("Connection", "keep-alive");
- request.setHeader("Upgrade-Insecure-Requests", "1");
- request.setHeader("Referer", "https://tv.naver.com");
-
- RequestConfig config = RequestConfig.custom()
- .setConnectTimeout(CONNECTION_TIMEOUT)
- .setSocketTimeout(CONNECTION_TIMEOUT)
- .build();
- request.setConfig(config);
-
- try (CloseableHttpClient httpClient = HttpClients.createDefault();
- CloseableHttpResponse response = httpClient.execute(request)) {
-
- int statusCode = response.getStatusLine().getStatusCode();
-
- if (statusCode >= 400) {
- logger.warn("HTTP Error {} for URL: {}", statusCode, url);
- return false; // 에러 발생 시 보안 헤더 없다고 간주
- }
-
- // 3D 관련 커스텀 헤더 확인
- if (response.containsHeader("X-Video-Type")) {
- String videoType = response.getFirstHeader("X-Video-Type").getValue();
- if (videoType.toLowerCase().contains("3d") ||
- videoType.toLowerCase().contains("stereoscopic")) {
- return true;
- }
- }
-
- // 기타 헤더에서 3D 관련 키워드 확인
- for (Header header : response.getAllHeaders()) {
- String headerValue = header.getValue().toLowerCase();
- if (headerValue.contains("3d") ||
- headerValue.contains("stereoscopic")) {
- return true;
- }
- }
-
- // 콘텐츠 타입 헤더 확인
- if (response.containsHeader(HttpHeaders.CONTENT_TYPE)) {
- String contentType = response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();
- return contentType.contains("3d") || contentType.contains("stereoscopic");
- }
-
- return false;
- }
- }
-
- /**
- * URL의 HTTP 헤더에서 Content-Type을 가져옵니다.
- *
- * @param url 확인할 URL
- * @return Content-Type 문자열, 헤더가 없거나 요청 실패 시 null
- * @throws IOException HTTP 요청 실패 시
- */
- public String getContentType(String url) throws IOException {
- HttpHead request = new HttpHead(url);
- RequestConfig config = RequestConfig.custom()
- .setConnectTimeout(CONNECTION_TIMEOUT)
- .setSocketTimeout(CONNECTION_TIMEOUT)
- .build();
- request.setConfig(config);
-
- try (CloseableHttpClient httpClient = HttpClients.createDefault();
- CloseableHttpResponse response = httpClient.execute(request)) {
-
- if (response.containsHeader(HttpHeaders.CONTENT_TYPE)) {
- return response.getFirstHeader(HttpHeaders.CONTENT_TYPE).getValue();
- }
-
- return null;
- }
- }
-}
diff --git a/backup/src/main/java/com/caliverse/video/global/util/OEmbedFetcher.java b/backup/src/main/java/com/caliverse/video/global/util/OEmbedFetcher.java
deleted file mode 100644
index c50e75b..0000000
--- a/backup/src/main/java/com/caliverse/video/global/util/OEmbedFetcher.java
+++ /dev/null
@@ -1,575 +0,0 @@
-package com.caliverse.video.global.util;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.http.client.methods.CloseableHttpResponse;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.impl.client.CloseableHttpClient;
-import org.apache.http.impl.client.HttpClients;
-import org.apache.http.util.EntityUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.io.IOException;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * oEmbed API를 활용하여 영상 메타데이터를 가져오는 유틸리티 클래스입니다.
- *
- *