ytcollector 1.0.6__tar.gz → 1.0.8__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ytcollector
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: SBS 데이터셋 수집기
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -18,7 +18,7 @@ YouTube 영상에서 얼굴, 자동차 번호판, 타투, 텍스트 자막을
18
18
 
19
19
  **필수 요구사항:**
20
20
  - Python 3.8 이상
21
- - FFmpeg (Mac: `brew install ffmpeg`) **👈 필수 설치! (영상 자르기에 필요)**
21
+ - FFmpeg (pip 설치 시 `imageio-ffmpeg`를 통해 자동으로 구성되나, 실패 시 Mac: `brew install ffmpeg` 설치 권장)
22
22
 
23
23
  **설치:**
24
24
  ```bash
@@ -52,15 +52,17 @@ face,https://www.youtube.com/watch?v=VIDEO_ID,2,30,설명
52
52
 
53
53
  이 프로그램은 **다운로드 → YOLO 검증 → (성공 시) 저장** 순서로 작동합니다. 타겟 객체가 없으면 자동으로 삭제됩니다.
54
54
 
55
- ### 기본 다운로드 (순차 실행)
56
- 안정적으로 하나씩 다운로드하고 검증합니다.
55
+ ### 주요 명령어 예시
56
+ 안정적으로 하나씩 다운로드하거나, 여러 태스크를 동시에 처리하고 목표 수량을 설정할 수 있습니다.
57
+
57
58
  ```bash
59
+ # 기본 다운로드 (태스크 하나)
58
60
  ytcollector download --task face
59
- ```
60
61
 
61
- ### 🚀 Fast 모드 (병렬 다운로드)
62
- 대량의 영상을 빠르게 수집할 사용합니다. (4개 스레드 동시 실행)
63
- ```bash
62
+ # 여러 태스크 동시에 실행 및 목표 수량(-n) 설정
63
+ ytcollector download --task face tattoo text -n 100
64
+
65
+ # 🚀 Fast 모드 (병렬 다운로드)
64
66
  ytcollector download --task face --fast
65
67
  ```
66
68
  * **방화벽 우회**: 랜덤 딜레이(1~3초)가 적용되어 차단을 방지합니다.
@@ -93,8 +95,8 @@ NAS_PATH_MAC = "/Volumes/Data/Private Dataset/..."
93
95
  | 명령어 | 설명 | 예시 |
94
96
  |--------|------|------|
95
97
  | `init` | 프로젝트 초기화 | `ytcollector init` |
96
- | `download` | 텍스트 파일 목록 대량 다운로드 | `ytcollector download --task face --fast` |
97
- | `download-single` | URL 1개만 테스트 다운로드 | `ytcollector download-single --task face ...` |
98
+ | `download` | 대량 다운로드 (여러 태스크, 개수 제한 가능) | `ytcollector download --task face tattoo -n 50` |
99
+ | `download-single` | URL 1개만 테스트 다운로드 | `ytcollector download-single --task face -u ...` |
98
100
  | `verify` | 수동 YOLO 검증 (기존 파일) | `ytcollector verify --task face` |
99
101
  | `list-tasks` | 지원하는 태스크 목록 확인 | `ytcollector list-tasks` |
100
102
 
@@ -6,7 +6,7 @@ YouTube 영상에서 얼굴, 자동차 번호판, 타투, 텍스트 자막을
6
6
 
7
7
  **필수 요구사항:**
8
8
  - Python 3.8 이상
9
- - FFmpeg (Mac: `brew install ffmpeg`) **👈 필수 설치! (영상 자르기에 필요)**
9
+ - FFmpeg (pip 설치 시 `imageio-ffmpeg`를 통해 자동으로 구성되나, 실패 시 Mac: `brew install ffmpeg` 설치 권장)
10
10
 
11
11
  **설치:**
12
12
  ```bash
@@ -40,15 +40,17 @@ face,https://www.youtube.com/watch?v=VIDEO_ID,2,30,설명
40
40
 
41
41
  이 프로그램은 **다운로드 → YOLO 검증 → (성공 시) 저장** 순서로 작동합니다. 타겟 객체가 없으면 자동으로 삭제됩니다.
