ytcollector 1.1.7__py3-none-any.whl → 1.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ytcollector/__init__.py CHANGED
@@ -22,7 +22,7 @@ from .analyzer import VideoAnalyzer, check_dependencies
22
22
  from .downloader import YouTubeDownloader
23
23
  from .cli import run, main as cli_main
24
24
 
25
- __version__ = "1.1.7"
25
+ __version__ = "1.1.8"
26
26
  __all__ = [
27
27
  # 주요 클래스
28
28
  "VideoAnalyzer",
ytcollector/analyzer.py CHANGED
@@ -21,7 +21,7 @@ except ImportError:
21
21
  EASYOCR_AVAILABLE = False
22
22
 
23
23
  try:
24
- from ultralytics import YOLOWorld
24
+ from ultralytics import YOLO
25
25
  YOLO_AVAILABLE = True
26
26
  except ImportError:
27
27
  YOLO_AVAILABLE = False
@@ -32,7 +32,7 @@ try:
32
32
  except ImportError:
33
33
  USE_GPU = False
34
34
 
35
- from .config import LICENSE_PLATE_PATTERNS, YOLO_MODEL_NAME, YOLO_CONFIDENCE, YOLO_PROMPTS
35
+ from .config import LICENSE_PLATE_PATTERNS, YOLO_MODEL_NAME, YOLO_CONFIDENCE
36
36
 
37
37
 
38
38
  class VideoAnalyzer:
@@ -59,16 +59,14 @@ class VideoAnalyzer:
59
59
  self.ocr_reader = easyocr.Reader(['ko', 'en'], gpu=USE_GPU, verbose=False)
60
60
 
61
61
  def _init_yolo(self):
62
- """YOLO-World 모델 초기화 (필요할 때만, 스레드 안전, GPU 가속 체크)"""
62
+ """YOLO 모델 초기화 (필요할 때만, 스레드 안전, GPU 가속 체크)"""
63
63
  if YOLO_AVAILABLE and self.yolo_model is None:
64
64
  with self._ocr_lock:
65
65
  if self.yolo_model is None:
66
66
  device = "cuda" if USE_GPU else "cpu"
67
- print(f" YOLO-World 모델 로딩 중... (Device: {device})")
68
- self.yolo_model = YOLOWorld(YOLO_MODEL_NAME)
67
+ print(f" YOLO 모델 로딩 중... (Device: {device})")
68
+ self.yolo_model = YOLO(YOLO_MODEL_NAME)
69
69
  self.yolo_model.to(device)
70
- # 감지할 클래스(프롬프트) 설정
71
- self.yolo_model.set_classes(YOLO_PROMPTS)
72
70
 
73
71
  def extract_frames(self, video_path, num_frames=10):
74
72
  """영상에서 균등 간격으로 프레임 추출"""
@@ -106,7 +104,7 @@ class VideoAnalyzer:
106
104
  gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30)
107
105
  )
108
106
 
109
- def detect_text(self, frame):
107
+ def detect_text(self, frame, return_positions=False):
110
108
  """EasyOCR로 텍스트 감지 (스레드 안전)"""
111
109
  if not EASYOCR_AVAILABLE:
112
110
  return []
@@ -114,7 +112,8 @@ class VideoAnalyzer:
114
112
  self._init_ocr()
115
113
  try:
116
114
  h, w = frame.shape[:2]
117
-
115
+ scale = 1.0
116
+
118
117
  # 가독성 개선을 위해 1080p 수준으로 리사이즈 (너무 작으면 인식률 저하)
119
118
  if w > 1280:
120
119
  scale = 1280 / w
@@ -124,22 +123,48 @@ class VideoAnalyzer:
124
123
  scale = 960 / w
125
124
  frame = cv2.resize(frame, (960, int(h * scale)), interpolation=cv2.INTER_CUBIC)
126
125
 
126
+ resized_h, resized_w = frame.shape[:2]
127
+
127
128
  # 전처리: 그레이스케일 및 대비 강화 (옵션)
128
129
  gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
129
130
  clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
130
131
  processed = clahe.apply(gray)
131
132
 
132
133
  with self._ocr_lock:
