Pepguy commited on
Commit
664cd68
·
verified ·
1 Parent(s): 48dce30

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +31 -45
app.py CHANGED
@@ -5,11 +5,10 @@ import base64
5
  import os
6
  import uuid
7
  import shutil
8
- import yt_dlp
9
- import socket
10
 
11
- # 🚨 THE FIX: Force IPv4 globally for this Python process
12
- # This prevents the "[Errno -5] No address associated with hostname" error
13
  orig_getaddrinfo = socket.getaddrinfo
14
  def hooked_getaddrinfo(*args, **kwargs):
15
  res = orig_getaddrinfo(*args, **kwargs)
@@ -20,11 +19,11 @@ app = FastAPI()
20
 
21
  @app.get("/")
22
  def greet_json():
23
- return {"status": "online", "message": "Viral Cat Media Server 1.6 - IPv4 Forced"}
24
 
25
  class VideoRequest(BaseModel):
26
  url: str
27
- platform: str
28
  is_pro: bool = False
29
  fps: int = 2
30
 
@@ -38,38 +37,24 @@ def process_video(req: VideoRequest):
38
  work_dir = f"/tmp/viralcat_{job_id}"
39
  os.makedirs(work_dir, exist_ok=True)
40
 
41
- video_path = f"{work_dir}/video.mp4"
42
- audio_path = f"{work_dir}/audio.mp3"
 
43
 
44
  try:
45
- # 1. Download Video
46
- # We use a very light config to avoid triggering YouTube's bot detection
47
- ydl_opts = {
48
- 'format': 'bestvideo[height<=480][ext=mp4]+bestaudio[ext=m4a]/best[height<=480][ext=mp4]/best',
49
- 'outtmpl': video_path,
50
- 'quiet': True,
51
- 'no_warnings': True,
52
- # Bypasses cloud IP blocking
53
- 'extractor_args': {'youtube': ['player_client=android,ios']},
54
- 'nocheckcertificate': True,
55
- 'cachedir': False
56
- }
57
-
58
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
59
- ydl.download([req.url])
60
-
61
- # Find the downloaded file (in case extension changed)
62
- downloaded_files = [f for f in os.listdir(work_dir) if os.path.isfile(os.path.join(work_dir, f))]
63
- if not downloaded_files:
64
- raise ValueError("File not found after download attempt.")
65
 
66
- actual_video_path = os.path.join(work_dir, downloaded_files[0])
67
-
68
- # 2. Extract Duration
 
 
69
  probe = subprocess.run([
70
  "ffprobe", "-v", "error", "-show_entries",
71
  "format=duration", "-of", "default=noprint_wrappers=1:nokey=1",
72
- actual_video_path
73
  ], capture_output=True, text=True, check=True)
74
 
75
  duration = float(probe.stdout.strip() or 0)
@@ -77,42 +62,43 @@ def process_video(req: VideoRequest):
77
  if duration > 120 and not req.is_pro:
78
  raise ValueError(f"Video is {duration:.1f}s. Max 120s for free users.")
79
 
80
- # 3. Extract Frames (2fps)
 
81
  subprocess.run([
82
- "ffmpeg", "-y", "-i", actual_video_path,
83
  "-vf", f"fps={req.fps}",
84
- "-q:v", "4", # Slightly lower quality (4) to keep base64 payload size reasonable
85
- f"{work_dir}/frame_%04d.jpg"
86
  ], check=True, capture_output=True)
87
 
88
- # 4. Extract Audio
89
  subprocess.run([
90
- "ffmpeg", "-y", "-i", actual_video_path,
91
  "-q:a", "0", "-map", "a", "-ac", "1", "-b:a", "64k", audio_path
92
  ], check=True, capture_output=True)
93
 
94
- # 5. Gather frames and convert
95
  frame_files = sorted([f for f in os.listdir(work_dir) if f.startswith("frame_") and f.endswith(".jpg")])
96
 
97
- # AI limits: If there are too many frames, the AI payload will crash.
98
- # We cap it at 50 frames max for this request.
99
- if len(frame_files) > 50:
100
- frame_files = frame_files[:50]
101
 
102
- frames_b64 =[file_to_base64(os.path.join(work_dir, f)) for f in frame_files]
103
  audio_b64 = file_to_base64(audio_path) if os.path.exists(audio_path) else None
104
 