42
42
 
43
- ### 기본 다운로드 (순차 실행)
44
- 안정적으로 하나씩 다운로드하고 검증합니다.
43
+ ### 주요 명령어 예시
44
+ 안정적으로 하나씩 다운로드하거나, 여러 태스크를 동시에 처리하고 목표 수량을 설정할 수 있습니다.
45
+
45
46
  ```bash
47
+ # 기본 다운로드 (태스크 하나)
46
48
  ytcollector download --task face
47
- ```
48
49
 
49
- ### 🚀 Fast 모드 (병렬 다운로드)
50
- 대량의 영상을 빠르게 수집할 사용합니다. (4개 스레드 동시 실행)
51
- ```bash
50
+ # 여러 태스크 동시에 실행 및 목표 수량(-n) 설정
51
+ ytcollector download --task face tattoo text -n 100
52
+
53
+ # 🚀 Fast 모드 (병렬 다운로드)
52
54
  ytcollector download --task face --fast
53
55
  ```
54
56
  * **방화벽 우회**: 랜덤 딜레이(1~3초)가 적용되어 차단을 방지합니다.
@@ -81,8 +83,8 @@ NAS_PATH_MAC = "/Volumes/Data/Private Dataset/..."
81
83
  | 명령어 | 설명 | 예시 |
82
84
  |--------|------|------|
83
85
  | `init` | 프로젝트 초기화 | `ytcollector init` |
84
- | `download` | 텍스트 파일 목록 대량 다운로드 | `ytcollector download --task face --fast` |
85
- | `download-single` | URL 1개만 테스트 다운로드 | `ytcollector download-single --task face ...` |
86
+ | `download` | 대량 다운로드 (여러 태스크, 개수 제한 가능) | `ytcollector download --task face tattoo -n 50` |
87
+ | `download-single` | URL 1개만 테스트 다운로드 | `ytcollector download-single --task face -u ...` |
86
88
  | `verify` | 수동 YOLO 검증 (기존 파일) | `ytcollector verify --task face` |
87
89
  | `list-tasks` | 지원하는 태스크 목록 확인 | `ytcollector list-tasks` |
88
90
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ytcollector"
7
- version = "1.0.6"
7
+ version = "1.0.8"
8
8
  description = "SBS 데이터셋 수집기"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -3,7 +3,7 @@ SBS Dataset Collector - YouTube 영상 수집 및 YOLO-World 검증 파이프라
