WAQASCHANNA commited on
Commit
89860f7
·
verified ·
1 Parent(s): 1ebf05f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -56
app.py CHANGED
@@ -1,57 +1,68 @@
1
  import os
 
2
  import streamlit as st
3
  from gtts import gTTS
4
  from pydub import AudioSegment
5
  from moviepy.editor import ImageClip, concatenate_videoclips, AudioFileClip
 
6
 
7
  # ==================================================================
8
  # Core Functions
9
  # ==================================================================
10
- def text_to_speech(slide_texts):
11
- """Convert scripts to voice-over audio"""
12
  audio_clips = []
13
  durations = []
14
 
15
  for i, text in enumerate(slide_texts):
16
- tts = gTTS(text=text, lang='en', slow=False)
17
- tts.save(f"temp_voice_{i}.mp3")
18
- clip = AudioSegment.from_mp3(f"temp_voice_{i}.mp3")
19
- audio_clips.append(clip)
20
- durations.append(len(clip)) # Duration in milliseconds
21
- os.remove(f"temp_voice_{i}.mp3") # Cleanup
 
 
 
22
 
23
  combined_audio = sum(audio_clips)
24
- combined_audio.export("voiceover.mp3", format="mp3")
25
- return durations
 
26
 
27
- def add_background_music(voice_file, music_file):
28
  """Mix voice-over with background music"""
29
- voice = AudioSegment.from_mp3(voice_file)
30
 
31
- if music_file:
32
- music = AudioSegment.from_file(music_file)
33
- music = music[:len(voice)].fade_out(2000)
34
- music = music - 25 # Reduce volume by 25dB
35
- final_audio = voice.overlay(music)
 
 
 
36
  else:
37
  final_audio = voice # No music
38
 
39
- final_audio.export("final_audio.mp3", format="mp3")
40
- return len(final_audio) / 1000 # Duration in seconds
 
41
 
42
- def create_video(img_paths, durations):
43
  """Generate video from images and audio"""
44
  clips = []
45
 
46
  for img_path, duration in zip(img_paths, durations):
47
- # Duration in seconds (convert from milliseconds)
48
  clip = ImageClip(img_path).set_duration(duration / 1000)
49
  clips.append(clip)
50
 
51
  video = concatenate_videoclips(clips, method="compose")
52
- video = video.set_audio(AudioFileClip("final_audio.mp3"))
53
- video.write_videofile("output.mp4", fps=24)
54
- return video
 
 
55
 
56
  # ==================================================================
57
  # Streamlit UI
@@ -59,6 +70,10 @@ def create_video(img_paths, durations):
59
  st.title("PNG Slides to Video Maker 🖼️➡️🎥")
60
  st.markdown("Upload PNG slides, add scripts, and generate a video!")
61
 
 
 
 
 
62
  # Step 1: Upload PNG slides
