Skip to content

Commit ef2e845

Browse files
committed
exploring moviepy done
1 parent 6a54a41 commit ef2e845

12 files changed

Lines changed: 321 additions & 0 deletions
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
# /// script
2+
# requires-python = ">=3.13"
3+
# dependencies = [
4+
# "moviepy",
5+
# "faster_whisper",
6+
# "pydub",
7+
# "onnxruntime",
8+
# "prompt_toolkit",
9+
# "openai",
10+
# "requests"
11+
# ]
12+
# ///
13+
14+
from moviepy import VideoFileClip
15+
from faster_whisper import WhisperModel
16+
from prompt_toolkit import prompt
17+
from prompt_toolkit.completion import PathCompleter
18+
from pathlib import Path
19+
import openai
20+
21+
import requests
22+
import os
23+
24+
from pydantic import BaseModel
25+
from typing import List
26+
27+
from uuid import uuid4
28+
29+
30+
class ScriptSegment(BaseModel):
31+
scene: str
32+
kecs: List[str]
33+
34+
35+
class Segment(BaseModel):
36+
logicalparts: List[str]
37+
38+
39+
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY") # Set this in your environment
40+
PEXELS_SEARCH_URL = "https://api.pexels.com/videos/search"
41+
42+
HEADERS = {"Authorization": PEXELS_API_KEY}
43+
44+
45+
def search_and_download_pexels_videos(
46+
kecs, output_dir="pexels_downloads", max_per_term=2
47+
):
48+
os.makedirs(output_dir, exist_ok=True)
49+
50+
for term in kecs:
51+
print(f"\n🔍 Searching Pexels for: {term}")
52+
params = {"query": term, "per_page": max_per_term}
53+
response = requests.get(PEXELS_SEARCH_URL, headers=HEADERS, params=params)
54+
55+
if response.status_code != 200:
56+
print(f"❌ Error searching '{term}': {response.text}")
57+
continue
58+
59+
videos = response.json().get("videos", [])
60+
for video in videos:
61+
url = video["video_files"][0]["link"]
62+
ext = url.split("?")[0].split(".")[-1]
63+
idx = str(uuid4())[:4]
64+
filename = f"{term.replace(' ', '_')}_{idx}.{ext}"
65+
66+
print(f"⬇️ Downloading: {filename}")
67+
vid_data = requests.get(url)
68+
with open(os.path.join(output_dir, filename), "wb") as f:
69+
f.write(vid_data.content)
70+
71+
72+
def select_mp4_file():
73+
print("Enter path to .mp4 file (Tab to autocomplete):")
74+
completer = PathCompleter(only_directories=False)
75+
path = prompt("File: ", completer=completer)
76+
77+
if path.lower().endswith(".mp4") and Path(path).exists():
78+
return path
79+
else:
80+
print("Invalid or non-existent file.")
81+
return None
82+
83+
84+
def extract_audio(mp4_path, audio_path="extracted_audio.wav"):
85+
video = VideoFileClip(mp4_path)
86+
video.audio.write_audiofile(audio_path, codec="pcm_s16le") # saves as WAV
87+
return audio_path
88+
89+
90+
def transcribe(audio_path, model_size="base"):
91+
model = WhisperModel(
92+
model_size, compute_type="int8"
93+
) # use "float16" if you have GPU
94+
segments, _ = model.transcribe(audio_path)
95+
96+
transcript = ""
97+
for segment in segments:
98+
transcript += f"{segment.text.strip()} "
99+
return transcript.strip()
100+
101+
102+
def write_transcript(transcript, transcript_file="transcript.txt"):
103+
with open(transcript_file, "w") as f:
104+
f.write(transcript)
105+
106+
107+
def extract_segments(transcript: str):
108+
SYSTEM_PROMPT = """
109+
You are a script analysis tool. Given a transcript, break it down into logical parts like scenes or topic sections of a script.
110+
return:
111+
- logicalparts: a list of parts from the given transcript.
112+
113+
Only respond in the provided JSON schema. No explanation.
114+
"""
115+
116+
client = openai.OpenAI()
117+
response = client.beta.chat.completions.parse(
118+
model="gpt-4o-mini-2024-07-18",
119+
messages=[
120+
{"role": "system", "content": SYSTEM_PROMPT},
121+
{"role": "user", "content": transcript},
122+
],
123+
temperature=0.5,
124+
response_format=Segment,
125+
)
126+
127+
return response.choices[0].message.parsed
128+
129+
130+
def extract_kecs(scene: str):
131+
SYSTEM_PROMPT = """
132+
You are a script analysis tool. Given a scene you have to provide the visual search keywords.
133+
return:
134+
- scene: the given scene
135+
- kecs: a list of 3 visual search keywords (KECs) that best represent the scene. Don't include character names, or other PIIs.
136+
137+
Only respond in the provided JSON schema. No explanation.
138+
"""
139+
140+
client = openai.OpenAI()
141+
response = client.beta.chat.completions.parse(
142+
model="gpt-4o-mini-2024-07-18",
143+
messages=[
144+
{"role": "system", "content": SYSTEM_PROMPT},
145+
{"role": "user", "content": f"The given scene is: {scene}"},
146+
],
147+
temperature=0.5,
148+
response_format=ScriptSegment,
149+
)
150+
151+
return response.choices[0].message.parsed
152+
153+
154+
if __name__ == "__main__":
155+
mp4_path = select_mp4_file()
156+
if not mp4_path:
157+
exit()
158+
159+
print(f"Selected: {mp4_path}")
160+
audio_path = extract_audio(mp4_path)
161+
162+
print("Transcribing...")
163+
transcript = transcribe(audio_path)
164+
165+
print("\n--- Transcript ---\n")
166+
print(transcript)
167+
write_transcript(transcript)
17 KB
Binary file not shown.

