sreepathi-ravikumar commited on
Commit
cf471fe
·
verified ·
1 Parent(s): a0f5f50

Update video2.py

Browse files
Files changed (1) hide show
  1. video2.py +81 -53
video2.py CHANGED
@@ -256,47 +256,68 @@ def audio_func(id, lines, lang):
256
  return asyncio.run(generate_tts(id, lines, lang))
257
  #-----------------------------
258
  #---------------------------------
259
- def video_func(id, lines,lang):
260
- duration, audio_path = audio_func(id,lines,lang)
 
 
 
 
 
 
 
 
 
 
261
  if not duration or not audio_path:
262
  print("Failed to generate audio.")
263
  return None
264
- #listf = lines.split("&&&")
265
- #TEXT = listf[0].strip()
266
- TEXT=lines[id]
267
  print("-----------------------------------------------------------------------------")
268
  print(TEXT)
269
  SKIP_SPACES = False
270
 
271
- FPS = 30 # Increased for smoother animation
272
- ANIMATION_FRAMES_PER_CHAR = 3 # Number of sub-frames for pen movement per character
273
- WIDTH, HEIGHT = 1280, 720 # Keep as is
274
  MARGIN_X, MARGIN_Y = 40, 60
275
- LINE_SPACING = 8 # additional px between lines
276
  FONT = cv2.FONT_HERSHEY_SIMPLEX
277
- FONT_SCALE = 1.5 # tweak for desired size
278
- THICKNESS = 2
279
- TEXT_COLOR = (0, 0, 0) # BGR
280
- BG_COLOR = (255, 255, 255) # BGR
 
 
 
281
  silent_video_name = f"silent_video{id}.mp4"
282
  silent_video_path = os.path.join(CLIPS_DIR, silent_video_name)
283
- FFMPEG_PRESET = "ultrafast" # fastest encode
284
- CRF = 23 # For faster encoding
285
  # Pen settings
286
- PEN_COLOR = (0, 0, 255) # Red pen for visibility (BGR)
287
- PEN_TIP_RADIUS = 5 # Size of pen tip circle
288
- PEN_LENGTH = 20 # Length of pen line
289
- PEN_THICKNESS = 2 # Thickness of pen line
290
- PEN_BASE_ANGLE = 45 # Base angle of pen (degrees)
291
- PEN_MOVEMENT_AMPLITUDE = 10 # How much the pen moves up/down (pixels)
292
  # ===================================
293
 
294
- # Helper: wrap text by pixel width using cv2.getTextSize
295
- def wrap_text_cv(text, font, font_scale, thickness, max_width):
296
  wrapped_lines = []
 
297
  for para in text.splitlines():
 
 
 
 
 
 
 
 
298
  if para == "":
299
- wrapped_lines.append("") # preserve blank line
 
300
  continue
301
  words = para.split(" ")
302
  cur = ""
@@ -308,6 +329,7 @@ def video_func(id, lines,lang):
308
  else:
309
  if cur != "":
310
  wrapped_lines.append(cur)
 
311
  (single_w, _), _ = cv2.getTextSize(w, font, font_scale, thickness)
312
  if single_w > max_width:
313
  chunk = ""
@@ -318,6 +340,7 @@ def video_func(id, lines,lang):
318
  chunk = cand2
319
  else:
320
  wrapped_lines.append(chunk)
 
321
  chunk = ch
322
  if chunk:
323
  cur = chunk
@@ -327,10 +350,12 @@ def video_func(id, lines,lang):
327
  cur = w
328
  if cur != "":
329
  wrapped_lines.append(cur)
330
- return wrapped_lines
331
- # Pre-wrap text
 
 
332
  text_area_width = WIDTH - 2 * MARGIN_X
333
- wrapped_lines = wrap_text_cv(TEXT, FONT, FONT_SCALE, THICKNESS, text_area_width)
334
  full_text = "\n".join(wrapped_lines)
335
  if not full_text:
336
  full_text = ""
@@ -345,20 +370,23 @@ def video_func(id, lines,lang):
345
  if total_glyphs == 0:
346
  print("No text to animate.")
347
  return None
348
- # Always render the minimal animation frames for the full text (no repeats or padding during rendering)
349
  min_frames = total_glyphs * ANIMATION_FRAMES_PER_CHAR
350
  print(f"Rendering {min_frames} minimal frames for full text animation.")
351
- # Pre-calc line heights and y_positions
352
  line_heights = []
