ytcollector 1.0.7__tar.gz → 1.0.9__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.9/PKG-INFO +207 -0
- ytcollector-1.0.9/README.md +174 -0
- ytcollector-1.0.9/pyproject.toml +67 -0
- ytcollector-1.0.9/ytcollector/__init__.py +39 -0
- ytcollector-1.0.9/ytcollector/analyzer.py +205 -0
- ytcollector-1.0.9/ytcollector/cli.py +202 -0
- ytcollector-1.0.9/ytcollector/config.py +71 -0
- ytcollector-1.0.9/ytcollector/dataset_builder.py +136 -0
- ytcollector-1.0.9/ytcollector/downloader.py +341 -0
- ytcollector-1.0.9/ytcollector.egg-info/PKG-INFO +207 -0
- {ytcollector-1.0.7 → ytcollector-1.0.9}/ytcollector.egg-info/SOURCES.txt +2 -3
- ytcollector-1.0.9/ytcollector.egg-info/entry_points.txt +4 -0
- ytcollector-1.0.9/ytcollector.egg-info/requires.txt +14 -0
- {ytcollector-1.0.7 → ytcollector-1.0.9}/ytcollector.egg-info/top_level.txt +0 -1
- ytcollector-1.0.7/PKG-INFO +0 -105
- ytcollector-1.0.7/README.md +0 -93
- ytcollector-1.0.7/config/settings.py +0 -39
- ytcollector-1.0.7/pyproject.toml +0 -24
- ytcollector-1.0.7/ytcollector/__init__.py +0 -14
- ytcollector-1.0.7/ytcollector/cli.py +0 -232
- ytcollector-1.0.7/ytcollector/config.py +0 -67
- ytcollector-1.0.7/ytcollector/downloader.py +0 -466
- ytcollector-1.0.7/ytcollector/utils.py +0 -144
- ytcollector-1.0.7/ytcollector/verifier.py +0 -187
- ytcollector-1.0.7/ytcollector.egg-info/PKG-INFO +0 -105
- ytcollector-1.0.7/ytcollector.egg-info/entry_points.txt +0 -2
- ytcollector-1.0.7/ytcollector.egg-info/requires.txt +0 -5
- {ytcollector-1.0.7 → ytcollector-1.0.9}/setup.cfg +0 -0
- {ytcollector-1.0.7 → ytcollector-1.0.9}/ytcollector.egg-info/dependency_links.txt +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ytcollector
|
|
3
|
+
Version: 1.0.9
|
|
4
|
+
Summary: YouTube 콘텐츠 수집기 - 얼굴, 번호판, 타투, 텍스트 감지
|
|
5
|
+
Author: YTCollector Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/ytcollector
|
|
8
|
+
Project-URL: Documentation, https://github.com/yourusername/ytcollector#readme
|
|
9
|
+
Project-URL: Repository, https://github.com/yourusername/ytcollector
|
|
10
|
+
Keywords: youtube,downloader,video-analysis,face-detection,ocr
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Requires-Python: >=3.8
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
Requires-Dist: yt-dlp>=2024.0.0
|
|
23
|
+
Provides-Extra: analysis
|
|
24
|
+
Requires-Dist: opencv-python>=4.5.0; extra == "analysis"
|
|
25
|
+
Requires-Dist: easyocr>=1.6.0; extra == "analysis"
|
|
26
|
+
Requires-Dist: numpy>=1.20.0; extra == "analysis"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
30
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
31
|
+
Provides-Extra: all
|
|
32
|
+
Requires-Dist: ytcollector[analysis,dev]; extra == "all"
|
|
33
|
+
|
|
34
|
+
# YouTube 콘텐츠 수집기
|
|
35
|
+
|
|
36
|
+
유튜브에서 특정 카테고리(얼굴, 번호판, 타투, 텍스트)의 영상을 자동으로 검색, 다운로드, 분석하여 수집하는 CLI 도구입니다.
|
|
37
|
+
|
|
38
|
+
## 설치
|
|
39
|
+
|
|
40
|
+
### 필수 패키지
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pip install yt-dlp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 분석 기능용 패키지 (권장)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install opencv-python easyocr numpy
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 사용법
|
|
53
|
+
|
|
54
|
+
### 기본 실행
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python main.py
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
기본값: 얼굴 카테고리 5개, 최대 3분 영상
|
|
61
|
+
|
|
62
|
+
### 옵션
|
|
63
|
+
|
|
64
|
+
| 옵션 | 설명 | 기본값 |
|
|
65
|
+
|------|------|--------|
|
|
66
|
+
| `-c`, `--categories` | 수집할 카테고리 | `face` |
|
|
67
|
+
| `-n`, `--count` | 카테고리당 다운로드 수 | `5` |
|
|
68
|
+
| `-d`, `--duration` | 최대 영상 길이(분) | `3` |
|
|
69
|
+
| `-o`, `--output` | 저장 경로 | `~/Downloads/youtube_collection` |
|
|
70
|
+
| `--fast` | 고속 모드 (병렬 다운로드) | 비활성화 |
|
|
71
|
+
| `-w`, `--workers` | 병렬 다운로드 수 | `3` |
|
|
72
|
+
| `--proxy` | 프록시 주소 | 없음 |
|
|
73
|
+
|
|
74
|
+
### 카테고리 종류
|
|
75
|
+
|
|
76
|
+
| 카테고리 | 설명 | 검색 소스 |
|
|
77
|
+
|----------|------|-----------|
|
|
78
|
+
| `face` | 얼굴/인물 | SBS 인터뷰, 런닝맨, 미운우리새끼 등 |
|
|
79
|
+
| `license_plate` | 자동차 번호판 | 중고차 매물, 세차 영상, 신차 출고 등 |
|
|
80
|
+
| `tattoo` | 타투/문신 | 타투 시술, 타투이스트 작업 영상 |
|
|
81
|
+
| `text` | 텍스트/자막 | SBS 예능 (런닝맨, 골목식당 등) |
|
|
82
|
+
|
|
83
|
+
## 예시
|
|
84
|
+
|
|
85
|
+
### 단일 카테고리
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# 얼굴 영상 10개 수집
|
|
89
|
+
python main.py -c face -n 10
|
|
90
|
+
|
|
91
|
+
# 번호판 영상 수집 (최대 5분)
|
|
92
|
+
python main.py -c license_plate -d 5
|
|
93
|
+
|
|
94
|
+
# 타투 영상 수집
|
|
95
|
+
python main.py -c tattoo -n 5
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 여러 카테고리
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# 얼굴과 텍스트 각 10개씩
|
|
102
|
+
python main.py -c face text -n 10
|
|
103
|
+
|
|
104
|
+
# 모든 카테고리 수집
|
|
105
|
+
python main.py -c face license_plate tattoo text -n 5
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 고속 모드
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
# 병렬 다운로드 (기본 3개 동시)
|
|
112
|
+
python main.py -c face -n 10 --fast
|
|
113
|
+
|
|
114
|
+
# 5개 동시 다운로드
|
|
115
|
+
python main.py -c face -n 10 --fast -w 5
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 저장 경로 지정
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
python main.py -c face -o /path/to/save
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 프록시 사용
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
python main.py -c face --proxy http://proxy.server:8080
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## SBS Dataset 구축 (URL 리스트 기반)
|
|
131
|
+
|
|
132
|
+
URL 리스트를 기반으로 영상을 수집하고 특정 시점을 기준으로 자동으로 클리핑(3분 미만)하는 기능입니다.
|
|
133
|
+
|
|
134
|
+
### 실행 방법
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
ytc-dataset youtube_url.txt
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### youtube_url.txt 형식
|
|
141
|
+
|
|
142
|
+
`URL, MM:SS, TaskName` 형식으로 작성합니다.
|
|
143
|
+
```text
|
|
144
|
+
https://www.youtube.com/watch?v=aqz-KE-bpKQ, 00:10, sample_task
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 상세 옵션
|
|
148
|
+
|
|
149
|
+
| 옵션 | 설명 | 기본값 |
|
|
150
|
+
|------|------|--------|
|
|
151
|
+
| `file` | URL 리스트 파일 경로 | (필수) |
|
|
152
|
+
| `-o`, `--output` | 저장 루트 경로 | `.` |
|
|
153
|
+
|
|
154
|
+
### 특징
|
|
155
|
+
- **자동 트리밍**: 지정된 MM:SS 시점 기준 $\pm$ 1.5분(총 3분)을 자동으로 자릅니다.
|
|
156
|
+
- **중복 방지**: 인덱스 기반으로 이미 다운로드/클리핑된 영상은 건너뜁니다.
|
|
157
|
+
- **저장 구조**: `./video/` (원본), `./video_clips/` (클립) 폴더가 생성됩니다.
|
|
158
|
+
|
|
159
|
+
## 출력 폴더 구조
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
저장경로/
|
|
163
|
+
├── 얼굴/ # 얼굴 감지된 영상
|
|
164
|
+
├── 번호판/ # 번호판 감지된 영상
|
|
165
|
+
├── 번호판_미감지/ # 번호판 미감지 (수동 확인용)
|
|
166
|
+
├── 타투/ # 타투 감지된 영상
|
|
167
|
+
├── 텍스트/ # 텍스트 감지된 영상
|
|
168
|
+
└── .archive.txt # 다운로드 기록 (중복 방지)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## 파일 구조
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
260202_test/
|
|
175
|
+
├── main.py # CLI 진입점
|
|
176
|
+
├── config.py # 설정 (검색어, UA 등)
|
|
177
|
+
├── analyzer.py # 영상 분석 (OpenCV, EasyOCR)
|
|
178
|
+
├── downloader.py # 다운로드 로직
|
|
179
|
+
└── README.md # 사용설명서
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## 분석 기능
|
|
183
|
+
|
|
184
|
+
| 감지 항목 | 사용 기술 | 설명 |
|
|
185
|
+
|-----------|-----------|------|
|
|
186
|
+
| 얼굴 | OpenCV Haar Cascade | 정면 얼굴 감지 |
|
|
187
|
+
| 텍스트 | EasyOCR | 한국어/영어 문자 인식 |
|
|
188
|
+
| 번호판 | EasyOCR + 정규식 | 번호판 패턴 매칭 |
|
|
189
|
+
| 타투 | OpenCV HSV 분석 | 피부 영역 내 잉크 패턴 |
|
|
190
|
+
|
|
191
|
+
## 주의사항
|
|
192
|
+
|
|
193
|
+
- 영상은 다운로드 후 분석하여 해당 카테고리가 감지된 경우에만 저장됩니다
|
|
194
|
+
- 번호판 카테고리는 미감지 영상도 별도 폴더에 보관됩니다 (수동 확인용)
|
|
195
|
+
- 이미 다운로드한 영상은 자동으로 스킵됩니다 (`.archive.txt` 기록)
|
|
196
|
+
- 비공개/삭제/저작권 영상은 자동 스킵됩니다
|
|
197
|
+
|
|
198
|
+
## 고속 모드 vs 일반 모드
|
|
199
|
+
|
|
200
|
+
| 항목 | 일반 모드 | 고속 모드 |
|
|
201
|
+
|------|-----------|-----------|
|
|
202
|
+
| 다운로드 | 순차 | 병렬 |
|
|
203
|
+
| 딜레이 | 0.5~1.5초 | 없음 |
|
|
204
|
+
| 재시도 | 3회 | 1회 |
|
|
205
|
+
| 타임아웃 | 30초 | 10초 |
|
|
206
|
+
|
|
207
|
+
고속 모드는 빠르지만 YouTube 차단 위험이 높아질 수 있습니다.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# YouTube 콘텐츠 수집기
|
|
2
|
+
|
|
3
|
+
유튜브에서 특정 카테고리(얼굴, 번호판, 타투, 텍스트)의 영상을 자동으로 검색, 다운로드, 분석하여 수집하는 CLI 도구입니다.
|
|
4
|
+
|
|
5
|
+
## 설치
|
|
6
|
+
|
|
7
|
+
### 필수 패키지
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install yt-dlp
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 분석 기능용 패키지 (권장)
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install opencv-python easyocr numpy
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 사용법
|
|
20
|
+
|
|
21
|
+
### 기본 실행
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
python main.py
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
기본값: 얼굴 카테고리 5개, 최대 3분 영상
|
|
28
|
+
|
|
29
|
+
### 옵션
|
|
30
|
+
|
|
31
|
+
| 옵션 | 설명 | 기본값 |
|
|
32
|
+
|------|------|--------|
|
|
33
|
+
| `-c`, `--categories` | 수집할 카테고리 | `face` |
|
|
34
|
+
| `-n`, `--count` | 카테고리당 다운로드 수 | `5` |
|
|
35
|
+
| `-d`, `--duration` | 최대 영상 길이(분) | `3` |
|
|
36
|
+
| `-o`, `--output` | 저장 경로 | `~/Downloads/youtube_collection` |
|
|
37
|
+
| `--fast` | 고속 모드 (병렬 다운로드) | 비활성화 |
|
|
38
|
+
| `-w`, `--workers` | 병렬 다운로드 수 | `3` |
|
|
39
|
+
| `--proxy` | 프록시 주소 | 없음 |
|
|
40
|
+
|
|
41
|
+
### 카테고리 종류
|
|
42
|
+
|
|
43
|
+
| 카테고리 | 설명 | 검색 소스 |
|
|
44
|
+
|----------|------|-----------|
|
|
45
|
+
| `face` | 얼굴/인물 | SBS 인터뷰, 런닝맨, 미운우리새끼 등 |
|
|
46
|
+
| `license_plate` | 자동차 번호판 | 중고차 매물, 세차 영상, 신차 출고 등 |
|
|
47
|
+
| `tattoo` | 타투/문신 | 타투 시술, 타투이스트 작업 영상 |
|
|
48
|
+
| `text` | 텍스트/자막 | SBS 예능 (런닝맨, 골목식당 등) |
|
|
49
|
+
|
|
50
|
+
## 예시
|
|
51
|
+
|
|
52
|
+
### 단일 카테고리
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# 얼굴 영상 10개 수집
|
|
56
|
+
python main.py -c face -n 10
|
|
57
|
+
|
|
58
|
+
# 번호판 영상 수집 (최대 5분)
|
|
59
|
+
python main.py -c license_plate -d 5
|
|
60
|
+
|
|
61
|
+
# 타투 영상 수집
|
|
62
|
+
python main.py -c tattoo -n 5
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 여러 카테고리
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 얼굴과 텍스트 각 10개씩
|
|
69
|
+
python main.py -c face text -n 10
|
|
70
|
+
|
|
71
|
+
# 모든 카테고리 수집
|
|
72
|
+
python main.py -c face license_plate tattoo text -n 5
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 고속 모드
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 병렬 다운로드 (기본 3개 동시)
|
|
79
|
+
python main.py -c face -n 10 --fast
|
|
80
|
+
|
|
81
|
+
# 5개 동시 다운로드
|
|
82
|
+
python main.py -c face -n 10 --fast -w 5
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 저장 경로 지정
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
python main.py -c face -o /path/to/save
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 프록시 사용
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python main.py -c face --proxy http://proxy.server:8080
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## SBS Dataset 구축 (URL 리스트 기반)
|
|
98
|
+
|
|
99
|
+
URL 리스트를 기반으로 영상을 수집하고 특정 시점을 기준으로 자동으로 클리핑(3분 미만)하는 기능입니다.
|
|
100
|
+
|
|
101
|
+
### 실행 방법
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
ytc-dataset youtube_url.txt
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### youtube_url.txt 형식
|
|
108
|
+
|
|
109
|
+
`URL, MM:SS, TaskName` 형식으로 작성합니다.
|
|
110
|
+
```text
|
|
111
|
+
https://www.youtube.com/watch?v=aqz-KE-bpKQ, 00:10, sample_task
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 상세 옵션
|
|
115
|
+
|
|
116
|
+
| 옵션 | 설명 | 기본값 |
|
|
117
|
+
|------|------|--------|
|
|
118
|
+
| `file` | URL 리스트 파일 경로 | (필수) |
|
|
119
|
+
| `-o`, `--output` | 저장 루트 경로 | `.` |
|
|
120
|
+
|
|
121
|
+
### 특징
|
|
122
|
+
- **자동 트리밍**: 지정된 MM:SS 시점 기준 $\pm$ 1.5분(총 3분)을 자동으로 자릅니다.
|
|
123
|
+
- **중복 방지**: 인덱스 기반으로 이미 다운로드/클리핑된 영상은 건너뜁니다.
|
|
124
|
+
- **저장 구조**: `./video/` (원본), `./video_clips/` (클립) 폴더가 생성됩니다.
|
|
125
|
+
|
|
126
|
+
## 출력 폴더 구조
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
저장경로/
|
|
130
|
+
├── 얼굴/ # 얼굴 감지된 영상
|
|
131
|
+
├── 번호판/ # 번호판 감지된 영상
|
|
132
|
+
├── 번호판_미감지/ # 번호판 미감지 (수동 확인용)
|
|
133
|
+
├── 타투/ # 타투 감지된 영상
|
|
134
|
+
├── 텍스트/ # 텍스트 감지된 영상
|
|
135
|
+
└── .archive.txt # 다운로드 기록 (중복 방지)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 파일 구조
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
260202_test/
|
|
142
|
+
├── main.py # CLI 진입점
|
|
143
|
+
├── config.py # 설정 (검색어, UA 등)
|
|
144
|
+
├── analyzer.py # 영상 분석 (OpenCV, EasyOCR)
|
|
145
|
+
├── downloader.py # 다운로드 로직
|
|
146
|
+
└── README.md # 사용설명서
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 분석 기능
|
|
150
|
+
|
|
151
|
+
| 감지 항목 | 사용 기술 | 설명 |
|
|
152
|
+
|-----------|-----------|------|
|
|
153
|
+
| 얼굴 | OpenCV Haar Cascade | 정면 얼굴 감지 |
|
|
154
|
+
| 텍스트 | EasyOCR | 한국어/영어 문자 인식 |
|
|
155
|
+
| 번호판 | EasyOCR + 정규식 | 번호판 패턴 매칭 |
|
|
156
|
+
| 타투 | OpenCV HSV 분석 | 피부 영역 내 잉크 패턴 |
|
|
157
|
+
|
|
158
|
+
## 주의사항
|
|
159
|
+
|
|
160
|
+
- 영상은 다운로드 후 분석하여 해당 카테고리가 감지된 경우에만 저장됩니다
|
|
161
|
+
- 번호판 카테고리는 미감지 영상도 별도 폴더에 보관됩니다 (수동 확인용)
|
|
162
|
+
- 이미 다운로드한 영상은 자동으로 스킵됩니다 (`.archive.txt` 기록)
|
|
163
|
+
- 비공개/삭제/저작권 영상은 자동 스킵됩니다
|
|
164
|
+
|
|
165
|
+
## 고속 모드 vs 일반 모드
|
|
166
|
+
|
|
167
|
+
| 항목 | 일반 모드 | 고속 모드 |
|
|
168
|
+
|------|-----------|-----------|
|
|
169
|
+
| 다운로드 | 순차 | 병렬 |
|
|
170
|
+
| 딜레이 | 0.5~1.5초 | 없음 |
|
|
171
|
+
| 재시도 | 3회 | 1회 |
|
|
172
|
+
| 타임아웃 | 30초 | 10초 |
|
|
173
|
+
|
|
174
|
+
고속 모드는 빠르지만 YouTube 차단 위험이 높아질 수 있습니다.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ytcollector"
|
|
7
|
+
version = "1.0.9"
|
|
8
|
+
description = "YouTube 콘텐츠 수집기 - 얼굴, 번호판, 타투, 텍스트 감지"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "YTCollector Team"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["youtube", "downloader", "video-analysis", "face-detection", "ocr"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
dependencies = [
|
|
29
|
+
"yt-dlp>=2024.0.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
analysis = [
|
|
34
|
+
"opencv-python>=4.5.0",
|
|
35
|
+
"easyocr>=1.6.0",
|
|
36
|
+
"numpy>=1.20.0",
|
|
37
|
+
]
|
|
38
|
+
dev = [
|
|
39
|
+
"pytest>=7.0.0",
|
|
40
|
+
"black>=23.0.0",
|
|
41
|
+
"ruff>=0.1.0",
|
|
42
|
+
]
|
|
43
|
+
all = [
|
|
44
|
+
"ytcollector[analysis,dev]",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
ytcollector = "ytcollector.cli:main"
|
|
49
|
+
ytc = "ytcollector.cli:main"
|
|
50
|
+
ytc-dataset = "ytcollector.dataset_builder:main"
|
|
51
|
+
|
|
52
|
+
[project.urls]
|
|
53
|
+
Homepage = "https://github.com/yourusername/ytcollector"
|
|
54
|
+
Documentation = "https://github.com/yourusername/ytcollector#readme"
|
|
55
|
+
Repository = "https://github.com/yourusername/ytcollector"
|
|
56
|
+
|
|
57
|
+
[tool.setuptools.packages.find]
|
|
58
|
+
where = ["."]
|
|
59
|
+
include = ["ytcollector*"]
|
|
60
|
+
|
|
61
|
+
[tool.black]
|
|
62
|
+
line-length = 100
|
|
63
|
+
target-version = ['py38']
|
|
64
|
+
|
|
65
|
+
[tool.ruff]
|
|
66
|
+
line-length = 100
|
|
67
|
+
select = ["E", "F", "W"]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""YouTube 콘텐츠 수집기 라이브러리
|
|
2
|
+
|
|
3
|
+
외부에서 라이브러리로 사용하거나 CLI로 실행할 수 있습니다.
|
|
4
|
+
|
|
5
|
+
라이브러리 사용 예시:
|
|
6
|
+
from ytcollector import YouTubeDownloader, run
|
|
7
|
+
|
|
8
|
+
# 방법 1: YouTubeDownloader 직접 사용
|
|
9
|
+
downloader = YouTubeDownloader(output_path="./videos")
|
|
10
|
+
count = downloader.collect("face", max_videos=5)
|
|
11
|
+
|
|
12
|
+
# 방법 2: run() 함수 사용 (간단한 방법)
|
|
13
|
+
results = run(categories=["face", "text"], count=3)
|
|
14
|
+
|
|
15
|
+
CLI 사용 예시:
|
|
16
|
+
ytcollector -c face -n 5
|
|
17
|
+
ytc -c face text --fast
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .config import CATEGORY_NAMES, CATEGORY_QUERIES, USER_AGENTS, LICENSE_PLATE_PATTERNS
|
|
21
|
+
from .analyzer import VideoAnalyzer, check_dependencies
|
|
22
|
+
from .downloader import YouTubeDownloader
|
|
23
|
+
from .cli import run, main as cli_main
|
|
24
|
+
|
|
25
|
+
__version__ = "1.0.0"
|
|
26
|
+
__all__ = [
|
|
27
|
+
# 주요 클래스
|
|
28
|
+
"VideoAnalyzer",
|
|
29
|
+
"YouTubeDownloader",
|
|
30
|
+
# 설정
|
|
31
|
+
"CATEGORY_NAMES",
|
|
32
|
+
"CATEGORY_QUERIES",
|
|
33
|
+
"USER_AGENTS",
|
|
34
|
+
"LICENSE_PLATE_PATTERNS",
|
|
35
|
+
# 유틸리티
|
|
36
|
+
"check_dependencies",
|
|
37
|
+
"run",
|
|
38
|
+
"cli_main",
|
|
39
|
+
]
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from .config import LICENSE_PLATE_PATTERNS
|
|
3
|
+
|
|
4
|
+
# 선택적 import
|
|
5
|
+
try:
|
|
6
|
+
import cv2
|
|
7
|
+
CV2_AVAILABLE = True
|
|
8
|
+
except ImportError:
|
|
9
|
+
CV2_AVAILABLE = False
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
import easyocr
|
|
13
|
+
EASYOCR_AVAILABLE = True
|
|
14
|
+
except ImportError:
|
|
15
|
+
EASYOCR_AVAILABLE = False
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import numpy as np
|
|
19
|
+
NUMPY_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
NUMPY_AVAILABLE = False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class VideoAnalyzer:
|
|
25
|
+
"""영상 분석 클래스 - 얼굴, 텍스트, 번호판, 타투 감지"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.ocr_reader = None
|
|
29
|
+
self.face_cascade = None
|
|
30
|
+
|
|
31
|
+
if CV2_AVAILABLE:
|
|
32
|
+
cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
|
|
33
|
+
self.face_cascade = cv2.CascadeClassifier(cascade_path)
|
|
34
|
+
|
|
35
|
+
def _init_ocr(self):
|
|
36
|
+
"""OCR 리더 초기화 (필요할 때만)"""
|
|
37
|
+
if EASYOCR_AVAILABLE and self.ocr_reader is None:
|
|
38
|
+
print(" OCR 엔진 초기화 중...")
|
|
39
|
+
self.ocr_reader = easyocr.Reader(['ko', 'en'], gpu=False, verbose=False)
|
|
40
|
+
|
|
41
|
+
def extract_frames(self, video_path, num_frames=10):
|
|
42
|
+
"""영상에서 균등 간격으로 프레임 추출"""
|
|
43
|
+
if not CV2_AVAILABLE:
|
|
44
|
+
return []
|
|
45
|
+
|
|
46
|
+
cap = cv2.VideoCapture(video_path)
|
|
47
|
+
if not cap.isOpened():
|
|
48
|
+
return []
|
|
49
|
+
|
|
50
|
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
51
|
+
if total_frames <= 0:
|
|
52
|
+
cap.release()
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
frame_indices = [int(i * total_frames / (num_frames + 1)) for i in range(1, num_frames + 1)]
|
|
56
|
+
frames = []
|
|
57
|
+
|
|
58
|
+
for idx in frame_indices:
|
|
59
|
+
cap.set(cv2.CAP_PROP_POS_FRAMES, idx)
|
|
60
|
+
ret, frame = cap.read()
|
|
61
|
+
if ret:
|
|
62
|
+
frames.append(frame)
|
|
63
|
+
|
|
64
|
+
cap.release()
|
|
65
|
+
return frames
|
|
66
|
+
|
|
67
|
+
def detect_faces(self, frame):
|
|
68
|
+
"""Haar Cascade로 얼굴 감지"""
|
|
69
|
+
if not CV2_AVAILABLE or self.face_cascade is None:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
73
|
+
return self.face_cascade.detectMultiScale(
|
|
74
|
+
gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def detect_text(self, frame):
|
|
78
|
+
"""EasyOCR로 텍스트 감지"""
|
|
79
|
+
if not EASYOCR_AVAILABLE:
|
|
80
|
+
return []
|
|
81
|
+
|
|
82
|
+
self._init_ocr()
|
|
83
|
+
try:
|
|
84
|
+
h, w = frame.shape[:2]
|
|
85
|
+
if w > 640:
|
|
86
|
+
scale = 640 / w
|
|
87
|
+
frame = cv2.resize(frame, (640, int(h * scale)))
|
|
88
|
+
|
|
89
|
+
results = self.ocr_reader.readtext(frame)
|
|
90
|
+
return [r[1] for r in results if r[2] > 0.3]
|
|
91
|
+
except:
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
def detect_license_plate(self, texts):
|
|
95
|
+
"""텍스트에서 번호판 패턴 감지"""
|
|
96
|
+
for text in texts:
|
|
97
|
+
text_clean = text.replace(' ', '').upper()
|
|
98
|
+
for pattern in LICENSE_PLATE_PATTERNS:
|
|
99
|
+
if re.search(pattern, text_clean):
|
|
100
|
+
return True
|
|
101
|
+
return False
|
|
102
|
+
|
|
103
|
+
def detect_tattoo(self, frame):
|
|
104
|
+
"""피부 영역에서 타투(어두운 잉크 패턴) 감지"""
|
|
105
|
+
if not CV2_AVAILABLE or not NUMPY_AVAILABLE:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
|
|
110
|
+
|
|
111
|
+
# 피부색 범위
|
|
112
|
+
lower_skin = np.array([0, 30, 80], dtype=np.uint8)
|
|
113
|
+
upper_skin = np.array([17, 170, 255], dtype=np.uint8)
|
|
114
|
+
skin_mask = cv2.inRange(hsv, lower_skin, upper_skin)
|
|
115
|
+
|
|
116
|
+
# 노이즈 제거
|
|
117
|
+
kernel = np.ones((5, 5), np.uint8)
|
|
118
|
+
skin_mask = cv2.morphologyEx(skin_mask, cv2.MORPH_OPEN, kernel)
|
|
119
|
+
skin_mask = cv2.morphologyEx(skin_mask, cv2.MORPH_CLOSE, kernel)
|
|
120
|
+
|
|
121
|
+
skin_pixels = cv2.countNonZero(skin_mask)
|
|
122
|
+
total_pixels = frame.shape[0] * frame.shape[1]
|
|
123
|
+
|
|
124
|
+
# 피부 영역 최소 10% 필요
|
|
125
|
+
if skin_pixels < total_pixels * 0.10:
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
# 피부 영역 내 어두운 픽셀(타투) 감지
|
|
129
|
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
130
|
+
skin_gray = cv2.bitwise_and(gray, gray, mask=skin_mask)
|
|
131
|
+
dark_mask = cv2.inRange(skin_gray, 1, 80)
|
|
132
|
+
|
|
133
|
+
dark_pixels = cv2.countNonZero(dark_mask)
|
|
134
|
+
dark_ratio = dark_pixels / max(skin_pixels, 1)
|
|
135
|
+
|
|
136
|
+
# 어두운 영역이 3~35%일 때 타투로 판정
|
|
137
|
+
if 0.03 < dark_ratio < 0.35:
|
|
138
|
+
contours, _ = cv2.findContours(dark_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
139
|
+
significant = [c for c in contours if cv2.contourArea(c) > 100]
|
|
140
|
+
return len(significant) >= 1
|
|
141
|
+
|
|
142
|
+
return False
|
|
143
|
+
except:
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
def analyze(self, video_path):
|
|
147
|
+
"""영상 전체 분석"""
|
|
148
|
+
results = {
|
|
149
|
+
'face': False,
|
|
150
|
+
'text': False,
|
|
151
|
+
'license_plate': False,
|
|
152
|
+
'tattoo': False,
|
|
153
|
+
'face_count': 0,
|
|
154
|
+
'detected_texts': []
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if not CV2_AVAILABLE:
|
|
158
|
+
print(" ⚠ OpenCV 미설치")
|
|
159
|
+
return results
|
|
160
|
+
|
|
161
|
+
frames = self.extract_frames(video_path, num_frames=8)
|
|
162
|
+
if not frames:
|
|
163
|
+
print(" ⚠ 프레임 추출 실패")
|
|
164
|
+
return results
|
|
165
|
+
|
|
166
|
+
all_texts = []
|
|
167
|
+
total_faces = 0
|
|
168
|
+
|
|
169
|
+
for i, frame in enumerate(frames):
|
|
170
|
+
# 얼굴
|
|
171
|
+
faces = self.detect_faces(frame)
|
|
172
|
+
if len(faces) > 0:
|
|
173
|
+
results['face'] = True
|
|
174
|
+
total_faces += len(faces)
|
|
175
|
+
|
|
176
|
+
# 텍스트 (일부 프레임만)
|
|
177
|
+
if i % 2 == 0 and EASYOCR_AVAILABLE:
|
|
178
|
+
texts = self.detect_text(frame)
|
|
179
|
+
if texts:
|
|
180
|
+
results['text'] = True
|
|
181
|
+
all_texts.extend(texts)
|
|
182
|
+
|
|
183
|
+
# 타투
|
|
184
|
+
if self.detect_tattoo(frame):
|
|
185
|
+
results['tattoo'] = True
|
|
186
|
+
|
|
187
|
+
# 번호판 (텍스트에서)
|
|
188
|
+
if all_texts:
|
|
189
|
+
results['license_plate'] = self.detect_license_plate(all_texts)
|
|
190
|
+
results['detected_texts'] = list(set(all_texts))[:10]
|
|
191
|
+
|
|
192
|
+
results['face_count'] = total_faces
|
|
193
|
+
return results
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def check_dependencies():
|
|
197
|
+
"""의존성 체크"""
|
|
198
|
+
missing = []
|
|
199
|
+
if not CV2_AVAILABLE:
|
|
200
|
+
missing.append("opencv-python")
|
|
201
|
+
if not EASYOCR_AVAILABLE:
|
|
202
|
+
missing.append("easyocr")
|
|
203
|
+
if not NUMPY_AVAILABLE:
|
|
204
|
+
missing.append("numpy")
|
|
205
|
+
return missing
|