Skip to content

Commit 2057181

Browse files
committed
Adding new video actions, not quite working the way I like though
1 parent f6bdf4c commit 2057181

File tree

5 files changed

+174
-14
lines changed

5 files changed

+174
-14
lines changed

‎logs/logs.txt

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
Running on local URL: http://127.0.0.1:7860
22

33
To create a public link, set `share=True` in `launch()`.
4-
action: create_image(args={"prompt":"a scary monster surrounded by happy children carrying a sign with a positive affirmation","model":"dall-e-3","size":"1024x1024","quality":"standard"}) -> a_scary_monster_surrounded_by_happy_children_carry.png
5-
assistant > Moviepy - Building video assistants_working_folder\monster_zoom.mp4.
6-
Moviepy - Writing video assistants_working_folder\monster_zoom.mp4
4+
assistant > action: create_image(args={"prompt":"A minimalistic office space with neutral colors, a simple desk, chair, and computer. The room is tidy and devoid of any personal touches or decorations. Lighting is plain and soft.","model":"dall-e-3","size":"1024x1024"}) -> A_minimalistic_office_space_with_neutral_colors_a_.png
5+
assistant > Moviepy - Building video assistants_working_folder\boring_office_with_text_effect.mp4.
6+
Moviepy - Writing video assistants_working_folder\boring_office_with_text_effect.mp4
77

88
Moviepy - Done !
9-
Moviepy - video ready assistants_working_folder\monster_zoom.mp4
10-
action: zoom_to(args={"image_filename":"a_scary_monster_surrounded_by_happy_children_carry.png","output_filename":"monster_zoom.mp4","duration":"10","zoom_factor_from":"2.0","zoom_factor_to":"1.0","zoom_start_position":"(0.5, 0.2)","zoom_end_position":"(0.5, 0.5)"}) -> monster_zoom.mp4
11-
assistant > MoviePy - Building file assistants_working_folder\monster_zoom.gif with imageio.
12-
action: create_gif_from_clip(args={"clip_filename":"monster_zoom.mp4","size":"512x512","fps":"15"}) -> GIF created: monster_zoom.gif
9+
Moviepy - video ready assistants_working_folder\boring_office_with_text_effect.mp4
10+
action: add_text_effect_to_image(args={"image_filename":"A_minimalistic_office_space_with_neutral_colors_a_.png","output_filename":"boring_office_with_text_effect.mp4","text":"Boring","text_position":"center","text_effect":"cascade"}) -> boring_office_with_text_effect.mp4
1311
assistant >

‎playground/assistant_actions/video_actions.py

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,128 @@
11
import cv2
22
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+
)
411
from playground.actions_manager import agent_action
512
import os
613
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
7126

8127

9128
def safe_eval(expr):

‎playground/assistant_actions/youtube_search.py

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,35 @@
88

99

1010
class YoutubeSearch:
11-
def __init__(self, search_terms: str, max_results=None):
11+
def __init__(self, search_terms: str, max_results=None, publish_time="today"):
1212
self.search_terms = search_terms
1313
self.max_results = int(max_results)
14+
self.filter = self._get_filter(publish_time)
1415
self.videos = self._search()
1516

17+
# Upload Date:
18+
# EgQIAhAB: Today
19+
# EgQIAxAB: This week
20+
# EgQIBBAB: This month
21+
# EgQIBRAB: This year
22+
23+
def _get_filter(self, publish_time):
24+
if publish_time == "today":
25+
return "EgQIAhAB"
26+
elif publish_time == "this week":
27+
return "EgQIAxAB"
28+
elif publish_time == "this month":
29+
return "EgQIBBAB"
30+
elif publish_time == "this year":
31+
return "EgQIBRAB"
32+
else:
33+
return "EgQIAhAB"
34+
1635
def _search(self):
1736
encoded_search = urllib.parse.quote_plus(self.search_terms)
1837
BASE_URL = "https://youtube.com"
1938
# This filter shows only videos uploaded in the last hour, sorted by relevance
20-
url = f"{BASE_URL}/results?search_query={encoded_search}&sp=EgQIARAB"
39+
url = f"{BASE_URL}/results?search_query={encoded_search}&sp={self.filter}"
2140
response = requests.get(url).text
2241
while "ytInitialData" not in response:
2342
response = requests.get(url).text
@@ -97,9 +116,32 @@ def to_json(self, clear_cache=True):
97116

98117

99118
@agent_action
100-
def search_youtube_videos(query: str, max_results=10):
101-
"""Searches for videos on YouTube based on the query string and returns the video titles and IDs."""
102-
results = YoutubeSearch(query, max_results=max_results)
119+
def search_youtube_videos(query: str, max_results=10, publish_time="this year"):
120+
"""
121+
Search for YouTube videos based on a query and filter by publish time.
122+
123+
This function uses the YouTube Search API to find videos matching the specified query.
124+
The results can be limited by the maximum number of results and filtered by the
125+
publish time (e.g., today, this week, this month, this year).
126+
127+
Args:
128+
query (str): The search query string.
129+
max_results (int, optional): The maximum number of search results to return. Default is 10.
130+
publish_time (str, optional): The time filter for the search results. Options include "today",
131+
"this week", "this month", and "this year". Default is "today".
132+
133+
Returns:
134+
list: A list of dictionaries containing video titles and their corresponding IDs.
135+
Each dictionary has the following keys:
136+
- "title" (str): The title of the video.
137+
- "id" (str): The YouTube ID of the video.
138+
139+
Example:
140+
>>> search_youtube_videos("create a YouTube channel", max_results=5, publish_time="this week")
141+
[{'title': 'How to Create a YouTube Channel in 2024 (Step-by-Step Tutorial)', 'id': 'abcd1234'},
142+
{'title': 'Creating a YouTube Channel: The Ultimate Guide', 'id': 'efgh5678'}]
143+
"""
144+
results = YoutubeSearch(query, max_results=max_results, publish_time=publish_time)
103145
videos = results.videos
104146
return [{"title": video["title"], "id": video["id"]} for video in videos]
105147

‎playground/assistants_utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,4 +176,4 @@ def submit_tool_outputs(self, tool_outputs, run_id):
176176
for text in stream.text_deltas:
177177
self.output_queue.put(("text", text))
178178
except Exception as e:
179-
self.output_queue.put(("text", str(e)))
179+
self.output_queue.put(("text", f"Error in tool outputs: {str(e)}"))

‎requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ python-pptx
1010
markdown
1111
moviepy
1212
opencv-python
13+
scipy

0 commit comments

Comments
 (0)