TDN-M commited on
Commit
1e06115
·
verified ·
1 Parent(s): 43d8f8b

Upload 8 files

Browse files
Files changed (8) hide show
  1. README.md +9 -12
  2. app.py +114 -0
  3. audio_processing.py +38 -0
  4. config.py +6 -0
  5. content_generation.py +122 -0
  6. requirements.txt +9 -0
  7. utils.py +101 -0
  8. video_processing.py +157 -0
README.md CHANGED
@@ -1,12 +1,9 @@
1
- ---
2
- title: GV A
3
- emoji: 📊
4
- colorFrom: indigo
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.0.2
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # Ứng dụng Tạo Nội dung và Video
2
+
3
+ Ứng dụng này cho phép bạn tạo nội dung và video tự động từ văn bản đầu vào.
4
+
5
+ ## Cài đặt
6
+
7
+ 1. Clone repository này:
8
+ ```bash
9
+ git clone <repository_url>
 
 
 
app.py ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import mimetypes
3
+ import os
4
+
5
+ import gradio as gr
6
+
7
+ from audio_processing import async_text_to_speech, text_to_speech
8
+ from content_generation import create_content, CONTENT_TYPES
9
+ from video_processing import create_video_func
10
+
11
+ # Cấu hình API keys (di chuyển sang config.py)
12
+ # GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
13
+ # OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
14
+
15
+ # Danh sách giọng đọc
16
+ VOICES = ["alloy", "echo", "fable", "onyx", "nova", "shimmer"]
17
+
18
+ # Danh sách ngôn ngữ (chưa được sử dụng trong mã)
19
+ LANGUAGES = ["Tiếng Anh", "Tiếng Việt", "Tiếng Hindi"]
20
+
21
+ # ... (CONTENT_TYPES, CONTENT_TYPE_INSTRUCTIONS)
22
+ # Danh sách loại nội dung và hướng dẫn mặc định cho từng loại
23
+ CONTENT_TYPES = ["podcast", "giới thiệu", "triết lý sống", "Phổ biến kiến thức thống kê"]
24
+ CONTENT_TYPE_INSTRUCTIONS = {
25
+ "podcast": """
26
+ Tone giọng: Gần gũi, thân thiện nhưng chuyên sâu, thể hiện sự am hiểu về chủ đề.
27
+ Cấu trúc:
28
+ - Bắt đầu bằng một câu hỏi kích thích tư duy hoặc một câu chuyện mở màn gây tò mò.
29
+ - Triển khai các luận điểm theo từng bước. Sử dụng câu từ mạnh mẽ, ví dụ điển hình hoặc những câu nói nổi tiếng.
30
+ - Xây dựng các phần chuyển tiếp mượt mà giữa các ý.
31
+ - Kết thúc podcast với một thông điệp sâu sắc, để lại sự suy ngẫm cho thính giả.
32
+ Mục tiêu: Mang lại kiến thức giá trị, lôi cuốn thính giả tham gia suy nghĩ và cảm nhận sâu sắc về chủ đề.
33
+ """,
34
+ "giới thiệu": """
35
+ Tone giọng: Chuyên nghiệp, gãy gọn nhưng vẫn có sự truyền cảm.
36
+ Cấu trúc:
37
+ - Bắt đầu với một câu khẳng định mạnh mẽ về đối tượng được giới thiệu.
38
+ - Giải thích mục tiêu của phần giới thiệu, nhấn mạnh tầm quan trọng hoặc sự khác biệt.
39
+ - Kết thúc với một lời kêu gọi hành động, khích lệ người nghe tiếp tục lắng nghe hoặc tham gia.
40
+ Mục tiêu: Đưa ra thông tin cô đọng, hấp dẫn, khiến người nghe cảm thấy bị thu hút và muốn tìm hiểu thêm.
41
+ """,
42
+ "triết lý sống": """
43
+ Tone giọng: Sâu sắc, truyền cảm hứng, mang tính chiêm nghiệm.
44
+ Cấu trúc:
45
+ - Bắt đầu bằng một câu hỏi sâu sắc hoặc ẩn dụ về cuộc sống.
46
+ - Triển khai các luận điểm chặt chẽ, xen lẫn cảm xúc và những ví dụ đời thực hoặc những câu nói triết lý.
47
+ - Kết thúc với một thông điệp sâu sắc, khơi dậy suy ngẫm cho người nghe.
48
+ Mục tiêu: Khơi gợi suy nghĩ sâu sắc về cuộc sống, khiến người nghe tìm thấy ý nghĩa hoặc giá trị trong câu chuyện.
49
+ """,
50
+ "Phổ biến kiến thức Thống kê": """
51
+ Tone giọng: Thân thiện, dễ hiểu, và mang tính giáo dục.
52
+ Cấu trúc:
53
+ - Bắt đầu với một câu hỏi hoặc một tình huống thực tế để thu hút sự chú ý.
54
+ - Giải thích các khái niệm thống kê cơ bản một cách đơn giản và dễ hiểu, sử dụng ví dụ thực tế để minh họa.
55
+ - Đưa ra các ứng dụng thực tế của thống kê trong đời sống hàng ngày hoặc trong các lĩnh vực cụ thể.
56
+ - Kết thúc với một thông điệp khuyến khích người nghe áp dụng kiến thức thống kê vào cuộc sống.
57
+ Mục tiêu: Giúp người nghe hiểu và yêu thích thống kê, thấy được giá trị và ứng dụng của nó trong cuộc sống.
58
+ """
59
+ }
60
+
61
+ def create_docx(content, output_path):
62
+ """
63
+ Tạo file docx từ nội dung.
64
+ """
65
+ doc = Document()
66
+ doc.add_paragraph(content)
67
+ doc.save(output_path)
68
+
69
+ def process_pdf(file_path):
70
+ """
71
+ Xử lý file PDF và trích xuất nội dung.
72
+ """
73
+ doc = fitz.open(file_path)
74
+ text = ""
75
+ for page in doc:
76
+ text += page.get_text()
77
+ return text
78
+
79
+ def process_docx(file_path):
80
+ """
81
+ Xử lý file DOCX và trích xuất nội dung.
82
+ """
83
+ doc = Document(file_path)
84
+ text = ""
85
+ for para in doc.paragraphs:
86
+ text += para.text
87
+ return text
88
+
89
+ # Giao diện Gradio
90
+ def interface():
91
+ with gr.Blocks() as app:
92
+ # ... (Các tab khác)
93
+
94
+ with gr.Tab("Tạo Âm thanh"):
95
+ text_input = gr.Textbox(label="Nhập văn bản để chuyển đổi")
96
+ voice_select = gr.Dropdown(label="Chọn giọng đọc", choices=VOICES) # Dropdown cho voice_select
97
+ audio_button = gr.Button("Tạo Âm thanh")
98
+ audio_output = gr.Audio(label="Âm thanh tạo ra")
99
+ download_audio = gr.File(label="Tải xuống file âm thanh", interactive=False)
100
+
101
+ def text_to_speech_func(text, voice):
102
+ audio_path = text_to_speech(text, voice, "Tiếng Việt")
103
+ return audio_path, audio_path
104
+
105
+ audio_button.click(text_to_speech_func, inputs=[text_input, voice_select], outputs=[audio_output, download_audio])
106
+
107
+ # ... (Các tab khác)
108
+
109
+ return app
110
+
111
+ # Khởi chạy ứng dụng
112
+ if __name__ == "__main__":
113
+ app = interface()
114
+ app.launch()
audio_processing.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # audio_processing.py
2
+ import asyncio
3
+ import os
4
+ import tempfile
5
+
6
+ from openai import OpenAI
7
+
8
+ # Lấy API key từ biến môi trường
9
+ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
10
+
11
+ # Khởi tạo client OpenAI
12
+ openai_client = OpenAI(api_key=OPENAI_API_KEY)
13
+
14
+ def text_to_speech(text, voice, language):
15
+ """
16
+ Chuyển đổi văn bản thành giọng nói bằng OpenAI API.
17
+ """
18
+ try:
19
+ response = openai_client.audio.speech.create(
20
+ model="tts-1",
21
+ voice=voice,
22
+ input=text
23
+ )
24
+
25
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio_file:
26
+ for chunk in response.iter_bytes(chunk_size=4096):
27
+ temp_audio_file.write(chunk)
28
+
29
+ return temp_audio_file.name
30
+ except Exception as e:
31
+ return f"Lỗi khi chuyển đổi văn bản thành giọng nói: {str(e)}"
32
+
33
+ async def async_text_to_speech(text, voice, language):
34
+ """
35
+ Chuyển đổi văn bản thành giọng nói (bất đồng bộ).
36
+ """
37
+ loop = asyncio.get_event_loop()
38
+ return await loop.run_in_executor(None, text_to_speech, text, voice, language)
config.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # Cấu hình API keys
4
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
5
+ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
6
+ PEXELS_API_KEY = os.environ.get("PEXELS_API_KEY")
content_generation.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # content_generation.py
2
+ from groq import Groq
3
+ from openai import OpenAI
4
+ import os
5
+
6
+ # Lấy API key từ biến môi trường
7
+ GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
8
+ OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
9
+
10
+ # Khởi tạo client OpenAI
11
+ openai_client = OpenAI(api_key=OPENAI_API_KEY)
12
+
13
+ # Danh sách loại nội dung và hướng dẫn mặc định cho từng loại
14
+ CONTENT_TYPES = ["podcast", "giới thiệu", "triết lý sống", "Phổ biến kiến thức thống kê"]
15
+ CONTENT_TYPE_INSTRUCTIONS = {
16
+ "podcast": """
17
+ Tone giọng: Gần gũi, thân thiện nhưng chuyên sâu, thể hiện sự am hiểu về chủ đề.
18
+ Cấu trúc:
19
+ - Bắt đầu bằng một câu hỏi kích thích tư duy hoặc một câu chuyện mở màn gây tò mò.
20
+ - Triển khai các luận điểm theo từng bước. Sử dụng câu từ mạnh mẽ, ví dụ điển hình hoặc những câu nói nổi tiếng.
21
+ - Xây dựng các phần chuyển tiếp mượt mà giữa các ý.
22
+ - Kết thúc podcast với một thông điệp sâu sắc, để lại sự suy ngẫm cho thính giả.
23
+ Mục tiêu: Mang lại kiến thức giá trị, lôi cuốn thính giả tham gia suy nghĩ và cảm nhận sâu sắc về chủ đề.
24
+ """,
25
+ "giới thiệu": """
26
+ Tone giọng: Chuyên nghiệp, gãy gọn nhưng vẫn có sự truyền cảm.
27
+ Cấu trúc:
28
+ - Bắt đầu với một câu khẳng định mạnh mẽ về đối tượng được giới thiệu.
29
+ - Giải thích mục tiêu của phần giới thiệu, nhấn mạnh tầm quan trọng hoặc sự khác biệt.
30
+ - Kết thúc với một lời kêu gọi hành động, khích lệ người nghe tiếp tục lắng nghe hoặc tham gia.
31
+ Mục tiêu: Đưa ra thông tin cô đọng, hấp dẫn, khiến người nghe cảm thấy bị thu hút và muốn tìm hiểu thêm.
32
+ """,
33
+ "triết lý sống": """
34
+ Tone giọng: Sâu sắc, truyền cảm hứng, mang tính chiêm nghiệm.
35
+ Cấu trúc:
36
+ - Bắt đầu bằng một câu hỏi sâu sắc hoặc ẩn dụ về cuộc sống.
37
+ - Triển khai các luận điểm chặt chẽ, xen lẫn cảm xúc và những ví dụ đời thực hoặc những câu nói triết lý.
38
+ - Kết thúc với một thông điệp sâu sắc, khơi dậy suy ngẫm cho người nghe.
39
+ Mục tiêu: Khơi gợi suy nghĩ sâu sắc về cuộc sống, khiến người nghe tìm thấy ý nghĩa hoặc giá trị trong câu chuyện.
40
+ """,
41
+ "Phổ biến kiến thức Thống kê": """
42
+ Tone giọng: Thân thiện, dễ hiểu, và mang tính giáo dục.
43
+ Cấu trúc:
44
+ - Bắt đầu với một câu hỏi hoặc một tình huống thực tế để thu hút sự chú ý.
45
+ - Giải thích các khái niệm thống kê cơ bản một cách đơn giản và dễ hiểu, sử dụng ví dụ thực tế để minh họa.
46
+ - Đưa ra các ứng dụng thực tế của thống kê trong đời sống hàng ngày hoặc trong các lĩnh vực cụ thể.
47
+ - Kết thúc với một thông điệp khuyến khích người nghe áp dụng kiến thức thống kê vào cuộc sống.
48
+ Mục tiêu: Giúp người nghe hiểu và yêu thích thống kê, thấy được giá trị và ứng dụng của nó trong cuộc sống.
49
+ """
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. Sử dụng giọng điệu trò chuyện và bao gồm bất kỳ bối cảnh hoặc giải thích cần thiết nào để làm cho nội dung dễ tiếp cận với khán giả.
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}
68
+ """
69
+
70
+ try:
71
+ client = Groq(api_key=GROQ_API_KEY)
72
+ chat_completion = client.chat.completions.create(
73
+ model="mixtral-8x7b-32768",
74
+ messages=[
75
+ {"role": "system", "content": general_instructions},
76
+ {"role": "user", "content": prompt}
77
+ ],
78
+ temperature=0.7,
79
+ max_tokens=8000
80
+ )
81
+ return chat_completion.choices[0].message.content
82
+ except Exception as e:
83
+ return f"Lỗi khi tạo nội dung: {str(e)}"
84
+
85
+ def extract_key_contents(script, num_contents=30):
86
+ """
87
+ Trích xuất các ý chính từ script.
88
+ """
89
+ try:
90
+ response = openai_client.chat.completions.create(
91
+ model="gpt-3.5-turbo",
92
+ messages=[
93
+ {"role": "system", "content": f"Bạn là một chuyên gia phân tích nội dung. Hãy trích xuất chính xác {num_contents} ý chính quan trọng nhất từ đoạn văn sau, mỗi ý không quá 20 từ."},
94
+ {"role": "user", "content": script}
95
+ ]
96
+ )
97
+
98
+ # In response để kiểm tra
99
+ print("Response từ OpenAI:", response)
100
+
101
+ key_contents = response.choices[0].message.content.split('\n')
102
+ return key_contents[:num_contents]
103
+ except Exception as e:
104
+ print(f"Lỗi khi trích xuất nội dung: {str(e)}")
105
+ return []
106
+
107
+ def generate_image_prompt(content):
108
+ """
109
+ Tạo prompt cho hình ảnh từ nội dung.
110
+ """
111
+ try:
112
+ response = openai_client.chat.completions.create(
113
+ model="gpt-3.5-turbo",
114
+ messages=[
115
+ {"role": "system", "content": "You are an expert at creating prompts for AI image generation. Create a short, concise prompt in English to visually describe the following content. The content may be in Vietnamese, but your prompt should always be in English."},
116
+ {"role": "user", "content": content}
117
+ ]
118
+ )
119
+ return response.choices[0].message.content.strip()
120
+ except Exception as e:
121
+ print(f"Lỗi khi tạo prompt cho hình ảnh: {str(e)}")
122
+ return f"A visual representation of: {content}" # Fallback prompt nếu có lỗi
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ gradio
2
+ groq
3
+ openai
4
+ python-docx
5
+ PyMuPDF
6
+ Pillow
7
+ sentence-transformers
8
+ moviepy
9
+ loguru
utils.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import glob
2
+ import os
3
+ import random
4
+
5
+ import requests
6
+ from loguru import logger
7
+ from moviepy.editor import AudioFileClip, VideoFileClip, concatenate_videoclips
8
+
9
+ def get_pexels_image(query):
10
+ """
11
+ Lấy ảnh từ Pexels API dựa trên query.
12
+ """
13
+ api_key = os.getenv('Pexels_API_KEY')
14
+ # Thêm từ khóa "Vietnamese" vào truy vấn và tăng số lượng ảnh lên 30
15
+ url = f"https://api.pexels.com/v1/search?query={query}%20Vietnamese&per_page=30"
16
+ headers = {"Authorization": api_key}
17
+ response = requests.get(url, headers=headers)
18
+ if response.status_code == 200:
19
+ data = response.json()
20
+ if data['photos']:
21
+ # Chọn ngẫu nhiên một ảnh từ kết quả
22
+ return random.choice(data['photos'])['src']['medium']
23
+ return None
24
+
25
+ def get_bgm_file(bgm_type: str = "random", bgm_file: str = ""):
26
+ """
27
+ Lấy file nhạc nền.
28
+ """
29
+ if not bgm_type:
30
+ return ""
31
+ if bgm_type == "random":
32
+ suffix = "*.mp3"
33
+ song_dir = utils.song_dir() # Đảm bảo utils.song_dir() trả về đường dẫn đúng
34
+ files = glob.glob(os.path.join(song_dir, suffix))
35
+ return random.choice(files) if files else ""
36
+
37
+ if os.path.exists(bgm_file):
38
+ return bgm_file
39
+
40
+ return ""
41
+
42
+ def combine_videos(combined_video_path: str,
43
+ video_paths: list[str],
44
+ audio_file: str,
45
+ max_clip_duration: int = 5,
46
+ threads: int = 2,
47
+ ) -> str:
48
+ """
49
+ Kết hợp nhiều video thành một video duy nhất.
50
+ """
51
+ audio_clip = AudioFileClip(audio_file)
52
+ audio_duration = audio_clip.duration
53
+ logger.info(f"Max duration of audio: {audio_duration} seconds")
54
+
55
+ clips = []
56
+ video_duration = 0
57
+
58
+ while video_duration < audio_duration:
59
+ random.shuffle(video_paths)
60
+
61
+ for video_path in video_paths:
62
+ clip = VideoFileClip(video_path).without_audio()
63
+ if (audio_duration - video_duration) < clip.duration:
64
+ clip = clip.subclip(0, (audio_duration - video_duration))
65
+ elif max_clip_duration < clip.duration:
66
+ clip = clip.subclip(0, max_clip_duration)
67
+ clip = clip.set_fps(30)
68
+ clips.append(clip)
69
+ video_duration += clip.duration
70
+
71
+ final_clip = concatenate_videoclips(clips)
72
+ final_clip = final_clip.set_fps(30)
73
+ logger.info(f"Writing combined video to {combined_video_path}")
74
+ final_clip.write_videofile(combined_video_path, threads=threads)
75
+ logger.success(f"Completed combining videos")
76
+ return combined_video_path
77
+
78
+ def generate_video(video_paths: list[str],
79
+ audio_path: str,
80
+ output_file: str,
81
+ ) -> str:
82
+ """
83
+ Tạo video cuối cùng bằng cách kết hợp video và âm thanh.
84
+ """
85
+ logger.info(f"Start generating video")
86
+ combined_video_path = "temp_combined_video.mp4"
87
+
88
+ combine_videos(combined_video_path, video_paths, audio_path)
89
+
90
+ # Add audio to the final video
91
+ final_video = VideoFileClip(combined_video_path)
92
+ audio_clip = AudioFileClip(audio_path)
93
+ final_video = final_video.set_audio(audio_clip)
94
+
95
+ logger.info(f"Writing final video to {output_file}")
96
+ final_video.write_videofile(output_file, audio_codec="aac")
97
+
98
+ # Remove temporary video
99
+ os.remove(combined_video_path)
100
+ logger.success(f"Completed generating video: {output_file}")
101
+ return output_file
video_processing.py ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import random
3
+ import shutil
4
+ import tempfile
5
+ from concurrent.futures import ThreadPoolExecutor
6
+ from moviepy.editor import (
7
+ AudioFileClip,
8
+ CompositeVideoClip,
9
+ ImageClip,
10
+ VideoFileClip,
11
+ concatenate_videoclips,
12
+ vfx,
13
+ )
14
+ from moviepy.video.tools.subtitles import SubtitlesClip
15
+ import tqdm
16
+
17
+ from sentence_transformers import SentenceTransformer, util
18
+
19
+ # Khởi tạo model sentence transformer
20
+ model = SentenceTransformer('all-MiniLM-L6-v2')
21
+
22
+ # Tăng số lượng ảnh lên 30
23
+ NUM_IMAGES = 30
24
+
25
+ def add_transitions(clips, transition_duration=1):
26
+ """
27
+ Thêm hiệu ứng chuyển cảnh giữa các clip.
28
+ """
29
+ final_clips = []
30
+ for i, clip in enumerate(clips):
31
+ start_time = i * (clip.duration - transition_duration)
32
+ end_time = start_time + clip.duration
33
+
34
+ if i > 0:
35
+ # Tạo hiệu ứng fade in
36
+ fade_in = clip.fx(vfx.fadeout, duration=transition_duration)
37
+ fade_in = fade_in.set_start(start_time)
38
+ final_clips.append(fade_in)
39
+
40
+ if i < len(clips) - 1:
41
+ # Tạo hiệu ứng fade out
42
+ fade_out = clip.fx(vfx.fadein, duration=transition_duration)
43
+ fade_out = fade_out.set_end(end_time)
44
+ final_clips.append(fade_out)
45
+
46
+ # Thêm clip gốc
47
+ final_clips.append(clip.set_start(start_time).set_end(end_time))
48
+
49
+ return CompositeVideoClip(final_clips)
50
+
51
+ def create_video(sentences, audio_files, video_files, output_path="output_video.mp4"):
52
+ """
53
+ Tạo video từ các câu, file âm thanh và file video.
54
+ """
55
+ clips = []
56
+ for sentence, audio_path, video_path in tqdm.tqdm(zip(sentences, audio_files, video_files), desc="Tạo video"):
57
+ audio = AudioFileClip(audio_path)
58
+ video = VideoFileClip(video_path).set_duration(audio.duration)
59
+ video = video.set_audio(audio)
60
+ clips.append(video)
61
+
62
+ final_video = concatenate_videoclips(clips, method="compose")
63
+ final_video.write_videofile(output_path, fps=24)
64
+ print(f"Đã tạo video: {output_path}")
65
+ return output_path
66
+
67
+ def process_images_parallel(image_patch, clip_duration):
68
+ """
69
+ Xử lý song song các hình ảnh.
70
+ """
71
+ with ThreadPoolExecutor() as executor:
72
+ futures = []
73
+ for content, image_path in image_patch:
74
+ if image_path:
75
+ future = executor.submit(ImageClip, image_path)
76
+ futures.append((future, clip_duration))
77
+
78
+ clips = []
79
+ for future, duration in futures:
80
+ clip = future.result().set_duration(duration)
81
+ clips.append(clip)
82
+
83
+ return clips
84
+
85
+ def process_script_for_video(script, dataset_path, use_dataset):
86
+ """
87
+ Xử lý script để tạo video.
88
+ """
89
+ sentences = extract_key_contents(script)
90
+ return sentences
91
+
92
+ def create_video_func(script, audio_path, dataset_path, use_dataset):
93
+ """
94
+ Hàm chính để tạo video.
95
+ """
96
+ sentences = process_script_for_video(script, dataset_path, use_dataset)
97
+
98
+ # Tạo thư mục tạm thời để lưu các file âm thanh tách biệt
99
+ temp_dir = tempfile.mkdtemp()
100
+
101
+ # Tách file âm thanh thành các đoạn nhỏ
102
+ audio_clips = split_audio(audio_path, len(sentences), temp_dir)
103
+
104
+ # Lấy đường dẫn của các video từ dataset
105
+ video_files = glob.glob(os.path.join(dataset_path, "*.mp4")) if use_dataset else []
106
+
107
+ # Đảm bảo số lượng câu, âm thanh và video là bằng nhau
108
+ min_length = min(len(sentences), len(audio_clips), len(video_files))
109
+ sentences = sentences[:min_length]
110
+ audio_clips = audio_clips[:min_length]
111
+ video_files = video_files[:min_length]
112
+
113
+ output_path = "output_video.mp4"
114
+ create_video(sentences, audio_clips, video_files, output_path)
115
+
116
+ # Xóa thư mục tạm thời
117
+ shutil.rmtree(temp_dir)
118
+
119
+ return output_path, output_path
120
+
121
+ def split_audio(audio_path, num_segments, output_dir):
122
+ """
123
+ Chia file âm thanh thành các đoạn nhỏ.
124
+ """
125
+ audio = AudioFileClip(audio_path)
126
+ duration = audio.duration
127
+ segment_duration = duration / num_segments
128
+
129
+ audio_clips = []
130
+ for i in range(num_segments):
131
+ start = i * segment_duration
132
+ end = (i + 1) * segment_duration
133
+ segment = audio.subclip(start, end)
134
+ output_path = os.path.join(output_dir, f"segment_{i}.mp3")
135
+ segment.write_audiofile(output_path)
136
+ audio_clips.append(output_path)
137
+
138
+ return audio_clips
139
+
140
+ def find_matching_image(prompt, dataset_path, threshold=0.5):
141
+ """
142
+ Tìm kiếm hình ảnh phù hợp với prompt trong dataset.
143
+ """
144
+ prompt_embedding = model.encode(prompt, convert_to_tensor=True)
145
+ best_match = None
146
+ best_score = -1
147
+
148
+ for filename in os.listdir(dataset_path):
149
+ if filename.lower().endswith(('.png', '.jpg', '.jpeg')):
150
+ image_path = os.path.join(dataset_path, filename)
151
+ image_name = os.path.splitext(filename)[0].replace('_', ' ')
152
+ image_embedding = model.encode(image_name, convert_to_tensor=True)
153
+ cosine_score = util.pytorch_cos_sim(prompt_embedding, image_embedding).item()
154
+ if cosine_score > best_score and cosine_score >= threshold:
155
+ best_score = cosine_score
156
+ best_match = image_path
157
+ return best_match