Spaces:
Sleeping
Sleeping
Commit ยท
ef5fc9d
1
Parent(s): 0b41dae
feat: apply yt-dlp, headless browser, and loading spinner UI
Browse files- myapp/app.py +70 -108
- myapp/step0_ingestion.py +30 -34
- requirements.txt +2 -2
myapp/app.py
CHANGED
|
@@ -1,53 +1,6 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import math
|
| 3 |
-
import uvicorn
|
| 4 |
-
from fastapi import FastAPI
|
| 5 |
-
from fastapi.responses import HTMLResponse
|
| 6 |
-
from fastapi.middleware.cors import CORSMiddleware
|
| 7 |
-
from pydantic import BaseModel
|
| 8 |
-
|
| 9 |
-
# ==========================================
|
| 10 |
-
# ๐ AI ๋ ๊ณ ๋ธ๋ก ๋ถ๋ฌ์ค๊ธฐ
|
| 11 |
-
# ==========================================
|
| 12 |
-
# ํด๋ ๊ตฌ์กฐ์ ๋ฐ๋ผ import ๊ฒฝ๋ก๊ฐ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค.
|
| 13 |
-
# myapp ํด๋ ์์์ ์คํ๋๋ค๋ฉด ์๋์ ๊ฐ์ด ์ ์งํ์ธ์.
|
| 14 |
-
from step0_ingestion import DataIngestionPipeline
|
| 15 |
-
from step1_lexical import LexicalAnalyzer
|
| 16 |
-
from step2_semantic import SemanticAnalyzer
|
| 17 |
-
from step3_rag import FactCheckerRAG
|
| 18 |
-
from step4_xai import XAIScorer
|
| 19 |
-
|
| 20 |
-
app = FastAPI()
|
| 21 |
-
|
| 22 |
-
# ๐ ์ธ๋ถ ํธ์ถ ํ์ฉ (CORS ์ค์ )
|
| 23 |
-
app.add_middleware(
|
| 24 |
-
CORSMiddleware,
|
| 25 |
-
allow_origins=["*"],
|
| 26 |
-
allow_credentials=True,
|
| 27 |
-
allow_methods=["*"],
|
| 28 |
-
allow_headers=["*"],
|
| 29 |
-
)
|
| 30 |
-
|
| 31 |
-
print("==================================================")
|
| 32 |
-
print(" โณ AI ์์ง ๋ฐ ๋ฅ๋ฌ๋ ๋ชจ๋ธ๋ค์ ๋ฉ๋ชจ๋ฆฌ์ ์ฌ๋ฆฌ๋ ์ค์
๋๋ค...")
|
| 33 |
-
print("==================================================")
|
| 34 |
-
# Render ๋ฌด๋ฃ ํ๋(512MB)์์ ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ์ด ๋ฐ์ํ ๊ฒฝ์ฐ,
|
| 35 |
-
# ์ด ์ด๊ธฐํ ๋ถ๋ถ๋ค์ ํจ์ ๋ด๋ถ๋ก ์ฎ๊ฒจ์ผ ํ ์๋ ์์ต๋๋ค.
|
| 36 |
-
ingestion = DataIngestionPipeline()
|
| 37 |
-
lexical = LexicalAnalyzer()
|
| 38 |
-
semantic = SemanticAnalyzer()
|
| 39 |
-
rag_checker = FactCheckerRAG()
|
| 40 |
-
xai_scorer = XAIScorer()
|
| 41 |
-
print("\nโ
[์๋ฒ ์ค๋น ์๋ฃ]\n")
|
| 42 |
-
|
| 43 |
-
class AdRequest(BaseModel):
|
| 44 |
-
video_url: str
|
| 45 |
-
product_url: str
|
| 46 |
-
|
| 47 |
# 1. ๐ ๋์๋ณด๋ ์น ํ๋ก ํธ์๋ (HTML)
|
| 48 |
@app.get("/", response_class=HTMLResponse)
|
| 49 |
async def serve_frontend():
|
| 50 |
-
# ์ ๊ณตํด์ฃผ์ HTML ์ฝ๋๋ฅผ ๊ทธ๋๋ก ์ ์งํฉ๋๋ค.
|
| 51 |
html_content = """
|
| 52 |
<!DOCTYPE html>
|
| 53 |
<html lang="ko">
|
|
@@ -62,24 +15,43 @@ async def serve_frontend():
|
|
| 62 |
.input-section { background: #1e1e1e; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
|
| 63 |
input { width: 100%; padding: 12px; margin: 8px 0; background: #2c2c2e; border: 1px solid #444; border-radius: 8px; color: #fff; box-sizing: border-box; }
|
| 64 |
button { width: 100%; padding: 15px; background: #4caf50; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; transition: 0.3s; }
|
|
|
|
| 65 |
.dashboard { display: none; grid-template-columns: 1fr 2fr; gap: 20px; margin-top: 20px; }
|
| 66 |
.card { background: #1e1e1e; padding: 20px; border-radius: 12px; }
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
.danger { color: #ff5252; font-weight: bold; }
|
| 69 |
.highlight { color: #4caf50; font-weight: bold; }
|
| 70 |
.details-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; }
|
| 71 |
.detail-item { background: #2c2c2e; padding: 15px; border-radius: 8px; }
|
|
|
|
| 72 |
</style>
|
| 73 |
</head>
|
| 74 |
<body>
|
| 75 |
<div class="container">
|
| 76 |
<h2>๐จ AI ๊ณผ๋๊ด๊ณ ํ์ง ๋์๋ณด๋ (Overview)</h2>
|
| 77 |
<div class="input-section">
|
| 78 |
-
<input type="text" id="video_url" placeholder="์ ํ๋ธ ์์ ๋งํฌ (
|
| 79 |
<input type="text" id="product_url" placeholder="์ํ ์์ธํ์ด์ง ๋งํฌ (ํ์)">
|
| 80 |
-
<button onclick="analyzeAd()">๋ถ์ ์์</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
</div>
|
| 82 |
-
|
|
|
|
|
|
|
| 83 |
<div class="dashboard" id="dashboard">
|
| 84 |
<div class="card"><h3>์ํ๋</h3><canvas id="scoreChart"></canvas><h1 id="scoreText"></h1><p id="statusText"></p></div>
|
| 85 |
<div class="card"><h3>AI ์ฌ๊ณ ํ๋ก</h3><div id="xaiReasoning"></div></div>
|
|
@@ -99,22 +71,53 @@ async def serve_frontend():
|
|
| 99 |
async function analyzeAd() {
|
| 100 |
const videoUrl = document.getElementById('video_url').value;
|
| 101 |
const productUrl = document.getElementById('product_url').value;
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
const
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 118 |
}
|
| 119 |
}
|
| 120 |
</script>
|
|
@@ -123,44 +126,3 @@ async def serve_frontend():
|
|
| 123 |
"""
|
| 124 |
return HTMLResponse(content=html_content)
|
| 125 |
|
| 126 |
-
# 2. ๋ถ์ API
|
| 127 |
-
@app.post("/api/analyze")
|
| 128 |
-
async def api_analyze(req: AdRequest):
|
| 129 |
-
try:
|
| 130 |
-
# Step 0: ๋ฐ์ดํฐ ์์ง
|
| 131 |
-
stt_text = ingestion.run_stt(ingestion.extract_audio_from_video(req.video_url)) if req.video_url.strip() else ""
|
| 132 |
-
ocr_text = ingestion.run_ocr_from_web(req.product_url) if req.product_url.strip() else ""
|
| 133 |
-
combined_text = f"{stt_text}\n{ocr_text}".strip()
|
| 134 |
-
|
| 135 |
-
if len(combined_text) < 5:
|
| 136 |
-
return {"status": "error", "error": "ํ
์คํธ๋ฅผ ์ฐพ์ง ๋ชปํ์ต๋๋ค."}
|
| 137 |
-
|
| 138 |
-
x1_score = lexical.calculate_x1_score(combined_text)
|
| 139 |
-
x2_score = semantic.calculate_x2_score(combined_text)
|
| 140 |
-
x3_score, matched_fact = rag_checker.calculate_x3_score(combined_text)
|
| 141 |
-
|
| 142 |
-
final_score, shap_vals, _ = xai_scorer.calculate_final_score_and_explain(x1_score, x2_score, x3_score)
|
| 143 |
-
|
| 144 |
-
# UI์ฉ ํ
์คํธ ์์ฑ (์ ๊ณตํด์ฃผ์ ๋ก์ง๊ณผ ๋์ผ)
|
| 145 |
-
detected_words = [word for word in lexical.lexicon.keys() if word in combined_text]
|
| 146 |
-
x1_details = f"์ ๋ฐ ๋จ์ด: {', '.join(detected_words)}" if detected_words else "๊ธ์น์ด ์์"
|
| 147 |
-
|
| 148 |
-
x2_details = f"๋ฌธ๋งฅ ์ํ๋: {x2_score:.1f}์ "
|
| 149 |
-
x3_details = f"์ฐธ์กฐ ๊ท์ : {matched_fact}"
|
| 150 |
-
|
| 151 |
-
xai_reasoning = "AI ๋ถ์ ์๋ฃ" # ๊ฐ๋จํ๊ฒ ์ถ์ฝ
|
| 152 |
-
|
| 153 |
-
return {
|
| 154 |
-
"status": "success",
|
| 155 |
-
"final_score": float(round(final_score, 2)),
|
| 156 |
-
"x1_details": x1_details,
|
| 157 |
-
"x2_details": x2_details,
|
| 158 |
-
"x3_details": x3_details,
|
| 159 |
-
"xai_reasoning": xai_reasoning
|
| 160 |
-
}
|
| 161 |
-
except Exception as e:
|
| 162 |
-
return {"status": "error", "error": str(e)}
|
| 163 |
-
|
| 164 |
-
if __name__ == "__main__":
|
| 165 |
-
# ๋ก์ปฌ ํ
์คํธ์ฉ
|
| 166 |
-
uvicorn.run("app:app", host="127.0.0.1", port=8000, reload=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# 1. ๐ ๋์๋ณด๋ ์น ํ๋ก ํธ์๋ (HTML)
|
| 2 |
@app.get("/", response_class=HTMLResponse)
|
| 3 |
async def serve_frontend():
|
|
|
|
| 4 |
html_content = """
|
| 5 |
<!DOCTYPE html>
|
| 6 |
<html lang="ko">
|
|
|
|
| 15 |
.input-section { background: #1e1e1e; padding: 20px; border-radius: 12px; margin-bottom: 20px; box-shadow: 0 4px 6px rgba(0,0,0,0.3); }
|
| 16 |
input { width: 100%; padding: 12px; margin: 8px 0; background: #2c2c2e; border: 1px solid #444; border-radius: 8px; color: #fff; box-sizing: border-box; }
|
| 17 |
button { width: 100%; padding: 15px; background: #4caf50; color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: bold; cursor: pointer; transition: 0.3s; }
|
| 18 |
+
button:disabled { background: #555; cursor: not-allowed; opacity: 0.7; }
|
| 19 |
.dashboard { display: none; grid-template-columns: 1fr 2fr; gap: 20px; margin-top: 20px; }
|
| 20 |
.card { background: #1e1e1e; padding: 20px; border-radius: 12px; }
|
| 21 |
+
|
| 22 |
+
/* ๐ ๋ก๋ฉ ์คํผ๋ CSS ์ถ๊ฐ */
|
| 23 |
+
#loading-spinner { display: none; text-align: center; margin: 30px 0; }
|
| 24 |
+
.spinner { display: inline-block; width: 50px; height: 50px; border: 5px solid #333; border-top: 5px solid #4caf50; border-radius: 50%; animation: spin 1s linear infinite; }
|
| 25 |
+
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
| 26 |
+
.loading-text { margin-top: 15px; font-size: 16px; font-weight: bold; color: #4caf50; }
|
| 27 |
+
.loading-subtext { font-size: 14px; color: #888; font-weight: normal; }
|
| 28 |
+
|
| 29 |
.danger { color: #ff5252; font-weight: bold; }
|
| 30 |
.highlight { color: #4caf50; font-weight: bold; }
|
| 31 |
.details-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; }
|
| 32 |
.detail-item { background: #2c2c2e; padding: 15px; border-radius: 8px; }
|
| 33 |
+
#error-message { display: none; background: #ff5252; color: white; padding: 15px; border-radius: 8px; margin-top: 20px; text-align: center; font-weight: bold; }
|
| 34 |
</style>
|
| 35 |
</head>
|
| 36 |
<body>
|
| 37 |
<div class="container">
|
| 38 |
<h2>๐จ AI ๊ณผ๋๊ด๊ณ ํ์ง ๋์๋ณด๋ (Overview)</h2>
|
| 39 |
<div class="input-section">
|
| 40 |
+
<input type="text" id="video_url" placeholder="์ ํ๋ธ ์์ ๋งํฌ (ํ์ฌ ํด๋ผ์ฐ๋ ํ๊ฒฝ์์๋ ๋น์๋๊ณ ํ
์คํธ ๊ถ์ฅ)">
|
| 41 |
<input type="text" id="product_url" placeholder="์ํ ์์ธํ์ด์ง ๋งํฌ (ํ์)">
|
| 42 |
+
<button id="analyze-btn" onclick="analyzeAd()">๋ถ์ ์์</button>
|
| 43 |
+
</div>
|
| 44 |
+
|
| 45 |
+
<div id="loading-spinner">
|
| 46 |
+
<div class="spinner"></div>
|
| 47 |
+
<p class="loading-text">
|
| 48 |
+
๐ง AI๊ฐ ์ด์ฌํ ํฉํธ์ฒดํฌ๋ฅผ ์งํ ์ค์
๋๋ค...<br>
|
| 49 |
+
<span class="loading-subtext">(๋ฅ๋ฌ๋ ๋ถ์์ผ๋ก ์ธํด ์ฝ 1~2๋ถ ์ ๋ ์์๋ ์ ์์ต๋๋ค)</span>
|
| 50 |
+
</p>
|
| 51 |
</div>
|
| 52 |
+
|
| 53 |
+
<div id="error-message"></div>
|
| 54 |
+
|
| 55 |
<div class="dashboard" id="dashboard">
|
| 56 |
<div class="card"><h3>์ํ๋</h3><canvas id="scoreChart"></canvas><h1 id="scoreText"></h1><p id="statusText"></p></div>
|
| 57 |
<div class="card"><h3>AI ์ฌ๊ณ ํ๋ก</h3><div id="xaiReasoning"></div></div>
|
|
|
|
| 71 |
async function analyzeAd() {
|
| 72 |
const videoUrl = document.getElementById('video_url').value;
|
| 73 |
const productUrl = document.getElementById('product_url').value;
|
| 74 |
+
|
| 75 |
+
// UI ์์ ๊ฐ์ ธ์ค๊ธฐ
|
| 76 |
+
const loadingSpinner = document.getElementById('loading-spinner');
|
| 77 |
+
const analyzeBtn = document.getElementById('analyze-btn');
|
| 78 |
+
const dashboard = document.getElementById('dashboard');
|
| 79 |
+
const detailsDashboard = document.getElementById('detailsDashboard');
|
| 80 |
+
const errorMsg = document.getElementById('error-message');
|
| 81 |
+
|
| 82 |
+
// ๐ ๋ก๋ฉ UI ์ผ๊ธฐ & ๋ฒํผ ์ ๊ธ
|
| 83 |
+
loadingSpinner.style.display = 'block';
|
| 84 |
+
analyzeBtn.disabled = true;
|
| 85 |
+
analyzeBtn.innerText = '๋ถ์ ์ค... โณ';
|
| 86 |
+
dashboard.style.display = 'none';
|
| 87 |
+
detailsDashboard.style.display = 'none';
|
| 88 |
+
errorMsg.style.display = 'none';
|
| 89 |
+
|
| 90 |
+
try {
|
| 91 |
+
const response = await fetch('/api/analyze', {
|
| 92 |
+
method: 'POST',
|
| 93 |
+
headers: { 'Content-Type': 'application/json' },
|
| 94 |
+
body: JSON.stringify({ video_url: videoUrl, product_url: productUrl })
|
| 95 |
+
});
|
| 96 |
+
|
| 97 |
+
const data = await response.json();
|
| 98 |
+
|
| 99 |
+
if (data.status === "success") {
|
| 100 |
+
dashboard.style.display = 'grid';
|
| 101 |
+
detailsDashboard.style.display = 'grid';
|
| 102 |
+
document.getElementById('scoreText').innerText = data.final_score + "์ ";
|
| 103 |
+
document.getElementById('xaiReasoning').innerHTML = data.xai_reasoning || "AI ๋ถ์ ์๋ฃ";
|
| 104 |
+
document.getElementById('x1Details').innerHTML = data.x1_details || "์ ๋ฐ ๋ด์ญ ์์";
|
| 105 |
+
document.getElementById('x2Details').innerHTML = data.x2_details || "์์ ํจ";
|
| 106 |
+
document.getElementById('x3Details').innerHTML = data.x3_details || "๊ท์ ์๋ฐ ์์";
|
| 107 |
+
} else {
|
| 108 |
+
// ๋ฐฑ์๋์์ ์๋ฌ๋ฅผ ๋ฐํํ ๊ฒฝ์ฐ
|
| 109 |
+
errorMsg.innerText = "โ ๋ถ์ ์คํจ: " + data.error;
|
| 110 |
+
errorMsg.style.display = 'block';
|
| 111 |
+
}
|
| 112 |
+
} catch (error) {
|
| 113 |
+
console.error('Fetch ์๋ฌ:', error);
|
| 114 |
+
errorMsg.innerText = "โ ์๋ฒ์ ํต์ ํ๋ ์ค ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.";
|
| 115 |
+
errorMsg.style.display = 'block';
|
| 116 |
+
} finally {
|
| 117 |
+
// ๐ ๋ก๋ฉ ๋๊ธฐ & ๋ฒํผ ์์๋ณต๊ตฌ
|
| 118 |
+
loadingSpinner.style.display = 'none';
|
| 119 |
+
analyzeBtn.disabled = false;
|
| 120 |
+
analyzeBtn.innerText = '๋ถ์ ์์';
|
| 121 |
}
|
| 122 |
}
|
| 123 |
</script>
|
|
|
|
| 126 |
"""
|
| 127 |
return HTMLResponse(content=html_content)
|
| 128 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
myapp/step0_ingestion.py
CHANGED
|
@@ -3,7 +3,7 @@ import gc
|
|
| 3 |
import requests
|
| 4 |
import torch
|
| 5 |
import whisper
|
| 6 |
-
|
| 7 |
from paddleocr import PaddleOCR
|
| 8 |
from playwright.sync_api import sync_playwright
|
| 9 |
import logging
|
|
@@ -20,13 +20,29 @@ class DataIngestionPipeline:
|
|
| 20 |
print("๐งน ๋ฉ๋ชจ๋ฆฌ ์ ๋ฆฌ ์๋ฃ")
|
| 21 |
|
| 22 |
def extract_audio_from_video(self, video_url, output_filename="temp_audio"):
|
| 23 |
-
print(f"\n๐ฅ [1] ์ ํ๋ธ ์์ ๋ค์ด๋ก๋ ์์: {video_url}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
try:
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
|
|
|
|
|
|
| 30 |
except Exception as e:
|
| 31 |
print(f"โ ๋ค์ด๋ก๋ ์คํจ: {e}")
|
| 32 |
return None
|
|
@@ -52,8 +68,12 @@ class DataIngestionPipeline:
|
|
| 52 |
|
| 53 |
raw_image_urls = []
|
| 54 |
with sync_playwright() as p:
|
| 55 |
-
print(" -> ๋ธ๋ผ์ฐ์ ์ฐฝ์ ๋์ฐ๊ณ ํ์ด์ง ๋ก๋ฉ์ ๊ธฐ๋ค๋ฆฝ๋๋ค...")
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
page = browser.new_page()
|
| 58 |
page.goto(product_url, wait_until="domcontentloaded", timeout=60000)
|
| 59 |
page.wait_for_timeout(3000)
|
|
@@ -151,28 +171,4 @@ class DataIngestionPipeline:
|
|
| 151 |
final_text = "\n".join(all_extracted_text) # ์ค๋ฐ๊ฟ์ผ๋ก ๊น๋ํ๊ฒ ํฉ์น๊ธฐ
|
| 152 |
print("\nโ
์นํ์ด์ง ์ด๋ฏธ์ง OCR ๋ณํ ์๋ฃ!")
|
| 153 |
return final_text
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
# ==========================================
|
| 157 |
-
# ์ค์ ์คํ ํ
์คํธ ์ฝ๋
|
| 158 |
-
# ==========================================
|
| 159 |
-
if __name__ == "__main__":
|
| 160 |
-
pipeline = DataIngestionPipeline()
|
| 161 |
-
|
| 162 |
-
# 1. ์ ํ๋ธ STT ํ
์คํธ
|
| 163 |
-
test_video_url = "https://youtu.be/SJxSDRxd8Dc?si=t8dnIQciFulUlbVW"
|
| 164 |
-
try:
|
| 165 |
-
audio_file = pipeline.extract_audio_from_video(test_video_url)
|
| 166 |
-
if audio_file:
|
| 167 |
-
stt_text = pipeline.run_stt(audio_file)
|
| 168 |
-
print(f"\n[STT ๊ฒฐ๊ณผ (์์ฑ -> ํ
์คํธ)]\n{stt_text[:500]}...\n")
|
| 169 |
-
except Exception as e:
|
| 170 |
-
print(f"์ ํ๋ธ/STT ์ฒ๋ฆฌ ์ค ์๋ฌ ๋ฐ์: {e}")
|
| 171 |
-
|
| 172 |
-
# 2. ์นํ์ด์ง OCR ํ
์คํธ
|
| 173 |
-
test_product_url = "https://brand.naver.com/pacsafe/products/9365045491"
|
| 174 |
-
try:
|
| 175 |
-
ocr_text = pipeline.run_ocr_from_web(test_product_url)
|
| 176 |
-
print(f"\n[์นํ์ด์ง OCR ๊ฒฐ๊ณผ (์์ธ ์ด๋ฏธ์ง -> ํ
์คํธ)]\n===========================\n{ocr_text}\n===========================")
|
| 177 |
-
except Exception as e:
|
| 178 |
-
print(f"OCR ์ฒ๋ฆฌ ์ค ์๋ฌ ๋ฐ์: {e}")
|
|
|
|
| 3 |
import requests
|
| 4 |
import torch
|
| 5 |
import whisper
|
| 6 |
+
import yt_dlp
|
| 7 |
from paddleocr import PaddleOCR
|
| 8 |
from playwright.sync_api import sync_playwright
|
| 9 |
import logging
|
|
|
|
| 20 |
print("๐งน ๋ฉ๋ชจ๋ฆฌ ์ ๋ฆฌ ์๋ฃ")
|
| 21 |
|
| 22 |
def extract_audio_from_video(self, video_url, output_filename="temp_audio"):
|
| 23 |
+
print(f"\n๐ฅ [1] ์ ํ๋ธ ์์ ๋ค์ด๋ก๋ ์์ (yt-dlp): {video_url}")
|
| 24 |
+
|
| 25 |
+
# ์ ํ๋ธ ๋ก๋ด ์ฐจ๋จ ์ฐํ ๋ฐ mp3 ์ถ์ถ ์ต์ ํ ์ต์
|
| 26 |
+
ydl_opts = {
|
| 27 |
+
'format': 'bestaudio/best',
|
| 28 |
+
'outtmpl': f'{output_filename}.%(ext)s',
|
| 29 |
+
'postprocessors': [{
|
| 30 |
+
'key': 'FFmpegExtractAudio',
|
| 31 |
+
'preferredcodec': 'mp3',
|
| 32 |
+
'preferredquality': '192',
|
| 33 |
+
}],
|
| 34 |
+
'quiet': True,
|
| 35 |
+
'no_warnings': True
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
try:
|
| 39 |
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 40 |
+
ydl.download([video_url])
|
| 41 |
+
|
| 42 |
+
# ๋ณํ๋ mp3 ํ์ผ๋ช
|
| 43 |
+
final_path = f"{output_filename}.mp3"
|
| 44 |
+
print(f"โ
์ค๋์ค ์ถ์ถ ์๋ฃ: {final_path}")
|
| 45 |
+
return final_path
|
| 46 |
except Exception as e:
|
| 47 |
print(f"โ ๋ค์ด๋ก๋ ์คํจ: {e}")
|
| 48 |
return None
|
|
|
|
| 68 |
|
| 69 |
raw_image_urls = []
|
| 70 |
with sync_playwright() as p:
|
| 71 |
+
print(" -> ๋ธ๋ผ์ฐ์ ์ฐฝ์ ๋ฐฑ๊ทธ๋ผ์ด๋๋ก ๋์ฐ๊ณ ํ์ด์ง ๋ก๋ฉ์ ๊ธฐ๋ค๋ฆฝ๋๋ค...")
|
| 72 |
+
# ๐จ ์ค์: ํ๊น
ํ์ด์ค(์๋ฒ) ํ๊ฒฝ์ ์ํ headless=True ๋ฐ ๋์ปค ํ์ ์ต์
์ถ๊ฐ!
|
| 73 |
+
browser = p.chromium.launch(
|
| 74 |
+
headless=True,
|
| 75 |
+
args=["--no-sandbox", "--disable-setuid-sandbox"]
|
| 76 |
+
)
|
| 77 |
page = browser.new_page()
|
| 78 |
page.goto(product_url, wait_until="domcontentloaded", timeout=60000)
|
| 79 |
page.wait_for_timeout(3000)
|
|
|
|
| 171 |
final_text = "\n".join(all_extracted_text) # ์ค๋ฐ๊ฟ์ผ๋ก ๊น๋ํ๊ฒ ํฉ์น๊ธฐ
|
| 172 |
print("\nโ
์นํ์ด์ง ์ด๋ฏธ์ง OCR ๋ณํ ์๋ฃ!")
|
| 173 |
return final_text
|
| 174 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -9,8 +9,8 @@ openai-whisper
|
|
| 9 |
--extra-index-url https://download.pytorch.org/whl/cpu
|
| 10 |
torch
|
| 11 |
sentence-transformers
|
| 12 |
-
pytubefix
|
| 13 |
python-mecab-ko
|
| 14 |
numpy
|
| 15 |
xgboost==1.7.6
|
| 16 |
-
shap
|
|
|
|
|
|
| 9 |
--extra-index-url https://download.pytorch.org/whl/cpu
|
| 10 |
torch
|
| 11 |
sentence-transformers
|
|
|
|
| 12 |
python-mecab-ko
|
| 13 |
numpy
|
| 14 |
xgboost==1.7.6
|
| 15 |
+
shap
|
| 16 |
+
yt-dlp
|