ujwal55 commited on
Commit
741bed3
·
1 Parent(s): 468ff2e
Files changed (2) hide show
  1. app.py +278 -307
  2. cookies/cookies.txt +0 -0
app.py CHANGED
@@ -1,360 +1,331 @@
1
- # app.py
2
- import os
3
- import subprocess
 
 
 
 
 
4
  import gradio as gr
5
- from youtube_search import YoutubeSearch
6
 
7
- OUTPUT_DIR = "video_clips"
8
- os.makedirs(OUTPUT_DIR, exist_ok=True)
9
 
10
- def download_first_video(topic):
11
- results = YoutubeSearch(topic + " physics animation", max_results=1).to_dict()
12
- if not results:
13
- return "No videos found."
14
 
15
- video_url = "https://www.youtube.com" + results[0]['url_suffix']
16
- video_id = video_url.split("v=")[-1]
17
- output_path = os.path.join(OUTPUT_DIR, f"{video_id}.mp4")
 
18
 
19
- try:
20
- subprocess.run([
21
- "yt-dlp", "-f", "mp4", "-o", output_path, video_url
22
- ], check=True)
23
- return f"✅ Downloaded: {video_url}"
24
- except Exception as e:
25
- return f"❌ Error: {str(e)}"
26
-
27
- gr.Interface(
28
- fn=download_first_video,
29
- inputs=gr.Textbox(label="Enter topic", placeholder="Newton's Laws"),
30
- outputs="text",
31
- title="YouTube Video Downloader"
32
- ).launch()
33
-
34
-
35
- # """
36
- # Physics Chapter Video Generator
37
- # Creates educational videos by combining title cards with relevant content.
38
- # """
39
-
40
- # import re, shutil, subprocess, textwrap, os, tempfile
41
- # from pathlib import Path
42
- # from typing import List, Optional
43
- # import gradio as gr
44
- # import random
45
-
46
- # # --- Oxylabs Proxy Configuration ---
47
- # PROXY_USERNAME = "ujwal_CmiMZ"
48
- # PROXY_PASSWORD = "xJv4DChht5P6y+u"
49
- # PROXY_COUNTRY = "US"
50
-
51
- # # List of proxy endpoints (Oxylabs DC Proxies)
52
- # PROXY_PORTS = [8001, 8002, 8003, 8004, 8005]
53
- # PROXY_HOST = "dc.oxylabs.io"
54
 
55
- # def get_random_proxy():
56
- # port = random.choice(PROXY_PORTS)
57
- # return f"http://user-{PROXY_USERNAME}-country-{PROXY_COUNTRY}:{PROXY_PASSWORD}@{PROXY_HOST}:{port}"
58
 
59
 
60
- # # ---------------- CONFIG ----------------
61
- # TITLE_DUR = 3 # seconds
62
- # SIZE = "1280x720" # resolution for cards
63
- # FPS = 30
64
- # CRF = 28 # Higher CRF for smaller files in HF Spaces
65
- # PRESET = "ultrafast" # Fastest encoding for HF Spaces
66
- # YT_MAX_RESULTS = 2 # Reduced for faster processing
67
- # MAX_VIDEO_LENGTH = 30 # Max seconds per video clip
68
- # MAX_TOPICS = 8 # Limit topics for HF Spaces resources
69
- # # ----------------------------------------
70
 
71
- # # ---------- helpers ----------
72
- # def run_cmd(cmd: list[str], timeout: int = 120) -> bool:
73
- # """Run command with timeout and proper error handling"""
74
- # try:
75
- # result = subprocess.run(
76
- # cmd,
77
- # check=True,
78
- # timeout=timeout,
79
- # capture_output=True,
80
- # text=True
81
- # )
82
- # return True
83
- # except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
84
- # print(f"Command failed:\n{' '.join(cmd)}\nError:\n{e.stderr if hasattr(e, 'stderr') else str(e)}")
85
- # return False
86
-
87
- # def yt_urls(query: str, max_results: int) -> List[str]:
88
- # """Get YouTube URLs for search query via proxy"""
89
- # try:
90
- # import requests
91
- # from youtube_search import YoutubeSearch
92
 