63
  uploaded_images = st.file_uploader(
64
  "Upload PNG Slides (in order)",
@@ -66,61 +81,86 @@ uploaded_images = st.file_uploader(
66
  accept_multiple_files=True
67
  )
68
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  # Step 2: Upload background music (optional)
70
  uploaded_music = st.file_uploader("Background Music (optional)", type=["mp3"])
 
71
 
72
- # Step 3: Add scripts for each slide
 
 
 
 
 
 
 
73
  slide_texts = []
74
  if uploaded_images:
75
- # Sort images by filename (assuming filenames are in order)
76
- uploaded_images.sort(key=lambda x: x.name)
77
- num_slides = len(uploaded_images)
78
-
79
- with st.expander(f"Add Scripts for {num_slides} Slides"):
80
- for i in range(num_slides):
81
  text = st.text_area(f"Slide {i+1} Text", key=f"slide_{i}")
82
- slide_texts.append(text)
83
 
84
- # Step 4: Generate video
85
  if st.button("Generate Video") and uploaded_images:
86
- if len(slide_texts) != num_slides:
87
- st.error(f"Please add scripts for all {num_slides} slides!")
 
88
  st.stop()
89
 
 
 
 
 
90
  with st.spinner("Creating your video..."):
91
  try:
92
- # 1. Save uploaded images to disk
93
- img_paths = []
94
  for img in uploaded_images:
95
- img_path = img.name
96
- with open(img_path, "wb") as f:
97
  f.write(img.getbuffer())
98
- img_paths.append(img_path)
99
-
100
  # 2. Generate voice-over
101
- durations = text_to_speech(slide_texts)
102
 
103
  # 3. Add background music
104
- music_path = "background_music.mp3" if uploaded_music else None
105
  if uploaded_music:
106
- with open(music_path, "wb") as f:
107
  f.write(uploaded_music.getbuffer())
108
- total_duration = add_background_music("voiceover.mp3", music_path)
109
 
 
 
 
 
 
 
110
  # 4. Create video
111
- create_video(img_paths, durations)
112
 
113
  # 5. Display video
114
  st.success("Video Ready! 🎉")
115
- st.video("output.mp4")
116
-
117
  # 6. Cleanup temporary files
118
- os.remove("voiceover.mp3")
119
- os.remove("final_audio.mp3")
120
- if uploaded_music:
121
- os.remove(music_path)
122
- for img_path in img_paths:
123
- os.remove(img_path)
124
-
125
  except Exception as e:
126
- st.error(f"Error: {str(e)}")
 
1
  import os
2
+ import tempfile
3
  import streamlit as st
4
  from gtts import gTTS
5
  from pydub import AudioSegment
6
  from moviepy.editor import ImageClip, concatenate_videoclips, AudioFileClip
7
+ from pydub.exceptions import CouldntDecodeError
8
 
9
  # ==================================================================
10
  # Core Functions
11
  # ==================================================================
12
+ def text_to_speech(slide_texts, lang='en'):
13
+ """Convert scripts to voice-over audio using temp files"""
14
  audio_clips = []
15
  durations = []
16
 
17
  for i, text in enumerate(slide_texts):
18
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as fp:
19
+ try:
20
+ tts = gTTS(text=text, lang=lang, slow=False)
21
+ tts.save(fp.name)
22
+ clip = AudioSegment.from_mp3(fp.name)
23
+ audio_clips.append(clip)
24
+ durations.append(len(clip)) # Duration in milliseconds
25
+ finally:
26
+ os.unlink(fp.name) # Cleanup temp file
27
 
28
  combined_audio = sum(audio_clips)
29
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as fp:
30
+ combined_audio.export(fp.name, format="mp3")
31
+ return durations, fp.name
32
 
33
+ def add_background_music(voice_path, music_path, volume_reduction=25):
34
  """Mix voice-over with background music"""
35
+ voice = AudioSegment.from_mp3(voice_path)
36
 
37
+ if music_path:
38
+ try:
39
+ music = AudioSegment.from_file(music_path)
40
+ music = music[:len(voice)].fade_out(2000)
41
+ music = music - volume_reduction
42
+ final_audio = voice.overlay(music)
43
+ except CouldntDecodeError:
44
+ raise ValueError("Invalid music file format")
45
  else:
46
  final_audio = voice # No music
47
 
48
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as fp:
49
+ final_audio.export(fp.name, format="mp3")
50
+ return len(final_audio) / 1000, fp.name # Duration in seconds
51
 
52
+ def create_video(img_paths, durations, audio_path):
53
  """Generate video from images and audio"""
54
  clips = []
55
 
56
  for img_path, duration in zip(img_paths, durations):
 
57
  clip = ImageClip(img_path).set_duration(duration / 1000)
58
  clips.append(clip)
59
 
60
  video = concatenate_videoclips(clips, method="compose")
61
+ video = video.set_audio(AudioFileClip(audio_path))
62
+
63
+ with tempfile.NamedTemporaryFile(suffix=".mp4", delete=False) as fp:
64
+ video.write_videofile(fp.name, fps=24)
65
+ return fp.name
66
 
67
  # ==================================================================
68
  # Streamlit UI
 
70
  st.title("PNG Slides to Video Maker 🖼️➡️🎥")
71
  st.markdown("Upload PNG slides, add scripts, and generate a video!")
72
 
73
+ # Initialize session state for slide order
74
+ if 'slide_order' not in st.session_state:
75
+ st.session_state.slide_order = []
76
+
77
  # Step 1: Upload PNG slides
78
  uploaded_images = st.file_uploader(
79
  "Upload PNG Slides (in order)",
 
81
  accept_multiple_files=True
82
  )
83
 
84
+ # Step 1a: Reorder slides
85
+ if uploaded_images:
86
+ st.subheader("Arrange Slide Order")
87
+ filenames = [img.name for img in uploaded_images]
88
+ st.session_state.slide_order = st.multiselect(
89
+ "Drag to reorder slides:",
90
+ filenames,
91
+ default=filenames
92
+ )
93
+ uploaded_images = [img for name in st.session_state.slide_order
94
+ for img in uploaded_images if img.name == name]
95
+
96
  # Step 2: Upload background music (optional)
97
  uploaded_music = st.file_uploader("Background Music (optional)", type=["mp3"])
98
+ music_volume = st.slider("Music Volume Reduction (dB)", 0, 30, 25) if uploaded_music else 0
99
 
100
+ # Step 3: Language selection
101
+ lang = st.selectbox(
102
+ "Voiceover Language",
103
+ options=['en', 'es', 'fr', 'de', 'ja', 'zh-CN', 'hi'],
104
+ index=0
105
+ )
106
+
107
+ # Step 4: Add scripts for each slide
108
  slide_texts = []
109
  if uploaded_images:
110
+ with st.expander(f"Add Scripts for {len(uploaded_images)} Slides"):
111
+ for i, img in enumerate(uploaded_images):
 
 
 
 
112
  text = st.text_area(f"Slide {i+1} Text", key=f"slide_{i}")
113
+ slide_texts.append(text.strip())
114
 
115
+ # Step 5: Generate video
116
  if st.button("Generate Video") and uploaded_images:
117
+ # Input validation
118
+ if len(slide_texts) != len(uploaded_images):
119
+ st.error(f"Please add scripts for all {len(uploaded_images)} slides!")
120
  st.stop()
121
 
122
+ if any(not text for text in slide_texts):
123
+ st.error("All slides must have non-empty text!")
124
+ st.stop()
125
+
126
  with st.spinner("Creating your video..."):
127
  try:
128
+ # 1. Save uploaded images to temp files
129
+ img_temp_files = []
130
  for img in uploaded_images:
131
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
 
132
  f.write(img.getbuffer())
133
+ img_temp_files.append(f.name)
134
+
135
  # 2. Generate voice-over
136
+ durations, voice_path = text_to_speech(slide_texts, lang)
137
 
138
  # 3. Add background music
139
+ music_path = None
140
  if uploaded_music:
141
+ with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as f:
142
  f.write(uploaded_music.getbuffer())
143
+ music_path = f.name
144
 
145
+ audio_duration, final_audio_path = add_background_music(
146
+ voice_path,
147
+ music_path,
148
+ music_volume
149
+ )
150
+
151
  # 4. Create video
152
+ video_path = create_video(img_temp_files, durations, final_audio_path)
153
 
154
  # 5. Display video
155
  st.success("Video Ready! 🎉")
156
+ st.video(video_path)
157
+
158
  # 6. Cleanup temporary files
159
+ for f in img_temp_files + [voice_path, final_audio_path] + ([music_path] if music_path else []):
160
+ os.unlink(f)
161
+ os.unlink(video_path)
162
+
163
+ except ValueError as e:
164
+ st.error(f"Audio Error: {str(e)}")
 
165
  except Exception as e:
166
+ st.error(f"Unexpected Error: {str(e)}")