yukee1992 commited on
Commit
193a671
·
verified ·
1 Parent(s): e36676f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +12 -218
app.py CHANGED
@@ -1,4 +1,3 @@
1
- import gradio as gr
2
  import os
3
  import uuid
4
  import requests
@@ -14,7 +13,7 @@ from huggingface_hub import HfApi, hf_hub_download
14
  from typing import Optional, List, Dict, Any
15
 
16
  print("=" * 60)
17
- print("🚀 TEXT STYLING SPACE - WITH FONT DATASET")
18
  print("=" * 60)
19
 
20
  # =============================================
@@ -48,11 +47,8 @@ def download_all_fonts():
48
 
49
  for file in files:
50
  if file.endswith(('.ttf', '.otf')):
51
- category = file.split('/')[0]
52
  font_name = os.path.basename(file)
53
- local_path = os.path.join(FONTS_DIR, category, font_name)
54
-
55
- os.makedirs(os.path.dirname(local_path), exist_ok=True)
56
 
57
  if not os.path.exists(local_path):
58
  print(f"⬇️ Downloading font: {font_name}")
@@ -70,7 +66,6 @@ def download_all_fonts():
70
  fonts[font_key] = {
71
  "path": local_path,
72
  "name": font_name,
73
- "category": category,
74
  "display_name": font_name.replace('.ttf', '').replace('.otf', '').replace('-', ' ')
75
  }
76
 
@@ -111,19 +106,10 @@ class TextStyle(BaseModel):
111
  margin: int = 20
112
  padding: int = 10
113
 
114
- class CaptionStyle(BaseModel):
115
- font_family: str
116
- font_size: int = 32
117
- color: str = "white"
118
- bg_color: str = "black@0.7"
119
- position: str = "bottom"
120
-
121
  class StylingRequest(BaseModel):
122
  project_id: str
123
  video_url: str
124
  title_overlay: Optional[TextStyle] = None
125
- subtitles: Optional[CaptionStyle] = None
126
- watermark: Optional[TextStyle] = None
127
 
128
  class StylingResponse(BaseModel):
129
  status: str
@@ -135,7 +121,6 @@ class StylingResponse(BaseModel):
135
  # HELPER FUNCTIONS
136
  # =============================================
137
  def download_file(url, local_path):
138
- """Download file from URL"""
139
  response = requests.get(url, stream=True)
140
  response.raise_for_status()
141
  with open(local_path, 'wb') as f:
@@ -144,7 +129,6 @@ def download_file(url, local_path):
144
  return local_path
145
 
146
  def upload_to_dataset(file_path, project_id, filename, subfolder="videos"):
147
- """Upload file to HF Dataset"""
148
  if not HF_TOKEN:
149
  return None
150
  try:
@@ -161,54 +145,30 @@ def upload_to_dataset(file_path, project_id, filename, subfolder="videos"):
161
  return None
162
 
163
  def get_font_path(font_family):
164
- """Get font path from font family name"""
165
- # Try exact match
166
- if font_family in FONTS:
167
- return FONTS[font_family]["path"]
168
-
169
- # Try case-insensitive match
170
  font_family_lower = font_family.lower().replace(' ', '_')
171
  for key, font_info in FONTS.items():
172
  if font_family_lower in key or key in font_family_lower:
173
  return font_info["path"]
174
-
175
- # Try to find by display name
176
- for font_info in FONTS.values():
177
- if font_family.lower() in font_info["display_name"].lower():
178
- return font_info["path"]
179
-
180
- # Fallback to first Chinese font or None
181
- for font_info in FONTS.values():
182
- if font_info["category"] == "chinese":
183
- return font_info["path"]
184
-
185
  return None
186
 
187
  def create_text_overlay(input_video, output_video, text_style):
188
- """Add text overlay to video"""
189
  font_path = get_font_path(text_style.font_family)
190
  if not font_path:
191
- print(f"⚠️ Font not found: {text_style.font_family}")
192
  return False
193
 
194
- # Position mapping
195
  pos_map = {
196
  "top-left": f"x={text_style.margin}:y={text_style.margin}",
197
  "top-right": f"x=w-tw-{text_style.margin}:y={text_style.margin}",
198
  "center": f"x=(w-tw)/2:y=(h-th)/2",
199
  "bottom-left": f"x={text_style.margin}:y=h-th-{text_style.margin}",
200
  "bottom-right": f"x=w-tw-{text_style.margin}:y=h-th-{text_style.margin}",
201
- "top-center": f"x=(w-tw)/2:y={text_style.margin}",
202
- "bottom-center": f"x=(w-tw)/2:y=h-th-{text_style.margin}"
203
  }
204
  position = pos_map.get(text_style.position, pos_map["center"])
205
 
206
- # Parse background color
207
  bg_parts = text_style.bg_color.split('@')