93
- # proxy_url = get_random_proxy()
94
- # proxies = {
95
- # "http": proxy_url,
96
- # "https": proxy_url,
97
- # }
98
 
99
- # # Monkey-patch YoutubeSearch to use proxy
100
- # original_get = requests.get
101
 
102
- # def proxied_get(*args, **kwargs):
103
- # kwargs["proxies"] = proxies
104
- # kwargs["verify"] = False
105
- # return original_get(*args, **kwargs)
106
 
107
- # requests.get = proxied_get
108
- # results = YoutubeSearch(query, max_results=max_results).to_dict()
109
- # requests.get = original_get # Restore
110
 
111
- # return ["https://www.youtube.com" + r["url_suffix"] for r in results]
112
- # except Exception as e:
113
- # print(f"YouTube search failed: {e}")
114
- # return []
115
 
116
 
117
- # def safe_filename(name: str) -> str:
118
- # """Create safe filename"""
119
- # return re.sub(r"[^\w\-\.]", "_", name)[:50] # Limit length
120
 
121
 
122
- # def dl_video(url: str, out: Path) -> bool:
123
- # """Download video with length limit using rotating proxy"""
124
- # out.parent.mkdir(exist_ok=True)
125
- # proxy = get_random_proxy()
126
 
127
- # cmd = [
128
- # "yt-dlp",
129
- # "--match-filter", f"duration<{MAX_VIDEO_LENGTH}",
130
- # "-f", "mp4",
131
- # "--merge-output-format", "mp4",
132
- # "-o", str(out),
133
- # "--no-playlist",
134
- # # "--no-check-certificate",
135
- # "--proxy", proxy,
136
- # url,
137
- # ]
138
- # return run_cmd(cmd, timeout=60)
 
139
 
140
 
141
- # def make_card(text: str, out: Path, dur: int = TITLE_DUR) -> bool:
142
- # """Create title card with text"""
143
- # # Wrap text for better display
144
- # wrapped = textwrap.wrap(text, width=25)
145
- # safe_text = "\\n".join(w.replace("'", r"\\'") for w in wrapped)
146
 
147
 
148
- # cmd = [
149
- # "ffmpeg",
150
- # "-loglevel", "error",
151
- # "-f", "lavfi", "-i", f"color=c=navy:s={SIZE}:d={dur}",
152
- # "-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo",
153
- # "-vf", (
154
- # f"drawtext=text='{safe_text}':fontcolor=white:fontsize=60:"
155
- # "x=(w-text_w)/2:y=(h-text_h)/2:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
156
- # ),
157
- # "-shortest", "-r", str(FPS),
158
- # "-c:v", "libx264", "-preset", PRESET, "-crf", str(CRF),
159
- # "-c:a", "aac", "-b:a", "96k",
160
- # "-movflags", "+faststart",
161
- # "-y", str(out),
162
- # ]
163
- # return run_cmd(cmd)
164
-
165
- # def extract_topics(text: str) -> List[str]:
166
- # """Extract topics from input text"""
167
- # topics = []
168
- # for line in text.splitlines():
169
- # line = line.strip()
170
- # if not line or len(topics) >= MAX_TOPICS:
171
- # continue
172
 
173
- # # Match numbered lists
174
- # if re.match(r"^\d+[\.)]\s+.+", line):
175
- # topic = re.sub(r"^\d+[\.)]\s*", "", line)
176
- # topics.append(topic)
177
- # # Match markdown headers
178
- # elif re.match(r"^#+\s+.+", line):
179
- # topic = re.sub(r"^#+\s*", "", line)
180
- # topics.append(topic)
181
- # # Match all caps titles
182
- # elif line.isupper() and 3 <= len(line) <= 50:
183
- # topics.append(line.title())
184
- # # Match regular lines as topics
185
- # elif len(line) > 3 and not line.startswith(('http', 'www')):
186
- # topics.append(line)
187
 
188
- # return topics[:MAX_TOPICS]
189
 