353
- for line in wrapped_lines:
354
- if line == "":
355
- (w, h), baseline = cv2.getTextSize("Ay", FONT, FONT_SCALE, THICKNESS)
356
- else:
357
- (w, h), baseline = cv2.getTextSize(line, FONT, FONT_SCALE, THICKNESS)
358
- line_heights.append(h + baseline + LINE_SPACING)
359
  y_positions = []
360
  y = MARGIN_Y
361
- for lh in line_heights:
 
 
 
 
 
 
 
 
 
362
  y_positions.append(y)
363
  y += lh
364
  # Prepare ffmpeg
@@ -372,18 +400,22 @@ def video_func(id, lines,lang):
372
  print("FFMPEG CMD:", ffmpeg_cmd)
373
 
374
  proc = subprocess.Popen(shlex.split(ffmpeg_cmd), stdin=subprocess.PIPE, bufsize=10**8)
375
- # Render function, modified: if pen_x <= 0, no pen
376
  def render_frame(visible_text, pen_x, pen_y, anim_offset):
377
  img = np.full((HEIGHT, WIDTH, 3), BG_COLOR, dtype=np.uint8)
378
  lines = visible_text.split("\n")
379
  for idx, line in enumerate(lines):
 
 
 
 
380
  x = MARGIN_X
381
  y = y_positions[idx]
382
- (w, h), baseline = cv2.getTextSize(line, FONT, FONT_SCALE, THICKNESS)
383
  y_draw = y + h
384
  if line != "":
385
- cv2.putText(img, line, (x, y_draw), FONT, FONT_SCALE, TEXT_COLOR, THICKNESS, lineType=cv2.LINE_AA)
386
- if pen_x > 0: # Only draw pen if pen_x > 0
387
  offset_y = int(PEN_MOVEMENT_AMPLITUDE * math.sin(anim_offset * math.pi))
388
  pen_tip_y = pen_y + offset_y
389
  angle_rad = math.radians(PEN_BASE_ANGLE)
@@ -404,7 +436,10 @@ def video_func(id, lines,lang):
404
  lines = visible_sub.split("\n")
405
  last_line = lines[-1]
406
  line_idx = len(lines) - 1
407
- (w, h), baseline = cv2.getTextSize(last_line, FONT, FONT_SCALE, THICKNESS)
 
 
 
408
  pen_x = MARGIN_X + w + 5
409
  pen_y = y_positions[line_idx] + h // 2
410
  last_pen_x = pen_x
@@ -414,7 +449,6 @@ def video_func(id, lines,lang):
414
  proc.stdin.write(frame_img.tobytes())
415
  frames_sent += 1
416
  prev_visible_sub = visible_sub
417
- # No repeat or remaining frames added during rendering - full minimal animation only
418
  proc.stdin.close()
419
  proc.wait()
420
  elapsed = time.time() - t0
@@ -422,7 +456,7 @@ def video_func(id, lines,lang):
422
  if not os.path.exists(silent_video_path):
423
  print("Silent video generation failed.")
424
  return None
425
- # Now combine with audio using MoviePy: always render full text animation, then adjust speed to match audio duration
426
  final_video_name = f"clip{id}.mp4"
427
  final_video_path = os.path.join(CLIPS_DIR, final_video_name)
428
  video_clip = VideoFileClip(silent_video_path)
@@ -433,15 +467,9 @@ def video_func(id, lines,lang):
433
  print(f"Adjusting video speed by factor: {speed_factor:.3f}")
434
  video_clip = video_clip.fx(speedx, speed_factor)
435
  final_clip = video_clip.set_audio(AudioFileClip(audio_path))
436
- # Write final video
437
- final_clip.write_videofile(final_video_path, codec='libx264', audio_codec='aac', preset='ultrafast', verbose=False, logger=None)
438
- # Print the final video file name
439
  print(f"Final video saved at: {final_video_path}")
440
- # For notebook display (comment out if not needed in HF Spaces)
441
- # if os.path.exists(final_video_path):
442
- # display(Video(final_video_path, embed=True, width=WIDTH, height=HEIGHT))
443
- # Clean up silent video if not needed
444
  os.remove(silent_video_path)
445
- return final_video_path
446
-
447
- #video
 
256
  return asyncio.run(generate_tts(id, lines, lang))
257
  #-----------------------------
258
  #---------------------------------
