WAQASCHANNA commited on
Commit
895252e
·
verified ·
1 Parent(s): d8392ee

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +196 -84
app.py CHANGED
@@ -10,108 +10,220 @@ from pydub.exceptions import CouldntDecodeError
10
  tempfile.tempdir = "/tmp"
11
 
12
  # ==================================================================
13
- # Core Functions (No changes needed here)
14
  # ==================================================================
15
- # [Keep all the core functions exactly as in previous version]
16
- # ... [text_to_speech, add_background_music, create_video functions] ...
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  # ==================================================================
19
- # Streamlit UI - Fixed Layout Version
20
  # ==================================================================
21
  st.title("PNG Slides to Video Maker 🖼️➡️🎥")
22
  st.markdown("Upload PNG slides, add scripts, and generate a video!")
23
 
24
- # --- File Upload Section ---
25
  uploaded_images = st.file_uploader(
26
- "Step 1: Upload PNG Slides",
27
- type=["png"],
28
  accept_multiple_files=True,
29
  key="main_uploader"
30
  )
31
 
32
- # --- Only show other controls if images are uploaded ---
33
- if uploaded_images:
34
- # --- Slide Ordering ---
35
- st.subheader("Step 2: Arrange Slide Order")
36
- filenames = [img.name for img in uploaded_images]
37
- st.session_state.slide_order = st.multiselect(
38
- "Drag to reorder slides:",
39
- filenames,
40
- default=filenames,
41
- key="sort_slides"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  )
43
- uploaded_images = [img for name in st.session_state.slide_order
44
- for img in uploaded_images if img.name == name]
45
 
