|
1 | 1 | import cv2
|
2 | 2 | import re
|
3 |
| -from moviepy.editor import VideoClip, concatenate_videoclips, VideoFileClip |
| 3 | +from moviepy.editor import ( |
| 4 | + VideoClip, |
| 5 | + concatenate_videoclips, |
| 6 | + VideoFileClip, |
| 7 | + ImageClip, |
| 8 | + TextClip, |
| 9 | + CompositeVideoClip, |
| 10 | +) |
4 | 11 | from playground.actions_manager import agent_action
|
5 | 12 | import os
|
6 | 13 | from playground.global_values import GlobalValues
|
| 14 | +import numpy as np |
| 15 | +from moviepy.video.tools.segmenting import findObjects |
| 16 | + |
| 17 | +# Helper function for rotation matrix |
| 18 | +rotMatrix = lambda a: np.array([[np.cos(a), np.sin(a)], [-np.sin(a), np.cos(a)]]) |
| 19 | + |
| 20 | + |
| 21 | +# Define movement functions for text effect |
| 22 | +def vortex(screenpos, i, nletters): |
| 23 | + d = lambda t: 1.0 / (0.3 + t**8) # damping |
| 24 | + a = i * np.pi / nletters # angle of the movement |
| 25 | + v = rotMatrix(a).dot([-1, 0]) |
| 26 | + if i % 2: |
| 27 | + v[1] = -v[1] |
| 28 | + return lambda t: screenpos + 400 * d(t) * rotMatrix(0.5 * d(t) * a).dot(v) |
| 29 | + |
| 30 | + |
| 31 | +def cascade(screenpos, i, nletters): |
| 32 | + v = np.array([0, -1]) |
| 33 | + d = lambda t: 1 if t < 0 else abs(np.sinc(t) / (1 + t**4)) |
| 34 | + return lambda t: screenpos + v * 400 * d(t - 0.15 * i) |
| 35 | + |
| 36 | + |
| 37 | +def arrive(screenpos, i, nletters): |
| 38 | + v = np.array([-1, 0]) |
| 39 | + d = lambda t: max(0, 3 - 3 * t) |
| 40 | + return lambda t: screenpos - 400 * v * d(t - 0.2 * i) |
| 41 | + |
| 42 | + |
| 43 | +def vortexout(screenpos, i, nletters): |
| 44 | + d = lambda t: max(0, t) # damping |
| 45 | + a = i * np.pi / nletters # angle of the movement |
| 46 | + v = rotMatrix(a).dot([-1, 0]) |
| 47 | + if i % 2: |
| 48 | + v[1] = -v[1] |
| 49 | + return lambda t: screenpos + 400 * d(t - 0.1 * i) * rotMatrix(-0.2 * d(t) * a).dot( |
| 50 | + v |
| 51 | + ) |
| 52 | + |
| 53 | + |
| 54 | +@agent_action |
| 55 | +def add_text_effect_to_image( |
| 56 | + image_filename, output_filename, text, text_position="center", text_effect="vortex" |
| 57 | +): |
| 58 | + """ |
| 59 | + Adds a text effect to an image and creates a video. |
| 60 | +
|
| 61 | + This function takes an image and overlays a text effect onto it, creating a video |
| 62 | + with a specified duration. The text effect includes animations such as vortex, cascade, |
| 63 | + arrive, and vortexout. The position of the text can also be specified. |
| 64 | +
|
| 65 | + Parameters: |
| 66 | + image_filename (str): The filename of the input image. |
| 67 | + output_filename (str): The filename of the output video. |
| 68 | + text (str): The text to be displayed with effects. |
| 69 | + text_position (str): The position of the text in the video ('bottom', 'center', 'top'). |
| 70 | + text_effect (str): The text effect to apply ('vortexout', 'arrive', 'cascade', 'vortex'). |
| 71 | +
|
| 72 | + Returns: |
| 73 | + str: The filename of the output video. |
| 74 | + """ |
| 75 | + # Load the image |
| 76 | + image_path = os.path.join(GlobalValues.ASSISTANTS_WORKING_FOLDER, image_filename) |
| 77 | + image_clip = ImageClip(image_path) |
| 78 | + screensize = image_clip.size |
| 79 | + |
| 80 | + # Create the text clip |
| 81 | + txt_clip = TextClip(text, color="white", font="Amiri-Bold", kerning=5, fontsize=100) |
| 82 | + cvc = CompositeVideoClip([txt_clip.set_pos("center")], size=screensize) |
| 83 | + |
| 84 | + # Locate and separate each letter |
| 85 | + letters = findObjects(cvc) # a list of ImageClips |
| 86 | + |
| 87 | + # Function to move letters |
| 88 | + def moveLetters(letters, funcpos): |
| 89 | + return [ |
| 90 | + letter.set_pos(funcpos(letter.screenpos, i, len(letters))) |
| 91 | + for i, letter in enumerate(letters) |
| 92 | + ] |
| 93 | + |
| 94 | + # Select the text effect function and set duration based on the effect |
| 95 | + effect_function = { |
| 96 | + "vortex": (vortex, 5), |
| 97 | + "cascade": (cascade, 5), |
| 98 | + "arrive": (arrive, 5), |
| 99 | + "vortexout": (vortexout, 5), |
| 100 | + }.get(text_effect, (vortex, 5)) |
| 101 | + |
| 102 | + effect_func, duration = effect_function |
| 103 | + |
| 104 | + # Create a clip with the selected text effect |
| 105 | + effect_clip = CompositeVideoClip( |
| 106 | + moveLetters(letters, effect_func), size=screensize |
| 107 | + ).set_duration(duration) |
| 108 | + |
| 109 | + # Position the text |
| 110 | + position_dict = { |
| 111 | + "bottom": ("center", "bottom"), |
| 112 | + "center": ("center", "center"), |
| 113 | + "top": ("center", "top"), |
| 114 | + } |
| 115 | + text_position = position_dict.get(text_position, ("center", "center")) |
| 116 | + |
| 117 | + # Overlay the text effect on the image |
| 118 | + final_clip = CompositeVideoClip( |
| 119 | + [image_clip.set_duration(duration), effect_clip.set_pos(text_position)] |
| 120 | + ) |
| 121 | + |
| 122 | + # Write the result to a file |
| 123 | + output_path = os.path.join(GlobalValues.ASSISTANTS_WORKING_FOLDER, output_filename) |
| 124 | + final_clip.write_videofile(output_path, fps=25) |
| 125 | + return output_filename |
7 | 126 |
|
8 | 127 |
|
9 | 128 | def safe_eval(expr):
|
|
0 commit comments