190
- # def create_physics_video(chapter_text: str, progress=gr.Progress()) -> Optional[str]:
191
- # """Generate educational physics video from chapter topics"""
192
- # if not chapter_text.strip():
193
- # return None
194
 
195
- # progress(0, desc="Extracting topics...")
196
- # topics = extract_topics(chapter_text)
197
 
198
- # if not topics:
199
- # return None
200
 
201
- # # Create temporary directory
202
- # with tempfile.TemporaryDirectory() as temp_dir:
203
- # temp_path = Path(temp_dir)
204
- # concat_paths: List[Path] = []
205
 
206
- # total_steps = len(topics) * 2 + 3 # topics * (card + video) + opening + closing + concat
207
- # current_step = 0
208
 
209
- # # Opening card
210
- # progress(current_step/total_steps, desc="Creating opening card...")
211
- # opening = temp_path / "00_opening.mp4"
212
- # if make_card("Physics Chapter Overview", opening):
213
- # concat_paths.append(opening)
214
- # current_step += 1
215
 
216
- # # Process each topic
217
- # for idx, topic in enumerate(topics, 1):
218
- # # Create title card
219
- # progress(current_step/total_steps, desc=f"Creating card for: {topic[:30]}...")
220
- # card = temp_path / f"title_{idx:02d}.mp4"
221
- # if make_card(topic, card):
222
- # concat_paths.append(card)
223
- # current_step += 1
224
 
225
- # # Try to download video
226
- # progress(current_step/total_steps, desc=f"Searching video for: {topic[:30]}...")
227
- # video_found = False
228
 
229
- # for url in yt_urls(f"{topic} physics explanation", YT_MAX_RESULTS):
230
- # vid_id_match = re.search(r"(?:v=|be/|shorts/)([\w\-]{11})", url)
231
- # if not vid_id_match:
232
- # continue
233
 
234
- # vid_path = temp_path / f"{safe_filename(vid_id_match.group(1))}.mp4"
235
- # if dl_video(url, vid_path):
236
- # concat_paths.append(vid_path)
237
- # video_found = True
238
- # break
239
 
240
- # if not video_found:
241
- # # Create a placeholder card if no video found
242
- # placeholder = temp_path / f"placeholder_{idx:02d}.mp4"
243
- # if make_card(f"Exploring: {topic}", placeholder, dur=5):
244
- # concat_paths.append(placeholder)
245
 
246
- # current_step += 1
247
 
248
- # # Closing card
249
- # progress(current_step/total_steps, desc="Creating closing card...")
250
- # closing = temp_path / "zz_closing.mp4"
251
- # if make_card("Thank you for learning!", closing):
252
- # concat_paths.append(closing)
253
- # current_step += 1
254
 
255
- # if len(concat_paths) < 2:
256
- # return None
257
 
258
- # # Create concat file
259
- # list_file = temp_path / "list.txt"
260
- # list_file.write_text(
261
- # "".join(f"file '{p.absolute()}'\n" for p in concat_paths),
262
- # encoding="utf-8"
263
- # )
264
 
265
- # # Final output path
266
- # output_path = "physics_chapter_video.mp4"
267
 
268
- # # Concatenate videos
269
- # progress(current_step/total_steps, desc="Creating final video...")
270
- # cmd = [
271
- # "ffmpeg",
272
- # "-loglevel", "error",
273
- # "-f", "concat", "-safe", "0", "-i", str(list_file),
274
- # "-c:v", "libx264", "-preset", PRESET, "-crf", str(CRF),
275
- # "-c:a", "aac", "-b:a", "128k",
276
- # "-movflags", "+faststart",
277
- # "-y", output_path,
278
- # ]
279
 
280
- # if run_cmd(cmd, timeout=300):
281
- # return output_path
282
 
283
- # return None
284
-
285
- # # Gradio Interface
286
- # def create_interface():
287
- # """Setup the web interface"""
288
- # with gr.Blocks(title="Physics Video Generator", theme=gr.themes.Soft()) as app:
289
- # gr.Markdown("""
290
- # # Physics Video Generator
291
 