46
- # --- Settings Section ---
47
- st.subheader("Step 3: Video Settings")
48
-
49
- col1, col2 = st.columns(2)
50
- with col1:
51
- transition_delay = st.slider(
52
- "Transition Delay (seconds)",
53
- min_value=0,
54
- max_value=5,
55
- value=2,
56
- help="Pause between slides after audio finishes"
 
 
 
 
 
57
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- with col2:
60
- gender = st.selectbox(
61
- "Voice Gender",
62
- options=['female', 'male'],
63
- help="Gender selection available for supported languages"
64
- )
65
 
66
- lang = st.selectbox(
67
- "Voice Language",
68
- ['en', 'es', 'fr', 'de', 'ja', 'zh-CN', 'hi'],
69
- index=0
70
- )
 
 
 
71
 
72
- # --- Script Input Section ---
73
- st.subheader("Step 4: Add Scripts")
74
- slide_texts = []
75
- with st.expander(f"Scripts for {len(uploaded_images)} Slides"):
76
- for i, img in enumerate(uploaded_images):
77
- text = st.text_area(
78
- f"Slide {i+1} Text",
79
- key=f"slide_{i}",
80
- placeholder="Enter text for this slide..."
81
  )
82
- slide_texts.append(text.strip())
83
-
84
- # --- Background Music Section ---
85
- st.subheader("Optional: Background Music")
86
- uploaded_music = st.file_uploader(
87
- "Upload background music (MP3)",
88
- type=["mp3"],
89
- key="music_uploader"
90
- )
91
- music_volume = st.slider(
92
- "Music Volume Reduction (dB)",
93
- 0, 30, 25,
94
- help="Higher values make music quieter"
95
- ) if uploaded_music else 0
96
-
97
- # --- Generate Button ---
98
- st.subheader("Step 5: Generate Video")
99
- if st.button("🚀 Generate Video", use_container_width=True):
100
- # Validation checks
101
- if len(slide_texts) != len(uploaded_images):
102
- st.error("Number of scripts doesn't match number of slides!")
103
- st.stop()
104
 
105
- if any(not text for text in slide_texts):
106
- st.error("All slides must have non-empty text!")
107
- st.stop()
 
 
108
 
109
- with st.spinner("Creating your masterpiece..."):
110
- try:
111
- # [Keep processing code from previous version]
112
- # ... [processing logic here] ...
113
-
114
- except Exception as e:
115
- st.error(f"Error: {str(e)}")
116
- else:
117
- st.info("ℹ️ Please upload PNG slides to begin")
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  tempfile.tempdir = "/tmp"
11
 
12
  # ==================================================================
13
+ # Core Functions
14
  # ==================================================================
15
+ def text_to_speech(slide_texts, lang='en', gender='female', transition_delay=0):
16
+ """Convert text to speech with gender selection and synchronized delays"""
17
+ audio_clips = []
18
+ durations = []
19
+
20
+ # Voice configuration mapping
21
+ tld_map = {
22
+ 'female': {'en': 'com', 'es': 'es', 'fr': 'fr', 'de': 'de', 'ja': 'co.jp'},
23
+ 'male': {'en': 'com.au', 'es': 'com.mx', 'fr': 'ca', 'de': 'de', 'ja': 'co.jp'}
24
+ }
25
+
26
+ for i, text in enumerate(slide_texts):
27
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as fp:
28
+ try:
29
+ # Generate speech with selected gender
30
+ tts = gTTS(
31
+ text=text,
32
+ lang=lang,
33
+ tld=tld_map[gender].get(lang, 'com'),
34
+ slow=False
35
+ )
36
+ tts.save(fp.name)
37
+ clip = AudioSegment.from_mp3(fp.name)
38
+
39
+ # Add transition delay as silence after each clip
40
+ silence = AudioSegment.silent(duration=transition_delay*1000)
41
+ clip_with_delay = clip + silence
42
+
43
+ audio_clips.append(clip_with_delay)
44
+ durations.append(len(clip_with_delay))
45
+ finally:
46
+ os.unlink(fp.name)
47
+
48
+ combined_audio = sum(audio_clips)
49
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as fp:
50
+ combined_audio.export(fp.name, format="mp3")
51
+ return durations, fp.name
52
+
53
+ def add_background_music(voice_path, music_path, volume_reduction=25):
54
+ """Mix voice-over with background music"""
55
+ voice = AudioSegment.from_mp3(voice_path)
56
+
57
+ if music_path:
58
+ try:
59
+ music = AudioSegment.from_file(music_path)
60
+ music = music[:len(voice)].fade_out(2000)
61
+ music = music - volume_reduction
62
+ final_audio = voice.overlay(music)
63
+ except CouldntDecodeError:
64
+ raise ValueError("Invalid music file format")
65
+ else:
66
+ final_audio = voice
67
+
68
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as fp:
69
+ final_audio.export(fp.name, format="mp3")
70
+ return len(final_audio) / 1000, fp.name
71
+
72
+ def create_video(img_paths, durations, audio_path):
73
+ """Generate video synchronized with audio"""
74
+ clips = []
75
+
76
+ for img_path, duration in zip(img_paths, durations):
77
+ clip = ImageClip(img_path).set_duration(duration / 1000)
78
+ clips.append(clip)
79
+
80
+ video = concatenate_videoclips(clips, method="compose")
81
+ video = video.set_audio(AudioFileClip(audio_path))
82
+
83
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as fp:
84
+ video.write_videofile(fp.name, fps=24, threads=4)
85
+ return fp.name
86
 
87
  # ==================================================================
88
+ # Streamlit UI
89
  # ==================================================================
90
  st.title("PNG Slides to Video Maker 🖼️➡️🎥")
91
  st.markdown("Upload PNG slides, add scripts, and generate a video!")
92
 
93
+ # Main file uploader
94
  uploaded_images = st.file_uploader(
95
+ "Step 1: Upload PNG Slides",
96
+ type=["png"],
97
  accept_multiple_files=True,
98
  key="main_uploader"
99
  )
100
 
101
+ if not uploaded_images:
102
+ st.info("ℹ️ Please upload PNG slides to begin")
103
+ st.stop()
104
+
105
+ # Slide ordering
106
+ st.subheader("Step 2: Arrange Slide Order")
107
+ filenames = [img.name for img in uploaded_images]
108
+ st.session_state.slide_order = st.multiselect(
109
+ "Drag to reorder slides:",
110
+ filenames,
111
+ default=filenames,
112
+ key="sort_slides"
113
+ )
114
+ uploaded_images = [img for name in st.session_state.slide_order
115
+ for img in uploaded_images if img.name == name]
116
+
117
+ # Video settings
118
+ st.subheader("Step 3: Video Settings")
119
+ col1, col2 = st.columns(2)
120
+ with col1:
121
+ transition_delay = st.slider(
122
+ "Transition Delay (seconds)",
123
+ min_value=0,
124
+ max_value=5,
125
+ value=2,
126
+ help="Silence between slides after voice finishes"
127
+ )
128
+ with col2:
129
+ gender = st.selectbox(
130
+ "Voice Gender",
131
+ options=['female', 'male'],
132
+ help="Gender selection for supported languages"
133
  )
 
 
134
 
135
+ lang = st.selectbox(
136
+ "Voice Language",
137
+ ['en', 'es', 'fr', 'de', 'ja', 'zh-CN', 'hi'],
138
+ index=0
139
+ )
140
+
141
+ # Script input
142
+ st.subheader("Step 4: Add Scripts")
143
+ slide_texts = []
144
+ with st.expander(f"Scripts for {len(uploaded_images)} Slides", expanded=True):
145
+ for i, img in enumerate(uploaded_images):
146
+ text = st.text_area(
147
+ f"Slide {i+1} Text",
148
+ key=f"slide_{i}",
149
+ placeholder="Enter text for this slide...",
150
+ height=100
151
  )
152
+ slide_texts.append(text.strip())
153
+
154
+ # Music settings
155
+ st.subheader("Step 5: Background Music (Optional)")
156
+ uploaded_music = st.file_uploader(
157
+ "Upload MP3 file",
158
+ type=["mp3"],
159
+ key="music_uploader"
160
+ )
161
+ music_volume = st.slider(
162
+ "Music Volume Reduction (dB)",
163
+ 0, 30, 25,
164
+ help="Higher values make background music quieter"
165
+ ) if uploaded_music else 0
166
+
167
+ # Generate button
168
+ st.subheader("Step 6: Generate Video")
169
+ if st.button("🚀 Generate Video", use_container_width=True, type="primary"):
170
+ # Validation
171
+ if len(slide_texts) != len(uploaded_images):
172
+ st.error("Number of scripts doesn't match number of slides!")
173
+ st.stop()
174
 
175
+ if any(not text for text in slide_texts):
176
+ st.error("All slides must have non-empty text!")
177
+ st.stop()
 
 
 
178
 
179
+ with st.spinner("Creating your video... This may take a minute ⏳"):
180
+ try:
181
+ # 1. Save images to temp files
182
+ img_paths = []
183
+ for img in uploaded_images:
184
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
185
+ f.write(img.getbuffer())
186
+ img_paths.append(f.name)
187
 
188
+ # 2. Generate voiceover with delays
189
+ durations, voice_path = text_to_speech(
190
+ slide_texts,
191
+ lang,
192
+ gender,
193
+ transition_delay
 
 
 
194
  )
195
+
196
+ # 3. Process background music
197
+ music_path = None
198
+ if uploaded_music:
199
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
200
+ f.write(uploaded_music.getbuffer())
201
+ music_path = f.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ audio_duration, final_audio_path = add_background_music(
204
+ voice_path,
205
+ music_path,
206
+ music_volume
207
+ )
208
 
209
+ # 4. Create video
210
+ video_path = create_video(img_paths, durations, final_audio_path)
211
+
212
+ # 5. Display result
213
+ st.success("✅ Video Ready! Play it below")
214
+ st.video(video_path)
215
+
216
+ # 6. Cleanup
217
+ cleanup_files = img_paths + [voice_path, final_audio_path]
218
+ if music_path:
219
+ cleanup_files.append(music_path)
220
+ cleanup_files.append(video_path)
221
+
222
+ for f in cleanup_files:
223
+ if os.path.exists(f):
224
+ os.unlink(f)
225
+
226
+ except ValueError as e:
227
+ st.error(f"Audio Error: {str(e)}")
228
+ except Exception as e:
229
+ st.error(f"Processing Error: {str(e)}")