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.
- {ytcollector-1.0.6 → ytcollector-1.0.8}/PKG-INFO +12 -10
- {ytcollector-1.0.6 → ytcollector-1.0.8}/README.md +11 -9
- {ytcollector-1.0.6 → ytcollector-1.0.8}/pyproject.toml +1 -1
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector/__init__.py +1 -1
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector/cli.py +4 -2
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector/downloader.py +150 -47
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector/verifier.py +1 -1
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector.egg-info/PKG-INFO +12 -10
- {ytcollector-1.0.6 → ytcollector-1.0.8}/config/settings.py +0 -0
- {ytcollector-1.0.6 → ytcollector-1.0.8}/setup.cfg +0 -0
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector/config.py +0 -0
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector/utils.py +0 -0
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector.egg-info/SOURCES.txt +0 -0
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector.egg-info/dependency_links.txt +0 -0
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector.egg-info/entry_points.txt +0 -0
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector.egg-info/requires.txt +0 -0
- {ytcollector-1.0.6 → ytcollector-1.0.8}/ytcollector.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ytcollector
|
|
3
|
-
Version: 1.0.
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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` |
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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` |
|
|
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
|
|
|
@@ -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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
logger.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
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.
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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` |
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|