259
+ import os
260
+ import subprocess
261
+ import shlex
262
+ import time
263
+ import math
264
+ import numpy as np
265
+ import cv2
266
+ from moviepy.editor import VideoFileClip, AudioFileClip
267
+ from moviepy.video.fx.speedx import speedx
268
+
269
+ def video_func(id, lines, lang):
270
+ duration, audio_path = audio_func(id, lines, lang)
271
  if not duration or not audio_path:
272
  print("Failed to generate audio.")
273
  return None
274
+ TEXT = lines[id]
 
 
275
  print("-----------------------------------------------------------------------------")
276
  print(TEXT)
277
  SKIP_SPACES = False
278
 
279
+ FPS = 30 # Keep for smoothness, but can reduce to 24 if needed for speed
280
+ ANIMATION_FRAMES_PER_CHAR = 2 # Reduced from 3 for faster rendering (less frames per char)
281
+ WIDTH, HEIGHT = 1280, 720
282
  MARGIN_X, MARGIN_Y = 40, 60
283
+ LINE_SPACING = 8
284
  FONT = cv2.FONT_HERSHEY_SIMPLEX
285
+ DEFAULT_FONT_SCALE = 1.5
286
+ HEADER_FONT_SCALE = 2.0 # Increased size for headers
287
+ DEFAULT_THICKNESS = 2
288
+ HEADER_THICKNESS = 3 # Bolder for headers
289
+ DEFAULT_TEXT_COLOR = (0, 0, 0) # BGR Black
290
+ HEADER_TEXT_COLOR = (255, 0, 0) # BGR Blue
291
+ BG_COLOR = (255, 255, 255) # BGR White
292
  silent_video_name = f"silent_video{id}.mp4"
293
  silent_video_path = os.path.join(CLIPS_DIR, silent_video_name)
294
+ FFMPEG_PRESET = "ultrafast"
295
+ CRF = 28 # Increased CRF for faster encoding (lower quality, but quicker)
296
  # Pen settings
297
+ PEN_COLOR = (0, 0, 255) # Red pen (BGR)
298
+ PEN_TIP_RADIUS = 5
299
+ PEN_LENGTH = 20
300
+ PEN_THICKNESS = 2
301
+ PEN_BASE_ANGLE = 45
302
+ PEN_MOVEMENT_AMPLITUDE = 10
303
  # ===================================
304
 
305
+ # Helper: wrap text by pixel width using cv2.getTextSize, now with per-line styles
306
+ def wrap_text_cv(text, font, default_font_scale, default_thickness, max_width):
307
  wrapped_lines = []
308
+ styles = [] # List of (is_header) for each wrapped line
309
  for para in text.splitlines():
310
+ is_header = para.strip().startswith("###")
311
+ if is_header:
312
+ para = para.strip()[3:].strip() # Remove "### " or "###"
313
+ font_scale = HEADER_FONT_SCALE
314
+ thickness = HEADER_THICKNESS
315
+ else:
316
+ font_scale = default_font_scale
317
+ thickness = default_thickness
318
  if para == "":
319
+ wrapped_lines.append("")
320
+ styles.append(False) # Not header
321
  continue
322
  words = para.split(" ")
323
  cur = ""
 
329
  else:
330
  if cur != "":
331
  wrapped_lines.append(cur)
332
+ styles.append(is_header)
333
  (single_w, _), _ = cv2.getTextSize(w, font, font_scale, thickness)
334
  if single_w > max_width:
335
  chunk = ""
 
340
  chunk = cand2
341
  else:
342
  wrapped_lines.append(chunk)
343
+ styles.append(is_header)
344
  chunk = ch
345
  if chunk:
346
  cur = chunk
 
350
  cur = w
351
  if cur != "":
352
  wrapped_lines.append(cur)
353
+ styles.append(is_header)
354
+ return wrapped_lines, styles
355
+
356
+ # Pre-wrap text with styles
357
  text_area_width = WIDTH - 2 * MARGIN_X
358
+ wrapped_lines, line_styles = wrap_text_cv(TEXT, FONT, DEFAULT_FONT_SCALE, DEFAULT_THICKNESS, text_area_width)
359
  full_text = "\n".join(wrapped_lines)
360
  if not full_text:
361
  full_text = ""
 
370
  if total_glyphs == 0:
371
  print("No text to animate.")
372
  return None
373
+ # Minimal frames
374
  min_frames = total_glyphs * ANIMATION_FRAMES_PER_CHAR