292
- # Transform your physics topics into engaging educational videos! This tool will:
293
- # - Create professional title slides for each topic
294
- # - Find relevant educational content
295
- # - Combine everything into a complete video
296
 
297
- # **How to use:** Enter your topics one per line, or use numbered lists, or markdown headers.
298
- # """)
299
 
300
- # with gr.Row():
301
- # with gr.Column():
302
- # chapter_input = gr.Textbox(
303
- # label="Chapter Topics",
304
- # placeholder="""Enter topics like:
305
- # 1. Newton's Laws of Motion
306
- # 2. Force and Acceleration
307
- # 3. Momentum and Impulse
308
- # 4. Energy Conservation
309
- # 5. Circular Motion
310
-
311
- # Or:
312
- # # Kinematics
313
- # # Dynamics
314
- # # Thermodynamics""",
315
- # lines=10,
316
- # max_lines=15
317
- # )
318
 
319
- # generate_btn = gr.Button("Create Physics Video", variant="primary", size="lg")
320
 
321
- # with gr.Column():
322
- # video_output = gr.Video(label="Your Physics Video")
323
 
324
- # gr.Markdown("""
325
- # ### Important Notes:
326
- # - Processing typically takes 2-5 minutes
327
- # - Videos are optimized for educational use
328
- # - Limited to 8 topics per session
329
- # - Each video segment is capped at 30 seconds
330
- # """)
331
 
332
- # generate_btn.click(
333
- # fn=create_physics_video,
334
- # inputs=[chapter_input],
335
- # outputs=[video_output],
336
- # show_progress=True
337
- # )
338
 
339
- # # Examples
340
- # gr.Examples(
341
- # examples=[
342
- # ["1. Newton's First Law\n2. Newton's Second Law\n3. Newton's Third Law\n4. Applications of Newton's Laws"],
343
- # ["# Wave Motion\n# Sound Waves\n# Light Waves\n# Electromagnetic Spectrum"],
344
- # ["THERMODYNAMICS\nHEAT TRANSFER\nENTROPY\nCARNOT CYCLE"],
345
- # ["Quantum Mechanics Basics\nWave-Particle Duality\nHeisenberg Uncertainty Principle\nQuantum Tunneling"]
346
- # ],
347
- # inputs=[chapter_input],
348
- # label="Example Topics"
349
- # )
350
 
351
- # return app
352
-
353
- # if __name__ == "__main__":
354
- # app = create_interface()
355
- # app.queue(max_size=3) # Limit concurrent users for HF Spaces
356
- # app.launch(
357
- # share=False,
358
- # server_name="0.0.0.0",
359
- # server_port=7860
360
- # )
 
1
+ """
2
+ Physics Chapter Video Generator
3
+ Creates educational videos by combining title cards with relevant content.
4
+ """
5
+
6
+ import re, shutil, subprocess, textwrap, os, tempfile
7
+ from pathlib import Path
8
+ from typing import List, Optional
9
  import gradio as gr
10
+ import random
11
 
12
+ print("CWD:", os.getcwd())
13
+ print("cookies.txt exists:", os.path.exists("./cookies/cookies.txt"))
14
 
 
 
 
 
15
 
16
+ # --- Oxylabs Proxy Configuration ---
17
+ PROXY_USERNAME = "ujwal_CmiMZ"
18
+ PROXY_PASSWORD = "xJv4DChht5P6y+u"
19
+ PROXY_COUNTRY = "US"
20
 
21
+ # List of proxy endpoints (Oxylabs DC Proxies)
22
+ PROXY_PORTS = [8001, 8002, 8003, 8004, 8005]
23
+ PROXY_HOST = "dc.oxylabs.io"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
+ def get_random_proxy():
26
+ port = random.choice(PROXY_PORTS)
27
+ return f"http://user-{PROXY_USERNAME}-country-{PROXY_COUNTRY}:{PROXY_PASSWORD}@{PROXY_HOST}:{port}"
28
 
29
 
