yukee1992 commited on
Commit
40b0e60
·
verified ·
1 Parent(s): a2c9013

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +84 -109
app.py CHANGED
@@ -13,7 +13,7 @@ from huggingface_hub import HfApi, hf_hub_download
13
  from typing import Optional, List, Dict, Any
14
 
15
  print("=" * 60)
16
- print("🚀 TEXT STYLING API - WITHOUT GRADIO")
17
  print("=" * 60)
18
 
19
  # =============================================
@@ -151,8 +151,11 @@ def get_font_path(font_family):
151
  return font_info["path"]
152
  return None
153
 
 
 
 
154
  def create_text_overlay(input_video, output_video, text_style):
155
- """Add text overlay to video - FIXED for Chinese fonts"""
156
  font_path = get_font_path(text_style.font_family)
157
  if not font_path:
158
  print(f"⚠️ Font not found: {text_style.font_family}")
@@ -160,75 +163,88 @@ def create_text_overlay(input_video, output_video, text_style):
160
 
161
  print(f"✅ Using font: {font_path}")
162
 
163
- # Register font with fontconfig (so FFmpeg can find it properly)
164
- font_dir = os.path.dirname(font_path)
165
- font_name = os.path.basename(font_path).split('.')[0]
166
 
167
- # Create fontconfig configuration
168
- fc_config = f"""<?xml version="1.0"?>
169
- <!DOCTYPE fontconfig SYSTEM "fonts.dtd">
170
- <fontconfig>
171
- <dir>{font_dir}</dir>
172
- <alias>
173
- <family>myfont</family>
174
- <prefer>
175
- <family>{font_name}</family>
176
- </prefer>
177
- </alias>
178
- </fontconfig>"""
179
 
180
- fc_path = "/tmp/fonts.conf"
181
- with open(fc_path, 'w') as f:
182
- f.write(fc_config)
183
 
184
- os.environ['FONTCONFIG_FILE'] = fc_path
 
 
 
 
 
185
 
186
- # Position mapping
187
  pos_map = {
188
- "top-left": f"x={text_style.margin}:y={text_style.margin}",
189
- "top-right": f"x=w-tw-{text_style.margin}:y={text_style.margin}",
190
- "center": f"x=(w-tw)/2:y=(h-th)/2",
191
- "bottom-left": f"x={text_style.margin}:y=h-th-{text_style.margin}",
192
- "bottom-right": f"x=w-tw-{text_style.margin}:y=h-th-{text_style.margin}",
193
- "top-center": f"x=(w-tw)/2:y={text_style.margin}",
194
- "bottom-center": f"x=(w-tw)/2:y=h-th-{text_style.margin}"
 
 
195
  }
196
- position = pos_map.get(text_style.position, pos_map["center"])
197
 
198
- # Parse background color
199
- bg_parts = text_style.bg_color.split('@')
200
- bg_color = bg_parts[0]
201
- bg_opacity = float(bg_parts[1]) if len(bg_parts) > 1 else 0.5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
- # Use font family name instead of direct path
204
- drawtext_filter = (
205
- f"drawtext=text='{text_style.text}':"
206
- f"fontfamily={font_name}:"
207
- f"fontsize={text_style.font_size}:"
208
- f"fontcolor={text_style.color}:"
209
- f"{position}:"
210
- f"box=1:"
211
- f"boxcolor={bg_color}@{bg_opacity}:"
212
- f"boxborderw={text_style.padding}"
213
- )
214
 
215
- print(f"🎬 FFmpeg filter: {drawtext_filter}")
216
 
 
217
  cmd = [
218
  'ffmpeg', '-y',
219
  '-i', input_video,
220
- '-vf', drawtext_filter,
221
  '-c:a', 'copy',
222
  output_video
223
  ]
224
 
 
225
  result = subprocess.run(cmd, capture_output=True, text=True)
226
 
227
  if result.returncode != 0:
228
  print(f"❌ FFmpeg error: {result.stderr}")
229
  return False
230
 
231
- print(f"✅ Video styled successfully")
232
  return True
233
 
234
  # =============================================
@@ -251,29 +267,46 @@ async def style_video(request: StylingRequest):
251
  work_dir = f"/tmp/styling/{request.project_id}_{uuid.uuid4().hex[:8]}"
252
  os.makedirs(work_dir, exist_ok=True)
253
 
 
254
  video_path = os.path.join(work_dir, "input.mp4")
255
  download_file(request.video_url, video_path)
256
 
257
  current_video = video_path
258
 
 
259
  if request.title_overlay:
260
  titled_path = os.path.join(work_dir, "titled.mp4")
