Vo Hoang Minh commited on
Commit
cc72d85
·
1 Parent(s): 1d83642
Files changed (3) hide show
  1. app.py +186 -147
  2. motion_processor.py +4 -6
  3. video_processor.py +7 -23
app.py CHANGED
@@ -1,4 +1,5 @@
1
  import gradio as gr
 
2
  from motion_processor import MotionProcessor
3
  from file_handler import FileHandler
4
  from video_processor import VideoProcessor
@@ -8,7 +9,7 @@ motion = MotionProcessor()
8
  files = FileHandler()
9
  processor = VideoProcessor(motion, files)
10
 
11
- # atexit.register(files.cleanup)
12
 
13
  def preview_files(uploaded_files):
14
  if not uploaded_files:
@@ -25,74 +26,63 @@ def filter_templates_by_tag(tag_filter):
25
  else:
26
  filtered = motion.get_templates_by_tag(tag_filter)
27
 
28
- choices = [f"{t['name']} ({', '.join(t.get('tags', []))})" for t in filtered]
29
  return gr.Dropdown(choices=choices, value=choices[0] if choices else None)
30
 
31
  def search_templates(search_query):
32
  if not search_query.strip():
33
- choices = [f"{t['name']} ({', '.join(t.get('tags', []))})" for t in motion.templates]
34
  else:
35
  filtered = motion.search_templates(search_query)
36
- choices = [f"{t['name']} ({', '.join(t.get('tags', []))})" for t in filtered]
37
 
38
  return gr.Dropdown(choices=choices, value=choices[0] if choices else None)
39
 
40
- def test_single_effect(test_file, template_choice, aspect):
41
- if not test_file or not template_choice:
42
- return None, "Please upload a test file and select a template"
43
-
44
- template_name = template_choice.split(" (")[0] # Extract name from "name (tags)"
45
-
46
- result, status = processor.test_effect(test_file.name, template_name, aspect)
47
- return result, status
48
-
49
- def create_motion_video(input_files, aspect, output_format, use_random, selected_template,
50
- workers, quality, fps, progress=gr.Progress()):
51
  def progress_fn(val, desc):
52
  progress(val, desc=desc)
53
 
 
 
 
54
  # Prepare template
55
  template = None
56
  if not use_random and selected_template:
57
- template_name = selected_template.split(" (")[0]
58
  template = next((t for t in motion.templates if t['name'] == template_name), None)
59
 
60
- # Advanced settings
61
- advanced_settings = {
62
- 'workers': workers,
63
- 'quality': quality,
64
- 'fps': fps
65
- }
66
-
67
- result, status = processor.create_video(
68
- input_files, aspect, output_format, progress_fn, template, advanced_settings
69
- )
70
-
71
- if result:
72
- return result, status, gr.DownloadButton(label="📥 Download", value=result, visible=True)
73
- return None, status, gr.DownloadButton(visible=False)
74
 
75
  def load_template(template_choice):
76
  if not template_choice:
77
  return "", 4, 1.0, 1.3, 0, 0, 0, 0, "", "", 0, 0
78
 
79
- template_name = template_choice.split(" (")[0]
80
  template = next((t for t in motion.templates if t['name'] == template_name), motion.templates[0])
81
 
82
  return (
83
  template['name'],
84
  template['duration'],
85
- template['scale'][0], template['scale'][1], # scale start, end
86
- template['pan'][0], template['pan'][1], # pan x1, y1
87
- template['pan'][2], template['pan'][3], # pan x2, y2
88
  ', '.join(template.get('tags', [])),
89
  template.get('desc', ''),
90
- template['rotate'][0], template['rotate'][1] # rotate start, end
91
  )
92
 
93
  def save_template(name, duration, scale_start, scale_end, x1, y1, x2, y2, tags, desc, rot_start, rot_end):
94
  if not name:
95
- return "Enter template name", gr.Dropdown()
96
 
97
  tag_list = [tag.strip() for tag in tags.split(',') if tag.strip()] if tags else []
98
 
@@ -106,7 +96,6 @@ def save_template(name, duration, scale_start, scale_end, x1, y1, x2, y2, tags,
106
  'desc': desc or f"Custom template: {name}"
107
  }
108
 
109
- # Update or add
110
  existing = next((i for i, t in enumerate(motion.templates) if t['name'] == name), None)
111
  if existing is not None:
112
  motion.templates[existing] = new_template