30
+ # ---------------- CONFIG ----------------
31
+ TITLE_DUR = 3 # seconds
32
+ SIZE = "1280x720" # resolution for cards
33
+ FPS = 30
34
+ CRF = 28 # Higher CRF for smaller files in HF Spaces
35
+ PRESET = "ultrafast" # Fastest encoding for HF Spaces
36
+ YT_MAX_RESULTS = 2 # Reduced for faster processing
37
+ MAX_VIDEO_LENGTH = 30 # Max seconds per video clip
38
+ MAX_TOPICS = 8 # Limit topics for HF Spaces resources
39
+ # ----------------------------------------
40
 
41
+ # ---------- helpers ----------
42
+ def run_cmd(cmd: list[str], timeout: int = 120) -> bool:
43
+ """Run command with timeout and proper error handling"""
44
+ try:
45
+ result = subprocess.run(
46
+ cmd,
47
+ check=True,
48
+ timeout=timeout,
49
+ capture_output=True,
50
+ text=True
51
+ )
52
+ return True
53
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
54
+ print(f"Command failed:\n{' '.join(cmd)}\nError:\n{e.stderr if hasattr(e, 'stderr') else str(e)}")
55
+ return False
56
+
57
+ def yt_urls(query: str, max_results: int) -> List[str]:
58
+ """Get YouTube URLs for search query via proxy"""
59
+ try:
60
+ import requests
61
+ from youtube_search import YoutubeSearch
62
 
63
+ proxy_url = get_random_proxy()
64
+ proxies = {
65
+ "http": proxy_url,
66
+ "https": proxy_url,
67
+ }
68
 
69
+ # Monkey-patch YoutubeSearch to use proxy
70
+ original_get = requests.get
71
 
72
+ def proxied_get(*args, **kwargs):
73
+ kwargs["proxies"] = proxies
74
+ kwargs["verify"] = False
75
+ return original_get(*args, **kwargs)
76
 
77
+ requests.get = proxied_get
78
+ results = YoutubeSearch(query, max_results=max_results).to_dict()
79
+ requests.get = original_get # Restore
80
 
81
+ return ["https://www.youtube.com" + r["url_suffix"] for r in results]
82
+ except Exception as e:
83
+ print(f"YouTube search failed: {e}")
84
+ return []
85
 
86
 
87
+ def safe_filename(name: str) -> str:
88
+ """Create safe filename"""
89
+ return re.sub(r"[^\w\-\.]", "_", name)[:50] # Limit length
90
 
91
 
92
+ def dl_video(url: str, out: Path) -> bool:
93
+ """Download video with length limit using rotating proxy"""
94
+ out.parent.mkdir(exist_ok=True)
95
+ proxy = get_random_proxy()
96
 
97
+ cmd = [
98
+ "yt-dlp",
99
+ "--match-filter", f"duration<{MAX_VIDEO_LENGTH}",
100
+ "-f", "mp4",
101
+ "--merge-output-format", "mp4",
102
+ "-o", str(out),
103
+ "--no-playlist",
104
+ # "--no-check-certificate",
105
+ "--proxy", proxy,
106
+ "--cookies", "./cookies/cookies.txt",
107
+ url,
108
+ ]
109
+ return run_cmd(cmd, timeout=60)
110
 
111
 
112
+ def make_card(text: str, out: Path, dur: int = TITLE_DUR) -> bool:
113
+ """Create title card with text"""
114
+ # Wrap text for better display
115
+ wrapped = textwrap.wrap(text, width=25)
116
+ safe_text = "\\n".join(w.replace("'", r"\\'") for w in wrapped)
117
 
118
 
