yonagush commited on
Commit
7698fcf
Β·
verified Β·
1 Parent(s): 1f1122c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +10 -17
app.py CHANGED
@@ -8,6 +8,12 @@ import asyncio
8
  import requests
9
  import numpy as np
10
  from PIL import Image, ImageDraw, ImageFont
 
 
 
 
 
 
11
  import gradio as gr
12
 
13
  # ---------- Constants ----------
@@ -66,16 +72,15 @@ async def _tts(text, voice, path):
66
  await edge_tts.Communicate(text, voice).save(path)
67
 
68
  def generate_audio(text, voice_key):
69
- # Sanitize text: remove problematic characters, ensure minimum length
70
- text = re.sub(r'[β€œβ€β€˜β€™]', '"', text) # replace smart quotes
71
- text = re.sub(r'[^\x00-\x7F]+', ' ', text) # remove non-ASCII
72
  text = re.sub(r'\s+', ' ', text).strip()
73
  if len(text) < 10:
74
  raise ValueError(f"Text too short ({len(text)} chars): '{text}'")
75
  print(f"[TTS] Original text length: {len(text)} chars")
76
  print(f"[TTS] Text snippet: {text[:200]}...")
77
 
78
- # Get voice ID
79
  voice_id = EDGE_VOICES.get(voice_key)
80
  if not voice_id:
81
  print(f"[TTS] Unknown voice key '{voice_key}', using default '{DEFAULT_VOICE}'")
@@ -83,7 +88,6 @@ def generate_audio(text, voice_key):
83
  print(f"[TTS] Using voice ID: {voice_id} (key: {voice_key})")
84
 
85
  out = tempfile.mktemp(suffix=".mp3")
86
- # Try selected voice, then fallback to default voice if it fails
87
  for attempt, try_voice in enumerate([voice_id, EDGE_VOICES[DEFAULT_VOICE]]):
88
  if attempt > 0:
89
  print(f"[TTS] Retry with fallback voice: {try_voice}")
@@ -122,14 +126,12 @@ def fetch_pexels_video(query, api_key):
122
  url = file["link"]
123
  break
124
  else:
125
- # fallback to any portrait
126
  for file in vid.get("video_files", []):
127
  if file.get("width", 0) < file.get("height", 0):
128
  url = file["link"]
129
  break
130
  else:
131
  continue
132
- # download
133
  r = requests.get(url, stream=True)
134
  out = tempfile.mktemp(suffix=".mp4")
135
  with open(out, "wb") as f:
@@ -140,10 +142,9 @@ def fetch_pexels_video(query, api_key):
140
  print(f"Pexels error: {e}")
141
  return None
142
 
143
- # ---------- Create final video (background loop + outro) ----------
144
  def create_reel(sentences, audio_path, bg_video_path, logo_path, outro_path):
145
  import moviepy.editor as mpe
146
- from moviepy.video.fx import loop
147
 
148
  W, H = VIDEO_W, VIDEO_H
149
  audio = mpe.AudioFileClip(audio_path)
@@ -153,7 +154,6 @@ def create_reel(sentences, audio_path, bg_video_path, logo_path, outro_path):
153
  # Background
154
  if bg_video_path and os.path.exists(bg_video_path):
155
  bg = mpe.VideoFileClip(bg_video_path)
156
- # Resize to cover 720x1280
157
  if bg.w / bg.h > W / H:
158
  bg = bg.resize(height=H)
159
  else:
@@ -162,11 +162,9 @@ def create_reel(sentences, audio_path, bg_video_path, logo_path, outro_path):
162
  if bg.duration < total_dur:
163
  bg = mpe.concatenate_videoclips([bg] * int(np.ceil(total_dur / bg.duration)))
164
  bg = bg.subclip(0, total_dur)
165
- # darken for text readability
166
  dark = mpe.ColorClip((W, H), color=(0,0,0)).set_opacity(0.3).set_duration(total_dur)
167
  bg_layer = mpe.CompositeVideoClip([bg, dark])
168
  else:
169
- # gradient fallback
170
  frame = np.zeros((H, W, 3), dtype=np.uint8)
171
  for i in range(H):
172
  frame[i] = [int(30 + i/H*100), int(20 + i/H*50), int(40 + i/H*80)]
@@ -177,7 +175,6 @@ def create_reel(sentences, audio_path, bg_video_path, logo_path, outro_path):
177
  img = Image.new("RGBA", (W, H), (0,0,0,0))
178
  draw = ImageDraw.Draw(img)
179
  font = get_font(60)
180
- # wrap
181
  words = text.split()
182
  lines = []
183
  cur = []
@@ -198,7 +195,6 @@ def create_reel(sentences, audio_path, bg_video_path, logo_path, outro_path):
198
  x = (W - (bbox[2]-bbox[0]))//2
199
  draw.text((x, y), line, font=font, fill=(255,255,255))
200
  y += line_h
201
- # logo overlay if provided
202
  if logo_path and os.path.exists(logo_path):
203
  try:
204
  logo = Image.open(logo_path).convert("RGBA")
