ffff
Browse files- app.py +8 -1
- content_generation.py +10 -1
- requirements.txt +31 -12
- sports_news.py +218 -0
- test_sports_news.py +116 -0
app.py
CHANGED
@@ -15,6 +15,7 @@ from TTS.tts.configs.xtts_config import XttsConfig
|
|
15 |
from TTS.tts.models.xtts import Xtts
|
16 |
from vinorm import TTSnorm
|
17 |
from content_generation import create_content # Nhập hàm create_content từ file content_generation.py
|
|
|
18 |
from PIL import Image
|
19 |
from pathlib import Path
|
20 |
import requests
|
@@ -250,6 +251,12 @@ def predict(
|
|
250 |
generated_text = create_content(prompt, content_type, language)
|
251 |
print(f"Generated text: {generated_text}")
|
252 |
prompt = generated_text
|
|
|
|
|
|
|
|
|
|
|
|
|
253 |
if language not in supported_languages:
|
254 |
metrics_text = gr.Warning(
|
255 |
f"Language you put {language} in is not in our Supported Languages, please choose from dropdown"
|
@@ -399,7 +406,7 @@ with gr.Blocks(analytics_enabled=False) as demo:
|
|
399 |
)
|
400 |
content_type_dropdown = gr.Dropdown(
|
401 |
label="Loại nội dung",
|
402 |
-
choices=["triết lý sống", "Theo yêu cầu"],
|
403 |
value="Theo yêu cầu",
|
404 |
)
|
405 |
ref_gr = gr.Audio(
|
|
|
15 |
from TTS.tts.models.xtts import Xtts
|
16 |
from vinorm import TTSnorm
|
17 |
from content_generation import create_content # Nhập hàm create_content từ file content_generation.py
|
18 |
+
from sports_news import get_sports_news_content # Nhập hàm lấy tin thể thao
|
19 |
from PIL import Image
|
20 |
from pathlib import Path
|
21 |
import requests
|
|
|
251 |
generated_text = create_content(prompt, content_type, language)
|
252 |
print(f"Generated text: {generated_text}")
|
253 |
prompt = generated_text
|
254 |
+
elif content_type in ["tin thể thao", "tin bóng đá"]:
|
255 |
+
print("I: Fetching sports news...")
|
256 |
+
news_type = "football" if content_type == "tin bóng đá" else "all"
|
257 |
+
sports_content = get_sports_news_content(news_type, language, 5)
|
258 |
+
print(f"Sports content: {sports_content}")
|
259 |
+
prompt = sports_content
|
260 |
if language not in supported_languages:
|
261 |
metrics_text = gr.Warning(
|
262 |
f"Language you put {language} in is not in our Supported Languages, please choose from dropdown"
|
|
|
406 |
)
|
407 |
content_type_dropdown = gr.Dropdown(
|
408 |
label="Loại nội dung",
|
409 |
+
choices=["triết lý sống", "Theo yêu cầu", "tin thể thao", "tin bóng đá"],
|
410 |
value="Theo yêu cầu",
|
411 |
)
|
412 |
ref_gr = gr.Audio(
|
content_generation.py
CHANGED
@@ -1,12 +1,13 @@
|
|
1 |
# content_generation.py
|
2 |
from groq import Groq
|
3 |
import os
|
|
|
4 |
|
5 |
# Lấy API key từ biến môi trường
|
6 |
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
|
7 |
|
8 |
# Danh sách loại nội dung và hướng dẫn mặc định cho từng loại
|
9 |
-
CONTENT_TYPES = ["triết lý sống", "Theo yêu cầu"]
|
10 |
CONTENT_TYPE_INSTRUCTIONS = {
|
11 |
"Theo yêu cầu": """
|
12 |
Bạn hãy nghe theo yêu cầu của người dùng để viết một kịch bản xuất sắc về văn phong, truyền cảm hứng và có giá trị thông tin. Không bao gồm bất kỳ chỗ giữ chỗ nào trong ngoặc như [Host] hoặc [Guest]. Thiết kế đầu ra của bạn để có thể đọc to -- nó sẽ được chuyển đổi trực tiếp thành âm thanh. Chỉ có một người nói, đó là bạn. Giữ đúng chủ đề và duy trì luồng hấp dẫn.
|
@@ -17,6 +18,14 @@ Bạn cần viết một kịch bản khiến người nghe bị cuốn vào t
|
|
17 |
Bạn sẽ chỉ viết nội dung sẽ thu âm thành tiếng mà không cần có thêm các phần chú thích trong ngoặc vuông hay ngoặc tròn hay bất cứ gì khác nằm ngoài phạm vi sẽ thu âm.. Thiết kế đầu ra của bạn để có thể đọc to -- nó sẽ được chuyển đổi trực tiếp thành âm thanh.
|
18 |
Chú trọng vào việc nhấn mạnh những thông điệp quan trọng bằng ngôn từ mạnh mẽ, ấn tượng. Văn phong cần có sự kết hợp giữa sự nhẹ nhàng, tĩnh lặng như một lời tâm sự, nhưng cũng đủ sức khơi gợi suy nghĩ sâu xa. Mỗi đoạn nội dung cần có điểm nhấn đáng nhớ, dễ in sâu vào tâm trí người nghe. Hãy đảm bảo nội dung rõ ràng, mạch lạc, và truyền tải được sự ấm áp, chân thành.
|
19 |
Độ dài kịch bản này là khoảng 1000 từ.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
"""
|
21 |
}
|
22 |
|
|
|
1 |
# content_generation.py
|
2 |
from groq import Groq
|
3 |
import os
|
4 |
+
from sports_news import get_sports_news_content
|
5 |
|
6 |
# Lấy API key từ biến môi trường
|
7 |
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
|
8 |
|
9 |
# Danh sách loại nội dung và hướng dẫn mặc định cho từng loại
|
10 |
+
CONTENT_TYPES = ["triết lý sống", "Theo yêu cầu", "tin thể thao", "tin bóng đá"]
|
11 |
CONTENT_TYPE_INSTRUCTIONS = {
|
12 |
"Theo yêu cầu": """
|
13 |
Bạn hãy nghe theo yêu cầu của người dùng để viết một kịch bản xuất sắc về văn phong, truyền cảm hứng và có giá trị thông tin. Không bao gồm bất kỳ chỗ giữ chỗ nào trong ngoặc như [Host] hoặc [Guest]. Thiết kế đầu ra của bạn để có thể đọc to -- nó sẽ được chuyển đổi trực tiếp thành âm thanh. Chỉ có một người nói, đó là bạn. Giữ đúng chủ đề và duy trì luồng hấp dẫn.
|
|
|
18 |
Bạn sẽ chỉ viết nội dung sẽ thu âm thành tiếng mà không cần có thêm các phần chú thích trong ngoặc vuông hay ngoặc tròn hay bất cứ gì khác nằm ngoài phạm vi sẽ thu âm.. Thiết kế đầu ra của bạn để có thể đọc to -- nó sẽ được chuyển đổi trực tiếp thành âm thanh.
|
19 |
Chú trọng vào việc nhấn mạnh những thông điệp quan trọng bằng ngôn từ mạnh mẽ, ấn tượng. Văn phong cần có sự kết hợp giữa sự nhẹ nhàng, tĩnh lặng như một lời tâm sự, nhưng cũng đủ sức khơi gợi suy nghĩ sâu xa. Mỗi đoạn nội dung cần có điểm nhấn đáng nhớ, dễ in sâu vào tâm trí người nghe. Hãy đảm bảo nội dung rõ ràng, mạch lạc, và truyền tải được sự ấm áp, chân thành.
|
20 |
Độ dài kịch bản này là khoảng 1000 từ.
|
21 |
+
""",
|
22 |
+
"tin thể thao": """
|
23 |
+
Bạn là một MC thể thao chuyên nghiệp, hãy đọc tin thể thao với giọng điệu sôi động, hấp dẫn và đầy nhiệt huyết. Sử dụng ngôn từ phù hợp với từng môn thể thao, tạo cảm giác hứng khởi cho người nghe.
|
24 |
+
Hãy đọc tin một cách tự nhiên, không cần thêm bình luận hay chú thích. Tập trung vào việc truyền tải thông tin chính xác và sinh động.
|
25 |
+
""",
|
26 |
+
"tin bóng đá": """
|
27 |
+
Bạn là một bình luận viên bóng đá chuyên nghiệp, hãy đọc tin bóng đá với giọng điệu đầy cảm xúc và nhiệt huyết. Sử dụng thuật ngữ bóng đá phù hợp, tạo không khí sôi động như đang ở sân vận động.
|
28 |
+
Đọc tin một cách tự nhiên, sinh động, không cần thêm bình luận cá nhân. Tập trung vào việc truyền tải thông tin về các trận đấu, cầu thủ và giải đấu.
|
29 |
"""
|
30 |
}
|
31 |
|
requirements.txt
CHANGED
@@ -1,18 +1,37 @@
|
|
1 |
-
#
|
2 |
-
TTS
|
3 |
typing-extensions>=4.8.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
4 |
cutlet
|
5 |
mecab-python3==1.0.6
|
6 |
langid
|
7 |
-
|
8 |
-
|
9 |
gradio==4.36.1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
10 |
groq
|
11 |
-
|
12 |
-
|
13 |
-
requests
|
14 |
-
Pillow
|
15 |
-
anyio
|
16 |
-
# Vietnamese 101
|
17 |
-
vinorm==2.0.7
|
18 |
-
underthesea==6.8.0
|
|
|
1 |
+
# Core TTS requirements
|
2 |
+
TTS>=0.22.0
|
3 |
typing-extensions>=4.8.0
|
4 |
+
torch>=1.9.0
|
5 |
+
torchaudio>=0.9.0
|
6 |
+
huggingface-hub>=0.16.0
|
7 |
+
|
8 |
+
# Audio processing
|
9 |
+
pydub>=0.25.1
|
10 |
+
soundfile>=0.12.1
|
11 |
+
|
12 |
+
# Vietnamese text processing
|
13 |
+
vinorm==2.0.7
|
14 |
+
underthesea==6.8.0
|
15 |
cutlet
|
16 |
mecab-python3==1.0.6
|
17 |
langid
|
18 |
+
|
19 |
+
# Web interface
|
20 |
gradio==4.36.1
|
21 |
+
|
22 |
+
# Image and video processing
|
23 |
+
opencv-python>=4.8.0
|
24 |
+
moviepy>=1.0.3
|
25 |
+
Pillow>=9.0.0
|
26 |
+
|
27 |
+
# API and web requests
|
28 |
+
requests>=2.28.0
|
29 |
+
feedparser>=6.0.10
|
30 |
+
beautifulsoup4>=4.11.0
|
31 |
+
lxml>=4.9.0
|
32 |
+
|
33 |
+
# Utilities
|
34 |
+
anyio>=3.6.0
|
35 |
groq
|
36 |
+
numpy>=1.21.0
|
37 |
+
pandas>=1.5.0
|
|
|
|
|
|
|
|
|
|
|
|
sports_news.py
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Module lấy tin thể thao quốc tế từ các nguồn miễn phí
|
3 |
+
Hỗ trợ RSS feeds và API miễn phí
|
4 |
+
"""
|
5 |
+
|
6 |
+
import requests
|
7 |
+
import feedparser
|
8 |
+
from bs4 import BeautifulSoup
|
9 |
+
import json
|
10 |
+
from datetime import datetime, timedelta
|
11 |
+
import re
|
12 |
+
from typing import List, Dict, Optional
|
13 |
+
import logging
|
14 |
+
|
15 |
+
# Cấu hình logging
|
16 |
+
logging.basicConfig(level=logging.INFO)
|
17 |
+
logger = logging.getLogger(__name__)
|
18 |
+
|
19 |
+
class SportsNewsAggregator:
|
20 |
+
"""Class tổng hợp tin thể thao từ nhiều nguồn miễn phí"""
|
21 |
+
|
22 |
+
def __init__(self):
|
23 |
+
self.sources = {
|
24 |
+
'bbc_sport': {
|
25 |
+
'url': 'http://feeds.bbci.co.uk/sport/rss.xml',
|
26 |
+
'type': 'rss',
|
27 |
+
'language': 'en'
|
28 |
+
},
|
29 |
+
'espn': {
|
30 |
+
'url': 'https://www.espn.com/espn/rss/news',
|
31 |
+
'type': 'rss',
|
32 |
+
'language': 'en'
|
33 |
+
},
|
34 |
+
'sky_sports': {
|
35 |
+
'url': 'https://www.skysports.com/rss/12040',
|
36 |
+
'type': 'rss',
|
37 |
+
'language': 'en'
|
38 |
+
},
|
39 |
+
'vnexpress_sport': {
|
40 |
+
'url': 'https://vnexpress.net/rss/the-thao.rss',
|
41 |
+
'type': 'rss',
|
42 |
+
'language': 'vi'
|
43 |
+
},
|
44 |
+
'thanhnien_sport': {
|
45 |
+
'url': 'https://thanhnien.vn/rss/the-thao.rss',
|
46 |
+
'type': 'rss',
|
47 |
+
'language': 'vi'
|
48 |
+
}
|
49 |
+
}
|
50 |
+
|
51 |
+
self.session = requests.Session()
|
52 |
+
self.session.headers.update({
|
53 |
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
|
54 |
+
})
|
55 |
+
|
56 |
+
def get_rss_news(self, source_config: Dict) -> List[Dict]:
|
57 |
+
"""Lấy tin từ RSS feed"""
|
58 |
+
try:
|
59 |
+
response = self.session.get(source_config['url'], timeout=10)
|
60 |
+
response.raise_for_status()
|
61 |
+
|
62 |
+
feed = feedparser.parse(response.content)
|
63 |
+
news_items = []
|
64 |
+
|
65 |
+
for entry in feed.entries[:10]: # Lấy 10 tin mới nhất
|
66 |
+
# Xử lý thời gian
|
67 |
+
pub_date = None
|
68 |
+
if hasattr(entry, 'published_parsed') and entry.published_parsed:
|
69 |
+
pub_date = datetime(*entry.published_parsed[:6])
|
70 |
+
elif hasattr(entry, 'updated_parsed') and entry.updated_parsed:
|
71 |
+
pub_date = datetime(*entry.updated_parsed[:6])
|
72 |
+
|
73 |
+
# Làm sạch nội dung
|
74 |
+
summary = self.clean_text(getattr(entry, 'summary', ''))
|
75 |
+
title = self.clean_text(getattr(entry, 'title', ''))
|
76 |
+
|
77 |
+
news_item = {
|
78 |
+
'title': title,
|
79 |
+
'summary': summary,
|
80 |
+
'link': getattr(entry, 'link', ''),
|
81 |
+
'published': pub_date.isoformat() if pub_date else None,
|
82 |
+
'source': source_config.get('name', 'Unknown'),
|
83 |
+
'language': source_config.get('language', 'en')
|
84 |
+
}
|
85 |
+
|
86 |
+
news_items.append(news_item)
|
87 |
+
|
88 |
+
return news_items
|
89 |
+
|
90 |
+
except Exception as e:
|
91 |
+
logger.error(f"Error fetching RSS from {source_config['url']}: {str(e)}")
|
92 |
+
return []
|
93 |
+
|
94 |
+
def clean_text(self, text: str) -> str:
|
95 |
+
"""Làm sạch văn bản HTML và ký tự đặc biệt"""
|
96 |
+
if not text:
|
97 |
+
return ""
|
98 |
+
|
99 |
+
# Loại bỏ HTML tags
|
100 |
+
soup = BeautifulSoup(text, 'html.parser')
|
101 |
+
text = soup.get_text()
|
102 |
+
|
103 |
+
# Loại bỏ ký tự xuống dòng và khoảng trắng thừa
|
104 |
+
text = re.sub(r'\s+', ' ', text).strip()
|
105 |
+
|
106 |
+
return text
|
107 |
+
|
108 |
+
def get_all_sports_news(self, language: Optional[str] = None, limit: int = 20) -> List[Dict]:
|
109 |
+
"""Lấy tin thể thao từ tất cả nguồn"""
|
110 |
+
all_news = []
|
111 |
+
|
112 |
+
for source_name, source_config in self.sources.items():
|
113 |
+
# Lọc theo ngôn ngữ nếu được chỉ định
|
114 |
+
if language and source_config.get('language') != language:
|
115 |
+
continue
|
116 |
+
|
117 |
+
logger.info(f"Fetching news from {source_name}")
|
118 |
+
source_config['name'] = source_name
|
119 |
+
|
120 |
+
if source_config['type'] == 'rss':
|
121 |
+
news_items = self.get_rss_news(source_config)
|
122 |
+
all_news.extend(news_items)
|
123 |
+
|
124 |
+
# Sắp xếp theo thời gian (mới nhất trước)
|
125 |
+
all_news.sort(key=lambda x: x.get('published', ''), reverse=True)
|
126 |
+
|
127 |
+
return all_news[:limit]
|
128 |
+
|
129 |
+
def get_football_news(self, limit: int = 10) -> List[Dict]:
|
130 |
+
"""Lấy tin bóng đá cụ thể"""
|
131 |
+
all_news = self.get_all_sports_news(limit=50)
|
132 |
+
|
133 |
+
# Lọc tin bóng đá
|
134 |
+
football_keywords = [
|
135 |
+
'football', 'soccer', 'fifa', 'premier league', 'champions league',
|
136 |
+
'bóng đá', 'world cup', 'euro', 'manchester', 'liverpool', 'arsenal',
|
137 |
+
'real madrid', 'barcelona', 'psg', 'bayern', 'juventus'
|
138 |
+
]
|
139 |
+
|
140 |
+
football_news = []
|
141 |
+
for news in all_news:
|
142 |
+
title_lower = news['title'].lower()
|
143 |
+
summary_lower = news['summary'].lower()
|
144 |
+
|
145 |
+
if any(keyword in title_lower or keyword in summary_lower
|
146 |
+
for keyword in football_keywords):
|
147 |
+
football_news.append(news)
|
148 |
+
|
149 |
+
return football_news[:limit]
|
150 |
+
|
151 |
+
def format_news_for_tts(self, news_items: List[Dict], language: str = 'vi') -> str:
|
152 |
+
"""Format tin tức thành văn bản phù hợp cho TTS"""
|
153 |
+
if not news_items:
|
154 |
+
return "Không có tin thể thao mới nào được tìm thấy."
|
155 |
+
|
156 |
+
if language == 'vi':
|
157 |
+
intro = "Tin thể thao quốc tế mới nhất:\n\n"
|
158 |
+
else:
|
159 |
+
intro = "Latest international sports news:\n\n"
|
160 |
+
|
161 |
+
formatted_text = intro
|
162 |
+
|
163 |
+
for i, news in enumerate(news_items[:5], 1): # Chỉ lấy 5 tin đầu
|
164 |
+
title = news['title']
|
165 |
+
summary = news['summary'][:200] + "..." if len(news['summary']) > 200 else news['summary']
|
166 |
+
|
167 |
+
if language == 'vi':
|
168 |
+
news_text = f"Tin thứ {i}: {title}. {summary}\n\n"
|
169 |
+
else:
|
170 |
+
news_text = f"News {i}: {title}. {summary}\n\n"
|
171 |
+
|
172 |
+
formatted_text += news_text
|
173 |
+
|
174 |
+
return formatted_text
|
175 |
+
|
176 |
+
# Hàm tiện ích để sử dụng trong app chính
|
177 |
+
def get_sports_news_content(news_type: str = 'all', language: str = 'vi', limit: int = 5) -> str:
|
178 |
+
"""
|
179 |
+
Hàm chính để lấy nội dung tin thể thao
|
180 |
+
|
181 |
+
Args:
|
182 |
+
news_type: 'all', 'football', 'basketball', etc.
|
183 |
+
language: 'vi' hoặc 'en'
|
184 |
+
limit: Số lượng tin tức
|
185 |
+
|
186 |
+
Returns:
|
187 |
+
Chuỗi văn bản đã format cho TTS
|
188 |
+
"""
|
189 |
+
try:
|
190 |
+
aggregator = SportsNewsAggregator()
|
191 |
+
|
192 |
+
if news_type == 'football':
|
193 |
+
news_items = aggregator.get_football_news(limit=limit)
|
194 |
+
else:
|
195 |
+
news_items = aggregator.get_all_sports_news(language=language, limit=limit)
|
196 |
+
|
197 |
+
return aggregator.format_news_for_tts(news_items, language)
|
198 |
+
|
199 |
+
except Exception as e:
|
200 |
+
logger.error(f"Error getting sports news: {str(e)}")
|
201 |
+
if language == 'vi':
|
202 |
+
return f"Xin lỗi, có lỗi khi lấy tin thể thao: {str(e)}"
|
203 |
+
else:
|
204 |
+
return f"Sorry, error getting sports news: {str(e)}"
|
205 |
+
|
206 |
+
# Test function
|
207 |
+
if __name__ == "__main__":
|
208 |
+
# Test lấy tin thể thao
|
209 |
+
print("Testing sports news aggregator...")
|
210 |
+
|
211 |
+
content = get_sports_news_content('all', 'vi', 3)
|
212 |
+
print("Vietnamese sports news:")
|
213 |
+
print(content)
|
214 |
+
print("\n" + "="*50 + "\n")
|
215 |
+
|
216 |
+
content_en = get_sports_news_content('all', 'en', 3)
|
217 |
+
print("English sports news:")
|
218 |
+
print(content_en)
|
test_sports_news.py
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
"""
|
3 |
+
Test script cho tính năng tin thể thao
|
4 |
+
Kiểm tra việc lấy tin từ các nguồn RSS
|
5 |
+
"""
|
6 |
+
|
7 |
+
import sys
|
8 |
+
import os
|
9 |
+
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
10 |
+
|
11 |
+
from sports_news import get_sports_news_content, SportsNewsAggregator
|
12 |
+
import asyncio
|
13 |
+
|
14 |
+
def test_basic_functionality():
|
15 |
+
"""Test cơ bản cho tính năng tin thể thao"""
|
16 |
+
print("🏃♂️ Testing Sports News Functionality...")
|
17 |
+
print("=" * 50)
|
18 |
+
|
19 |
+
try:
|
20 |
+
# Test lấy tin thể thao tiếng Việt
|
21 |
+
print("📰 Testing Vietnamese sports news...")
|
22 |
+
vi_content = get_sports_news_content('all', 'vi', 3)
|
23 |
+
print("Vietnamese Content:")
|
24 |
+
print(vi_content[:500] + "..." if len(vi_content) > 500 else vi_content)
|
25 |
+
print("\n" + "-" * 30 + "\n")
|
26 |
+
|
27 |
+
# Test lấy tin bóng đá
|
28 |
+
print("⚽ Testing football news...")
|
29 |
+
football_content = get_sports_news_content('football', 'vi', 2)
|
30 |
+
print("Football Content:")
|
31 |
+
print(football_content[:500] + "..." if len(football_content) > 500 else football_content)
|
32 |
+
print("\n" + "-" * 30 + "\n")
|
33 |
+
|
34 |
+
# Test lấy tin tiếng Anh
|
35 |
+
print("🌍 Testing English sports news...")
|
36 |
+
en_content = get_sports_news_content('all', 'en', 2)
|
37 |
+
print("English Content:")
|
38 |
+
print(en_content[:500] + "..." if len(en_content) > 500 else en_content)
|
39 |
+
print("\n" + "-" * 30 + "\n")
|
40 |
+
|
41 |
+
print("✅ All tests completed successfully!")
|
42 |
+
return True
|
43 |
+
|
44 |
+
except Exception as e:
|
45 |
+
print(f"❌ Error during testing: {str(e)}")
|
46 |
+
return False
|
47 |
+
|
48 |
+
def test_aggregator_directly():
|
49 |
+
"""Test trực tiếp SportsNewsAggregator class"""
|
50 |
+
print("🔧 Testing SportsNewsAggregator directly...")
|
51 |
+
print("=" * 50)
|
52 |
+
|
53 |
+
try:
|
54 |
+
aggregator = SportsNewsAggregator()
|
55 |
+
|
56 |
+
# Test lấy tin từ một nguồn cụ thể
|
57 |
+
print("📡 Testing individual RSS sources...")
|
58 |
+
|
59 |
+
# Test với VnExpress
|
60 |
+
vnexpress_config = {
|
61 |
+
'url': 'https://vnexpress.net/rss/the-thao.rss',
|
62 |
+
'type': 'rss',
|
63 |
+
'language': 'vi',
|
64 |
+
'name': 'vnexpress_sport'
|
65 |
+
}
|
66 |
+
|
67 |
+
news_items = aggregator.get_rss_news(vnexpress_config)
|
68 |
+
print(f"VnExpress Sports: Found {len(news_items)} articles")
|
69 |
+
|
70 |
+
if news_items:
|
71 |
+
print("Sample article:")
|
72 |
+
print(f"Title: {news_items[0]['title']}")
|
73 |
+
print(f"Summary: {news_items[0]['summary'][:200]}...")
|
74 |
+
|
75 |
+
print("\n" + "-" * 30 + "\n")
|
76 |
+
|
77 |
+
# Test format cho TTS
|
78 |
+
print("🎤 Testing TTS formatting...")
|
79 |
+
formatted = aggregator.format_news_for_tts(news_items[:2], 'vi')
|
80 |
+
print("Formatted for TTS:")
|
81 |
+
print(formatted[:300] + "..." if len(formatted) > 300 else formatted)
|
82 |
+
|
83 |
+
print("✅ Aggregator test completed successfully!")
|
84 |
+
return True
|
85 |
+
|
86 |
+
except Exception as e:
|
87 |
+
print(f"❌ Error during aggregator testing: {str(e)}")
|
88 |
+
import traceback
|
89 |
+
traceback.print_exc()
|
90 |
+
return False
|
91 |
+
|
92 |
+
def main():
|
93 |
+
"""Chạy tất cả các test"""
|
94 |
+
print("🚀 Starting Sports News Tests...")
|
95 |
+
print("=" * 60)
|
96 |
+
|
97 |
+
# Test cơ bản
|
98 |
+
basic_success = test_basic_functionality()
|
99 |
+
|
100 |
+
print("\n" + "=" * 60 + "\n")
|
101 |
+
|
102 |
+
# Test aggregator
|
103 |
+
aggregator_success = test_aggregator_directly()
|
104 |
+
|
105 |
+
print("\n" + "=" * 60)
|
106 |
+
print("📊 TEST SUMMARY:")
|
107 |
+
print(f"Basic functionality: {'✅ PASS' if basic_success else '❌ FAIL'}")
|
108 |
+
print(f"Aggregator test: {'✅ PASS' if aggregator_success else '❌ FAIL'}")
|
109 |
+
|
110 |
+
if basic_success and aggregator_success:
|
111 |
+
print("🎉 All tests passed! Sports news feature is ready to use.")
|
112 |
+
else:
|
113 |
+
print("⚠️ Some tests failed. Please check the errors above.")
|
114 |
+
|
115 |
+
if __name__ == "__main__":
|
116 |
+
main()
|