119
+ cmd = [
120
+ "ffmpeg",
121
+ "-loglevel", "error",
122
+ "-f", "lavfi", "-i", f"color=c=navy:s={SIZE}:d={dur}",
123
+ "-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo",
124
+ "-vf", (
125
+ f"drawtext=text='{safe_text}':fontcolor=white:fontsize=60:"
126
+ "x=(w-text_w)/2:y=(h-text_h)/2:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
127
+ ),
128
+ "-shortest", "-r", str(FPS),
129
+ "-c:v", "libx264", "-preset", PRESET, "-crf", str(CRF),
130
+ "-c:a", "aac", "-b:a", "96k",
131
+ "-movflags", "+faststart",
132
+ "-y", str(out),
133
+ ]
134
+ return run_cmd(cmd)
135
+
136
+ def extract_topics(text: str) -> List[str]:
137
+ """Extract topics from input text"""
138
+ topics = []
139
+ for line in text.splitlines():
140
+ line = line.strip()
141
+ if not line or len(topics) >= MAX_TOPICS:
142
+ continue
143
 
144
+ # Match numbered lists
145
+ if re.match(r"^\d+[\.)]\s+.+", line):
146
+ topic = re.sub(r"^\d+[\.)]\s*", "", line)
147
+ topics.append(topic)
148
+ # Match markdown headers
149
+ elif re.match(r"^#+\s+.+", line):
150
+ topic = re.sub(r"^#+\s*", "", line)
151
+ topics.append(topic)
152
+ # Match all caps titles
153
+ elif line.isupper() and 3 <= len(line) <= 50:
154
+ topics.append(line.title())
155
+ # Match regular lines as topics
156
+ elif len(line) > 3 and not line.startswith(('http', 'www')):
157
+ topics.append(line)
158
 
159
+ return topics[:MAX_TOPICS]
160
 
161
+ def create_physics_video(chapter_text: str, progress=gr.Progress()) -> Optional[str]:
162
+ """Generate educational physics video from chapter topics"""
163
+ if not chapter_text.strip():
164
+ return None
165
 
166
+ progress(0, desc="Extracting topics...")
167
+ topics = extract_topics(chapter_text)
168
 
169
+ if not topics:
170
+ return None
171
 
172
+ # Create temporary directory
173
+ with tempfile.TemporaryDirectory() as temp_dir:
174
+ temp_path = Path(temp_dir)
175
+ concat_paths: List[Path] = []
176
 
177
+ total_steps = len(topics) * 2 + 3 # topics * (card + video) + opening + closing + concat
178
+ current_step = 0
179
 
180
+ # Opening card
181
+ progress(current_step/total_steps, desc="Creating opening card...")
182
+ opening = temp_path / "00_opening.mp4"
183
+ if make_card("Physics Chapter Overview", opening):
184
+ concat_paths.append(opening)
185
+ current_step += 1
186
 
187
+ # Process each topic
188
+ for idx, topic in enumerate(topics, 1):
189
+ # Create title card
190
+ progress(current_step/total_steps, desc=f"Creating card for: {topic[:30]}...")
191
+ card = temp_path / f"title_{idx:02d}.mp4"
192
+ if make_card(topic, card):
193
+ concat_paths.append(card)
194
+ current_step += 1
195
 
196
+ # Try to download video
197
+ progress(current_step/total_steps, desc=f"Searching video for: {topic[:30]}...")
198
+ video_found = False
199
 
200
+ for url in yt_urls(f"{topic} physics explanation", YT_MAX_RESULTS):
201
+ vid_id_match = re.search(r"(?:v=|be/|shorts/)([\w\-]{11})", url)
202
+ if not vid_id_match:
203
+ continue
204
 
205
+ vid_path = temp_path / f"{safe_filename(vid_id_match.group(1))}.mp4"
206
+ if dl_video(url, vid_path):
207
+ concat_paths.append(vid_path)
208
+ video_found = True
209
+ break
210
 
211
+ if not video_found:
212
+ # Create a placeholder card if no video found
213
+ placeholder = temp_path / f"placeholder_{idx:02d}.mp4"
214
+ if make_card(f"Exploring: {topic}", placeholder, dur=5):
215
+ concat_paths.append(placeholder)
216
 
217
+ current_step += 1
218
 
219
+ # Closing card
220
+ progress(current_step/total_steps, desc="Creating closing card...")
221
+ closing = temp_path / "zz_closing.mp4"
222
+ if make_card("Thank you for learning!", closing):
223
+ concat_paths.append(closing)
224
+ current_step += 1
225
 