3
3
  """
4
4
  from pathlib import Path
5
5
 
6
- __version__ = "1.0.6"
6
+ __version__ = "1.0.8"
7
7
  __author__ = "SBS Dataset Team"
8
8
 
9
9
  # Package root directory
@@ -69,9 +69,9 @@ def run_download(args):
69
69
 
70
70
  if args.fast:
71
71
  from .downloader import download_from_txt_parallel
72
- results = download_from_txt_parallel(txt_file, task, base_dir, max_count=args.count)
72
+ results = download_from_txt_parallel(txt_file, task, base_dir, max_count=args.count, skip_verify=args.skip_verify)
73
73
  else:
74
- results = download_from_txt(txt_file, task, base_dir, max_count=args.count)
74
+ results = download_from_txt(txt_file, task, base_dir, max_count=args.count, skip_verify=args.skip_verify)
75
75
 
76
76
  success_count = sum(1 for r in results if r.get('success'))
77
77
  total_success += success_count
@@ -189,6 +189,7 @@ Examples:
189
189
  download_parser.add_argument('--task', '-t', required=True, nargs='+', choices=VALID_TASKS, help='One or more tasks (e.g. face tattoo)')
190
190
  download_parser.add_argument('--count', '-n', type=int, help='Max videos to collect (default: 1000)')
191
191
  download_parser.add_argument('--fast', action='store_true', help='Enable fast parallel downloading')
192
+ download_parser.add_argument('--skip-verify', '-S', action='store_true', help='Skip YOLO verification and save all clips')
192
193
 
193
194
  # Download single
194
195
  single_parser = subparsers.add_parser('download-single', help='Download single video')
@@ -206,6 +207,7 @@ Examples:
206
207
  pipeline_parser = subparsers.add_parser('pipeline', help='Full pipeline')
207
208
  pipeline_parser.add_argument('--task', '-t', required=True, nargs='+', choices=VALID_TASKS)
208
209
  pipeline_parser.add_argument('--verify', action='store_true')
210
+ pipeline_parser.add_argument('--skip-verify', '-S', action='store_true', help='Skip verification in download stage')
209
211
 
210
212
  # List tasks
211
213
  subparsers.add_parser('list-tasks', help='List available tasks')
@@ -49,6 +49,35 @@ class VideoDownloader:
49
49
  'channel': info.get('channel'),
50
50
  'upload_date': info.get('upload_date'),
51
51
  }
52
+
53
+ def search_youtube(self, query: str, max_results: int = 50) -> List[Dict]:
54
+ """YouTube 검색을 통해 상위 결과의 URL 목록 반환"""
55
+ ydl_opts = {
56
+ 'quiet': True,
57
+ 'no_warnings': True,
58
+ 'extract_flat': True,
59
+ 'force_generic_extractor': False,
60
+ }
61
+
62
+ search_query = f"ytsearch{max_results}:{query}"
63
+ logger.info(f"Searching YouTube for: '{query}' (Max {max_results} results)")
64
+
65
+ results = []
66
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
67
+ try:
68
+ info = ydl.extract_info(search_query, download=False)
69
+ if 'entries' in info:
70
+ for entry in info['entries']:
71
+ if entry:
72
+ results.append({
73
+ 'url': f"https://www.youtube.com/watch?v={entry['id']}",
74
+ 'title': entry.get('title'),
75
+ 'id': entry['id']
76
+ })
77
+ except Exception as e:
78
+ logger.error(f"Search failed: {e}")
79
+
80
+ return results
52
81
 
53
82
  def calculate_clip_range(
54
83
  self,
@@ -116,7 +145,8 @@ class VideoDownloader:
116
145
  self,
117
146
  url: str,
118
147
  timestamp_min: int,
119
- timestamp_sec: int
148
+ timestamp_sec: int,
149
+ skip_verify: bool = False
120
150
  ) -> Tuple[Optional[Path], Optional[dict]]:
121
151
  """
122
152
  특정 타임스탬프 기준으로 ±1:30 클립 다운로드
@@ -178,18 +208,24 @@ class VideoDownloader:
178
208
  try:
179
209
  self.download_segment(url, start_sec, end_sec, temp_path)
180
210
 
181
- # 5. YOLO 검증
182
- logger.info(f"Verifying content for task: {self.task_type}...")
183
- # verifier 모듈 사용하여 검증
184
- verify_result = verify_clip(temp_path, self.task_type, self.base_dir)
185
-
186
- if not verify_result.get('is_valid', False):
187
- logger.warning(f"Verification failed: No {self.task_type} detected. Deleting...")
188
- if temp_path.exists():
189
- temp_path.unlink()
190
- return None, None
211
+ # 5. YOLO 검증 (skip_verify가 False일 때만 수행)
212
+ if not skip_verify:
213
+ logger.info(f"Verifying content for task: {self.task_type}...")
214
+ # verifier 모듈 사용하여 검증
215
+ verify_result = verify_clip(temp_path, self.task_type, self.base_dir)
216
+
217
+ logger.info(f"Verification Info - Rate: {verify_result.get('summary', {}).get('detection_rate'):.2%}, Is Valid: {verify_result.get('is_valid')}")
218
+
219
+ if not verify_result.get('is_valid', False):
220
+ logger.warning(f"Verification failed: No {self.task_type} detected (Rate: {verify_result.get('summary', {}).get('detection_rate'):.2%}). Deleting...")
221
+ if temp_path.exists():
222
+ temp_path.unlink()
223
+ return None, None
224
+ else:
225
+ logger.info(f"Skipping verification for task: {self.task_type} (--skip-verify enabled)")
226
+ verify_result = {'is_valid': True, 'skipped': True}
191
227
 
