Sodagraph commited on
Commit
8659b57
·
1 Parent(s): eda02a7

영상 임베딩 기능 추가

Browse files
backend/app/youtube_parser.py CHANGED
@@ -17,27 +17,21 @@ load_dotenv()
17
  # --- Loguru 설정 시작 ---
18
  # 기본 핸들러(콘솔 출력) 제거
19
  logger.remove()
20
-
21
- # 콘솔 출력 핸들러: INFO 레벨 이상만 출력, 색상 적용, 간결한 포맷
22
- logger.add(
23
- sys.stderr, # 표준 에러 스트림 (콘솔)
24
- level="INFO",
25
- colorize=True,
26
- format="<green>{time:HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>"
27
- )
28
-
29
- # 파일 출력 핸들러: DEBUG 레벨 이상 모든 로그를 파일로 저장, 10MB마다 새 파일 생성
30
- logger.add(
31
- "debug_yt_dlp.log", # 로그 파일 이름
32
- level="DEBUG",
33
- rotation="10 MB", # 파일 크기가 10MB가 되면 새 파일 생성
34
- compression="zip", # 압축하여 저장 (선택 사항)
35
- enqueue=True, # 비동기 로깅 (성능 향상)
36
- format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}"
37
- )
38
  # --- Loguru 설정 끝 ---
39
 
40
-
 
 
 
 
 
 
 
 
 
 
41
  async def get_youtube_video_id(url: str) -> str | None:
42
  """
43
  유튜브 URL에서 비디오 ID를 추출합니다.
 
17
  # --- Loguru 설정 시작 ---
18
  # 기본 핸들러(콘솔 출력) 제거
19
  logger.remove()
20
+ # 콘솔에 INFO 레벨 이상 로그 출력
21
+ logger.add(sys.stderr, level="INFO")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  # --- Loguru 설정 끝 ---
23
 
24
+ def validate_youtube_url(url):
25
+ ydl_opts = {
26
+ 'quiet': True
27
+ }
28
+ try:
29
+ with YoutubeDL(ydl_opts) as ydl:
30
+ ydl.extract_info(url, download=False)
31
+ return True
32
+ except:
33
+ return False
34
+
35
  async def get_youtube_video_id(url: str) -> str | None:
36
  """
37
  유튜브 URL에서 비디오 ID를 추출합니다.