133
- # 원본(컬러)과 전처리(그레이) 중 선택 가능하나 보통 EasyOCR은 컬러에서 잘 작동함
134
- # 대비 강화된 그레이스케일을 사용해봄
135
134
  results = self.ocr_reader.readtext(processed)
136
-
135
+
136
+ if return_positions:
137
+ # 위치 정보와 함께 반환 (bbox, text, conf, y_ratio)
138
+ texts_with_pos = []
139
+ for r in results:
140
+ if r[2] > 0.25:
141
+ bbox = r[0]
142
+ # bbox의 y 중심점 계산 (0~1 비율)
143
+ y_center = (bbox[0][1] + bbox[2][1]) / 2
144
+ y_ratio = y_center / resized_h
145
+ texts_with_pos.append({
146
+ 'text': r[1],
147
+ 'conf': r[2],
148
+ 'y_ratio': y_ratio
149
+ })
150
+ return texts_with_pos
151
+
137
152
  # 신뢰도 임계값 0.25로 약간 하향 조정 (기존 0.3)
138
153
  return [r[1] for r in results if r[2] > 0.25]
139
154
  except Exception as e:
140
155
  print(f" ⚠ OCR 에러: {e}")
141
156
  return []
142
157
 
158
+ def detect_subtitle(self, frame):
159
+ """자막 감지 - 화면 하단 60%~95% 영역의 텍스트만 필터링"""
160
+ texts_with_pos = self.detect_text(frame, return_positions=True)
161
+ if not texts_with_pos:
162
+ return []
163
+
164
+ # 자막은 주로 화면 하단에 위치 (y_ratio 0.6 ~ 0.95)
165
+ subtitles = [t['text'] for t in texts_with_pos if 0.6 <= t['y_ratio'] <= 0.95]
166
+ return subtitles
167
+
143
168
  def detect_license_plate(self, frame, texts=None):