105
  return {
106
  "success": True,
107
  "total_frames": len(frames_b64),
 
108
  "frames": frames_b64,
109
  "audio": audio_b64
110
  }
111
 
112
  except Exception as e:
 
113
  raise HTTPException(status_code=500, detail=str(e))
114
 
115
  finally:
116
- # 🚨 GARBAGE COLLECTION: Wipe the folder immediately
117
  if os.path.exists(work_dir):
118
  shutil.rmtree(work_dir, ignore_errors=True)
 
5
  import os
6
  import uuid
7
  import shutil
8
+ from pytube import YouTube
9
+ import socket
10
 
11
+ # Force IPv4 to prevent DNS errors on cloud hosting
 
12
  orig_getaddrinfo = socket.getaddrinfo
13
  def hooked_getaddrinfo(*args, **kwargs):
14
  res = orig_getaddrinfo(*args, **kwargs)
 
19
 
20
  @app.get("/")
21
  def greet_json():
22
+ return {"status": "online", "engine": "PyTube + FFmpeg"}
23
 
24
  class VideoRequest(BaseModel):
25
  url: str
26
+ platform: str # Note: PyTube will ignore this and assume YouTube
27
  is_pro: bool = False
28
  fps: int = 2
29
 
 
37
  work_dir = f"/tmp/viralcat_{job_id}"
38
  os.makedirs(work_dir, exist_ok=True)
39
 
40
+ video_filename = "video.mp4"
41
+ video_path = os.path.join(work_dir, video_filename)
42
+ audio_path = os.path.join(work_dir, "audio.mp3")
43
 
44
  try:
45
+ # 1. Download using PyTube
46
+ print(f"[PYTUBE] Downloading: {req.url}")
47
+ yt = YouTube(req.url)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
+ # Select the lowest resolution progressive stream to save bandwidth and time
50
+ stream = yt.streams.filter(progressive=True, file_extension='mp4').order_by('resolution').first()
51
+ stream.download(output_path=work_dir, filename=video_filename)
52
+
53
+ # 2. Extract Duration via ffprobe
54
  probe = subprocess.run([
55
  "ffprobe", "-v", "error", "-show_entries",
56
  "format=duration", "-of", "default=noprint_wrappers=1:nokey=1",
57
+ video_path
58
  ], capture_output=True, text=True, check=True)
59
 
60
  duration = float(probe.stdout.strip() or 0)
 
62
  if duration > 120 and not req.is_pro:
63
  raise ValueError(f"Video is {duration:.1f}s. Max 120s for free users.")
64
 
65
+ # 3. Extract Frames (FPS based)
66
+ # Using -q:v 2 for high quality frames so AI sees clearly
67
  subprocess.run([
68
+ "ffmpeg", "-y", "-i", video_path,
69
  "-vf", f"fps={req.fps}",
70
+ "-q:v", "2", f"{work_dir}/frame_%04d.jpg"
 
71
  ], check=True, capture_output=True)
72
 
73
+ # 4. Extract Audio (64k mono mp3 for small payload)
74
  subprocess.run([
75
+ "ffmpeg", "-y", "-i", video_path,
76
  "-q:a", "0", "-map", "a", "-ac", "1", "-b:a", "64k", audio_path
77
  ], check=True, capture_output=True)
78
 
79
+ # 5. Convert to Base64
80
  frame_files = sorted([f for f in os.listdir(work_dir) if f.startswith("frame_") and f.endswith(".jpg")])
81
 
82
+ # Safeguard: Limit to 60 frames max to prevent Node.js payload crash
83
+ if len(frame_files) > 60:
84
+ frame_files = frame_files[:60]
 
85
 
86
+ frames_b64 = [file_to_base64(os.path.join(work_dir, f)) for f in frame_files]
87
  audio_b64 = file_to_base64(audio_path) if os.path.exists(audio_path) else None
88
 
89
  return {
90
  "success": True,
91
  "total_frames": len(frames_b64),
92
+ "duration": duration,
93
  "frames": frames_b64,
94
  "audio": audio_b64
95
  }
96
 
97
  except Exception as e:
98
+ print(f"[ERROR] {str(e)}")
99
  raise HTTPException(status_code=500, detail=str(e))
100
 
101
  finally:
102
+ # CLEANUP
103
  if os.path.exists(work_dir):
104
  shutil.rmtree(work_dir, ignore_errors=True)