backend/dependencies.json DELETED
@@ -1,15 +0,0 @@
1
- // backend/dependencies.json
2
- {
3
- "fastapi": "FastAPI (웹 프레임워크)",
4
- "uvicorn": "Uvicorn (FastAPI 서버)",
5
- "python-dotenv": "환경 변수 관리",
6
- "youtube-transcript-api": "유튜브 자막 추출",
7
- "langchain": "RAG 프레임워크 (LangChain)",
8
- "langchain-community": "LangChain 커뮤니티 모듈 (문서 로더, 벡터스토어 등)",
9
- "sentence-transformers": "임베딩 모델",
10
- "chromadb": "벡터 데이터베이스 (경량)",
11
- "transformers": "LLM 로드 (Hugging Face Transformers)",
12
- "accelerate": "LLM 메모리 최적화",
13
- "torch": "PyTorch (트랜스포머 라이브러리 의존성)",
14
- "faiss-cpu": "FAISS (벡터 검색 라이브러리 - CPU 버전)"
15
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
backend/requirements.txt CHANGED
@@ -1,136 +1,11 @@
1
- accelerate==0.30.1
2
- aiohappyeyeballs==2.6.1
3
- aiohttp==3.12.13
4
- aiosignal==1.3.2
5
- annotated-types==0.7.0
6
- anyio==4.9.0
7
- asgiref==3.8.1
8
- attrs==25.3.0
9
- backoff==2.2.1
10
- bcrypt==4.3.0
11
- build==1.2.2.post1
12
- cachetools==5.5.2
13
- certifi==2025.6.15
14
- charset-normalizer==3.4.2
15
- chroma-hnswlib==0.7.3
16
- chromadb==0.5.3
17
- click==8.2.1
18
- coloredlogs==15.0.1
19
- dataclasses-json==0.6.7
20
- defusedxml==0.7.1
21
- distro==1.9.0
22
- dnspython==2.7.0
23
- durationpy==0.10
24
- email_validator==2.2.0
25
- faiss-cpu==1.11.0
26
  fastapi==0.111.0
27
- fastapi-cli==0.0.7
28
- filelock==3.18.0
29
- frozenlist==1.7.0
30
- fsspec==2025.5.1
31
- # Google API Client (YouTube Data API v3 사용을 위해 필요)
32
- google-api-python-client==2.138.0 # 현재 최신 안정 버전 (확인 필요)
33
- google-auth==2.40.3
34
- googleapis-common-protos==1.70.0
35
- grpcio==1.73.0
36
- greenlet==3.2.3
37
- h11==0.16.0
38
- hf-xet==1.1.3
39
- httpcore==1.0.9
40
- httptools==0.6.4
41
- httpx==0.28.1
42
- huggingface-hub==0.33.0
43
- humanfriendly==10.0
44
- idna==3.10
45
- importlib_metadata==8.7.0
46
- importlib_resources==6.5.2
47
- Jinja2==3.1.6
48
- joblib==1.5.1
49
- jsonpatch==1.33
50
- jsonpointer==3.0.0
51
- kubernetes==33.1.0
52
- langchain==0.2.5
53
- langchain-community==0.2.5
54
- langchain-core==0.2.43
55
- langchain-text-splitters==0.2.4
56
- langsmith==0.1.147
57
- loguru==0.7.3
58
- markdown-it-py==3.0.0
59
- MarkupSafe==3.0.2
60
- marshmallow==3.26.1
61
- mdurl==0.1.2
62
- mmh3==5.1.0
63
- mpmath==1.3.0
64
- multidict==6.4.4
65
- mypy_extensions==1.1.0
66
- networkx==3.5
67
- numpy==1.26.4
68
- oauthlib==3.2.2
69
- onnxruntime==1.22.0
70
- opentelemetry-api==1.34.1
71
- opentelemetry-exporter-otlp-proto-common==1.34.1
72
- opentelemetry-exporter-otlp-proto-grpc==1.34.1
73
- opentelemetry-instrumentation==0.55b1
74
- opentelemetry-instrumentation-asgi==0.55b1
75
- opentelemetry-instrumentation-fastapi==0.55b1
76
- opentelemetry-proto==1.34.1
77
- opentelemetry-sdk==1.34.1
78
- opentelemetry-semantic-conventions==0.55b1
79
- opentelemetry-util-http==0.55b1
80
- orjson==3.10.18
81
- overrides==7.7.0
82
- packaging==24.2
83
- pillow==11.2.1
84
- posthog==5.0.0
85
- propcache==0.3.2
86
- protobuf==5.29.5
87
- psutil==7.0.0
88
- pyasn1==0.6.1
89
- pyasn1_modules==0.4.2
90
- pydantic==2.11.7
91
- pydantic_core==2.33.2
92
- Pygments==2.19.1
93
- PyPika==0.48.9
94
- pyproject_hooks==1.2.0
95
- python-dateutil==2.9.0.post0
96
- python-dotenv==1.0.1
97
- python-multipart==0.0.20
98
- PyYAML==6.0.2
99
- regex==2024.11.6
100
- requests==2.32.4
101
- requests-oauthlib==2.0.0
102
- requests-toolbelt==1.0.0
103
- rich==14.0.0
104
- rich-toolkit==0.14.7
105
- rsa==4.9.1
106
- safetensors==0.5.3
107
- scikit-learn==1.7.0
108
- scipy==1.15.3
109
- sentence-transformers==2.7.0
110
- shellingham==1.5.4
111
- six==1.17.0
112
- sniffio==1.3.1
113
- SQLAlchemy==2.0.41
114
- starlette==0.37.2
115
- sympy==1.14.0
116
- tenacity==8.5.0
117
- threadpoolctl==3.6.0
118
- tokenizers==0.19.1
119
- torch==2.3.1
120
- tqdm==4.67.1
121
- transformers==4.41.2
122
- typer==0.16.0
123
- typing-inspect==0.9.0
124
- typing-inspection==0.4.1
125
- typing_extensions==4.14.0
126
- ujson==5.10.0
127
- urllib3==2.4.0
128
  uvicorn==0.30.1
129
- uvloop==0.21.0
130
- watchfiles==1.1.0
131
- websocket-client==1.8.0
132
- websockets==15.0.1
133
- wrapt==1.17.2
134
- yarl==1.20.1
135
  yt-dlp==2025.6.9
136
- zipp==3.23.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  fastapi==0.111.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  uvicorn==0.30.1
 
 
 
 
 
 
3
  yt-dlp==2025.6.9
4
+ loguru==0.7.3
5
+ python-dotenv==1.0.1
6
+ requests==2.32.3
7
+ sentence-transformers==3.2.1
8
+ faiss-cpu==1.9.0
9
+ numpy==1.26.4
10
+ pydantic==2.9.2
11
+ torch==2.6.0
frontend/src/App.vue CHANGED
@@ -1,11 +1,23 @@
1
  <template>
2
  <div id="app">
3
- <h1>YouTube RAG Explorer</h1>
4
  <p>영상 URL과 찾고 싶은 내용을 입력해주세요.</p>
5
 
6
  <div class="input-section">
7
  <label for="videoUrl">YouTube 영상 URL:</label>
8
- <input type="text" id="videoUrl" v-model="videoUrl" placeholder="예: https://www.youtube.com/watch?v=..." />
 
 
 
 
 
 
 
 
 
 
 
 
9
  </div>
10
 
11
  <div class="input-section">
@@ -46,10 +58,26 @@ export default {
46
  loading: false,
47
  results: [],
48
  errorMessage: '',
49
- responseMessage: ''
 
50
  };
51
  },