208
  bg_color = bg_parts[0]
209
  bg_opacity = float(bg_parts[1]) if len(bg_parts) > 1 else 0.5
210
 
211
- # Create filter
212
  drawtext_filter = (
213
  f"drawtext=text='{text_style.text}':"
214
  f"fontfile={font_path}:"
@@ -220,26 +180,9 @@ def create_text_overlay(input_video, output_video, text_style):
220
  f"boxborderw={text_style.padding}"
221
  )
222
 
223
- cmd = [
224
- 'ffmpeg', '-y',
225
- '-i', input_video,
226
- '-vf', drawtext_filter,
227
- '-c:a', 'copy',
228
- output_video
229
- ]
230
-
231
  result = subprocess.run(cmd, capture_output=True, text=True)
232
- if result.returncode != 0:
233
- print(f"❌ Text overlay error: {result.stderr}")
234
- return False
235
-
236
- return True
237
-
238
- def add_subtitles(input_video, output_video, subtitle_style, tts_url=None):
239
- """Add subtitles to video"""
240
- # For now, just copy the video
241
- shutil.copy(input_video, output_video)
242
- return True
243
 
244
  # =============================================
245
  # API ENDPOINTS
@@ -247,104 +190,48 @@ def add_subtitles(input_video, output_video, subtitle_style, tts_url=None):
247
 
248
  @app.get("/health")
249
  async def health():
250
- return {
251
- "status": "healthy",
252
- "fonts_loaded": len(FONTS),
253
- "font_categories": list(set(f["category"] for f in FONTS.values()))
254
- }
255
 
256
  @app.get("/fonts")
257
  async def list_fonts():
258
- """List all available fonts"""
259
- font_list = []
260
- for key, font_info in FONTS.items():
261
- font_list.append({
262
- "id": key,
263
- "name": font_info["display_name"],
264
- "category": font_info["category"],
265
- "file": font_info["name"]
266
- })
267
- return {"fonts": font_list}
268
 
269
  @app.post("/api/style", response_model=StylingResponse)
270
- async def style_video(request: StylingRequest, background_tasks: BackgroundTasks):
271
- """Add text overlays and subtitles to video"""
272
  try:
273
  print(f"\n🎨 Styling video for project: {request.project_id}")
274
 
275
  work_dir = f"/tmp/styling/{request.project_id}_{uuid.uuid4().hex[:8]}"
276
  os.makedirs(work_dir, exist_ok=True)
277
 
278
- # Download base video
279
- print(f"⬇️ Downloading video from: {request.video_url}")
280
  video_path = os.path.join(work_dir, "input.mp4")
281
  download_file(request.video_url, video_path)
282
 
283
  current_video = video_path
284
 
285
- # Add title overlay if provided
286
  if request.title_overlay:
287
- print(f"📝 Adding title: {request.title_overlay.text}")
288
  titled_path = os.path.join(work_dir, "titled.mp4")
289
  if create_text_overlay(current_video, titled_path, request.title_overlay):
290
  current_video = titled_path
291
- print("✅ Title added")
292
-
293
- # Add subtitles if provided
294
- if request.subtitles:
295
- print(f"📝 Adding subtitles with font: {request.subtitles.font_family}")
296
- subtitled_path = os.path.join(work_dir, "subtitled.mp4")
297
- # For now, just copy
298
- shutil.copy(current_video, subtitled_path)
299
- current_video = subtitled_path
300
- print("✅ Subtitles added")
301
 
302
- # Add watermark if provided
303
- if request.watermark:
304
- print(f"💧 Adding watermark: {request.watermark.text}")
305
- watermarked_path = os.path.join(work_dir, "watermarked.mp4")
306
- if create_text_overlay(current_video, watermarked_path, request.watermark):
307
- current_video = watermarked_path
308
- print("✅ Watermark added")
309
-
310
- # Upload styled video
311
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
312
  styled_filename = f"styled_{timestamp}.mp4"
313
- styled_url = upload_to_dataset(
314
- current_video,
315
- request.project_id,
316
- styled_filename,
317
- "videos"
318
- )
319
 
320
- # Cleanup
321
  shutil.rmtree(work_dir, ignore_errors=True)
322
 
323
  if styled_url:
324
- return StylingResponse(
325
- status="success",
326
- project_id=request.project_id,
327
- styled_video_url=styled_url
328
- )
329
  else:
330
- return StylingResponse(
331
- status="error",
332
- project_id=request.project_id,
333
- error="Failed to upload styled video"
334
- )
335
 
336
  except Exception as e:
337
- print(f" Error: {e}")
338
- return StylingResponse(
339
- status="error",
340
- project_id=request.project_id,
341
- error=str(e)
342
- )
343
 
344
  @app.get("/")
345
  async def root():
