ujwal55 commited on
Commit
c1d21ce
·
1 Parent(s): 9ebaf42

Pushing code to huggingface spaces

Browse files
Files changed (3) hide show
  1. README.md +69 -0
  2. app.py +287 -0
  3. requirements.txt +3 -0
README.md CHANGED
@@ -11,3 +11,72 @@ short_description: 'An automated tool that creates engaging physics educational
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
14
+
15
+
16
+ ---
17
+ title: Physics Video Generator
18
+ emoji: 🎬
19
+ colorFrom: blue
20
+ colorTo: purple
21
+ sdk: gradio
22
+ sdk_version: 4.44.0
23
+ app_file: app.py
24
+ pinned: false
25
+ license: mit
26
+ python_version: 3.11
27
+ ---
28
+
29
+ # Physics Chapter Video Generator 🎬
30
+
31
+ An automated tool that creates engaging physics educational videos by combining title cards with relevant YouTube content.
32
+
33
+ ## Features
34
+
35
+ - 📝 **Smart Topic Extraction**: Supports numbered lists, markdown headers, and plain text
36
+ - 🎨 **Dynamic Title Cards**: Auto-generated cards with proper text wrapping
37
+ - 🔍 **Intelligent Video Search**: Finds relevant physics videos for each topic
38
+ - 🎬 **Seamless Concatenation**: Combines everything into one cohesive video
39
+ - ⚡ **Optimized for Spaces**: Fast processing with resource-conscious settings
40
+
41
+ ## How to Use
42
+
43
+ 1. Enter your physics chapter topics (one per line)
44
+ 2. Click "Generate Physics Video"
45
+ 3. Wait 2-5 minutes for processing
46
+ 4. Download your custom physics video!
47
+
48
+ ## Input Formats Supported
49
+
50
+ ```
51
+ 1. Newton's Laws of Motion
52
+ 2. Force and Acceleration
53
+ 3. Energy Conservation
54
+ ```
55
+
56
+ ```
57
+ # Kinematics
58
+ # Dynamics
59
+ # Thermodynamics
60
+ ```
61
+
62
+ ```
63
+ WAVE MOTION
64
+ SOUND WAVES
65
+ ELECTROMAGNETIC WAVES
66
+ ```
67
+
68
+ ## Technical Details
69
+
70
+ - Built with Gradio for easy web interface
71
+ - Uses FFmpeg for video processing
72
+ - yt-dlp for YouTube video downloading
73
+ - Optimized encoding settings for web deployment
74
+ - Resource limits: 8 topics max, 30s per video clip
75
+
76
+ ## Limitations
77
+
78
+ - Processing time depends on video availability
79
+ - Quality optimized for fast generation
80
+ - Limited to publicly available YouTube content
81
+ - Designed for educational use
82
+
app.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Fast Physics-Chapter Visual Explainer for Hugging Face Spaces
4
+ • Downloads best audio+video for each topic
5
+ • Generates title cards with FFmpeg drawtext (+ silent audio)
6
+ • Concatenates everything in a single FFmpeg pass
7
+ • Optimized for HF Spaces with Gradio interface
8
+ """
9
+
10
+ import re, shutil, subprocess, textwrap, os, tempfile
11
+ from pathlib import Path
12
+ from typing import List, Optional
13
+ import gradio as gr
14
+
15
+ # ---------------- CONFIG ----------------
16
+ TITLE_DUR = 3 # seconds
17
+ SIZE = "1280x720" # resolution for cards
18
+ FPS = 30
19
+ CRF = 28 # Higher CRF for smaller files in HF Spaces
20
+ PRESET = "ultrafast" # Fastest encoding for HF Spaces
21
+ YT_MAX_RESULTS = 2 # Reduced for faster processing
22
+ MAX_VIDEO_LENGTH = 30 # Max seconds per video clip
23
+ MAX_TOPICS = 8 # Limit topics for HF Spaces resources
24
+ # ----------------------------------------
25
+
26
+ # ---------- helpers ----------
27
+ def run_cmd(cmd: list[str], timeout: int = 120) -> bool:
28
+ """Run command with timeout and proper error handling"""
29
+ try:
30
+ result = subprocess.run(
31
+ cmd,
32
+ check=True,
33
+ timeout=timeout,
34
+ capture_output=True,
35
+ text=True
36
+ )
37
+ return True
38
+ except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
39
+ print(f"Command failed: {' '.join(cmd[:3])}... - {str(e)}")
40
+ return False
41
+
42
+ def yt_urls(query: str, max_results: int) -> List[str]:
43
+ """Get YouTube URLs for search query"""
44
+ try:
45
+ from youtube_search import YoutubeSearch
46
+ results = YoutubeSearch(query, max_results=max_results).to_dict()
47
+ return ["https://www.youtube.com" + r["url_suffix"] for r in results]
48
+ except Exception as e:
49
+ print(f"YouTube search failed: {e}")
50
+ return []
51
+
52
+ def safe_filename(name: str) -> str:
53
+ """Create safe filename"""
54
+ return re.sub(r"[^\w\-\.]", "_", name)[:50] # Limit length
55
+
56
+ def dl_video(url: str, out: Path) -> bool:
57
+ """Download video with length limit"""
58
+ out.parent.mkdir(exist_ok=True)
59
+ cmd = [
60
+ "yt-dlp",
61
+ "-f", "worst[height<=480]/worst", # Lower quality for HF Spaces
62
+ "--merge-output-format", "mp4",
63
+ "-o", str(out),
64
+ "--max-duration", str(MAX_VIDEO_LENGTH),
65
+ "--no-playlist",
66
+ url,
67
+ ]
68
+ return run_cmd(cmd, timeout=60)
69
+
70
+ def make_card(text: str, out: Path, dur: int = TITLE_DUR) -> bool:
71
+ """Create title card with text"""
72
+ # Wrap text for better display
73
+ wrapped = "\\n".join(textwrap.wrap(text, width=25))
74
+
75
+ cmd = [
76
+ "ffmpeg",
77
+ "-loglevel", "error",
78
+ "-f", "lavfi", "-i", f"color=c=navy:s={SIZE}:d={dur}",
79
+ "-f", "lavfi", "-i", "anullsrc=r=44100:cl=stereo",
80
+ "-vf", (
81
+ f"drawtext=text='{wrapped}':fontcolor=white:fontsize=60:"
82
+ "x=(w-text_w)/2:y=(h-text_h)/2:fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf"
83
+ ),
84
+ "-shortest", "-r", str(FPS),
85
+ "-c:v", "libx264", "-preset", PRESET, "-crf", str(CRF),
86
+ "-c:a", "aac", "-b:a", "96k",
87
+ "-movflags", "+faststart",
88
+ "-y", str(out),
89
+ ]
90
+ return run_cmd(cmd)
91
+
92
+ def extract_topics(text: str) -> List[str]:
93
+ """Extract topics from input text"""
94
+ topics = []
95
+ for line in text.splitlines():
96
+ line = line.strip()
97
+ if not line or len(topics) >= MAX_TOPICS:
98
+ continue
99
+
100
+ # Match numbered lists
101
+ if re.match(r"^\d+[\.)]\s+.+", line):
102
+ topic = re.sub(r"^\d+[\.)]\s*", "", line)
103
+ topics.append(topic)
104
+ # Match markdown headers
105
+ elif re.match(r"^#+\s+.+", line):
106
+ topic = re.sub(r"^#+\s*", "", line)
107
+ topics.append(topic)
108
+ # Match all caps titles
109
+ elif line.isupper() and 3 <= len(line) <= 50:
110
+ topics.append(line.title())
111
+ # Match regular lines as topics
112
+ elif len(line) > 3 and not line.startswith(('http', 'www')):
113
+ topics.append(line)
114
+
115
+ return topics[:MAX_TOPICS]
116
+
117
+ def create_physics_video(chapter_text: str, progress=gr.Progress()) -> Optional[str]:
118
+ """Main function to create physics video"""
119
+ if not chapter_text.strip():
120
+ return None
121
+
122
+ progress(0, desc="Extracting topics...")
123
+ topics = extract_topics(chapter_text)
124
+
125
+ if not topics:
126
+ return None
127
+
128
+ # Create temporary directory
129
+ with tempfile.TemporaryDirectory() as temp_dir:
130
+ temp_path = Path(temp_dir)
131
+ concat_paths: List[Path] = []
132
+
133
+ total_steps = len(topics) * 2 + 3 # topics * (card + video) + opening + closing + concat
134
+ current_step = 0
135
+
136
+ # Opening card
137
+ progress(current_step/total_steps, desc="Creating opening card...")
138
+ opening = temp_path / "00_opening.mp4"
139
+ if make_card("Physics Chapter Overview", opening):
140
+ concat_paths.append(opening)
141
+ current_step += 1
142
+
143
+ # Process each topic
144
+ for idx, topic in enumerate(topics, 1):
145
+ # Create title card
146
+ progress(current_step/total_steps, desc=f"Creating card for: {topic[:30]}...")
147
+ card = temp_path / f"title_{idx:02d}.mp4"
148
+ if make_card(topic, card):
149
+ concat_paths.append(card)
150
+ current_step += 1
151
+
152
+ # Try to download video
153
+ progress(current_step/total_steps, desc=f"Searching video for: {topic[:30]}...")
154
+ video_found = False
155
+
156
+ for url in yt_urls(f"{topic} physics explanation", YT_MAX_RESULTS):
157
+ vid_id_match = re.search(r"(?:v=|be/|shorts/)([\w\-]{11})", url)
158
+ if not vid_id_match:
159
+ continue
160
+
161
+ vid_path = temp_path / f"{safe_filename(vid_id_match.group(1))}.mp4"
162
+ if dl_video(url, vid_path):
163
+ concat_paths.append(vid_path)
164
+ video_found = True
165
+ break
166
+
167
+ if not video_found:
168
+ # Create a placeholder card if no video found
169
+ placeholder = temp_path / f"placeholder_{idx:02d}.mp4"
170
+ if make_card(f"Exploring: {topic}", placeholder, dur=5):
171
+ concat_paths.append(placeholder)
172
+
173
+ current_step += 1
174
+
175
+ # Closing card
176
+ progress(current_step/total_steps, desc="Creating closing card...")
177
+ closing = temp_path / "zz_closing.mp4"
178
+ if make_card("Thank you for learning!", closing):
179
+ concat_paths.append(closing)
180
+ current_step += 1
181
+
182
+ if len(concat_paths) < 2:
183
+ return None
184
+
185
+ # Create concat file
186
+ list_file = temp_path / "list.txt"
187
+ list_file.write_text(
188
+ "".join(f"file '{p.absolute()}'\n" for p in concat_paths),
189
+ encoding="utf-8"
190
+ )
191
+
192
+ # Final output path
193
+ output_path = "physics_chapter_video.mp4"
194
+
195
+ # Concatenate videos
196
+ progress(current_step/total_steps, desc="Creating final video...")
197
+ cmd = [
198
+ "ffmpeg",
199
+ "-loglevel", "error",
200
+ "-f", "concat", "-safe", "0", "-i", str(list_file),
201
+ "-c:v", "libx264", "-preset", PRESET, "-crf", str(CRF),
202
+ "-c:a", "aac", "-b:a", "128k",
203
+ "-movflags", "+faststart",
204
+ "-y", output_path,
205
+ ]
206
+
207
+ if run_cmd(cmd, timeout=300):
208
+ return output_path
209
+
210
+ return None
211
+
212
+ # Gradio Interface
213
+ def create_interface():
214
+ """Create Gradio interface"""
215
+ with gr.Blocks(title="Physics Video Generator", theme=gr.themes.Soft()) as app:
216
+ gr.Markdown("""
217
+ # 🎬 Physics Chapter Video Generator
218
+
219
+ Create engaging physics videos by entering chapter topics! The app will:
220
+ - Generate title cards for each topic
221
+ - Search for relevant physics videos
222
+ - Combine everything into one cohesive video
223
+
224
+ **Input Format:** Enter topics one per line, or use numbered lists, or markdown headers.
225
+ """)
226
+
227
+ with gr.Row():
228
+ with gr.Column():
229
+ chapter_input = gr.Textbox(
230
+ label="Chapter Topics",
231
+ placeholder="""Enter topics like:
232
+ 1. Newton's Laws of Motion
233
+ 2. Force and Acceleration
234
+ 3. Momentum and Impulse
235
+ 4. Energy Conservation
236
+ 5. Circular Motion
237
+
238
+ Or:
239
+ # Kinematics
240
+ # Dynamics
241
+ # Thermodynamics""",
242
+ lines=10,
243
+ max_lines=15
244
+ )
245
+
246
+ generate_btn = gr.Button("🎬 Generate Physics Video", variant="primary", size="lg")
247
+
248
+ with gr.Column():
249
+ video_output = gr.Video(label="Generated Video")
250
+
251
+ gr.Markdown("""
252
+ ### ⚠️ Note:
253
+ - Processing may take 2-5 minutes depending on topic count
254
+ - Videos are optimized for fast generation
255
+ - Maximum 8 topics per video
256
+ - Each video clip is limited to 30 seconds
257
+ """)
258
+
259
+ generate_btn.click(
260
+ fn=create_physics_video,
261
+ inputs=[chapter_input],
262
+ outputs=[video_output],
263
+ show_progress=True
264
+ )
265
+
266
+ # Examples
267
+ gr.Examples(
268
+ examples=[
269
+ ["1. Newton's First Law\n2. Newton's Second Law\n3. Newton's Third Law\n4. Applications of Newton's Laws"],
270
+ ["# Wave Motion\n# Sound Waves\n# Light Waves\n# Electromagnetic Spectrum"],
271
+ ["THERMODYNAMICS\nHEAT TRANSFER\nENTROPY\nCARNOT CYCLE"],
272
+ ["Quantum Mechanics Basics\nWave-Particle Duality\nHeisenberg Uncertainty Principle\nQuantum Tunneling"]
273
+ ],
274
+ inputs=[chapter_input],
275
+ label="Example Topics"
276
+ )
277
+
278
+ return app
279
+
280
+ if __name__ == "__main__":
281
+ app = create_interface()
282
+ app.queue(max_size=3) # Limit concurrent users for HF Spaces
283
+ app.launch(
284
+ share=False,
285
+ server_name="0.0.0.0",
286
+ server_port=7860
287
+ )
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=4.0.0
2
+ youtube-search-python>=1.6.6
3
+ yt-dlp>=2023.12.30