Spaces:
Sleeping
Sleeping
Update src/text_to_video.py
Browse files- src/text_to_video.py +52 -61
src/text_to_video.py
CHANGED
@@ -7,6 +7,7 @@ from moviepy.video.VideoClip import ColorClip
|
|
7 |
import os
|
8 |
from itertools import accumulate
|
9 |
import pysrt
|
|
|
10 |
|
11 |
|
12 |
def format_time(seconds):
|
@@ -74,80 +75,70 @@ def concatenate_audio_files(audio_folder, output_audio_path):
|
|
74 |
final_audio.write_audiofile(output_audio_path, codec = 'libmp3lame')
|
75 |
|
76 |
print(f"File audio đã được lưu tại: {output_audio_path}")
|
77 |
-
def create_video_from_images(image_folder, audio_path, output_video_path):
|
78 |
-
# Đọc file âm thanh để lấy thời lượng
|
79 |
-
audio = AudioFileClip(audio_path)
|
80 |
-
total_duration = audio.duration # Tổng thời lượng video bằng thời lượng audio
|
81 |
-
|
82 |
-
# Đọc tất cả các file ảnh trong thư mục và sắp xếp theo tên
|
83 |
-
image_files = [file for file in sorted(os.listdir(image_folder)) if file.endswith(".png")]
|
84 |
-
|
85 |
-
if not image_files:
|
86 |
-
raise ValueError("Không tìm thấy ảnh nào trong thư mục!")
|
87 |
-
|
88 |
-
# Tính thời lượng hiển thị cho mỗi ảnh
|
89 |
-
duration_per_image = total_duration / len(image_files)
|
90 |
-
|
91 |
-
# Tạo danh sách các clip ảnh
|
92 |
-
clips = [ImageClip(f"{img}").with_duration(duration_per_image).resized(width=1280) for img in image_files]
|
93 |
-
|
94 |
-
# Ghép các clip ảnh lại với nhau
|
95 |
-
final_video = concatenate_videoclips(clips, method="chain")
|
96 |
-
|
97 |
-
# Gán âm thanh vào video
|
98 |
-
final_video .audio = audio
|
99 |
-
|
100 |
-
# Xuất video
|
101 |
-
final_video.write_videofile(output_video_path, codec="libx264", audio_codec="aac", fps=30)
|
102 |
-
|
103 |
-
print(f"Video đã được lưu tại: {output_video_path}")
|
104 |
def wrap_text(text, max_width):
|
105 |
"""
|
106 |
Tự động xuống dòng để vừa với chiều rộng max_width.
|
107 |
"""
|
108 |
import textwrap
|
109 |
return "\n".join(textwrap.wrap(text, width=max_width))
|
110 |
-
def
|
111 |
"""
|
112 |
-
|
113 |
|
114 |
-
:param
|
115 |
-
:param
|
|
|
116 |
:param output_video_path: Đường dẫn lưu video đầu ra
|
|
|
117 |
"""
|
118 |
-
|
119 |
-
|
120 |
-
|
|
|
121 |
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
subtitle_clips = [] # Danh sách các đoạn phụ đề
|
126 |
-
|
127 |
-
# Xử lý từng dòng phụ đề
|
128 |
-
for sub in subs:
|
129 |
-
# Chuyển thời gian thành giây
|
130 |
-
start_time = sub.start.ordinal / 1000 # Chuyển từ milliseconds sang giây
|
131 |
-
end_time = sub.end.ordinal / 1000
|
132 |
-
font = "BeVietnamPro-Light.ttf"
|
133 |
-
# Tạo clip phụ đề
|
134 |
-
txt_clip = TextClip(font=font, text=wrap_text(sub.text, max_width=85), font_size=30, stroke_color="black", stroke_width=3, color="#fff")
|
135 |
-
|
136 |
-
# Đặt vị trí hiển thị (giữa phía dưới video)
|
137 |
-
txt_clip = txt_clip.with_position(('center', 'bottom')).with_duration(end_time - start_time).with_start(start_time)
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
146 |
|
147 |
-
print(f"Video với phụ đề đã được lưu tại: {output_video_path}")
|
148 |
def text_to_video():
|
149 |
duration_time = get_audio_duration("./")
|
150 |
create_srt_from_time_and_text(duration_time, './', 'subtitle.srt')
|
151 |
-
concatenate_audio_files("./","final_audio.mp3")
|
152 |
-
create_video_from_images("./","final_audio.mp3","output.mp4")
|
153 |
-
add_subtitles_to_video("output.mp4", "subtitle.srt", "final_output.mp4")
|
|
|
7 |
import os
|
8 |
from itertools import accumulate
|
9 |
import pysrt
|
10 |
+
import subprocess
|
11 |
|
12 |
|
13 |
def format_time(seconds):
|
|
|
75 |
final_audio.write_audiofile(output_audio_path, codec = 'libmp3lame')
|
76 |
|
77 |
print(f"File audio đã được lưu tại: {output_audio_path}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
def wrap_text(text, max_width):
|
79 |
"""
|
80 |
Tự động xuống dòng để vừa với chiều rộng max_width.
|
81 |
"""
|
82 |
import textwrap
|
83 |
return "\n".join(textwrap.wrap(text, width=max_width))
|
84 |
+
def create_video(image_folder=None, audio_path=None, subtitle_path=None, output_video_path=None, convert_audio=False):
|
85 |
"""
|
86 |
+
Tạo video từ ảnh và âm thanh, có thể thêm phụ đề và chuyển đổi định dạng âm thanh.
|
87 |
|
88 |
+
:param image_folder: Thư mục chứa ảnh (nếu tạo video từ ảnh)
|
89 |
+
:param audio_path: Đường dẫn file âm thanh (nếu tạo video từ ảnh và âm thanh)
|
90 |
+
:param subtitle_path: Đường dẫn file phụ đề .srt (nếu muốn thêm phụ đề)
|
91 |
:param output_video_path: Đường dẫn lưu video đầu ra
|
92 |
+
:param convert_audio: Nếu True, chuyển đổi âm thanh sang AAC
|
93 |
"""
|
94 |
+
if image_folder and audio_path:
|
95 |
+
# Tạo video từ ảnh và âm thanh
|
96 |
+
audio = AudioFileClip(audio_path)
|
97 |
+
total_duration = audio.duration
|
98 |
|
99 |
+
image_files = [file for file in sorted(os.listdir(image_folder)) if file.endswith(".png")]
|
100 |
+
if not image_files:
|
101 |
+
raise ValueError("Không tìm thấy ảnh nào trong thư mục!")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
|
103 |
+
duration_per_image = total_duration / len(image_files)
|
104 |
+
clips = [ImageClip(f"{img}").with_duration(duration_per_image).resized(width=1280) for img in image_files]
|
105 |
+
final_video = concatenate_videoclips(clips, method="chain")
|
106 |
+
final_video.audio = audio
|
107 |
+
final_video.write_videofile(output_video_path, codec="libx264", audio_codec="aac", fps=30)
|
108 |
+
print(f"✅ Video đã được tạo tại: {output_video_path}")
|
109 |
+
|
110 |
+
if convert_audio:
|
111 |
+
# Chuyển đổi định dạng âm thanh của video
|
112 |
+
command = [
|
113 |
+
"ffmpeg", "-i", output_video_path,
|
114 |
+
"-c:v", "copy", "-c:a", "aac", "-b:a", "192k",
|
115 |
+
"-y", output_video_path.replace(".mp4", "_aac.mp4")
|
116 |
+
]
|
117 |
+
try:
|
118 |
+
subprocess.run(command, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
119 |
+
print(f"✅ Đã chuyển đổi âm thanh: {output_video_path.replace('.mp4', '_aac.mp4')}")
|
120 |
+
except subprocess.CalledProcessError as e:
|
121 |
+
print(f"❌ Lỗi khi chuyển đổi âm thanh: {e.stderr.decode()}")
|
122 |
+
|
123 |
+
if subtitle_path:
|
124 |
+
# Thêm phụ đề vào video
|
125 |
+
video = VideoFileClip(output_video_path)
|
126 |
+
subs = pysrt.open(subtitle_path)
|
127 |
+
subtitle_clips = []
|
128 |
+
|
129 |
+
for sub in subs:
|
130 |
+
start_time = sub.start.ordinal / 1000
|
131 |
+
end_time = sub.end.ordinal / 1000
|
132 |
+
font = "BeVietnamPro-Light.ttf"
|
133 |
+
txt_clip = TextClip(font=font, text=wrap_text(sub.text, max_width=85), font_size=30, stroke_color="black", stroke_width=3, color="#fff")
|
134 |
+
txt_clip = txt_clip.with_position(('center', 'bottom')).with_duration(end_time - start_time).with_start(start_time)
|
135 |
+
subtitle_clips.append(txt_clip)
|
136 |
+
|
137 |
+
final_video = CompositeVideoClip([video] + subtitle_clips)
|
138 |
+
final_video.write_videofile(output_video_path.replace(".mp4", "_subtitled.mp4"), fps=video.fps, codec='libx264', threads=4)
|
139 |
+
print(f"✅ Video với phụ đề đã được lưu tại: {output_video_path.replace('.mp4', '_subtitled.mp4')}")
|
140 |
|
|
|
141 |
def text_to_video():
|
142 |
duration_time = get_audio_duration("./")
|
143 |
create_srt_from_time_and_text(duration_time, './', 'subtitle.srt')
|
144 |
+
concatenate_audio_files("./","final_audio.mp3")create_video(image_folder="./", audio_path="final_audio.mp3", subtitle_path="subtitle.srt", output_video_path="final_output.mp4")
|
|
|
|