192
- # 6. 검증 통과 -> 최종 저장 (순차적 파일명 생성)
228
+ # 6. 검증 통과(혹은 건너뜀) -> 최종 저장 (순차적 파일명 생성)
193
229
  final_path = get_clip_path(self.base_dir, self.task_type, filename=None)
194
230
 
195
231
  # 이동 (네트워크 드라이브면 shutil.move 사용)
@@ -247,19 +283,17 @@ def parse_txt_line(line: str) -> Optional[Dict]:
247
283
  return None
248
284
 
249
285
 
250
- def download_from_txt(txt_path: Path, task_type: str, base_dir: Path = None, max_count: int = None) -> list:
286
+ def download_from_txt(txt_path: Path, task_type: str, base_dir: Path = None, max_count: int = None, skip_verify: bool = False) -> list:
251
287
  """TXT 파일에서 다운로드 실행 (순차)"""
252
- # 기존 로직을 process_single_item 함수로 분리하여 재사용할 수 있으면 좋겠지만,
253
- # 코드 구조상 일단 순차 실행 유지하고 parallel 함수 별도 구현
254
- return _process_download_loop(txt_path, task_type, base_dir, parallel=False, max_count=max_count)
288
+ return _process_download_loop(txt_path, task_type, base_dir, parallel=False, max_count=max_count, skip_verify=skip_verify)
255
289
 
256
290
 
257
- def download_from_txt_parallel(txt_path: Path, task_type: str, base_dir: Path = None, max_count: int = None) -> list:
291
+ def download_from_txt_parallel(txt_path: Path, task_type: str, base_dir: Path = None, max_count: int = None, skip_verify: bool = False) -> list:
258
292
  """TXT 파일에서 병렬 다운로드 실행 (Fast Mode)"""
259
- return _process_download_loop(txt_path, task_type, base_dir, parallel=True, max_count=max_count)
293
+ return _process_download_loop(txt_path, task_type, base_dir, parallel=True, max_count=max_count, skip_verify=skip_verify)
260
294
 
261
295
 
262
- def _process_download_loop(txt_path: Path, task_type: str, base_dir: Path = None, parallel: bool = False, max_count: int = None) -> list:
296
+ def _process_download_loop(txt_path: Path, task_type: str, base_dir: Path = None, parallel: bool = False, max_count: int = None, skip_verify: bool = False) -> list:
263
297
  from .config import MAX_VIDEOS_PER_TASK, MAX_WORKERS, REQUEST_DELAY_MIN, REQUEST_DELAY_MAX
264
298
  import time
265
299
  import random