261
  if create_text_overlay(current_video, titled_path, request.title_overlay):
262
  current_video = titled_path
263
 
 
264
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
265
  styled_filename = f"styled_{timestamp}.mp4"
266
  styled_url = upload_to_dataset(current_video, request.project_id, styled_filename, "videos")
267
 
 
268
  shutil.rmtree(work_dir, ignore_errors=True)
269
 
270
  if styled_url:
271
- return StylingResponse(status="success", project_id=request.project_id, styled_video_url=styled_url)
 
 
 
 
272
  else:
273
- return StylingResponse(status="error", project_id=request.project_id, error="Upload failed")
 
 
 
 
274
 
275
  except Exception as e:
276
- return StylingResponse(status="error", project_id=request.project_id, error=str(e))
 
 
 
 
 
277
 
278
  @app.get("/")
279
  async def root():
@@ -288,64 +321,6 @@ async def root():
288
  "fonts_loaded": len(FONTS)
289
  }
290
 
291
- @app.get("/test-font/{font_name}")
292
- async def test_font(font_name: str):
293
- """Generate a test image with the specified font"""
294
- try:
295
- # Create a simple test image
296
- from PIL import Image, ImageDraw, ImageFont
297
- import io
298
- import base64
299
-
300
- # Get font path
301
- font_path = None
302
- for key, font_info in FONTS.items():
303
- if font_name in key or font_name in font_info["name"]:
304
- font_path = font_info["path"]
305
- break
306
-
307
- if not font_path:
308
- return {"error": f"Font {font_name} not found"}
309
-
310
- # Create image
311
- img = Image.new('RGB', (800, 400), color='white')
312
- d = ImageDraw.Draw(img)
313
-
314
- # Test texts
315
- test_texts = [
316
- "English Text Test",
317
- "中文测试文本",
318
- "荆南麦圆体测试",
319
- f"Font: {font_name}"
320
- ]
321
-
322
- y_position = 50
323
- for text in test_texts:
324
- try:
325
- font = ImageFont.truetype(font_path, 36)
326
- d.text((50, y_position), text, fill='black', font=font)
327
- y_position += 60
328
- except:
329
- d.text((50, y_position), f"Failed to render: {text}", fill='red', font=ImageFont.load_default())
330
- y_position += 60
331
-
332
- # Save to bytes
333
- img_bytes = io.BytesIO()
334
- img.save(img_bytes, format='PNG')
335
- img_bytes = img_bytes.getvalue()
336
-
337
- # Return as base64
338
- return {
339
- "font_name": font_name,
340
- "font_path": font_path,
341
- "test_image": base64.b64encode(img_bytes).decode('utf-8'),
342
- "message": "Image contains: English, Chinese, and font name"
343
- }
344
-
345
- except Exception as e:
346
- return {"error": str(e)}
347
-
348
-
349
  # =============================================
350
  # RUN
351
  # =============================================
 
13
  from typing import Optional, List, Dict, Any
14
 
15
  print("=" * 60)
16
+ print("🚀 TEXT STYLING API - ASS SUBTITLE METHOD")
17
  print("=" * 60)
18
 
19
  # =============================================
 
151
  return font_info["path"]
152
  return None
153
 
154
+ # =============================================
155
+ # ASS SUBTITLE METHOD - WORKS FOR CHINESE
156
+ # =============================================
157
  def create_text_overlay(input_video, output_video, text_style):
158
+ """Add text overlay using ASS subtitles - WORKS FOR CHINESE"""
159
  font_path = get_font_path(text_style.font_family)
160
  if not font_path:
161
  print(f"⚠️ Font not found: {text_style.font_family}")
 
163
 
164
  print(f"✅ Using font: {font_path}")
165
 
166
+ # Create working directory for ASS file
167
+ work_dir = os.path.dirname(output_video)
168
+ ass_file = os.path.join(work_dir, "subtitle.ass")
169
 
170
+ # Color mapping
171
+ color_map = {
172
+ "white": "FFFFFF", "black": "000000", "red": "FF0000",
173
+ "green": "00FF00", "blue": "0000FF", "yellow": "FFFF00",
174
+ "gold": "FFD700", "purple": "800080", "cyan": "00FFFF"
175
+ }
 
 
 
 
 
 
176
 
177
+ # Get font color
178
+ font_color_hex = color_map.get(text_style.color.lower(), "FFFFFF")
 
179
 