movie_py_explored/pjt1/comp1.mp4

2.01 MB
Binary file not shown.
2.97 MB
Binary file not shown.

movie_py_explored/pjt1/pjt1.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from moviepy import * # type: ignore
2+
import numpy as np
3+
4+
# starts with image clip
5+
6+
bg_image = ImageClip("pjt_bg.png").with_duration(110)
7+
8+
# bg_image.preview(fps=10)
9+
10+
topic_expl_list = [
11+
"Studying in top U.S. institutions connects you with motivated, driven peers—your future collaborators, co-founders, and lifelong professional network.",
12+
"Alumni from top universities often occupy leadership roles across industries, offering mentorship, referrals, and career opportunities for new graduates.",
13+
"Graduates from U.S. institutions often land jobs with competitive starting packages, thanks to strong brand value and skill-based training.",
14+
"The culture encourages excellence, critical thinking, and resilience—building the mindset that high performers and leaders thrive on.",
15+
"Students get access to real-world, high-impact projects through research labs, industry tie-ups, and team-based coursework.",
16+
"Top U.S. campuses are startup-friendly environments, with incubators, funding access, and mentors helping students turn ideas into companies.",
17+
"Programs like OPT and STEM extensions offer a legal path to work in the U.S. post-graduation, with higher chances of H1B sponsorship.",
18+
"Diverse campuses offer global perspectives, improving communication, empathy, and adaptability—skills essential in today’s world."
19+
]
20+
topics_list = [""
21+
"1) Network of high performance students",
22+
"2) Strong Alumni of the college",
23+
"3) Getting Higher starting salary",
24+
"4) Mindset of high achiever",
25+
"5) Working on high value project",
26+
"6) Innovation Hubs and Startups",
27+
"7) Higher Chances US Visa",
28+
"8) Exposure to different cultures"
29+
]
30+
31+
# Use method='label' (faster but single-line only):
32+
33+
topic_clips = [TextClip(font="Bebas-Regular.ttf",
34+
text=tp,
35+
font_size=175,
36+
color='white',
37+
method="label") for tp in topics_list]
38+
39+
topic_clips[0] = topic_clips[0].with_position((45, 100))
40+
topic_clips[1] = topic_clips[1].with_position((45, 250))
41+
topic_clips[2] = topic_clips[2].with_position((45, 400))
42+
topic_clips[3] = topic_clips[3].with_position((45, 550))
43+
topic_clips[4] = topic_clips[4].with_position((45, 650))
44+
topic_clips[5] = topic_clips[5].with_position((45, 750))
45+
topic_clips[6] = topic_clips[6].with_position((45, 850))
46+
topic_clips[7] = topic_clips[7].with_position((45, 950))
47+
48+
topic_clips[0] = topic_clips[0].with_start(5).with_end(bg_image.end)
49+
topic_clips[1] = topic_clips[1].with_start(17).with_end(bg_image.end)
50+
topic_clips[2] = topic_clips[2].with_start(29).with_end(bg_image.end)
51+
topic_clips[3] = topic_clips[3].with_start(41).with_end(bg_image.end)
52+
topic_clips[4] = topic_clips[4].with_start(53).with_end(bg_image.end)
53+
topic_clips[5] = topic_clips[5].with_start(65).with_end(bg_image.end)
54+
topic_clips[6] = topic_clips[6].with_start(77).with_end(bg_image.end)
55+
topic_clips[7] = topic_clips[7].with_start(89).with_end(bg_image.end)
56+
57+
# print("Topic 1 start:", topic_clips[7].start)
58+
59+
topic_clips[0] = topic_clips[0].with_effects([vfx.CrossFadeIn(1)])
60+
topic_clips[1] = topic_clips[1].with_effects([vfx.CrossFadeIn(1)])
61+
topic_clips[2] = topic_clips[2].with_effects([vfx.CrossFadeIn(1)])
62+
topic_clips[3] = topic_clips[3].with_effects([vfx.CrossFadeIn(1)])
63+
topic_clips[4] = topic_clips[4].with_effects([vfx.CrossFadeIn(1)])
64+
topic_clips[5] = topic_clips[5].with_effects([vfx.CrossFadeIn(1)])
65+
topic_clips[6] = topic_clips[6].with_effects([vfx.CrossFadeIn(1)])
66+
topic_clips[6] = topic_clips[6].with_effects([vfx.CrossFadeIn(1)])
67+
68+
compo1 = CompositeVideoClip([bg_image, topic_clips[0],topic_clips[1],
69+
topic_clips[2],topic_clips[3],
70+
topic_clips[4],topic_clips[5],
71+
topic_clips[6],topic_clips[7]])
72+
73+
74+
75+
topic_aud = AudioFileClip("topics.wav", fps=10)
76+
77+
topic_aud.with_start(0)
78+
79+
compo1.with_audio(topic_aud)
80+
81+
compo1.write_videofile("comp1.mp4", fps=10)