346
  return {
347
- "name": "Text Styling Space",
348
  "version": "1.0.0",
349
  "endpoints": {
350
  "style": "POST /api/style",
@@ -354,99 +241,6 @@ async def root():
354
  "fonts_loaded": len(FONTS)
355
  }
356
 
357
- # =============================================
358
- # GRADIO INTERFACE (for testing)
359
- # =============================================
360
- def create_gradio_interface():
361
- with gr.Blocks(title="Text Styling Space", theme=gr.themes.Soft()) as demo:
362
- gr.Markdown("# 🎨 Text Styling Space")
363
- gr.Markdown("Add beautiful text overlays and captions to your videos")
364
-
365
- with gr.Row():
366
- with gr.Column(scale=1):
367
- project_id = gr.Textbox(
368
- label="Project ID",
369
- placeholder="Enter project ID"
370
- )
371
- video_url = gr.Textbox(
372
- label="Video URL",
373
- placeholder="URL from core combiner"
374
- )
375
-
376
- gr.Markdown("### Title Overlay")
377
- title_text = gr.Textbox(
378
- label="Title Text",
379
- placeholder="Enter title text"
380
- )
381
- title_font = gr.Dropdown(
382
- label="Font Family",
383
- choices=list(FONTS.keys()) if FONTS else [],
384
- value=list(FONTS.keys())[0] if FONTS else None
385
- )
386
- title_size = gr.Slider(24, 120, value=60, label="Font Size")
387
- title_color = gr.Dropdown(
388
- ["white", "black", "red", "gold", "yellow", "cyan", "purple"],
389
- value="gold",
390
- label="Font Color"
391
- )
392
- title_bg = gr.Dropdown(
393
- ["black", "white", "red", "blue", "purple"],
394
- value="black",
395
- label="Background Color"
396
- )
397
- title_opacity = gr.Slider(0.1, 1.0, value=0.5, label="Background Opacity")
398
- title_position = gr.Dropdown(
399
- ["center", "top-center", "bottom-center", "top-left", "top-right", "bottom-left", "bottom-right"],
400
- value="center",
401
- label="Position"
402
- )
403
-
404
- style_btn = gr.Button("🎨 Style Video", variant="primary")
405
-
406
- with gr.Column(scale=1):
407
- output = gr.JSON(label="Result")
408
- styled_video = gr.Video(label="Styled Video Preview")
409
-
410
- def style_video_ui(pid, vid_url, title_txt, t_font, t_size, t_color, t_bg, t_opacity, t_pos):
411
- # Build request
412
- request = {
413
- "project_id": pid,
414
- "video_url": vid_url,
415
- "title_overlay": {
416
- "text": title_txt,
417
- "font_family": t_font,
418
- "font_size": t_size,
419
- "color": t_color,
420
- "bg_color": f"{t_bg}@{t_opacity}",
421
- "position": t_pos,
422
- "margin": 20,
423
- "padding": 10
424
- } if title_txt else None
425
- }
426
-
427
- # Call API
428
- import requests
429
- response = requests.post(
430
- "http://localhost:7860/api/style",
431
- json=request
432
- )
433
- return response.json(), response.json().get("styled_video_url")
434
-
435
- style_btn.click(
436
- fn=style_video_ui,
437
- inputs=[
438
- project_id, video_url, title_text, title_font, title_size,
439
- title_color, title_bg, title_opacity, title_position
440
- ],
441
- outputs=[output, styled_video]
442
- )
443
-
444
- return demo
445
-
446
- # Mount Gradio interface
447
- demo = create_gradio_interface()
448
- app = gr.mount_gradio_app(app, demo, path="/")
449
-
450
  # =============================================
451
  # RUN
452
  # =============================================
 
 
1
  import os
2
  import uuid
3
  import requests
 
13
  from typing import Optional, List, Dict, Any
14
 
15
  print("=" * 60)
16
+ print("🚀 TEXT STYLING API - WITHOUT GRADIO")
17
  print("=" * 60)
18
 
19
  # =============================================
 
47
 
48
  for file in files:
49
  if file.endswith(('.ttf', '.otf')):
 
50
  font_name = os.path.basename(file)
51
+ local_path = os.path.join(FONTS_DIR, font_name)
 
 
52
 
53
  if not os.path.exists(local_path):
54
  print(f"⬇️ Downloading font: {font_name}")
 
66
  fonts[font_key] = {
67
  "path": local_path,
68
  "name": font_name,
 
69
  "display_name": font_name.replace('.ttf', '').replace('.otf', '').replace('-', ' ')
70
  }
71
 
 
106
  margin: int = 20
107
  padding: int = 10
108
 
 
 
 
 
 
 
 
109
  class StylingRequest(BaseModel):
110
  project_id: str
111
  video_url: str
112
  title_overlay: Optional[TextStyle] = None
 
 
113
 
114
  class StylingResponse(BaseModel):
115
  status: str
 
121
  # HELPER FUNCTIONS
122
  # =============================================
123
  def download_file(url, local_path):
 
124
  response = requests.get(url, stream=True)
125
  response.raise_for_status()
126
  with open(local_path, 'wb') as f:
 
129
  return local_path
130
 
131
  def upload_to_dataset(file_path, project_id, filename, subfolder="videos"):
 
132
  if not HF_TOKEN:
133
  return None
134
  try:
 
145
  return None
146
 
147
  def get_font_path(font_family):
 
 
 
 
 
 
148
  font_family_lower = font_family.lower().replace(' ', '_')
149
  for key, font_info in FONTS.items():
150
  if font_family_lower in key or key in font_family_lower:
151
  return font_info["path"]
 
 
 
 
 
 
 
 
 
 
 
152
  return None
153
 
154
  def create_text_overlay(input_video, output_video, text_style):
 
155
  font_path = get_font_path(text_style.font_family)
156
  if not font_path:
 
157
  return False
158
 
 
159
  pos_map = {
160
  "top-left": f"x={text_style.margin}:y={text_style.margin}",
161
  "top-right": f"x=w-tw-{text_style.margin}:y={text_style.margin}",
162
  "center": f"x=(w-tw)/2:y=(h-th)/2",
163
  "bottom-left": f"x={text_style.margin}:y=h-th-{text_style.margin}",
164
  "bottom-right": f"x=w-tw-{text_style.margin}:y=h-th-{text_style.margin}",
 
 
165
  }
166
  position = pos_map.get(text_style.position, pos_map["center"])
167
 
 
168
  bg_parts = text_style.bg_color.split('@')
169
  bg_color = bg_parts[0]
170
  bg_opacity = float(bg_parts[1]) if len(bg_parts) > 1 else 0.5
171
 
 
172
  drawtext_filter = (
173
  f"drawtext=text='{text_style.text}':"
174
  f"fontfile={font_path}:"
 
180
  f"boxborderw={text_style.padding}"
181
  )
182
 
183
+ cmd = ['ffmpeg', '-y', '-i', input_video, '-vf', drawtext_filter, '-c:a', 'copy', output_video]
 
 
 
 
 
 
 
184
  result = subprocess.run(cmd, capture_output=True, text=True)
185
+ return result.returncode == 0
 
 
 
 
 
 
 
 
 
 
186
 
187
  # =============================================
188
  # API ENDPOINTS
 
190
 
191
  @app.get("/health")
192
  async def health():
193
+ return {"status": "healthy", "fonts_loaded": len(FONTS)}
 
 
 
 
194
 
195
  @app.get("/fonts")
196
  async def list_fonts():
197
+ return {"fonts": list(FONTS.keys())}
 
 
 
 
 
 
 
 
 
198
 
199
  @app.post("/api/style", response_model=StylingResponse)
200
+ async def style_video(request: StylingRequest):
 
201
  try:
202
  print(f"\n🎨 Styling video for project: {request.project_id}")
203
 
204
  work_dir = f"/tmp/styling/{request.project_id}_{uuid.uuid4().hex[:8]}"
205
  os.makedirs(work_dir, exist_ok=True)
206
 
 
 
207
  video_path = os.path.join(work_dir, "input.mp4")
208
  download_file(request.video_url, video_path)
209
 
210
  current_video = video_path
211
 
 
212
  if request.title_overlay:
 
213
  titled_path = os.path.join(work_dir, "titled.mp4")
214
  if create_text_overlay(current_video, titled_path, request.title_overlay):
215
  current_video = titled_path
 
 
 
 
 
 
 
 
 
 
216
 
 
 
 
 
 
 
 
 
 
217
  timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
218
  styled_filename = f"styled_{timestamp}.mp4"
219
+ styled_url = upload_to_dataset(current_video, request.project_id, styled_filename, "videos")
 
 
 
 
 
220
 
 
221
  shutil.rmtree(work_dir, ignore_errors=True)
222
 
223
  if styled_url:
224
+ return StylingResponse(status="success", project_id=request.project_id, styled_video_url=styled_url)
 
 
 
 
225
  else:
226
+ return StylingResponse(status="error", project_id=request.project_id, error="Upload failed")
 
 
 
 
227
 
228
  except Exception as e:
229
+ return StylingResponse(status="error", project_id=request.project_id, error=str(e))
 
 
 
 
 
230
 
231
  @app.get("/")
232
  async def root():
233
  return {
234
+ "name": "Text Styling API",
235
  "version": "1.0.0",
236
  "endpoints": {
237
  "style": "POST /api/style",
 
241
  "fonts_loaded": len(FONTS)
242
  }
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  # =============================================
245
  # RUN
246
  # =============================================