180
+ # Parse background color
181
+ bg_parts = text_style.bg_color.split('@')
182
+ bg_color_name = bg_parts[0]
183
+ bg_opacity = float(bg_parts[1]) if len(bg_parts) > 1 else 0.5
184
+ bg_color_hex = color_map.get(bg_color_name.lower(), "000000")
185
+ bg_alpha = int((1 - bg_opacity) * 255) # ASS opacity: 0=transparent, 255=opaque
186
 
187
+ # Map position to ASS alignment (1-9)
188
  pos_map = {
189
+ "bottom-left": 1,
190
+ "bottom-center": 2,
191
+ "bottom-right": 3,
192
+ "left": 4,
193
+ "center": 5,
194
+ "right": 6,
195
+ "top-left": 7,
196
+ "top-center": 8,
197
+ "top-right": 9
198
  }
199
+ alignment = pos_map.get(text_style.position, 5)
200
 
201
+ # Calculate margins
202
+ margin_l = text_style.margin if alignment in [1,4,7] else 0
203
+ margin_r = text_style.margin if alignment in [3,6,9] else 0
204
+ margin_v = text_style.margin
205
+
206
+ # Get font name from path (remove extension)
207
+ font_name = os.path.basename(font_path).split('.')[0]
208
+
209
+ # Create ASS file content with proper Chinese support
210
+ ass_content = f"""[Script Info]
211
+ ; Script generated by Video Styling Space
212
+ ScriptType: v4.00+
213
+ PlayResX: 1920
214
+ PlayResY: 1080
215
+ ScaledBorderAndShadow: yes
216
+
217
+ [V4+ Styles]
218
+ Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
219
+ Style: Default,{font_name},{text_style.font_size},&H00{font_color_hex},&H000000FF,&H00000000,&H{bg_alpha:02X}{bg_color_hex},0,0,0,0,100,100,0,0,1,1,0,{alignment},{margin_l},{margin_r},{margin_v},1
220
+
221
+ [Events]
222
+ Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
223
+ Dialogue: 0,0:00:00.00,0:00:10.00,Default,,0,0,0,,{text_style.text}"""
224
 
225
+ # Write ASS file with UTF-8 encoding (essential for Chinese)
226
+ with open(ass_file, 'w', encoding='utf-8') as f:
227
+ f.write(ass_content)
 
 
 
 
 
 
 
 
228
 
229
+ print(f"📝 Created ASS subtitle file")
230
 
231
+ # Use FFmpeg to burn subtitles
232
  cmd = [
233
  'ffmpeg', '-y',
234
  '-i', input_video,
235
+ '-vf', f"ass={ass_file}",
236
  '-c:a', 'copy',
237
  output_video
238
  ]
239
 
240
+ print(f"🎬 Running FFmpeg with ASS filter...")
241
  result = subprocess.run(cmd, capture_output=True, text=True)
242
 
243
  if result.returncode != 0:
244
  print(f"❌ FFmpeg error: {result.stderr}")
245
  return False
246
 
247
+ print(f"✅ Video styled successfully with ASS subtitles")
248
  return True
249
 
250
  # =============================================
 
267
  work_dir = f"/tmp/styling/{request.project_id}_{uuid.uuid4().hex[:8]}"
268
  os.makedirs(work_dir, exist_ok=True)
269
 
270
+ # Download video
271
  video_path = os.path.join(work_dir, "input.mp4")
272
  download_file(request.video_url, video_path)
273
 
274
  current_video = video_path
275
 
276
+ # Add title overlay if provided
277
  if request.title_overlay:
278
  titled_path = os.path.join(work_dir, "titled.mp4")
279
  if create_text_overlay(current_video, titled_path, request.title_overlay):
280
  current_video = titled_path
281
 
282
+ # Upload styled video
283
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
284
  styled_filename = f"styled_{timestamp}.mp4"
285
  styled_url = upload_to_dataset(current_video, request.project_id, styled_filename, "videos")
286
 
287
+ # Cleanup
288
  shutil.rmtree(work_dir, ignore_errors=True)
289
 
290
  if styled_url:
291
+ return StylingResponse(
292
+ status="success",
293
+ project_id=request.project_id,
294
+ styled_video_url=styled_url
295
+ )
296
  else:
297
+ return StylingResponse(
298
+ status="error",
299
+ project_id=request.project_id,
300
+ error="Failed to upload styled video"
301
+ )
302
 
303
  except Exception as e:
304
+ print(f" Error: {e}")
305
+ return StylingResponse(
306
+ status="error",
307
+ project_id=request.project_id,
308
+ error=str(e)
309
+ )
310
 
311
  @app.get("/")
312
  async def root():
 
321
  "fonts_loaded": len(FONTS)
322
  }
323
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  # =============================================
325
  # RUN
326
  # =============================================