226
+ if len(concat_paths) < 2:
227
+ return None
228
 
229
+ # Create concat file
230
+ list_file = temp_path / "list.txt"
231
+ list_file.write_text(
232
+ "".join(f"file '{p.absolute()}'\n" for p in concat_paths),
233
+ encoding="utf-8"
234
+ )
235
 
236
+ # Final output path
237
+ output_path = "physics_chapter_video.mp4"
238
 
239
+ # Concatenate videos
240
+ progress(current_step/total_steps, desc="Creating final video...")
241
+ cmd = [
242
+ "ffmpeg",
243
+ "-loglevel", "error",
244
+ "-f", "concat", "-safe", "0", "-i", str(list_file),
245
+ "-c:v", "libx264", "-preset", PRESET, "-crf", str(CRF),
246
+ "-c:a", "aac", "-b:a", "128k",
247
+ "-movflags", "+faststart",
248
+ "-y", output_path,
249
+ ]
250
 
251
+ if run_cmd(cmd, timeout=300):
252
+ return output_path
253
 
254
+ return None
255
+
256
+ # Gradio Interface
257
+ def create_interface():
258
+ """Setup the web interface"""
259
+ with gr.Blocks(title="Physics Video Generator", theme=gr.themes.Soft()) as app:
260
+ gr.Markdown("""
261
+ # Physics Video Generator
262
 
263
+ Transform your physics topics into engaging educational videos! This tool will:
264
+ - Create professional title slides for each topic
265
+ - Find relevant educational content
266
+ - Combine everything into a complete video
267
 
268
+ **How to use:** Enter your topics one per line, or use numbered lists, or markdown headers.
269
+ """)
270
 
271
+ with gr.Row():
272
+ with gr.Column():
273
+ chapter_input = gr.Textbox(
274
+ label="Chapter Topics",
275
+ placeholder="""Enter topics like:
276
+ 1. Newton's Laws of Motion
277
+ 2. Force and Acceleration
278
+ 3. Momentum and Impulse
279
+ 4. Energy Conservation
280
+ 5. Circular Motion
281
+
282
+ Or:
283
+ # Kinematics
284
+ # Dynamics
285
+ # Thermodynamics""",
286
+ lines=10,
287
+ max_lines=15
288
+ )
289
 
290
+ generate_btn = gr.Button("Create Physics Video", variant="primary", size="lg")
291
 
292
+ with gr.Column():
293
+ video_output = gr.Video(label="Your Physics Video")
294
 
295
+ gr.Markdown("""
296
+ ### Important Notes:
297
+ - Processing typically takes 2-5 minutes
298
+ - Videos are optimized for educational use
299
+ - Limited to 8 topics per session
300
+ - Each video segment is capped at 30 seconds
301
+ """)
302
 
303
+ generate_btn.click(
304
+ fn=create_physics_video,
305
+ inputs=[chapter_input],
306
+ outputs=[video_output],
307
+ show_progress=True
308
+ )
309
 
310
+ # Examples
311
+ gr.Examples(
312
+ examples=[
313
+ ["1. Newton's First Law\n2. Newton's Second Law\n3. Newton's Third Law\n4. Applications of Newton's Laws"],
314
+ ["# Wave Motion\n# Sound Waves\n# Light Waves\n# Electromagnetic Spectrum"],
315
+ ["THERMODYNAMICS\nHEAT TRANSFER\nENTROPY\nCARNOT CYCLE"],
316
+ ["Quantum Mechanics Basics\nWave-Particle Duality\nHeisenberg Uncertainty Principle\nQuantum Tunneling"]
317
+ ],
318
+ inputs=[chapter_input],
319
+ label="Example Topics"
320
+ )
321
 
322
+ return app
323
+
324
+ if __name__ == "__main__":
325
+ app = create_interface()
326
+ app.queue(max_size=3) # Limit concurrent users for HF Spaces
327
+ app.launch(
328
+ share=False,
329
+ server_name="0.0.0.0",
330
+ server_port=7860
331
+ )
cookies/cookies.txt ADDED
The diff for this file is too large to render. See raw diff