@@ -317,7 +351,8 @@ def _process_download_loop(txt_path: Path, task_type: str, base_dir: Path = None
317
351
  output_path, metadata = downloader.download_clip_at_timestamp(
318
352
  url=data['url'],
319
353
  timestamp_min=data['timestamp_min'],
320
- timestamp_sec=data['timestamp_sec']
354
+ timestamp_sec=data['timestamp_sec'],
355
+ skip_verify=skip_verify
321
356
  )
322
357
 
323
358
  if output_path is None:
@@ -357,34 +392,102 @@ def _process_download_loop(txt_path: Path, task_type: str, base_dir: Path = None
357
392
  'status': 'error'
358
393
  }
359
394
 
360
- if parallel:
361
- with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
362
- futures = [executor.submit(process_item, item) for item in items]
395
+ # --- 1단계: youtube_url.txt 파일 목록 처리 ---
396
+ if items:
397
+ if parallel:
398
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
399
+ futures = [executor.submit(process_item, item) for item in items]
400
+
401
+ # 진행 상황 표시
402
+ from tqdm import tqdm
403
+ for future in tqdm(as_completed(futures), total=len(items), desc="Fast Download"):
404
+ try:
405
+ res = future.result()
406
+ results.append(res)
407
+ if res.get('status') == 'limit_reached' or downloader.get_saved_video_count() >= limit:
408
+ logger.info(f"Download limit ({limit}) reached. Stopping.")
409
+ executor.shutdown(wait=False, cancel_futures=True)
410
+ break
411
+ except Exception:
412
+ continue
413
+ else:
414
+ # 순차 실행
415
+ for item in items:
416
+ if downloader.get_saved_video_count() >= limit:
417
+ break
418
+ res = process_item(item)
419
+ results.append(res)
420
+ if res.get('status') == 'limit_reached':
421
+ break
422
+
423
+ # --- 2단계: 목표 수량을 못 채웠을 경우 YouTube 검색Fallback (반복 시도) ---
424
+ max_search_attempts = 5 # 최대 검색 시도 횟수
425
+ search_attempt = 0
426
+ processed_urls = set(data['url'] for data in items) # 이미 처리한 URL 중복 검색 방지
427
+
428
+ while downloader.get_saved_video_count() < limit and search_attempt < max_search_attempts:
429
+ current_count = downloader.get_saved_video_count()
430
+ remaining = limit - current_count
431
+
432
+ logger.info(f"\n[Search Attempt {search_attempt+1}] Target not reached ({current_count}/{limit}). Searching YouTube for '{task_type}'...")
433
+
434
+ # 검색어: 태스크 이름
435
+ # 검색 결과 개수를 점진적으로 늘리거나 조절 가능
436
+ search_results = downloader.search_youtube(task_type, max_results=min(100, remaining * 5))
437
+
438
+ if not search_results:
439
+ logger.warning("No more search results found.")
440
+ break
441
+
442
+ # 새로운 URL만 필터링
443
+ new_entries = [e for e in search_results if e['url'] not in processed_urls]
444
+ if not new_entries:
445
+ logger.info("No new unique videos found in this search attempt.")
446
+ search_attempt += 1
447
+ continue
448
+
449
+ search_items = []
450
+ for entry in new_entries:
451
+ processed_urls.add(entry['url'])
452
+ # 검색 결과는 타임스탬프 정보가 없으므로 여러 지점 시도 가능 (현재는 1분 지점 고정)
453
+ search_items.append({
454
+ 'task_type': task_type,
455
+ 'url': entry['url'],
456
+ 'timestamp_min': 1,
457
+ 'timestamp_sec': 0,
458
+ 'description': f"Auto-searched: {entry['title']}"
459
+ })
363
460
 
364
- # 진행 상황 표시
365
- from tqdm import tqdm
366
- for future in tqdm(as_completed(futures), total=len(items), desc="Fast Download"):
367
- try:
368
- res = future.result()
369
- results.append(res)
370
- if res.get('status') == 'limit_reached' or downloader.get_saved_video_count() >= limit:
371
- logger.warning(f"Download limit ({limit}) reached. Stopping remaining tasks.")
372
- executor.shutdown(wait=False, cancel_futures=True)
373
- break
374
- except Exception:
375
- continue
461
+ logger.info(f"Processing {len(search_items)} new search results...")
462
+
463
+ if parallel:
464
+ with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
465
+ futures = [executor.submit(process_item, item) for item in search_items]
466
+ from tqdm import tqdm
467
+ for future in tqdm(as_completed(futures), total=len(search_items), desc=f"Search Fallback #{search_attempt+1}"):
468
+ try:
469
+ res = future.result()
470
+ results.append(res)
471
+ if res.get('status') == 'limit_reached' or downloader.get_saved_video_count() >= limit:
472
+ executor.shutdown(wait=False, cancel_futures=True)
473
+ break
474
+ except Exception:
475
+ continue
476
+ else:
477
+ for item in search_items:
478
+ if downloader.get_saved_video_count() >= limit:
479
+ break
480
+ res = process_item(item)
481
+ results.append(res)
482
+ if res.get('status') == 'limit_reached':
483
+ break
484
+
485
+ search_attempt += 1
486
+
487
+ final_count = downloader.get_saved_video_count()
488
+ if final_count < limit:
489
+ logger.warning(f"Finished search attempts. Final count: {final_count}/{limit}")
376
490
  else:
377
- # 순차 실행
378
- for item in items:
379
- # 루프 시작 전 체크
380
- if downloader.get_saved_video_count() >= limit:
381
- logger.info(f"Target count ({limit}) reached. Stopping.")
382
- break
383
-
384
- res = process_item(item)
385
- results.append(res)
386
- if res.get('status') == 'limit_reached':
387
- logger.info(f"Target count ({limit}) reached. Stopping.")
388
- break
491
+ logger.info(f"Successfully reached target count: {final_count}/{limit}")
389
492
 
390
493
  return results
@@ -136,7 +136,7 @@ class YOLOWorldVerifier:
136
136
  'frame_results': frame_results,
137
137
  'verified_at': datetime.now().isoformat(),
138
138
  'model': self.model_name,
139
- 'is_valid': detection_rate > 0.1,
139
+ 'is_valid': detection_rate > 0.01, # 1% 이상 탐지되면 유효한 것으로 간주 (기존 10%에서 하향)
140
140
  }
