# 🀝 κΈ°μ—¬ κ°€μ΄λ“œ Video URL Analyzer ν”„λ‘œμ νŠΈμ— κΈ°μ—¬ν•΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€! ## πŸš€ μ‹œμž‘ν•˜κΈ° ### 1. Repository Fork & Clone ```bash git clone https://github.com/your-username/video-url-analyzer.git cd video-url-analyzer ``` ### 2. 개발 ν™˜κ²½ μ„€μ • #### μš”κ΅¬μ‚¬ν•­ - **Java**: 17 이상 - **Gradle**: 7.0 이상 #### μ˜μ‘΄μ„± μ„€μΉ˜ ```bash ./gradlew build ``` ## πŸ”§ λΉŒλ“œ 및 ν…ŒμŠ€νŠΈ ### λΉŒλ“œ ```bash ./gradlew clean build ``` ### ν…ŒμŠ€νŠΈ μ‹€ν–‰ ```bash ./gradlew test ``` ### Fat JAR 생성 (μ˜μ‘΄μ„± 포함) ```bash ./gradlew fatJar ``` ### ν…ŒμŠ€νŠΈ 컀버리지 확인 ```bash ./gradlew test jacocoTestReport # κ²°κ³Ό: build/reports/jacoco/test/html/index.html ``` ## πŸ—οΈ ν”„λ‘œμ νŠΈ ꡬ쑰 ``` video-url-analyzer/ β”œβ”€β”€ src/ β”‚ β”œβ”€β”€ main/java/com/caliverse/ β”‚ β”‚ β”œβ”€β”€ analyzer/ β”‚ β”‚ β”‚ β”œβ”€β”€ VideoAnalyzer.java # 메인 클래슀 β”‚ β”‚ β”‚ β”œβ”€β”€ entity/ β”‚ β”‚ β”‚ β”‚ └── UrlType.java # URL νƒ€μž… Enum β”‚ β”‚ β”‚ β”œβ”€β”€ model/ β”‚ β”‚ β”‚ β”‚ └── VideoAnalysisResult.java # κ²°κ³Ό λͺ¨λΈ β”‚ β”‚ β”‚ β”œβ”€β”€ platform/ # ν”Œλž«νΌλ³„ 뢄석기 β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ PlatformAnalyzer.java β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ PlatformAnalyzerFactory.java β”‚ β”‚ β”‚ β”‚ └── impl/ β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ HttpBasedPlatformAnalyzer.java # HTTP 기반 좔상 클래슀 β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ NaverTvPlatformAnalyzer.java β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ KakaoTvPlatformAnalyzer.java β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ YouTubePlatformAnalyzer.java β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ VimeoPlatformAnalyzer.java β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ TikTokPlatformAnalyzer.java β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ TwitterPlatformAnalyzer.java β”‚ β”‚ β”‚ β”‚ β”œβ”€β”€ WeiboPlatformAnalyzer.java β”‚ β”‚ β”‚ β”‚ └── DirectUrlPlatformAnalyzer.java β”‚ β”‚ β”‚ └── service/ β”‚ β”‚ β”‚ └── VideoAnalyzerService.java # λΉ„λ””μ˜€ 뢄석 둜직 β”‚ β”‚ └── global/ β”‚ β”‚ β”œβ”€β”€ common/ β”‚ β”‚ β”‚ β”œβ”€β”€ CommonConstants.java # μƒμˆ˜ (도메인, API URL, μ •κ·œμ‹) β”‚ β”‚ β”‚ └── Messages.java # λ©”μ‹œμ§€ 관리 β”‚ β”‚ └── util/ # μœ ν‹Έλ¦¬ν‹° β”‚ β”‚ β”œβ”€β”€ HttpUtils.java # HTTP μš”μ²­ μœ ν‹Έ β”‚ β”‚ └── UrlParser.java # URL νŒŒμ‹± μœ ν‹Έ β”‚ └── test/java/com/caliverse/analyzer/ # ν…ŒμŠ€νŠΈ μ½”λ“œ β”‚ └── VideoAnalyzerTest.java β”œβ”€β”€ build.gradle # λΉŒλ“œ μ„€μ • β”œβ”€β”€ README.md # μ‚¬μš©μž λ¬Έμ„œ └── CONTRIBUTING.md # 이 파일 ``` ### μ£Όμš” μ»΄ν¬λ„ŒνŠΈ #### 1. VideoAnalyzer (메인 API) - λΉ„λ””μ˜€ URL λΆ„μ„μ˜ μ§„μž…μ  - ν”Œλž«νΌλ³„ 뢄석기 λ˜λŠ” 일반 λΉ„λ””μ˜€ 뢄석 선택 #### 2. PlatformAnalyzer μΈν„°νŽ˜μ΄μŠ€ - ν”Œλž«νΌλ³„ νŠΉν™” 둜직 κ΅¬ν˜„μ„ μœ„ν•œ μΈν„°νŽ˜μ΄μŠ€ - μƒˆλ‘œμš΄ ν”Œλž«νΌ μΆ”κ°€ μ‹œ 이 μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ±°λ‚˜ HttpBasedPlatformAnalyzer 상속 #### 3. HttpBasedPlatformAnalyzer (좔상 클래슀) - HTTP μš”μ²­ 기반 ν”Œλž«νΌ λΆ„μ„κΈ°μ˜ 곡톡 둜직 - oEmbed API 호좜 및 응닡 처리 - 각 ν”Œλž«νΌμ€ `parseResponse()` λ©”μ„œλ“œλ§Œ κ΅¬ν˜„ #### 4. VideoAnalyzerService - 일반 λΉ„λ””μ˜€ URL 뢄석 둜직 - 보호 검사, 3D 검사, AVPlayer μž¬μƒ κ°€λŠ₯ μ—¬λΆ€ 검사 #### 5. CommonConstants & Messages - `CommonConstants`: 도메인, API URL, μ •κ·œμ‹ νŒ¨ν„΄ λ“± - `Messages`: λͺ¨λ“  λ©”μ‹œμ§€ λ¬Έμžμ—΄ (κ΅­μ œν™” μ€€λΉ„) ## πŸ§ͺ ν…ŒμŠ€νŠΈ 예제 ### κΈ°λ³Έ ν…ŒμŠ€νŠΈ ꡬ쑰 ```java import org.junit.Test; import static org.junit.Assert.*; public class VideoAnalyzerTest { @Test public void testNaverTvUrl() { VideoAnalyzer analyzer = new VideoAnalyzer(); VideoAnalysisResult result = analyzer.analyzeUrl("https://tv.naver.com/v/84373511"); assertNotNull(result); assertNotNull(result.isSuccess()); assertNotNull(result.getReason()); } @Test public void testDrmProtectedUrl() { VideoAnalyzer analyzer = new VideoAnalyzer(); String drmUrl = "https://example.com/video.m3u8?drm=widevine"; VideoAnalysisResult result = analyzer.analyzeUrl(drmUrl); assertFalse(result.isSuccess()); assertTrue(result.getReason().contains("DRM")); } } ``` ## 🌟 μƒˆλ‘œμš΄ ν”Œλž«νΌ μΆ”κ°€ν•˜κΈ° ### 방법 1: HttpBasedPlatformAnalyzer 상속 (ꢌμž₯ - oEmbed 지원 ν”Œλž«νΌ) ```java public class InstagramPlatformAnalyzer extends HttpBasedPlatformAnalyzer { private static final String PLATFORM_NAME = "Instagram"; private static final String PLATFORM_DOMAIN = "instagram.com"; private static final String API_ENDPOINT = "https://api.instagram.com/oembed"; private final ObjectMapper objectMapper; public InstagramPlatformAnalyzer() { this.objectMapper = new ObjectMapper(); } @Override protected String getApiEndpoint() { return API_ENDPOINT; } @Override protected String getPlatformDomain() { return PLATFORM_DOMAIN; } @Override public String getPlatformName() { return PLATFORM_NAME; } @Override public boolean canHandle(String url) { if (url == null) return false; return url.toLowerCase().contains(PLATFORM_DOMAIN); } @Override protected VideoAnalysisResult parseResponse(String response, String originalUrl) throws IOException { try { JsonNode rootNode = objectMapper.readTree(response); // ν•„μˆ˜ ν•„λ“œ μΆ”μΆœ String type = getStringValue(rootNode, "type"); String title = getStringValue(rootNode, "title"); if (type == null || title == null) { return VideoAnalysisResult.failure("Instagram oEmbed 응닡이 μœ νš¨ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€"); } logger.debug("Instagram 검증 성곡: {}", title); return VideoAnalysisResult.success(); } catch (Exception e) { throw new IOException("Instagram 응닡 νŒŒμ‹± μ‹€νŒ¨: " + e.getMessage(), e); } } } ``` ### 방법 2: PlatformAnalyzer 직접 κ΅¬ν˜„ (HTML νŒŒμ‹±μ΄ ν•„μš”ν•œ 경우) ```java public class CustomPlatformAnalyzer implements PlatformAnalyzer { @Override public String getPlatformName() { return "CustomPlatform"; } @Override public boolean canHandle(String url) { return url != null && url.contains("custom.com"); } @Override public VideoAnalysisResult analyze(String url) { try { // μ»€μŠ€ν…€ 뢄석 둜직 (HTML νŒŒμ‹± λ“±) String htmlContent = fetchHtmlContent(url); String videoUrl = extractVideoUrl(htmlContent); if (videoUrl != null) { return VideoAnalysisResult.success(); } else { return VideoAnalysisResult.failure("λΉ„λ””μ˜€ URL을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€"); } } catch (Exception e) { return VideoAnalysisResult.failure("뢄석 μ‹€νŒ¨: " + e.getMessage()); } } private String fetchHtmlContent(String url) throws IOException { // HTTP μš”μ²­ κ΅¬ν˜„ } private String extractVideoUrl(String html) { // HTML νŒŒμ‹± κ΅¬ν˜„ } } ``` ### 3. Factory에 등둝 ```java public class PlatformAnalyzerFactory { public PlatformAnalyzerFactory() { this.analyzers = new ArrayList<>(); // HTTP 기반 ν”Œλž«νΌ 뢄석기듀을 등둝 analyzers.add(new NaverTvPlatformAnalyzer()); analyzers.add(new KakaoTvPlatformAnalyzer()); analyzers.add(new YouTubePlatformAnalyzer()); analyzers.add(new VimeoPlatformAnalyzer()); analyzers.add(new TikTokPlatformAnalyzer()); analyzers.add(new TwitterPlatformAnalyzer()); analyzers.add(new WeiboPlatformAnalyzer()); // μƒˆλ‘œμš΄ 뢄석기 μΆ”κ°€ analyzers.add(new InstagramPlatformAnalyzer()); // 직접 URL λΆ„μ„κΈ°λŠ” κ°€μž₯ λ§ˆμ§€λ§‰μ— 등둝 (fallback) analyzers.add(new DirectUrlPlatformAnalyzer()); } } ``` ### 4. ν…ŒμŠ€νŠΈ μž‘μ„± ```java @Test public void testInstagramUrl() { VideoAnalyzer analyzer = new VideoAnalyzer(); VideoAnalysisResult result = analyzer.analyzeUrl("https://www.instagram.com/p/ABC123/"); assertNotNull("κ²°κ³Όκ°€ null이 μ•„λ‹ˆμ–΄μ•Ό ν•©λ‹ˆλ‹€", result); assertTrue("Instagram URL은 μž¬μƒ κ°€λŠ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€", result.isSuccess()); System.out.println("Instagram: " + result); } ``` ### 5. μ‹€μ œ 예제: 웨이보 ν”Œλž«νΌ μ›¨μ΄λ³΄λŠ” oEmbedλ₯Ό μ§€μ›ν•˜μ§€ μ•Šμ•„ HTML νŒŒμ‹± λ°©μ‹μœΌλ‘œ κ΅¬ν˜„λ˜μ—ˆμŠ΅λ‹ˆλ‹€. μžμ„Έν•œ κ΅¬ν˜„μ€ `WeiboPlatformAnalyzer.java`λ₯Ό μ°Έκ³ ν•˜μ„Έμš”. μ£Όμš” νŠΉμ§•: - HTMLμ—μ„œ `og:video`, `og:title`, `og:image` λ©”νƒ€νƒœκ·Έ μΆ”μΆœ - `$render_data` μŠ€ν¬λ¦½νŠΈμ—μ„œ JSON 데이터 νŒŒμ‹± - μ—¬λŸ¬ URL νŒ¨ν„΄ 지원 (tv/show, video/show, m.weibo.cn) --- λ‹€μ‹œ ν•œλ²ˆ κΈ°μ—¬ν•΄μ£Όμ…”μ„œ κ°μ‚¬λ“œλ¦½λ‹ˆλ‹€! πŸŽ‰