from moviepy.video.io.VideoFileClip import VideoFileClip, AudioFileClip from moviepy.video.VideoClip import TextClip, ImageClip from moviepy.video.compositing.CompositeVideoClip import concatenate_videoclips, CompositeVideoClip from moviepy.audio.AudioClip import concatenate_audioclips from moviepy.video.tools.subtitles import SubtitlesClip from moviepy.video.VideoClip import ColorClip import os from itertools import accumulate import pysrt def format_time(seconds): """Chuyển đổi thời gian (giây) thành định dạng SRT hh:mm:ss,ms""" mins, sec = divmod(seconds, 60) hours, mins = divmod(mins, 60) return f"{int(hours):02}:{int(mins):02}:{int(sec):02},{int((sec % 1) * 1000):03}" def get_audio_duration(audio_path): # Lọc các file có đuôi .mp3 audio_paths = os.listdir(audio_path) audio_list = [file for file in audio_paths if file.endswith(".mp3")] # Khởi tạo danh sách audio duration duration_list = [] for audio_path in audio_list: # Mở file âm thanh và lấy thời gian with AudioFileClip(f"{audio_path}") as audio: duration_list.append(audio.duration) # Tính tổng tích lũy thời gian duration_list = [format_time(time) for time in list(accumulate(duration_list))] return [format_time(0.0)] + duration_list def create_srt_from_time_and_text(duration_time, text_folder, output_srt): subtitle = "" subtitle_index = 1 text_list = sorted([file for file in os.listdir(text_folder) if file.endswith('.txt') and file != "text.txt" and file != "requirements.txt"]) print(f"Accessing duration_time list:{duration_time} with lenght: {len(duration_time)}") # Duyệt qua các mốc thời gian và file text for i in range(len(duration_time) - 1): print(f"Accessing text_list list: {text_list} with length: {len(text_list)}") start_time = duration_time[i] end_time = duration_time[i + 1] # Lấy tên file text tương ứng text_file = text_list[i] text_path = os.path.join(text_folder, text_file) if os.path.exists(text_path): with open(text_path, 'r', encoding='utf-8') as f: text = f.read().strip() # Thêm phần subtitle vào chuỗi kết quả subtitle += f"{subtitle_index}\n{start_time} --> {end_time}\n{text}\n\n" subtitle_index += 1 else: print(f"File {text_file} không tồn tại!") # Lưu vào file SRT with open(output_srt, 'w', encoding='utf-8') as f: f.write(subtitle) def concatenate_audio_files(audio_folder, output_audio_path): """ Concatenate all .mp3 files in a folder into a single audio file. Parameters: audio_folder (str): The folder containing audio files. output_audio_path (str): The path to save the concatenated audio. """ print("\n[DEBUG] Function: concatenate_audio_files") print(f"Received parameters -> audio_folder: {audio_folder}, output_audio_path: {output_audio_path}") # Validate if the folder exists if not os.path.isdir(audio_folder): print(f"[ERROR] Directory does not exist: {audio_folder}") return audio_clips = [] audio_files = sorted([f for f in os.listdir(audio_folder) if f.endswith('.mp3')]) print(f"[DEBUG] Found audio files: {audio_files}") if not audio_files: print("[ERROR] No .mp3 files found in the directory!") return # Load audio clips for file in audio_files: audio_path = os.path.join(audio_folder, file) try: print(f"[DEBUG] Loading audio file: {audio_path}") audio_clip = AudioFileClip(audio_path) audio_clips.append(audio_clip) except Exception as e: print(f"[ERROR] Failed to load {file}: {e}") # Ensure there are clips to concatenate if not audio_clips: print("[ERROR] No valid audio clips to concatenate!") return try: print("[DEBUG] Concatenating audio clips...") final_audio = concatenate_audioclips(audio_clips) # Save the final concatenated audio print(f"[DEBUG] Saving final audio to: {output_audio_path}") final_audio.write_audiofile(output_audio_path, codec='libmp3lame') print(f"✅ File audio has been saved at: {output_audio_path}") except Exception as e: print(f"[ERROR] Failed to concatenate and save audio: {e}") import os from moviepy.editor import ImageClip, AudioFileClip, concatenate_videoclips def create_video_from_images(image_folder, audio_path, output_video_path): """ Create a video from images and an audio file. Parameters: image_folder (str): Folder containing images. audio_path (str): Path to the background audio file. output_video_path (str): Path to save the final video. """ print("\n[DEBUG] Function: create_video_from_images") print(f"Received parameters -> image_folder: {image_folder}, audio_path: {audio_path}, output_video_path: {output_video_path}") # Validate input folder and file existence if not os.path.isdir(image_folder): print(f"[ERROR] Image folder does not exist: {image_folder}") return if not os.path.isfile(audio_path): print(f"[ERROR] Audio file does not exist: {audio_path}") return try: # Load audio file print(f"[DEBUG] Loading audio file: {audio_path}") audio = AudioFileClip(audio_path) total_duration = audio.duration print(f"[DEBUG] Audio duration: {total_duration:.2f} seconds") # Get image files image_files = sorted([file for file in os.listdir(image_folder) if file.endswith(".png")]) print(f"[DEBUG] Found image files: {image_files}") if not image_files: print("[ERROR] No images found in the directory!") return # Calculate duration per image duration_per_image = total_duration / len(image_files) print(f"[DEBUG] Duration per image: {duration_per_image:.2f} seconds") # Create image clips clips = [] for img in image_files: img_path = os.path.join(image_folder, img) try: print(f"[DEBUG] Loading image: {img_path}") clip = ImageClip(img_path).with_duration(duration_per_image).resize(width=1280) clips.append(clip) except Exception as e: print(f"[ERROR] Failed to load image {img}: {e}") if not clips: print("[ERROR] No valid image clips to create video!") return # Concatenate image clips print("[DEBUG] Concatenating image clips...") final_video = concatenate_videoclips(clips, method="chain") # Add audio to video print("[DEBUG] Attaching audio to video...") final_video.audio = audio # Export video print(f"[DEBUG] Saving final video to: {output_video_path}") final_video.write_videofile(output_video_path, codec="libx264", audio_codec="aac", fps=30) print(f"✅ Video has been saved at: {output_video_path}") except Exception as e: print(f"[ERROR] An error occurred: {e}") def wrap_text(text, max_width): """ Tự động xuống dòng để vừa với chiều rộng max_width. """ import textwrap return "\n".join(textwrap.wrap(text, width=max_width)) def add_subtitles_to_video(video_path, subtitle_path, output_video_path): """ Thêm phụ đề từ file .srt trực tiếp vào video. :param video_path: Đường dẫn video gốc :param subtitle_path: Đường dẫn file .srt :param output_video_path: Đường dẫn lưu video đầu ra """ # Đọc file video video = VideoFileClip(video_path) # Đọc file .srt subs = pysrt.open(subtitle_path) subtitle_clips = [] # Danh sách các đoạn phụ đề # Xử lý từng dòng phụ đề for sub in subs: # Chuyển thời gian thành giây start_time = sub.start.ordinal / 1000 # Chuyển từ milliseconds sang giây end_time = sub.end.ordinal / 1000 font = "BeVietnamPro-Light.ttf" # Tạo clip phụ đề txt_clip = TextClip(font=font, text=wrap_text(sub.text, max_width=85), font_size=30, stroke_color="black", stroke_width=3, color="#fff") # Đặt vị trí hiển thị (giữa phía dưới video) txt_clip = txt_clip.with_position(('center', 'bottom')).with_duration(end_time - start_time).with_start(start_time) subtitle_clips.append(txt_clip) # Ghép phụ đề vào video final_video = CompositeVideoClip([video] + subtitle_clips) # Xuất video với phụ đề final_video.write_videofile(output_video_path, fps=video.fps, codec='libx264', threads=4) print(f"Video với phụ đề đã được lưu tại: {output_video_path}") def text_to_video(): duration_time = get_audio_duration("./") create_srt_from_time_and_text(duration_time, './', 'subtitle.srt') concatenate_audio_files("./","final_audio.mp3") create_video_from_images("./","final_audio.mp3","output.mp4") add_subtitles_to_video("output.mp4", "subtitle.srt", "final_output.mp4")