Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,9 +8,9 @@ import uuid
|
|
| 8 |
import shutil
|
| 9 |
import yt_dlp
|
| 10 |
import socket
|
|
|
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
# Prevents "[Errno -5] No address associated with hostname" on cloud hosts
|
| 14 |
orig_getaddrinfo = socket.getaddrinfo
|
| 15 |
def hooked_getaddrinfo(*args, **kwargs):
|
| 16 |
res = orig_getaddrinfo(*args, **kwargs)
|
|
@@ -21,7 +21,7 @@ app = FastAPI()
|
|
| 21 |
|
| 22 |
class VideoRequest(BaseModel):
|
| 23 |
url: str
|
| 24 |
-
platform: str
|
| 25 |
is_pro: bool = False
|
| 26 |
fps: int = 2
|
| 27 |
|
|
@@ -30,7 +30,11 @@ def file_to_base64(filepath):
|
|
| 30 |
with open(filepath, "rb") as f:
|
| 31 |
return base64.b64encode(f.read()).decode('utf-8')
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
@app.get("/", response_class=HTMLResponse)
|
| 35 |
def index():
|
| 36 |
return """
|
|
@@ -70,7 +74,7 @@ def index():
|
|
| 70 |
const results = document.getElementById('results');
|
| 71 |
|
| 72 |
btn.disabled = true;
|
| 73 |
-
btn.innerText = "Processing...
|
| 74 |
status.innerText = "Downloading and extracting frames...";
|
| 75 |
results.innerHTML = "";
|
| 76 |
|
|
@@ -87,11 +91,11 @@ def index():
|
|
| 87 |
const data = await res.json();
|
| 88 |
if(data.success) {
|
| 89 |
status.innerText = "Success! Extracted " + data.total_frames + " frames.";
|
| 90 |
-
data.frames.slice(0,
|
| 91 |
results.innerHTML += `<img src="data:image/jpeg;base64,${img}" class="rounded-lg">`;
|
| 92 |
});
|
| 93 |
} else {
|
| 94 |
-
status.innerText = "Error: " + data.
|
| 95 |
}
|
| 96 |
} catch(e) {
|
| 97 |
status.innerText = "Network Error: " + e.message;
|
|
@@ -104,7 +108,6 @@ def index():
|
|
| 104 |
</html>
|
| 105 |
"""
|
| 106 |
|
| 107 |
-
# ββ 3. MEDIA PROCESSING ROUTE ββ
|
| 108 |
@app.post("/process-video")
|
| 109 |
def process_video(req: VideoRequest):
|
| 110 |
job_id = str(uuid.uuid4())
|
|
@@ -114,28 +117,24 @@ def process_video(req: VideoRequest):
|
|
| 114 |
video_path = os.path.join(work_dir, "video.mp4")
|
| 115 |
audio_path = os.path.join(work_dir, "audio.mp3")
|
| 116 |
|
|
|
|
|
|
|
| 117 |
try:
|
| 118 |
-
#
|
| 119 |
ydl_opts = {
|
| 120 |
-
'format': '
|
| 121 |
'outtmpl': video_path,
|
| 122 |
'quiet': True,
|
| 123 |
'no_warnings': True,
|
| 124 |
'nocheckcertificate': True,
|
| 125 |
-
|
| 126 |
-
'
|
| 127 |
-
'add_header': [
|
| 128 |
-
'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
| 129 |
-
'Accept-Language: en-us',
|
| 130 |
-
'Connection: keep-alive',
|
| 131 |
-
]
|
| 132 |
}
|
| 133 |
|
| 134 |
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 135 |
-
ydl.download([
|
| 136 |
|
| 137 |
if not os.path.exists(video_path):
|
| 138 |
-
# Find the actual file if extension differed
|
| 139 |
files = os.listdir(work_dir)
|
| 140 |
if files: video_path = os.path.join(work_dir, files[0])
|
| 141 |
else: raise ValueError("Download failed - No file saved.")
|
|
@@ -164,10 +163,7 @@ def process_video(req: VideoRequest):
|
|
| 164 |
"-q:a", "0", "-map", "a", "-ac", "1", "-b:a", "64k", audio_path
|
| 165 |
], check=True, capture_output=True)
|
| 166 |
|
| 167 |
-
# COLLECT DATA
|
| 168 |
frame_files = sorted([f for f in os.listdir(work_dir) if f.startswith("frame_") and f.endswith(".jpg")])
|
| 169 |
-
|
| 170 |
-
# Max limit for AI context windows
|
| 171 |
if len(frame_files) > 80: frame_files = frame_files[:80]
|
| 172 |
|
| 173 |
frames_b64 = [file_to_base64(os.path.join(work_dir, f)) for f in frame_files]
|
|
@@ -186,6 +182,5 @@ def process_video(req: VideoRequest):
|
|
| 186 |
return {"success": False, "error": str(e)}
|
| 187 |
|
| 188 |
finally:
|
| 189 |
-
# π¨ GARBAGE COLLECTION
|
| 190 |
if os.path.exists(work_dir):
|
| 191 |
shutil.rmtree(work_dir, ignore_errors=True)
|
|
|
|
| 8 |
import shutil
|
| 9 |
import yt_dlp
|
| 10 |
import socket
|
| 11 |
+
import re
|
| 12 |
|
| 13 |
+
# Force IPv4
|
|
|
|
| 14 |
orig_getaddrinfo = socket.getaddrinfo
|
| 15 |
def hooked_getaddrinfo(*args, **kwargs):
|
| 16 |
res = orig_getaddrinfo(*args, **kwargs)
|
|
|
|
| 21 |
|
| 22 |
class VideoRequest(BaseModel):
|
| 23 |
url: str
|
| 24 |
+
platform: str
|
| 25 |
is_pro: bool = False
|
| 26 |
fps: int = 2
|
| 27 |
|
|
|
|
| 30 |
with open(filepath, "rb") as f:
|
| 31 |
return base64.b64encode(f.read()).decode('utf-8')
|
| 32 |
|
| 33 |
+
def clean_url(url: str):
|
| 34 |
+
# Removes trailing text like "This post is shared via..."
|
| 35 |
+
match = re.search(r'(https?://[^\s]+)', url)
|
| 36 |
+
return match.group(0) if match else url
|
| 37 |
+
|
| 38 |
@app.get("/", response_class=HTMLResponse)
|
| 39 |
def index():
|
| 40 |
return """
|
|
|
|
| 74 |
const results = document.getElementById('results');
|
| 75 |
|
| 76 |
btn.disabled = true;
|
| 77 |
+
btn.innerText = "Processing...";
|
| 78 |
status.innerText = "Downloading and extracting frames...";
|
| 79 |
results.innerHTML = "";
|
| 80 |
|
|
|
|
| 91 |
const data = await res.json();
|
| 92 |
if(data.success) {
|
| 93 |
status.innerText = "Success! Extracted " + data.total_frames + " frames.";
|
| 94 |
+
data.frames.slice(0, 12).forEach(img => {
|
| 95 |
results.innerHTML += `<img src="data:image/jpeg;base64,${img}" class="rounded-lg">`;
|
| 96 |
});
|
| 97 |
} else {
|
| 98 |
+
status.innerText = "Error: " + data.error;
|
| 99 |
}
|
| 100 |
} catch(e) {
|
| 101 |
status.innerText = "Network Error: " + e.message;
|
|
|
|
| 108 |
</html>
|
| 109 |
"""
|
| 110 |
|
|
|
|
| 111 |
@app.post("/process-video")
|
| 112 |
def process_video(req: VideoRequest):
|
| 113 |
job_id = str(uuid.uuid4())
|
|
|
|
| 117 |
video_path = os.path.join(work_dir, "video.mp4")
|
| 118 |
audio_path = os.path.join(work_dir, "audio.mp3")
|
| 119 |
|
| 120 |
+
target_url = clean_url(req.url)
|
| 121 |
+
|
| 122 |
try:
|
| 123 |
+
# π¨ FORMAT FIX: Use 'best' to avoid split-stream errors on TikTok/IG
|
| 124 |
ydl_opts = {
|
| 125 |
+
'format': 'best[height<=720]',
|
| 126 |
'outtmpl': video_path,
|
| 127 |
'quiet': True,
|
| 128 |
'no_warnings': True,
|
| 129 |
'nocheckcertificate': True,
|
| 130 |
+
'user_agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
|
| 131 |
+
'extractor_args': {'youtube': ['player_client=android,web']}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
}
|
| 133 |
|
| 134 |
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
| 135 |
+
ydl.download([target_url])
|
| 136 |
|
| 137 |
if not os.path.exists(video_path):
|
|
|
|
| 138 |
files = os.listdir(work_dir)
|
| 139 |
if files: video_path = os.path.join(work_dir, files[0])
|
| 140 |
else: raise ValueError("Download failed - No file saved.")
|
|
|
|
| 163 |
"-q:a", "0", "-map", "a", "-ac", "1", "-b:a", "64k", audio_path
|
| 164 |
], check=True, capture_output=True)
|
| 165 |
|
|
|
|
| 166 |
frame_files = sorted([f for f in os.listdir(work_dir) if f.startswith("frame_") and f.endswith(".jpg")])
|
|
|
|
|
|
|
| 167 |
if len(frame_files) > 80: frame_files = frame_files[:80]
|
| 168 |
|
| 169 |
frames_b64 = [file_to_base64(os.path.join(work_dir, f)) for f in frame_files]
|
|
|
|
| 182 |
return {"success": False, "error": str(e)}
|
| 183 |
|
| 184 |
finally:
|
|
|
|
| 185 |
if os.path.exists(work_dir):
|
| 186 |
shutil.rmtree(work_dir, ignore_errors=True)
|