@@ -259,16 +255,13 @@ def create_video_from_script(script_text, groq_key, pexels_key, voice_key,
259
  return None, "❌ No script"
260
  full_script = " ".join(sentences)
261
 
262
- # Generate audio (with retry and fallback)
263
  try:
264
  audio_path = generate_audio(full_script, voice_key)
265
  except Exception as e:
266
  return None, f"❌ Audio generation failed: {str(e)}"
267
 
268
- # fetch background video from Pexels
269
  bg_video = None
270
  if pexels_key:
271
- # simple keyword from first sentence
272
  kw = re.sub(r'[^\w\s]', '', sentences[0]).split()[:3]
273
  kw = " ".join(kw) if kw else "news"
274
  print(f"[Pexels] Searching for: {kw}")
 
8
  import requests
9
  import numpy as np
10
  from PIL import Image, ImageDraw, ImageFont
11
+
12
+ # ---------- PILLOW COMPATIBILITY FIX ----------
13
+ # MoviePy uses Image.ANTIALIAS which was removed in Pillow 10+
14
+ if not hasattr(Image, 'ANTIALIAS'):
15
+ Image.ANTIALIAS = Image.LANCZOS
16
+
17
  import gradio as gr
18
 
19
  # ---------- Constants ----------
 
72
  await edge_tts.Communicate(text, voice).save(path)
73
 
74
  def generate_audio(text, voice_key):
75
+ # Sanitize text
76
+ text = re.sub(r'[β€œβ€β€˜β€™]', '"', text)
77
+ text = re.sub(r'[^\x00-\x7F]+', ' ', text)
78
  text = re.sub(r'\s+', ' ', text).strip()
79
  if len(text) < 10:
80
  raise ValueError(f"Text too short ({len(text)} chars): '{text}'")
81
  print(f"[TTS] Original text length: {len(text)} chars")
82
  print(f"[TTS] Text snippet: {text[:200]}...")
83
 
 
84
  voice_id = EDGE_VOICES.get(voice_key)
85
  if not voice_id:
86
  print(f"[TTS] Unknown voice key '{voice_key}', using default '{DEFAULT_VOICE}'")
 
88
  print(f"[TTS] Using voice ID: {voice_id} (key: {voice_key})")
89
 
90
  out = tempfile.mktemp(suffix=".mp3")
 
91
  for attempt, try_voice in enumerate([voice_id, EDGE_VOICES[DEFAULT_VOICE]]):
92
  if attempt > 0:
93
  print(f"[TTS] Retry with fallback voice: {try_voice}")
 
126
  url = file["link"]
127
  break
128
  else:
 
129
  for file in vid.get("video_files", []):
130
  if file.get("width", 0) < file.get("height", 0):
131
  url = file["link"]
132
  break
133
  else:
134
  continue
 
135
  r = requests.get(url, stream=True)
136
  out = tempfile.mktemp(suffix=".mp4")
137
  with open(out, "wb") as f:
 
142
  print(f"Pexels error: {e}")
143
  return None
144
 
145
+ # ---------- Create final video ----------
146
  def create_reel(sentences, audio_path, bg_video_path, logo_path, outro_path):
147
  import moviepy.editor as mpe
 
148
 
149
  W, H = VIDEO_W, VIDEO_H
150
  audio = mpe.AudioFileClip(audio_path)
 
154
  # Background
155
  if bg_video_path and os.path.exists(bg_video_path):
156
  bg = mpe.VideoFileClip(bg_video_path)
 
157
  if bg.w / bg.h > W / H:
158
  bg = bg.resize(height=H)
159
  else:
 
162
  if bg.duration < total_dur:
163
  bg = mpe.concatenate_videoclips([bg] * int(np.ceil(total_dur / bg.duration)))
164
  bg = bg.subclip(0, total_dur)
 
165
  dark = mpe.ColorClip((W, H), color=(0,0,0)).set_opacity(0.3).set_duration(total_dur)
166
  bg_layer = mpe.CompositeVideoClip([bg, dark])
167
  else:
 
168
  frame = np.zeros((H, W, 3), dtype=np.uint8)
169
  for i in range(H):
170
  frame[i] = [int(30 + i/H*100), int(20 + i/H*50), int(40 + i/H*80)]
 
175
  img = Image.new("RGBA", (W, H), (0,0,0,0))
176
  draw = ImageDraw.Draw(img)
177
  font = get_font(60)
 
178
  words = text.split()
179
  lines = []
180
  cur = []
 
195
  x = (W - (bbox[2]-bbox[0]))//2
196
  draw.text((x, y), line, font=font, fill=(255,255,255))
197
  y += line_h
 
198
  if logo_path and os.path.exists(logo_path):
199
  try:
200
  logo = Image.open(logo_path).convert("RGBA")
 
255
  return None, "❌ No script"
256
  full_script = " ".join(sentences)
257
 
 
258
  try:
259
  audio_path = generate_audio(full_script, voice_key)
260
  except Exception as e:
261
  return None, f"❌ Audio generation failed: {str(e)}"
262
 
 
263
  bg_video = None
264
  if pexels_key:
 
265
  kw = re.sub(r'[^\w\s]', '', sentences[0]).split()[:3]
266
  kw = " ".join(kw) if kw else "news"
267
  print(f"[Pexels] Searching for: {kw}")