375
  print(f"Rendering {min_frames} minimal frames for full text animation.")
376
+ # Pre-calc line heights and y_positions with per-line styles
377
  line_heights = []
 
 
 
 
 
 
378
  y_positions = []
379
  y = MARGIN_Y
380
+ for i, line in enumerate(wrapped_lines):
381
+ is_header = line_styles[i]
382
+ font_scale = HEADER_FONT_SCALE if is_header else DEFAULT_FONT_SCALE
383
+ thickness = HEADER_THICKNESS if is_header else DEFAULT_THICKNESS
384
+ if line == "":
385
+ (w, h), baseline = cv2.getTextSize("Ay", FONT, font_scale, thickness)
386
+ else:
387
+ (w, h), baseline = cv2.getTextSize(line, FONT, font_scale, thickness)
388
+ lh = h + baseline + LINE_SPACING
389
+ line_heights.append(lh)
390
  y_positions.append(y)
391
  y += lh
392
  # Prepare ffmpeg
 
400
  print("FFMPEG CMD:", ffmpeg_cmd)
401
 
402
  proc = subprocess.Popen(shlex.split(ffmpeg_cmd), stdin=subprocess.PIPE, bufsize=10**8)
403
+ # Render function, now with per-line colors and styles
404
  def render_frame(visible_text, pen_x, pen_y, anim_offset):
405
  img = np.full((HEIGHT, WIDTH, 3), BG_COLOR, dtype=np.uint8)
406
  lines = visible_text.split("\n")
407
  for idx, line in enumerate(lines):
408
+ is_header = line_styles[idx]
409
+ font_scale = HEADER_FONT_SCALE if is_header else DEFAULT_FONT_SCALE
410
+ thickness = HEADER_THICKNESS if is_header else DEFAULT_THICKNESS
411
+ color = HEADER_TEXT_COLOR if is_header else DEFAULT_TEXT_COLOR
412
  x = MARGIN_X
413
  y = y_positions[idx]
414
+ (w, h), baseline = cv2.getTextSize(line, FONT, font_scale, thickness)
415
  y_draw = y + h
416
  if line != "":
417
+ cv2.putText(img, line, (x, y_draw), FONT, font_scale, color, thickness, lineType=cv2.LINE_AA)
418
+ if pen_x > 0:
419
  offset_y = int(PEN_MOVEMENT_AMPLITUDE * math.sin(anim_offset * math.pi))
420
  pen_tip_y = pen_y + offset_y
421
  angle_rad = math.radians(PEN_BASE_ANGLE)
 
436
  lines = visible_sub.split("\n")
437
  last_line = lines[-1]
438
  line_idx = len(lines) - 1
439
+ is_header = line_styles[line_idx]
440
+ font_scale = HEADER_FONT_SCALE if is_header else DEFAULT_FONT_SCALE
441
+ thickness = HEADER_THICKNESS if is_header else DEFAULT_THICKNESS
442
+ (w, h), baseline = cv2.getTextSize(last_line, FONT, font_scale, thickness)
443
  pen_x = MARGIN_X + w + 5
444
  pen_y = y_positions[line_idx] + h // 2
445
  last_pen_x = pen_x
 
449
  proc.stdin.write(frame_img.tobytes())
450
  frames_sent += 1
451
  prev_visible_sub = visible_sub
 
452
  proc.stdin.close()
453
  proc.wait()
454
  elapsed = time.time() - t0
 
456
  if not os.path.exists(silent_video_path):
457
  print("Silent video generation failed.")
458
  return None
459
+ # Combine with audio using MoviePy
460
  final_video_name = f"clip{id}.mp4"
461
  final_video_path = os.path.join(CLIPS_DIR, final_video_name)
462
  video_clip = VideoFileClip(silent_video_path)
 
467
  print(f"Adjusting video speed by factor: {speed_factor:.3f}")
468
  video_clip = video_clip.fx(speedx, speed_factor)
469
  final_clip = video_clip.set_audio(AudioFileClip(audio_path))
470
+ # Write final video with faster settings
471
+ final_clip.write_videofile(final_video_path, codec='libx264', audio_codec='aac', preset='ultrafast', verbose=False, logger=None, threads=4) # Added threads for multi-threading
 
472
  print(f"Final video saved at: {final_video_path}")
473
+ # Clean up
 
 
 
474
  os.remove(silent_video_path)
475
+ return final_video_path