@@ -114,8 +103,8 @@ def save_template(name, duration, scale_start, scale_end, x1, y1, x2, y2, tags,
114
  motion.templates.append(new_template)
115
 
116
  motion.save_templates(motion.templates)
117
- choices = [f"{t['name']} ({', '.join(t.get('tags', []))})" for t in motion.templates]
118
- return f"Saved '{name}'!", gr.Dropdown(choices=choices, value=f"{name} ({', '.join(tag_list)})")
119
 
120
  # Get all unique tags
121
  all_tags = set()
@@ -124,99 +113,98 @@ for template in motion.templates:
124
  tag_choices = ["all"] + sorted(list(all_tags))
125
 
126
  # Interface
127
- with gr.Blocks(title="Ken Burns Story Creator", css="""
128
- .gradio-container {max-width: 1400px !important}
129
- .compact-group {margin: 5px 0 !important}
130
- """) as app:
131
- gr.Markdown("# 🎬 Ken Burns Story Creator\nCreate motion videos for YouTube, TikTok, Reels with 30+ effects")
132
 
133
  with gr.Tabs():
 
134
  with gr.Tab("📹 Create Video"):
135
  with gr.Row():
136
  with gr.Column(scale=2):
137
- # Input
138
- files_input = gr.File(label="Upload Images/Videos", file_count="multiple",
139
- file_types=["image", "video"])
 
 
 
 
140
 
141
  # Preview
142
  with gr.Group():
143
- gr.Markdown("### Preview")
144
- preview_img = gr.Image(label="Files Preview", height=200)
145
- preview_info = gr.Textbox(label="Info", interactive=False)
146
-
147
- # Effect Selection
148
- with gr.Group():
149
- gr.Markdown("### 🎭 Effect Selection")
150
-
151
- with gr.Row():
152
- use_random = gr.Checkbox(label="Random Effects", value=True)
153
-
154
- with gr.Column(visible=False) as effect_selector:
155
- with gr.Row():
156
- tag_filter = gr.Dropdown(
157
- choices=tag_choices, value="all",
158
- label="Filter by Tag", scale=1
159
- )
160
- search_box = gr.Textbox(
161
- label="Search Effects", placeholder="zoom, pan, dramatic...", scale=2
162
- )
163
-
164
- template_dropdown = gr.Dropdown(
165
- choices=[f"{t['name']} ({', '.join(t.get('tags', []))})" for t in motion.templates],
166
- label="Select Effect"
167
- )
168
 
169
- # Settings
 
170
  with gr.Row():
 
171
  aspect_ratio = gr.Dropdown(
172
  choices=['reels', 'tiktok', 'youtube', 'square', 'widescreen'],
173
  value='reels',
174
  label="📱 Platform"
175
  )
176
- output_format = gr.Radio(['mp4', 'gif'], value='mp4', label="Format")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
 
178
  with gr.Column(scale=1):
179
- # Test Effect
180
- with gr.Group():
181
- gr.Markdown("### 🧪 Test Effect")
182
- test_file = gr.File(label="Test Image", file_types=["image"])
183
- test_btn = gr.Button("🎬 Test Effect", size="sm")
184
- test_result = gr.Video(label="Test Result", height=200)
185
- test_status = gr.Textbox(label="Test Status", interactive=False)
186
 
187
- # Create Video
188
- create_btn = gr.Button("🚀 Create Story", variant="primary", size="lg")
189
- status = gr.Textbox(label="Status", interactive=False)
190
- output_video = gr.Video(label="Result", height=300)
191
- download_btn = gr.DownloadButton(label="📥 Download", visible=False)
192
-
193
- with gr.Tab("⚙️ Advanced Settings"):
194
- gr.Markdown("### Advanced Processing Settings")
195
-
196
- with gr.Row():
197
- workers = gr.Slider(1, 8, value=4, step=1, label="🔄 Parallel Workers")
198
- fps = gr.Slider(15, 60, value=25, step=5, label="🎞️ FPS")
199
-
200
- with gr.Row():
201
- quality = gr.Dropdown(
202
- choices=['ultrafast', 'superfast', 'veryfast', 'faster', 'fast', 'medium', 'slow'],
203
- value='fast',
204
- label="🏆 Quality Preset"
205
- )
206
 
207
- with gr.Tab("🎭 Templates"):
208
- gr.Markdown("### Motion Templates Manager")
 
 
209
 
210
  with gr.Row():
211
  with gr.Column(scale=2):
212
  template_dropdown_edit = gr.Dropdown(
213
- choices=[f"{t['name']} ({', '.join(t.get('tags', []))})" for t in motion.templates],
214
- value=f"{motion.templates[0]['name']} ({', '.join(motion.templates[0].get('tags', []))})",
215
- label="Template"
216
  )
217
 
218
  with gr.Row():
219
- name_input = gr.Textbox(label="Name", placeholder="template_name")
220
  duration_input = gr.Number(label="Duration (s)", value=4, minimum=2, maximum=10)
221
 
222
  with gr.Row():
@@ -235,8 +223,7 @@ with gr.Blocks(title="Ken Burns Story Creator", css="""
235
 
236
  tags_input = gr.Textbox(
237
  label="Tags (comma separated)",
238
- placeholder="zoom, left, dramatic",
239
- value=""
240
  )
241
 
242
  desc_input = gr.Textbox(
@@ -249,57 +236,109 @@ with gr.Blocks(title="Ken Burns Story Creator", css="""
249
  template_status = gr.Textbox(label="Status", interactive=False)
250
 
251
  with gr.Column(scale=1):
252
- gr.Markdown("### 📋 Available Tags")
253
- gr.Markdown(f"**{len(tag_choices)-1} unique tags:**")
254
- gr.Markdown(", ".join(sorted(tag_choices[1:]))) # Exclude "all"
255
-
256
- gr.Markdown("### 📖 Template Guide")
257
  gr.Markdown("""
258
- **Scale**: Zoom level (1.0 = normal)
259
- **Pan X1,Y1**: Start position
260
- **Pan X2,Y2**: End position
261
- **Duration**: Effect length in seconds
262
- **Tags**: For easy searching
 
 
 
 
 
 
 
 
 
263
  """)
 
 
 
264
 
 
265
  with gr.Tab("ℹ️ Help"):
266
  gr.Markdown(f"""
267
  ### 📖 How to Use
268
- 1. **Upload** your images/videos
269
- 2. **Choose effects**: Random or specific template
270
- 3. **Test effects** on single image first
271
- 4. **Select platform** aspect ratio
272
- 5. **Create** your story video
273
 
274
- ### 🎭 Available Effects ({len(motion.templates)} total)
 
 
 
 
 
 
 
 
 
 
 
275
 
276
- **Zoom Effects:**
277
- - Center zoom in/out
278
- - Directional zooms (left, right, top, bottom)
279
- - Dramatic push/pull effects
280
 
281
- **Pan Effects:**
282
- - Horizontal/vertical pans
283
- - Diagonal movements
284
- - Arc and spiral motions
285
 
286
- **Cinematic Effects:**
287
- - Slow drifts and breathing
288
- - Focus shifts and reveals
289
- - Documentary style movements
290
 
291
- ### 📱 Platform Settings
292
- - **Reels/TikTok**: 9:16 (1080x1920)
293
- - **YouTube**: 16:9 (1920x1080)
294
- - **Square**: 1:1 (1080x1080)
295
 
296
- ### Advanced Settings
297
- - **Workers**: Parallel processing (more = faster)
298
- - **Quality**: FFmpeg preset (fast vs quality)
299
- - **FPS**: Frame rate (higher = smoother)
 
 
300
  """)
301
 
302
- # Events
303
- files_input.change(preview_files, [files_input], [preview_img, preview_info])
304
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  app.launch()
 
1
  import gradio as gr
2
+ import atexit
3
  from motion_processor import MotionProcessor
4
  from file_handler import FileHandler
5
  from video_processor import VideoProcessor
 
9
  files = FileHandler()
10
  processor = VideoProcessor(motion, files)
11
 
12
+ atexit.register(files.cleanup)
13
 
14
  def preview_files(uploaded_files):
15
  if not uploaded_files:
 
26
  else:
27
  filtered = motion.get_templates_by_tag(tag_filter)
28
 
29
+ choices = [f"{t['name']} - {t.get('desc', '')[:50]}..." for t in filtered]
30
  return gr.Dropdown(choices=choices, value=choices[0] if choices else None)
31
 
32
  def search_templates(search_query):
33
  if not search_query.strip():
34
+ choices = [f"{t['name']} - {t.get('desc', '')[:50]}..." for t in motion.templates]
35
  else:
36
  filtered = motion.search_templates(search_query)
37
+ choices = [f"{t['name']} - {t.get('desc', '')[:50]}..." for t in filtered]
38
 
39
  return gr.Dropdown(choices=choices, value=choices[0] if choices else None)
40
 
41
+ def create_motion_video(input_files, aspect, output_format, use_random, selected_template, progress=gr.Progress()):
 
 
 
 
 
 
 
 
 
 
42
  def progress_fn(val, desc):
43
  progress(val, desc=desc)
44
 
45
+ if not input_files:
46
+ return None, "❌ No files provided", gr.DownloadButton(visible=False)
47
+
48
  # Prepare template
49
  template = None
50
  if not use_random and selected_template:
51
+ template_name = selected_template.split(" - ")[0]
52
  template = next((t for t in motion.templates if t['name'] == template_name), None)
53
 
54
+ try:
55
+ result, status = processor.create_video(
56
+ input_files, aspect, output_format, progress_fn, template
57
+ )
58
+
59
+ if result:
60
+ return result, f"✅ {status}", gr.DownloadButton(label="📥 Download Video", value=result, visible=True)
61
+ return None, f"❌ {status}", gr.DownloadButton(visible=False)
62
+ except Exception as e:
63
+ return None, f"❌ Error: {str(e)}", gr.DownloadButton(visible=False)
 
 
 
 
64
 
65
  def load_template(template_choice):
66
  if not template_choice:
67
  return "", 4, 1.0, 1.3, 0, 0, 0, 0, "", "", 0, 0
68
 
69
+ template_name = template_choice.split(" - ")[0]
70
  template = next((t for t in motion.templates if t['name'] == template_name), motion.templates[0])
71
 
72
  return (
73
  template['name'],
74
  template['duration'],
75
+ template['scale'][0], template['scale'][1],
76
+ template['pan'][0], template['pan'][1],
77
+ template['pan'][2], template['pan'][3],
78
  ', '.join(template.get('tags', [])),
79
  template.get('desc', ''),
80
+ template['rotate'][0], template['rotate'][1]
81
  )
82
 
83
  def save_template(name, duration, scale_start, scale_end, x1, y1, x2, y2, tags, desc, rot_start, rot_end):
84
  if not name:
85
+ return "Enter template name", gr.Dropdown()
86
 
87
  tag_list = [tag.strip() for tag in tags.split(',') if tag.strip()] if tags else []
88
 
 
96
  'desc': desc or f"Custom template: {name}"
97
  }
98
 
 
99
  existing = next((i for i, t in enumerate(motion.templates) if t['name'] == name), None)
100
  if existing is not None:
101
  motion.templates[existing] = new_template
 
103
  motion.templates.append(new_template)
104
 
105
  motion.save_templates(motion.templates)
106
+ choices = [f"{t['name']} - {t.get('desc', '')[:50]}..." for t in motion.templates]
107
+ return f"Saved '{name}'!", gr.Dropdown(choices=choices, value=f"{name} - {desc[:50]}...")
108
 
109
  # Get all unique tags
110
  all_tags = set()
 
113
  tag_choices = ["all"] + sorted(list(all_tags))
114
 
115
  # Interface
116
+ with gr.Blocks(title="Ken Burns Story Creator", theme=gr.themes.Soft()) as app:
117
+ gr.Markdown("# 🎬 Ken Burns Story Creator", elem_classes="text-center")
118
+ gr.Markdown("Create motion videos for YouTube, TikTok, Reels with 35+ professional effects", elem_classes="text-center")
 
 
119
 
120
  with gr.Tabs():
121
+ # Main tab - Create Video
122
  with gr.Tab("📹 Create Video"):
123
  with gr.Row():
124
  with gr.Column(scale=2):
125
+ # File input
126
+ gr.Markdown("### 📁 Upload Your Media")
127
+ files_input = gr.File(
128
+ label="Select Images/Videos",
129
+ file_count="multiple",
130
+ file_types=["image", "video"]
131
+ )
132
 
133
  # Preview
134
  with gr.Group():
135
+ preview_img = gr.Image(label="📸 Preview", height=200, show_label=False)
136
+ preview_info = gr.Textbox(label="Info", interactive=False, show_label=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
 
138
+ # Motion settings
139
+ gr.Markdown("### 🎭 Motion Effects")
140
  with gr.Row():
141
+ use_random = gr.Checkbox(label="🎲 Random Effects", value=True)
142
  aspect_ratio = gr.Dropdown(
143
  choices=['reels', 'tiktok', 'youtube', 'square', 'widescreen'],
144
  value='reels',
145
  label="📱 Platform"
146
  )
147
+
148
+ # Effect selector (hidden by default)
149
+ with gr.Column(visible=False) as effect_selector:
150
+ with gr.Row():
151
+ tag_filter = gr.Dropdown(
152
+ choices=tag_choices,
153
+ value="all",
154
+ label="🏷️ Filter by Tag"
155
+ )
156
+ search_box = gr.Textbox(
157
+ label="🔍 Search",
158
+ placeholder="zoom, pan, dramatic..."
159
+ )
160
+
161
+ template_dropdown = gr.Dropdown(
162
+ choices=[f"{t['name']} - {t.get('desc', '')[:50]}..." for t in motion.templates],
163
+ label="🎬 Select Effect",
164
+ interactive=True
165
+ )
166
 
167
  with gr.Column(scale=1):
168
+ # Settings
169
+ gr.Markdown("### ⚙️ Settings")
170
+ output_format = gr.Radio(
171
+ choices=['mp4', 'gif'],
172
+ value='mp4',
173
+ label="📄 Output Format"
174
+ )
175
 
176
+ # Create button
177
+ create_btn = gr.Button(
178
+ "🚀 Create Motion Video",
179
+ variant="primary",
180
+ size="lg",
181
+ scale=2
182
+ )
183
+
184
+ # Results
185
+ status = gr.Textbox(label="📊 Status", interactive=False)
186
+ output_video = gr.Video(label="🎥 Result", height=300)
187
+ download_btn = gr.DownloadButton(
188
+ label="📥 Download Video",
189
+ visible=False,
190
+ variant="secondary"
191
+ )
 
 
 
192
 
193
+ # Templates tab
194
+ with gr.Tab("🎭 Motion Templates"):
195
+ gr.Markdown("### 🛠️ Template Editor")
196
+ gr.Markdown(f"**{len(motion.templates)} available templates** - Create and customize motion effects")
197
 
198
  with gr.Row():
199
  with gr.Column(scale=2):
200
  template_dropdown_edit = gr.Dropdown(
201
+ choices=[f"{t['name']} - {t.get('desc', '')[:50]}..." for t in motion.templates],
202
+ value=f"{motion.templates[0]['name']} - {motion.templates[0].get('desc', '')[:50]}..." if motion.templates else None,
203
+ label="📋 Select Template to Edit"
204
  )
205
 
206
  with gr.Row():
207
+ name_input = gr.Textbox(label="Name", placeholder="my_effect")
208
  duration_input = gr.Number(label="Duration (s)", value=4, minimum=2, maximum=10)
209
 
210
  with gr.Row():
 
223
 
224
  tags_input = gr.Textbox(
225
  label="Tags (comma separated)",
226
+ placeholder="zoom, dramatic, smooth"
 
227
  )
228
 
229
  desc_input = gr.Textbox(
 
236
  template_status = gr.Textbox(label="Status", interactive=False)
237
 
238
  with gr.Column(scale=1):
239
+ gr.Markdown("### 📖 Quick Guide")
 
 
 
 
240
  gr.Markdown("""
241
+ **Scale**: Zoom level
242
+ - 1.0 = normal size
243
+ - 1.3 = 30% bigger
244
+ - 0.8 = 20% smaller
245
+
246
+ **Pan**: Movement direction
247
+ - X: left (-) / right (+)
248
+ - Y: up (-) / down (+)
249
+
250
+ **Rotate**: Angle in degrees
251
+ - Positive = clockwise
252
+ - Negative = counter-clockwise
253
+
254
+ **Duration**: 3-7s optimal for social media
255
  """)
256
+
257
+ gr.Markdown("### 🏷️ Popular Tags")
258
+ gr.Markdown(", ".join(sorted(tag_choices[1:8]))) # Show first 7 tags
259
 
260
+ # Help tab
261
  with gr.Tab("ℹ️ Help"):
262
  gr.Markdown(f"""
263
  ### 📖 How to Use
 
 
 
 
 
264
 
265
+ 1. **📁 Upload**: Select your images or videos
266
+ 2. **📱 Platform**: Choose aspect ratio (Reels 9:16, YouTube 16:9, Square 1:1)
267
+ 3. **🎭 Effects**: Use random or select specific motion templates
268
+ 4. **🚀 Create**: Click the create button and wait
269
+ 5. **📥 Download**: Get your final video
270
+
271
+ ### 🎬 Motion Effects ({len(motion.templates)} total)
272
+
273
+ **🔍 Zoom Effects**: Center, corners, dramatic, slow cinematic
274
+ **↔️ Pan Effects**: Horizontal, vertical, diagonal movements
275
+ **🌀 Complex**: Zoom+pan, parallax, spiral, documentary style
276
+ **🎨 Special**: Breathing, pendulum, tilt, intimate close-ups
277
 
278
+ ### 📱 Perfect for Social Media
 
 
 
279
 
280
+ - **TikTok/Reels**: 9:16 vertical format
281
+ - **YouTube Shorts**: 16:9 or 9:16
282
+ - **Instagram Posts**: 1:1 square format
283
+ - **Stories**: Any format works
284
 
285
+ ### ⚡ Performance Tips
 
 
 
286
 
287
+ - Use high-resolution images (1080p+)
288
+ - Keep videos under 60 seconds for social media
289
+ - MP4 for quality, GIF for lightweight sharing
290
+ - Random effects create dynamic variety
291
 
292
+ ### 🛠Technical
293
+
294
+ - Processing: FFmpeg with Ken Burns effects
295
+ - Parallel processing for speed
296
+ - Optimized for 25-30 FPS output
297
+ - Quality presets: fast/medium/slow
298
  """)
299
 
300
+ # Event handlers
301
+ files_input.change(
302
+ fn=preview_files,
303
+ inputs=[files_input],
304
+ outputs=[preview_img, preview_info]
305
+ )
306
+
307
+ create_btn.click(
308
+ fn=create_motion_video,
309
+ inputs=[files_input, aspect_ratio, output_format, use_random, template_dropdown],
310
+ outputs=[output_video, status, download_btn]
311
+ )
312
+
313
+ use_random.change(
314
+ fn=lambda x: gr.update(visible=not x),
315
+ inputs=[use_random],
316
+ outputs=[effect_selector]
317
+ )
318
+
319
+ tag_filter.change(
320
+ fn=filter_templates_by_tag,
321
+ inputs=[tag_filter],
322
+ outputs=[template_dropdown]
323
+ )
324
+
325
+ search_box.change(
326
+ fn=search_templates,
327
+ inputs=[search_box],
328
+ outputs=[template_dropdown]
329
+ )
330
+
331
+ template_dropdown_edit.change(
332
+ fn=load_template,
333
+ inputs=[template_dropdown_edit],
334
+ outputs=[name_input, duration_input, scale_start, scale_end, x1, y1, x2, y2, tags_input, desc_input, rot_start, rot_end]
335
+ )
336
+
337
+ save_btn.click(
338
+ fn=save_template,
339
+ inputs=[name_input, duration_input, scale_start, scale_end, x1, y1, x2, y2, tags_input, desc_input, rot_start, rot_end],
340
+ outputs=[template_status, template_dropdown_edit]
341
+ )
342
+
343
+
344
  app.launch()
motion_processor.py CHANGED
@@ -1,7 +1,4 @@
1
- import json
2
- import random
3
- import subprocess
4
- import math
5
  from pathlib import Path
6
 
7
  class MotionProcessor:
@@ -38,13 +35,14 @@ class MotionProcessor:
38
  if query in t['name'].lower() or
39
  any(query in tag.lower() for tag in t.get('tags', []))]
40
 
41
- def apply_motion(self, input_path, output_path, template=None, aspect='youtube', quality='fast', fps=25):
42
  if not template:
43
  template = self.get_random_template()
44
 
45
  resolution = self.aspect_ratios[aspect]
46
  w, h = map(int, resolution.split(':'))
47
  duration = template['duration']
 
48
 
49
  # Extract values - đơn giản
50
  scale_start, scale_end = template['scale']
@@ -87,7 +85,7 @@ class MotionProcessor:
87
  'ffmpeg', '-y', '-i', input_path,
88
  '-filter_complex', filter_complex,
89
  '-map', '[v]', '-t', str(duration),
90
- '-c:v', 'libx264', '-preset', quality, '-crf', '23',
91
  output_path
92
  ]
93
 
 
1
+ import json, random, subprocess, math
 
 
 
2
  from pathlib import Path
3
 
4
  class MotionProcessor:
 
35
  if query in t['name'].lower() or
36
  any(query in tag.lower() for tag in t.get('tags', []))]
