Upload 6 files
Browse files- app.py +188 -234
- content_generation.py +1 -10
- video_processing.py +0 -1
app.py
CHANGED
@@ -1,235 +1,189 @@
|
|
1 |
-
import asyncio
|
2 |
-
import mimetypes
|
3 |
-
import os
|
4 |
-
import tempfile
|
5 |
-
import glob
|
6 |
-
import
|
7 |
-
|
8 |
-
from
|
9 |
-
from
|
10 |
-
from
|
11 |
-
from
|
12 |
-
from
|
13 |
-
from
|
14 |
-
from
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
"
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
""
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
confirm_button
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
def create_video_func(script, audio_file, keywords, max_clip_duration, join_order, bgm_file):
|
191 |
-
""" Tạo video từ các thông tin đầu vào. """
|
192 |
-
try:
|
193 |
-
# 1. Tính toán thời lượng video
|
194 |
-
audio_clip = AudioFileClip(audio_file)
|
195 |
-
video_duration = audio_clip.duration
|
196 |
-
|
197 |
-
# 2. Tìm kiếm và tải video từ Pexels
|
198 |
-
video_paths = []
|
199 |
-
for keyword in keywords:
|
200 |
-
for _ in range(int(video_duration // max_clip_duration)): # Chia thời lượng video cho thời lượng tối đa mỗi clip
|
201 |
-
image_url = get_pexels_image(keyword)
|
202 |
-
if image_url:
|
203 |
-
video_path = download_video_from_pexels(image_url) # Cần định nghĩa hàm download_video_from_pexels
|
204 |
-
video_paths.append(video_path)
|
205 |
-
|
206 |
-
# 3. Ghép video
|
207 |
-
temp_dir = tempfile.mkdtemp()
|
208 |
-
if join_order: # Ghép ngẫu nhiên
|
209 |
-
random.shuffle(video_paths)
|
210 |
-
combined_video_path = os.path.join(temp_dir, "combined_video.mp4")
|
211 |
-
combine_videos(combined_video_path, video_paths, audio_file, max_clip_duration)
|
212 |
-
|
213 |
-
# 4. Gộp audio và nhạc nền
|
214 |
-
final_video_path = "final_video.mp4"
|
215 |
-
bgm_clip = AudioFileClip(bgm_file)
|
216 |
-
final_audio = CompositeAudioClip([audio_clip, bgm_clip])
|
217 |
-
final_video = VideoFileClip(combined_video_path).set_audio(final_audio)
|
218 |
-
final_video.write_videofile(final_video_path)
|
219 |
-
|
220 |
-
return final_video_path
|
221 |
-
except Exception as e:
|
222 |
-
print(f"Lỗi khi tạo video: {e}")
|
223 |
-
return None
|
224 |
-
|
225 |
-
# Liên kết nút với hàm xử lý video
|
226 |
-
video_button.click(create_video_func,
|
227 |
-
inputs=[script_input, audio_file, keywords_output, max_clip_duration, join_order, bgm_files],
|
228 |
-
outputs=video_output)
|
229 |
-
|
230 |
-
return app
|
231 |
-
|
232 |
-
# Khởi chạy ứng dụng
|
233 |
-
if __name__ == "__main__":
|
234 |
-
app = interface()
|
235 |
app.launch()
|
|
|
1 |
+
import asyncio
|
2 |
+
import mimetypes
|
3 |
+
import os
|
4 |
+
import tempfile
|
5 |
+
import glob
|
6 |
+
import gradio as gr
|
7 |
+
from docx import Document
|
8 |
+
from audio_processing import async_text_to_speech, text_to_speech
|
9 |
+
from content_generation import create_content, CONTENT_TYPES
|
10 |
+
from video_processing import create_video_func
|
11 |
+
from moviepy.editor import AudioFileClip, CompositeAudioClip
|
12 |
+
from utils import (combine_videos, get_pexels_image, get_bgm_file)
|
13 |
+
from video_processing import create_video
|
14 |
+
from content_generation import create_content, CONTENT_TYPES
|
15 |
+
|
16 |
+
def create_docx(content, output_path):
|
17 |
+
"""
|
18 |
+
Tạo file docx từ nội dung.
|
19 |
+
"""
|
20 |
+
doc = Document()
|
21 |
+
doc.add_paragraph(content)
|
22 |
+
doc.save(output_path)
|
23 |
+
|
24 |
+
def process_pdf(file_path):
|
25 |
+
"""
|
26 |
+
Xử lý file PDF và trích xuất nội dung.
|
27 |
+
"""
|
28 |
+
doc = fitz.open(file_path)
|
29 |
+
text = ""
|
30 |
+
for page in doc:
|
31 |
+
text += page.get_text()
|
32 |
+
return text
|
33 |
+
|
34 |
+
def process_docx(file_path):
|
35 |
+
"""
|
36 |
+
Xử lý file DOCX và trích xuất nội dung.
|
37 |
+
"""
|
38 |
+
doc = Document(file_path)
|
39 |
+
text = ""
|
40 |
+
for para in doc.paragraphs:
|
41 |
+
text += para.text
|
42 |
+
return text
|
43 |
+
|
44 |
+
def get_bgm_file_list():
|
45 |
+
"""
|
46 |
+
Trả về danh sách các tệp nhạc nền.
|
47 |
+
"""
|
48 |
+
# Giả sử bạn có một thư mục chứa các tệp nhạc nền
|
49 |
+
song_dir = "/data/bg-music"
|
50 |
+
return [os.path.basename(file) for file in glob.glob(os.path.join(song_dir, "*.mp3"))]
|
51 |
+
|
52 |
+
# Giao diện Gradio
|
53 |
+
def interface():
|
54 |
+
with gr.Blocks() as app:
|
55 |
+
gr.Markdown("# Ứng dụng Tạo Nội dung và Video")
|
56 |
+
|
57 |
+
with gr.Tab("Tạo Nội dung"):
|
58 |
+
prompt = gr.Textbox(label="Nhập yêu cầu nội dung")
|
59 |
+
file_upload = gr.File(label="Tải lên file kèm theo", type="filepath")
|
60 |
+
|
61 |
+
# Sử dụng gr.Radio thay vì gr.CheckboxGroup
|
62 |
+
content_type = gr.Radio(label="Chọn loại nội dung",
|
63 |
+
choices=CONTENT_TYPES,
|
64 |
+
value=None) # Giá trị mặc định là không có gì được chọn
|
65 |
+
|
66 |
+
content_button = gr.Button("Tạo Nội dung")
|
67 |
+
content_output = gr.Textbox(label="Nội dung tạo ra", interactive=True)
|
68 |
+
confirm_button = gr.Button("Xác nhận nội dung")
|
69 |
+
download_docx = gr.File(label="Tải xuống file DOCX", interactive=False)
|
70 |
+
download_audio = gr.File(label="Tải xuống file âm thanh", interactive=False)
|
71 |
+
status_message = gr.Label(label="Trạng thái")
|
72 |
+
|
73 |
+
def generate_content(prompt, file, content_type):
|
74 |
+
try:
|
75 |
+
status = "Đang xử lý..."
|
76 |
+
if file and os.path.exists(file):
|
77 |
+
mime_type, _ = mimetypes.guess_type(file)
|
78 |
+
if mime_type == "application/pdf":
|
79 |
+
file_content = process_pdf(file)
|
80 |
+
prompt = f"{prompt}\n\nDưới đây là nội dung của file tài liệu:\n\n{file_content}"
|
81 |
+
elif mime_type in (
|
82 |
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
83 |
+
"application/msword"):
|
84 |
+
file_content = process_docx(file)
|
85 |
+
prompt = f"{prompt}\n\nDưới đây là nội dung của file tài liệu:\n\n{file_content}"
|
86 |
+
else:
|
87 |
+
raise ValueError("Định dạng file không được hỗ trợ.")
|
88 |
+
|
89 |
+
if not content_type:
|
90 |
+
raise ValueError("Vui lòng chọn một loại nội dung")
|
91 |
+
|
92 |
+
script_content = create_content(prompt, content_type, "Tiếng Việt")
|
93 |
+
docx_path = "script.docx"
|
94 |
+
create_docx(script_content, docx_path)
|
95 |
+
|
96 |
+
status = "Đã tạo nội dung thành công!"
|
97 |
+
return script_content, docx_path, status
|
98 |
+
except Exception as e:
|
99 |
+
status = f"Đã xảy ra lỗi: {str(e)}"
|
100 |
+
return "", None, status
|
101 |
+
|
102 |
+
async def confirm_content(content):
|
103 |
+
docx_path = "script.docx"
|
104 |
+
create_docx(content, docx_path)
|
105 |
+
|
106 |
+
audio_path = await async_text_to_speech(content, "alloy", "Tiếng Việt")
|
107 |
+
return docx_path, audio_path, "Nội dung đã được xác nhận và âm thanh đã được tạo!"
|
108 |
+
|
109 |
+
content_button.click(generate_content,
|
110 |
+
inputs=[prompt, file_upload, content_type],
|
111 |
+
outputs=[content_output, download_docx, status_message])
|
112 |
+
|
113 |
+
confirm_button.click(lambda x: asyncio.run(confirm_content(x)),
|
114 |
+
inputs=[content_output],
|
115 |
+
outputs=[download_docx, download_audio, status_message])
|
116 |
+
|
117 |
+
with gr.Tab("Tạo Âm thanh"):
|
118 |
+
text_input = gr.Textbox(label="Nhập văn bản để chuyển đổi")
|
119 |
+
voice_select = gr.Dropdown(label="Chọn giọng đọc",
|
120 |
+
choices=VOICES) # Dropdown cho voice_select
|
121 |
+
audio_button = gr.Button("Tạo Âm thanh")
|
122 |
+
audio_output = gr.Audio(label="Âm thanh tạo ra")
|
123 |
+
download_audio = gr.File(label="Tải xuống file âm thanh", interactive=False)
|
124 |
+
|
125 |
+
def text_to_speech_func(text, voice):
|
126 |
+
audio_path = text_to_speech(text, voice, "Tiếng Việt")
|
127 |
+
return audio_path, audio_path
|
128 |
+
|
129 |
+
audio_button.click(text_to_speech_func,
|
130 |
+
inputs=[text_input, voice_select],
|
131 |
+
outputs=[audio_output, download_audio])
|
132 |
+
|
133 |
+
with gr.Tab("Tạo Video"):
|
134 |
+
script_input = gr.Textbox(label="Nhập kịch bản")
|
135 |
+
audio_file = gr.File(label="Chọn file âm thanh", type="filepath")
|
136 |
+
keywords_output = gr.Textbox(label="Từ khóa", interactive=True)
|
137 |
+
max_clip_duration = gr.Slider(minimum=2, maximum=5, step=1, label="Thời lượng tối đa mỗi video (giây)")
|
138 |
+
join_order = gr.Checkbox(label="Ghép ngẫu nhiên", value=True) # Mặc định là ghép ngẫu nhiên
|
139 |
+
bgm_files = gr.Dropdown(choices=get_bgm_file_list(), label="Chọn nhạc nền")
|
140 |
+
video_output = gr.Video(label="Video tạo ra")
|
141 |
+
|
142 |
+
# Thêm nút để tạo video
|
143 |
+
video_button = gr.Button("Tạo Video")
|
144 |
+
|
145 |
+
def create_video_func(script, audio_file, keywords, max_clip_duration, join_order, bgm_file):
|
146 |
+
""" Tạo video từ các thông tin đầu vào. """
|
147 |
+
try:
|
148 |
+
# 1. Tính toán thời lượng video
|
149 |
+
audio_clip = AudioFileClip(audio_file)
|
150 |
+
video_duration = audio_clip.duration
|
151 |
+
|
152 |
+
# 2. Tìm kiếm và tải video từ Pexels
|
153 |
+
video_paths = []
|
154 |
+
for keyword in keywords.split(','):
|
155 |
+
video_url = get_pexels_video(keyword.strip())
|
156 |
+
if video_url:
|
157 |
+
video_path = download_video(video_url) # Sử dụng hàm download_video
|
158 |
+
video_paths.append(video_path)
|
159 |
+
|
160 |
+
# 3. Ghép video
|
161 |
+
temp_dir = tempfile.mkdtemp()
|
162 |
+
if join_order: # Ghép ngẫu nhiên
|
163 |
+
random.shuffle(video_paths)
|
164 |
+
combined_video_path = os.path.join(temp_dir, "combined_video.mp4")
|
165 |
+
combine_videos(combined_video_path, video_paths, audio_file, max_clip_duration)
|
166 |
+
|
167 |
+
# 4. Gộp audio và nhạc nền
|
168 |
+
final_video_path = "final_video.mp4"
|
169 |
+
bgm_clip = AudioFileClip(bgm_file)
|
170 |
+
final_audio = CompositeAudioClip([audio_clip, bgm_clip])
|
171 |
+
final_video = VideoFileClip(combined_video_path).set_audio(final_audio)
|
172 |
+
final_video.write_videofile(final_video_path)
|
173 |
+
|
174 |
+
return final_video_path
|
175 |
+
except Exception as e:
|
176 |
+
print(f"Lỗi khi tạo video: {e}")
|
177 |
+
return None
|
178 |
+
|
179 |
+
# Liên kết nút với hàm xử lý video
|
180 |
+
video_button.click(create_video_func,
|
181 |
+
inputs=[script_input, audio_file, keywords_output, max_clip_duration, join_order, bgm_files],
|
182 |
+
outputs=video_output)
|
183 |
+
|
184 |
+
return app
|
185 |
+
|
186 |
+
# Khởi chạy ứng dụng
|
187 |
+
if __name__ == "__main__":
|
188 |
+
app = interface()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
189 |
app.launch()
|
content_generation.py
CHANGED
@@ -50,18 +50,9 @@ CONTENT_TYPE_INSTRUCTIONS = {
|
|
50 |
}
|
51 |
|
52 |
def create_content(prompt, content_type, language):
|
53 |
-
"""
|
54 |
-
Tạo nội dung dựa trên prompt, loại nội dung và ngôn ngữ.
|
55 |
-
"""
|
56 |
content_type_instructions = CONTENT_TYPE_INSTRUCTIONS.get(content_type, "")
|
57 |
general_instructions = f"""
|
58 |
-
Viết một kịch bản dựa trên các ý chính và ý tưởng sáng tạo từ yêu cầu của người dùng
|
59 |
-
Bắt đầu kịch bản bằng cách nêu rõ đây là một bài tóm tắt, tham chiếu đến tiêu đề hoặc đề mục trong văn bản đầu vào. Nếu văn bản đầu vào không có tiêu đề, hãy đưa ra một tóm tắt ngắn gọn về nội dung được đề cập để mở đầu.
|
60 |
-
Bao gồm các định nghĩa và thuật ngữ rõ ràng, cùng với ví dụ cho tất cả các vấn đề chính.
|
61 |
-
Không bao gồm bất kỳ placeholder nào trong ngoặc vuông như [Host] hoặc [Guest]. Thiết kế đầu ra của bạn để được đọc to - nó sẽ được chuyển đổi trực tiếp thành âm thanh.
|
62 |
-
Chỉ có một người nói, bạn. Giữ đúng chủ đề và duy trì một luồng hấp dẫn.
|
63 |
-
Tóm tắt một cách tự nhiên những hiểu biết và bài học chính từ bài tóm tắt. Điều này nên diễn ra một cách tự nhiên từ cuộc trò chuyện, nhắc lại các điểm chính một cách thân mật, như trong một cuộc trò chuyện.
|
64 |
-
Bài tóm tắt nên có khoảng 3000 từ.
|
65 |
Hãy tuân theo những hướng dẫn cụ thể sau cho thể loại {content_type}:
|
66 |
{content_type_instructions}
|
67 |
Ngôn ngữ sử dụng: {language}
|
|
|
50 |
}
|
51 |
|
52 |
def create_content(prompt, content_type, language):
|
|
|
|
|
|
|
53 |
content_type_instructions = CONTENT_TYPE_INSTRUCTIONS.get(content_type, "")
|
54 |
general_instructions = f"""
|
55 |
+
Viết một kịch bản dựa trên các ý chính và ý tưởng sáng tạo từ yêu cầu của người dùng...
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
Hãy tuân theo những hướng dẫn cụ thể sau cho thể loại {content_type}:
|
57 |
{content_type_instructions}
|
58 |
Ngôn ngữ sử dụng: {language}
|
video_processing.py
CHANGED
@@ -2,7 +2,6 @@ import os
|
|
2 |
import random
|
3 |
import shutil
|
4 |
import tempfile
|
5 |
-
from content_generation import extract_key_contents
|
6 |
from concurrent.futures import ThreadPoolExecutor
|
7 |
from moviepy.editor import (
|
8 |
AudioFileClip,
|
|
|
2 |
import random
|
3 |
import shutil
|
4 |
import tempfile
|
|
|
5 |
from concurrent.futures import ThreadPoolExecutor
|
6 |
from moviepy.editor import (
|
7 |
AudioFileClip,
|