movie_py_explored/pjt1/pjt_bg.png

2.92 MB
Loading
3.33 MB
Binary file not shown.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from moviepy import * # type: ignore
2+
3+
txt = TextClip(font="Bebas-Regular.ttf", text="Hello", font_size=400, color="white")
4+
there = TextClip(font="Bebas-Regular.ttf", text="There", font_size=400, color="green")
5+
hru = TextClip(font="Bebas-Regular.ttf", text="How are you", font_size=400, color="green")
6+
7+
txt = txt.with_duration(5).with_effects([vfx.SlideIn(1, "left")]).with_position((45, 100))
8+
there = there.with_duration(5).with_effects([vfx.SlideIn(1, "left")]).with_position((745, 100))
9+
hru = hru.with_duration(5).with_effects([vfx.SlideIn(1, "left")]).with_position((45, 500))
10+
11+
bg = ImageClip("pjt_bg.png").with_duration(5)
12+
13+
video = CompositeVideoClip([bg, txt, there, hru])
14+
15+
video.write_videofile("simple.mp4", fps=5)
2.53 MB
Binary file not shown.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from moviepy import * # type: ignore
2+
3+
txt = TextClip(font="Bebas-Regular.ttf", text="Hello", font_size=140, color="white")
4+
there = TextClip(font="Bebas-Regular.ttf", text="There", font_size=140, color="yellow")
5+
hru = TextClip(font="Bebas-Regular.ttf", text="How are you", font_size=140, color="red")
6+
7+
txt = txt.with_duration(5).with_effects([vfx.CrossFadeIn(1)]).with_position((45, 20))
8+
there = there.with_duration(5).with_effects([vfx.CrossFadeIn(1)]).with_position((345, 20))
9+
hru = hru.with_duration(5).with_effects([vfx.CrossFadeIn(1)]).with_position((45, 150))
10+
11+
bg = VideoFileClip("registration.mp4")
12+
13+
video = CompositeVideoClip([bg, txt, there, hru])
14+
15+
video.write_videofile("simple_vid.mp4", fps=30)

0 commit comments

Comments
 (0)