141
141
 
142
142
  logger.info(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ytcollector
3
- Version: 1.0.6
3
+ Version: 1.0.8
4
4
  Summary: SBS 데이터셋 수집기
5
5
  Requires-Python: >=3.9
6
6
  Description-Content-Type: text/markdown
@@ -18,7 +18,7 @@ YouTube 영상에서 얼굴, 자동차 번호판, 타투, 텍스트 자막을
18
18
 
19
19
  **필수 요구사항:**
20
20
  - Python 3.8 이상
21
- - FFmpeg (Mac: `brew install ffmpeg`) **👈 필수 설치! (영상 자르기에 필요)**
21
+ - FFmpeg (pip 설치 시 `imageio-ffmpeg`를 통해 자동으로 구성되나, 실패 시 Mac: `brew install ffmpeg` 설치 권장)
22
22
 
23
23
  **설치:**
24
24
  ```bash
@@ -52,15 +52,17 @@ face,https://www.youtube.com/watch?v=VIDEO_ID,2,30,설명
52
52
 
53
53
  이 프로그램은 **다운로드 → YOLO 검증 → (성공 시) 저장** 순서로 작동합니다. 타겟 객체가 없으면 자동으로 삭제됩니다.
54
54
 
55
- ### 기본 다운로드 (순차 실행)
56
- 안정적으로 하나씩 다운로드하고 검증합니다.
55
+ ### 주요 명령어 예시
56
+ 안정적으로 하나씩 다운로드하거나, 여러 태스크를 동시에 처리하고 목표 수량을 설정할 수 있습니다.
57
+
57
58
  ```bash
59
+ # 기본 다운로드 (태스크 하나)
58
60
  ytcollector download --task face
59
- ```
60
61
 
61
- ### 🚀 Fast 모드 (병렬 다운로드)
62
- 대량의 영상을 빠르게 수집할 사용합니다. (4개 스레드 동시 실행)
63
- ```bash
62
+ # 여러 태스크 동시에 실행 및 목표 수량(-n) 설정
63
+ ytcollector download --task face tattoo text -n 100
64
+
65
+ # 🚀 Fast 모드 (병렬 다운로드)
64
66
  ytcollector download --task face --fast
65
67
  ```
66
68
  * **방화벽 우회**: 랜덤 딜레이(1~3초)가 적용되어 차단을 방지합니다.
@@ -93,8 +95,8 @@ NAS_PATH_MAC = "/Volumes/Data/Private Dataset/..."
93
95
  | 명령어 | 설명 | 예시 |
94
96
  |--------|------|------|
95
97
  | `init` | 프로젝트 초기화 | `ytcollector init` |
96
- | `download` | 텍스트 파일 목록 대량 다운로드 | `ytcollector download --task face --fast` |
97
- | `download-single` | URL 1개만 테스트 다운로드 | `ytcollector download-single --task face ...` |
98
+ | `download` | 대량 다운로드 (여러 태스크, 개수 제한 가능) | `ytcollector download --task face tattoo -n 50` |
99
+ | `download-single` | URL 1개만 테스트 다운로드 | `ytcollector download-single --task face -u ...` |
98
100
  | `verify` | 수동 YOLO 검증 (기존 파일) | `ytcollector verify --task face` |
99
101
  | `list-tasks` | 지원하는 태스크 목록 확인 | `ytcollector list-tasks` |
100
102
 
File without changes