37
 
38
+ def apply_motion(self, input_path, output_path, template=None, aspect='youtube'):
39
  if not template:
40
  template = self.get_random_template()
41
 
42
  resolution = self.aspect_ratios[aspect]
43
  w, h = map(int, resolution.split(':'))
44
  duration = template['duration']
45
+ fps = 25 # Fixed FPS
46
 
47
  # Extract values - đơn giản
48
  scale_start, scale_end = template['scale']
 
85
  'ffmpeg', '-y', '-i', input_path,
86
  '-filter_complex', filter_complex,
87
  '-map', '[v]', '-t', str(duration),
88
+ '-c:v', 'libx264', '-preset', 'fast', '-crf', '23',
89
  output_path
90
  ]
91
 
video_processor.py CHANGED
@@ -15,7 +15,7 @@ class VideoProcessor:
15
  return None
16
 
17
  def create_video(self, input_files, aspect='youtube', output_format='mp4', progress_fn=None,
18
- selected_template=None, advanced_settings=None):
19
  files = self.files.get_files(input_files)
20
  if not files:
21
  return None, "No valid files found"
@@ -27,19 +27,14 @@ class VideoProcessor:
27
  segments_dir = self.files.temp_path("segments")
28
  os.makedirs(segments_dir, exist_ok=True)