52
  methods: {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  async processVideo() {
54
  this.errorMessage = '';
55
  this.results = [];
@@ -68,8 +96,6 @@ export default {
68
  } else {
69
  this.responseMessage = response.data.message || "결과를 찾을 수 없습니다.";
70
  }
71
-
72
-
73
  } catch (error) {
74
  console.error("API 호출 중 오류 발생:", error);
75
  if (error.response) {
@@ -186,4 +212,22 @@ button:disabled {
186
  .result-item a:hover {
187
  text-decoration: underline;
188
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  </style>
 
1
  <template>
2
  <div id="app">
3
+ <h1>YouTube Transcript Extraction</h1>
4
  <p>영상 URL과 찾고 싶은 내용을 입력해주세요.</p>
5
 
6
  <div class="input-section">
7
  <label for="videoUrl">YouTube 영상 URL:</label>
8
+ <input type="text" id="videoUrl" v-model="videoUrl" placeholder="예: https://www.youtube.com/watch?v=..." @input="updateVideoEmbed" />
9
+ </div>
10
+
11
+ <!-- 유튜브 영상 임베딩 -->
12
+ <div v-if="videoEmbedUrl" class="video-embed">
13
+ <iframe
14
+ :src="videoEmbedUrl"
15
+ width="100%"
16
+ height="400"
17
+ frameborder="0"
18
+ allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
19
+ allowfullscreen
20
+ ></iframe>
21
  </div>
22
 
23
  <div class="input-section">
 
58
  loading: false,
59
  results: [],
60
  errorMessage: '',
61
+ responseMessage: '',
62
+ videoEmbedUrl: ''
63
  };
64
  },
65
  methods: {
66
+ // 유튜브 비디오 ID 추출 및 임베딩 URL 저장
67
+ updateVideoEmbed() {
68
+ this.videoEmbedUrl = '';
69
+ this.errorMessage = '';
70
+ if (!this.videoUrl) return;
71
+
72
+ // 유튜브 URL에서 비디오 ID 추출
73
+ const regex = /(?:youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/)|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
74
+ const match = this.videoUrl.match(regex);
75
+ if (match && match[1]) {
76
+ this.videoEmbedUrl = `https://www.youtube.com/embed/${match[1]}`;
77
+ } else{
78
+ this.errorMessage = '유요한 유튜브 URL을 입력해주세요.';
79
+ }
80
+ },
81
  async processVideo() {
82
  this.errorMessage = '';
83
  this.results = [];
 
96
  } else {
97
  this.responseMessage = response.data.message || "결과를 찾을 수 없습니다.";
98
  }
 
 
99
  } catch (error) {
100
  console.error("API 호출 중 오류 발생:", error);
101
  if (error.response) {
 
212
  .result-item a:hover {
213
  text-decoration: underline;
214
  }
215
+
216
+ .video-embed{
217
+ margin: 20px 0;
218
+ border-radius: 8px;
219
+ overflow: hidden;
220
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
221
+ position: relative;
222
+ padding-bottom: 56.25%;
223
+ height: 0;
224
+ }
225
+
226
+ .video-embed iframe {
227
+ position: absolute;
228
+ top: 0;
229
+ left: 0;
230
+ width: 100%;
231
+ height: 100%;
232
+ }
233
  </style>