144
169
  """
145
170
  ROI 기반 번호판 감지 (최적화 버전)
@@ -153,19 +178,26 @@ class VideoAnalyzer:
153
178
  self._init_yolo()
154
179
  results = self.yolo_model(frame, verbose=False, conf=YOLO_CONFIDENCE)
155
180
 
156
- yolo_high_conf = False
181
+ yolo_detected = False
157
182
  roi_ocr_matched = False
158
-
183
+
159
184
  for r in results:
160
185
  # 0: license plate
161
186
  for box in r.boxes:
162
187
  if box.cls == 0:
163
- conf = float(box.conf)
164
- if conf > 0.8:
165
- yolo_high_conf = True
166
-
167
- # ROI 크로핑 및 타겟 OCR
188
+ yolo_detected = True
189
+
190
+ # ROI 크기 필터링 (너무 작거나 큰 영역 제외)
168
191
  x1, y1, x2, y2 = map(int, box.xyxy[0])
192
+ roi_w, roi_h = x2 - x1, y2 - y1
193
+ frame_h, frame_w = frame.shape[:2]
194
+ roi_area_ratio = (roi_w * roi_h) / (frame_w * frame_h)
195
+
196
+ # ROI가 전체 화면의 0.1% ~ 20% 사이여야 함
197
+ if roi_area_ratio < 0.001 or roi_area_ratio > 0.2:
198
+ continue
199
+
200
+ # ROI 크로핑 및 타겟 OCR
169
201
  h, w = frame.shape[:2]
170
202
  # 패딩 10% 추가
171
203
  pad_w = int((x2 - x1) * 0.1)
@@ -192,8 +224,8 @@ class VideoAnalyzer:
192
224
  if roi_ocr_matched: break
193
225
  if roi_ocr_matched: break
194
226
 
195
- # 최종 판정
196
- if roi_ocr_matched or yolo_high_conf:
227
+ # 최종 판정: YOLO 감지 + OCR 패턴 매칭 둘 다 만족해야 함 (오탐지 방지)
228
+ if yolo_detected and roi_ocr_matched:
197
229
  return True
198
230
 
199
231
  except Exception as e:
@@ -305,14 +337,19 @@ class VideoAnalyzer:
305
337
  results['license_plate'] = True
306
338
  detected_now = True
307
339
 
308
- # 텍스트 감지 (일반 텍스트 카테고리이거나 번호판 수집 중에도 텍스트 로그 기록을 위해 실행)
309
- # 번호판 감지가 필요 없는 경우 전체 OCR을 건너뛰어 속도 향상 가능
310
- if target_category == 'text' or (detected_now and target_category != 'license_plate'):
311
- texts = self.detect_text(frame)
340
+ # 텍스트/자막 감지
341
+ if target_category == 'text':
342
+ # 자막 영역 필터링 (화면 하단 60%~95%)
343
+ texts = self.detect_subtitle(frame)
312
344
  if texts:
313
345
  results['text'] = True
314
346
  all_texts.extend(texts)
315
347
  detected_now = True
348
+ elif detected_now and target_category != 'license_plate':
349
+ texts = self.detect_text(frame)
350
+ if texts:
351
+ results['text'] = True
352
+ all_texts.extend(texts)
316
353
  elif target_category == 'license_plate' and not results['license_plate']:
317
354
  # 번호판을 못 찾은 경우에만 전체 화면 OCR 한 번 더 시도 (보수적 접근)
318
355
  texts = self.detect_text(frame)
ytcollector/config.py CHANGED
@@ -11,21 +11,21 @@ USER_AGENTS = [
11
11
  CATEGORY_QUERIES = {
12
12
  'face': [
13
13
  "SBS 인터뷰 클립",
14
- "런닝맨 멤버 인터뷰",
15
- "SBS 뉴스 인터뷰",
16
- "미운우리새끼 인터뷰",
17
- "SBS 스페셜 인물",
18
- "집사부일체 인터뷰",
19
- "그것이알고싶다 인터뷰",
20
- "SBS 연예대상 소감",
14
+ "MBC 뉴스 인터뷰",
15
+ "tvN 유퀴즈 일반인 인터뷰",
16
+ "JTBC 뉴스룸 초대석",
17
+ "기자회견 현장",
18
+ "연예대상 레드카펫 직캠",
19
+ "길거리 인터뷰 브이로그",
20
+ "무대인사 직캠 얼굴",
21
21
  ],
22
22
  'license_plate': [
23
- "중고차 매물 소개",
24
- "자동차 세차 영상",
25
- "신차 출고 브이로그",
26
- "자동차 튜닝 작업",
27
- "엔카 허위매물",
28
- "주차장 만차",
23
+ "SBS 드라마 자동차 추격 장면",
24
+ "SBS 드라마 주차장 씬",
25
+ "SBS 드라마 차량 씬",
26
+ "SBS 드라마 운전 장면",
27
+ "SBS 드라마 택시 장면",
28
+ "SBS 드라마 교통사고 장면",
29
29
  ],
30
30
  'tattoo': [
31
31
  "타투 시술 영상",
@@ -36,11 +36,14 @@ CATEGORY_QUERIES = {
36
36
  "tattoo session",
37
37
  ],
38
38
  'text': [
39
- "SBS 런닝맨 레전드",
40
- "SBS 예능 쇼츠",
41
- "재미있는 자막 영상 쇼츠",
42
- "SBS 파워FM 보이는 라디오",
43
- "SBS 연예대상 소감",
39
+ "SBS 런닝맨 자막 레전드",
40
+ "SBS 미우새 자막 모음",
41
+ "tvN 놀라운토요일 자막",
42
+ "JTBC 아는형님 자막 레전드",
43
+ "MBC 나혼자산다 자막",
44
+ "유튜버 예능 자막 편집",
45
+ "브이로그 자막 효과",
46
+ "먹방 자막 편집",
44
47
  ],
45
48
  }
46
49
 
@@ -69,9 +72,8 @@ BLACKLIST_KEYWORDS = {
69
72
  }
70
73
 
71
74
  # YOLO 설정
72
- YOLO_MODEL_NAME = 'yolov8s-world.pt' # YOLO-World 모델 (Open Vocabulary)
73
- YOLO_CONFIDENCE = 0.3 # YOLO-World는 임계값을 약간 낮게 설정 가능
74
- YOLO_PROMPTS = ["license plate"]
75
+ YOLO_MODEL_NAME = 'license_plate_detector.pt' # 번호판 전용 학습 모델
76
+ YOLO_CONFIDENCE = 0.5 # 오탐지 방지를 위해 신뢰도 상향
75
77
 
76
78
  # 번호판 정규식 패턴 (한국 자동차 번호판 중심)
77
79
  LICENSE_PLATE_PATTERNS = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ytcollector
3
- Version: 1.1.7
3
+ Version: 1.1.9
4
4
  Summary: YouTube 콘텐츠 수집기 - 얼굴, 번호판, 타투, 텍스트 감지
5
5
  Author: YTCollector Team
6
6
  License: MIT
@@ -44,14 +44,12 @@ Requires-Dist: ytcollector[analysis,dev]; extra == "all"
44
44
  pip install yt-dlp
45
45
  ```
46
46
 
47
- ### 분석 기능용 패키지 (권장 - v1.1.6+)
47
+ ### 분석 기능용 패키지 (권장 - v1.1.9+)
48
48
 
49
49
  분석 기능을 원활하게 사용하려면 아래 패키지들이 필요합니다. GPU(CUDA)가 설치된 경우 자동으로 가속이 활성화됩니다.
50
50
 
51
51
  ```bash
52
52
  pip install opencv-python easyocr numpy ultralytics
53
- # YOLO-World 기능을 사용하려면 아래 CLIP 라이브러리 수동 설치가 필요합니다.
54
- pip install "git+https://github.com/ultralytics/CLIP.git"
55
53
  ```
56
54
 
57
55
  ## 사용법
@@ -80,10 +78,10 @@ ytcollector
80
78
 
81
79
  | 카테고리 | 설명 | 검색 소스 |
82
80
  |----------|------|-----------|
83
- | `face` | 얼굴/인물 | SBS 인터뷰, 런닝맨, 미운우리새끼 등 |
84
- | `license_plate` | 자동차 번호판 | 중고차 매물, 세차 영상, 신차 출고 등 |
81
+ | `face` | 얼굴/인물 | SBS/MBC/tvN/JTBC 인터뷰, 기자회견 등 |
82
+ | `license_plate` | 자동차 번호판 | SBS 드라마 자동차/추격/주차장 등 |
85
83
  | `tattoo` | 타투/문신 | 타투 시술, 타투이스트 작업 영상 |
86
- | `text` | 텍스트/자막 | SBS 예능 (런닝맨, 골목식당) |
84
+ | `text` | 텍스트/자막 | SBS/tvN/JTBC/MBC 예능, 유튜버 자막 편집 등 |
87
85
 
88
86
  ## 예시
89
87
 
@@ -190,14 +188,16 @@ https://www.youtube.com/watch?v=aqz-KE-bpKQ, 00:10, sample_task
190
188
  | 감지 항목 | 사용 기술 | 설명 |
191
189
  |-----------|-----------|------|
192
190
  | 얼굴 | OpenCV Haar Cascade | 정면 얼굴 감지 |
193
- | 텍스트 | EasyOCR | 한국어/영어 문자 인식 (분석 품질 및 프레임 수 개선) |
194
- | 번호판 | YOLO-World + ROI OCR | v1.1.6: YOLO로 감지 해당 영역만 OCR (속도 2x, 정확도 향상) |
191
+ | 텍스트 | EasyOCR | 한국어/영어 문자 인식 |
192
+ | 번호판 | YOLOv8 전용 모델 + OCR | v1.1.9: 번호판 전용 학습 모델 + 한국 번호판 패턴 매칭 |
195
193
  | 타투 | OpenCV HSV 분석 | 피부 영역 내 잉크 패턴 감지 |
196
194
 
197
- ### 주요 최적화 (v1.1.5~1.1.6)
198
- - **ROI 기반 감지**: 전체 화면이 아닌 YOLO가 지정한 영역만 OCR하여 속도와 정확도 대폭 향상
195
+ ### 주요 최적화 (v1.1.5~1.1.9)
196
+ - **번호판 전용 모델** (v1.1.9): YOLO-World 번호판 전용 학습 모델로 교체 (감지율 대폭 향상, 모델 크기 27MB→6MB)
197
+ - **오탐지 방지** (v1.1.9): YOLO 감지 + OCR 패턴 매칭 둘 다 만족해야 번호판 판정, ROI 크기 필터링 추가
198
+ - **ROI 기반 감지**: 전체 화면이 아닌 YOLO가 지정한 영역만 OCR하여 속도와 정확도 향상
199
199
  - **GPU 가속 지원**: CUDA 사용 가능 시 YOLO 및 OCR 자동 가속
200
- - **로그 기반 중복 방지**: 로컬 파일이 없어도 `youtube_url_*.txt` 기록을 참조하여 중복 분석 방지
200
+ - **로그 기반 중복 방지**: `youtube_url_*.txt` 기록을 참조하여 중복 분석 방지
201
201
 
202
202
  ## 주의사항
203
203
 
@@ -0,0 +1,12 @@
1
+ ytcollector/__init__.py,sha256=EaxBP_0Fv0LEFdg067uZxBrQHwOKGX8u08Y4b5uF1-Q,1094
2
+ ytcollector/analyzer.py,sha256=7VJt4chc25HsEz8OwBDZhTz_8LnkpgSBM6mJKQpIUls,14391
3
+ ytcollector/cli.py,sha256=aHF4EuQRPLKh65lnkI_dZ0ResztlVjpHlS5iHfzmpig,5577
4
+ ytcollector/config.py,sha256=HmcFqMV1Z3kClnGKYi0q9cRZIXMRPAxIEI8cggatfrU,3199
5
+ ytcollector/dataset_builder.py,sha256=nfArEwszoCln48n3T0Eff_4OOaYv8FF0YH8cARBGMWQ,2608
6
+ ytcollector/downloader.py,sha256=TeC6agUmSPHZSZ9jdoc42i8i_NobzTEkoRtAIgW80kI,14544
7
+ ytcollector/utils.py,sha256=6XDif-e3GbMHmUvTsBT0YblxNxYnS-2I8HnmjMBZs-M,4254
8
+ ytcollector-1.1.9.dist-info/METADATA,sha256=WQIup61B02yB9ddtOaFGtlDYoV4nJVRD-vvVI_sVjc0,7208
9
+ ytcollector-1.1.9.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
+ ytcollector-1.1.9.dist-info/entry_points.txt,sha256=waiVuSJJYt-6_DAal-T4JkHgejo7wKYLdKrEI7tZ-ms,127
11
+ ytcollector-1.1.9.dist-info/top_level.txt,sha256=wozNyCUm0eMOm-9U81yTql6oGaM2O5rWVBXDb93zzyQ,12
12
+ ytcollector-1.1.9.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- ytcollector/__init__.py,sha256=uXLmOZ3da_7GwVej0hDAuiUJsw5XweBBUrUs4sJo7J4,1094
2
- ytcollector/analyzer.py,sha256=raw7tcERbNG8cDTnKDr95VA3Nju9We4jW7XGheUUPWE,13097
3
- ytcollector/cli.py,sha256=aHF4EuQRPLKh65lnkI_dZ0ResztlVjpHlS5iHfzmpig,5577
4
- ytcollector/config.py,sha256=ZjyDWQg4haJPwUlP-eW0hXa_I2g9wyNaI8y5mxEU0vc,3040
5
- ytcollector/dataset_builder.py,sha256=nfArEwszoCln48n3T0Eff_4OOaYv8FF0YH8cARBGMWQ,2608
6
- ytcollector/downloader.py,sha256=TeC6agUmSPHZSZ9jdoc42i8i_NobzTEkoRtAIgW80kI,14544
7
- ytcollector/utils.py,sha256=6XDif-e3GbMHmUvTsBT0YblxNxYnS-2I8HnmjMBZs-M,4254
8
- ytcollector-1.1.7.dist-info/METADATA,sha256=Qyop1AVyK-BmWgrjsGUMiYbE55kmOZSwMKYWEADQzRk,7156
9
- ytcollector-1.1.7.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
10
- ytcollector-1.1.7.dist-info/entry_points.txt,sha256=waiVuSJJYt-6_DAal-T4JkHgejo7wKYLdKrEI7tZ-ms,127
11
- ytcollector-1.1.7.dist-info/top_level.txt,sha256=wozNyCUm0eMOm-9U81yTql6oGaM2O5rWVBXDb93zzyQ,12
12
- ytcollector-1.1.7.dist-info/RECORD,,