29
 
30
- # Advanced settings
31
- workers = advanced_settings.get('workers', 4) if advanced_settings else 4
32
- quality = advanced_settings.get('quality', 'fast') if advanced_settings else 'fast'
33
- fps = advanced_settings.get('fps', 25) if advanced_settings else 25
34
-
35
  processed = []
36
- with ThreadPoolExecutor(max_workers=workers) as executor:
37
  futures = []
38
  for i, file_path in enumerate(files):
39
  output_path = os.path.join(segments_dir, f"seg_{i:03d}.mp4")
40
  # Use selected template or random
41
  template = selected_template if selected_template else None
42
- future = executor.submit(self.process_single, file_path, output_path, aspect, template, quality, fps)
43
  futures.append(future)
44
 
45
  for future in futures:
@@ -71,20 +66,9 @@ class VideoProcessor:
71
 
72
  return final_output, f"Created video from {len(files)} files"
73
 
74
-
75
- def test_effect(self, test_file, template_name, aspect='youtube'):
76
- """Test single effect on one file"""
77
- if not test_file:
78
- return None, "No test file provided"
79
-
80
- template = next((t for t in self.motion.templates if t['name'] == template_name), None)
81
- if not template:
82
- return None, f"Template '{template_name}' not found"
83
-
84
- test_output = self.files.temp_path(f"test_{template_name}.mp4")
85
-
86
  try:
87
- result = self.motion.apply_motion(test_file, test_output, template, aspect)
88
- return result, f"Test completed with '{template_name}' effect"
89
  except Exception as e:
90
- return None, f"Test failed: {str(e)}"
 
 
15
  return None
16
 
17
  def create_video(self, input_files, aspect='youtube', output_format='mp4', progress_fn=None,
18
+ selected_template=None):
19
  files = self.files.get_files(input_files)
20
  if not files:
21
  return None, "No valid files found"
 
27
  segments_dir = self.files.temp_path("segments")
28
  os.makedirs(segments_dir, exist_ok=True)
29
 
 
 
 
 
 
30
  processed = []
31
+ with ThreadPoolExecutor(max_workers=4) as executor:
32
  futures = []
33
  for i, file_path in enumerate(files):
34
  output_path = os.path.join(segments_dir, f"seg_{i:03d}.mp4")
35
  # Use selected template or random
36
  template = selected_template if selected_template else None
37
+ future = executor.submit(self.process_single, file_path, output_path, aspect, template)
38
  futures.append(future)
39
 
40
  for future in futures:
 
66
 
67
  return final_output, f"Created video from {len(files)} files"
68
 
69
+ def process_single(self, file_path, output_path, aspect, template=None):
 
 
 
 
 
 
 
 
 
 
 
70
  try:
71
+ return self.motion.apply_motion(file_path, output_path, template, aspect)
 
72
  except Exception as e:
73
+ print(f"Error processing {file_path}: {e}")
74
+ return None