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