Spaces:
Running
on
Zero
Running
on
Zero
Update app.py
Browse files
app.py
CHANGED
@@ -1638,3 +1638,451 @@ class UnifiedAudioConverter:
|
|
1638 |
|
1639 |
|
1640 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1638 |
|
1639 |
|
1640 |
|
1641 |
+
conversation_json["conversation"])
|
1642 |
+
)
|
1643 |
+
|
1644 |
+
return final_audio_path, conversation_text
|
1645 |
+
|
1646 |
+
def _create_output_directory(self) -> str:
|
1647 |
+
"""Create a unique output directory"""
|
1648 |
+
random_bytes = os.urandom(8)
|
1649 |
+
folder_name = base64.urlsafe_b64encode(random_bytes).decode("utf-8")
|
1650 |
+
os.makedirs(folder_name, exist_ok=True)
|
1651 |
+
return folder_name
|
1652 |
+
|
1653 |
+
def _combine_audio_files(self, filenames: List[str], output_file: str) -> None:
|
1654 |
+
"""Combine multiple audio files into one"""
|
1655 |
+
if not filenames:
|
1656 |
+
raise ValueError("No input files provided")
|
1657 |
+
|
1658 |
+
try:
|
1659 |
+
audio_segments = []
|
1660 |
+
for filename in filenames:
|
1661 |
+
if os.path.exists(filename):
|
1662 |
+
audio_segment = AudioSegment.from_file(filename)
|
1663 |
+
audio_segments.append(audio_segment)
|
1664 |
+
|
1665 |
+
if audio_segments:
|
1666 |
+
combined = sum(audio_segments)
|
1667 |
+
combined.export(output_file, format="wav")
|
1668 |
+
|
1669 |
+
# Clean up temporary files
|
1670 |
+
for filename in filenames:
|
1671 |
+
if os.path.exists(filename):
|
1672 |
+
os.remove(filename)
|
1673 |
+
|
1674 |
+
except Exception as e:
|
1675 |
+
raise RuntimeError(f"Failed to combine audio files: {e}")
|
1676 |
+
|
1677 |
+
|
1678 |
+
# Global converter instance
|
1679 |
+
converter = UnifiedAudioConverter(ConversationConfig())
|
1680 |
+
|
1681 |
+
|
1682 |
+
async def synthesize(article_input, input_type: str = "URL", mode: str = "Local", tts_engine: str = "Edge-TTS", language: str = "English"):
|
1683 |
+
"""Main synthesis function - handles URL, PDF, and Keyword inputs"""
|
1684 |
+
try:
|
1685 |
+
# Extract text based on input type
|
1686 |
+
if input_type == "URL":
|
1687 |
+
if not article_input or not isinstance(article_input, str):
|
1688 |
+
return "Please provide a valid URL.", None
|
1689 |
+
text = converter.fetch_text(article_input)
|
1690 |
+
elif input_type == "PDF":
|
1691 |
+
if not article_input:
|
1692 |
+
return "Please upload a PDF file.", None
|
1693 |
+
text = converter.extract_text_from_pdf(article_input)
|
1694 |
+
else: # Keyword
|
1695 |
+
if not article_input or not isinstance(article_input, str):
|
1696 |
+
return "Please provide a keyword or topic.", None
|
1697 |
+
text = search_and_compile_content(article_input, language)
|
1698 |
+
text = f"Keyword-based content:\n{text}"
|
1699 |
+
|
1700 |
+
# Limit text to max words
|
1701 |
+
words = text.split()
|
1702 |
+
if len(words) > converter.config.max_words:
|
1703 |
+
text = " ".join(words[:converter.config.max_words])
|
1704 |
+
|
1705 |
+
# Extract conversation based on mode
|
1706 |
+
if mode == "Local":
|
1707 |
+
try:
|
1708 |
+
conversation_json = converter.extract_conversation_local(text, language)
|
1709 |
+
except Exception as e:
|
1710 |
+
print(f"Local mode failed: {e}, trying API fallback")
|
1711 |
+
api_key = os.environ.get("TOGETHER_API_KEY")
|
1712 |
+
if api_key:
|
1713 |
+
converter.initialize_api_mode(api_key)
|
1714 |
+
conversation_json = converter.extract_conversation_api(text, language)
|
1715 |
+
else:
|
1716 |
+
raise RuntimeError("Local mode failed and no API key available for fallback")
|
1717 |
+
else: # API mode
|
1718 |
+
api_key = os.environ.get("TOGETHER_API_KEY")
|
1719 |
+
if not api_key:
|
1720 |
+
print("API key not found, falling back to local mode")
|
1721 |
+
conversation_json = converter.extract_conversation_local(text, language)
|
1722 |
+
else:
|
1723 |
+
try:
|
1724 |
+
converter.initialize_api_mode(api_key)
|
1725 |
+
conversation_json = converter.extract_conversation_api(text, language)
|
1726 |
+
except Exception as e:
|
1727 |
+
print(f"API mode failed: {e}, falling back to local mode")
|
1728 |
+
conversation_json = converter.extract_conversation_local(text, language)
|
1729 |
+
|
1730 |
+
# Generate conversation text
|
1731 |
+
conversation_text = "\n".join(
|
1732 |
+
f"{turn.get('speaker', f'Speaker {i+1}')}: {turn['text']}"
|
1733 |
+
for i, turn in enumerate(conversation_json["conversation"])
|
1734 |
+
)
|
1735 |
+
|
1736 |
+
return conversation_text, None
|
1737 |
+
|
1738 |
+
except Exception as e:
|
1739 |
+
return f"Error: {str(e)}", None
|
1740 |
+
|
1741 |
+
|
1742 |
+
async def regenerate_audio(conversation_text: str, tts_engine: str = "Edge-TTS", language: str = "English"):
|
1743 |
+
"""Regenerate audio from edited conversation text"""
|
1744 |
+
if not conversation_text.strip():
|
1745 |
+
return "Please provide conversation text.", None
|
1746 |
+
|
1747 |
+
try:
|
1748 |
+
conversation_json = converter.parse_conversation_text(conversation_text)
|
1749 |
+
|
1750 |
+
if not conversation_json["conversation"]:
|
1751 |
+
return "No valid conversation found in the text.", None
|
1752 |
+
|
1753 |
+
# Edge TTS ์ ์ฉ ์ธ์ด๋ ์๋์ผ๋ก Edge-TTS ์ฌ์ฉ
|
1754 |
+
if language in EDGE_TTS_ONLY_LANGUAGES and tts_engine != "Edge-TTS":
|
1755 |
+
tts_engine = "Edge-TTS"
|
1756 |
+
|
1757 |
+
# Generate audio based on TTS engine
|
1758 |
+
if tts_engine == "Edge-TTS":
|
1759 |
+
output_file, _ = await converter.text_to_speech_edge(conversation_json, language)
|
1760 |
+
elif tts_engine == "Spark-TTS":
|
1761 |
+
if not SPARK_AVAILABLE:
|
1762 |
+
return "Spark TTS not available. Please install required dependencies and clone the Spark-TTS repository.", None
|
1763 |
+
converter.initialize_spark_tts()
|
1764 |
+
output_file, _ = converter.text_to_speech_spark(conversation_json, language)
|
1765 |
+
else: # MeloTTS
|
1766 |
+
if not MELO_AVAILABLE:
|
1767 |
+
return "MeloTTS not available. Please install required dependencies.", None
|
1768 |
+
if language in EDGE_TTS_ONLY_LANGUAGES:
|
1769 |
+
return f"MeloTTS does not support {language}. Please use Edge-TTS for this language.", None
|
1770 |
+
converter.initialize_melo_tts()
|
1771 |
+
output_file, _ = converter.text_to_speech_melo(conversation_json)
|
1772 |
+
|
1773 |
+
return "Audio generated successfully!", output_file
|
1774 |
+
|
1775 |
+
except Exception as e:
|
1776 |
+
return f"Error generating audio: {str(e)}", None
|
1777 |
+
|
1778 |
+
|
1779 |
+
def synthesize_sync(article_input, input_type: str = "URL", mode: str = "Local", tts_engine: str = "Edge-TTS", language: str = "English"):
|
1780 |
+
"""Synchronous wrapper for async synthesis"""
|
1781 |
+
return asyncio.run(synthesize(article_input, input_type, mode, tts_engine, language))
|
1782 |
+
|
1783 |
+
|
1784 |
+
def regenerate_audio_sync(conversation_text: str, tts_engine: str = "Edge-TTS", language: str = "English"):
|
1785 |
+
"""Synchronous wrapper for async audio regeneration"""
|
1786 |
+
return asyncio.run(regenerate_audio(conversation_text, tts_engine, language))
|
1787 |
+
|
1788 |
+
|
1789 |
+
def update_tts_engine_for_language(language):
|
1790 |
+
"""์ธ์ด๋ณ TTS ์์ง ์ต์
์
๋ฐ์ดํธ"""
|
1791 |
+
if language in EDGE_TTS_ONLY_LANGUAGES:
|
1792 |
+
language_info = {
|
1793 |
+
"Korean": "ํ๊ตญ์ด๋ Edge-TTS๋ง ์ง์๋ฉ๋๋ค",
|
1794 |
+
"Japanese": "ๆฅๆฌ่ชใฏEdge-TTSใฎใฟใตใใผใใใใฆใใพใ",
|
1795 |
+
"French": "Le franรงais n'est pris en charge que par Edge-TTS",
|
1796 |
+
"German": "Deutsch wird nur von Edge-TTS unterstรผtzt",
|
1797 |
+
"Spanish": "El espaรฑol solo es compatible con Edge-TTS",
|
1798 |
+
"Italian": "L'italiano รจ supportato solo da Edge-TTS",
|
1799 |
+
"Portuguese": "O portuguรชs รฉ suportado apenas pelo Edge-TTS",
|
1800 |
+
"Dutch": "Nederlands wordt alleen ondersteund door Edge-TTS",
|
1801 |
+
"Thai": "เธ เธฒเธฉเธฒเนเธเธขเธฃเธญเธเธฃเธฑเธเนเธเธเธฒเธฐ Edge-TTS เนเธเนเธฒเธเธฑเนเธ",
|
1802 |
+
"Vietnamese": "Tiแบฟng Viแปt chแป ฤฦฐแปฃc hแป trแปฃ bแปi Edge-TTS",
|
1803 |
+
"Arabic": "ุงูุนุฑุจูุฉ ู
ุฏุนูู
ุฉ ููุท ู
ู Edge-TTS",
|
1804 |
+
"Hebrew": "ืขืืจืืช ื ืชืืืช ืจืง ืขื ืืื Edge-TTS",
|
1805 |
+
"Indonesian": "Bahasa Indonesia hanya didukung oleh Edge-TTS",
|
1806 |
+
"Hindi": "เคนเคฟเคเคฆเฅ เคเฅเคตเคฒ Edge-TTS เคฆเฅเคตเคพเคฐเคพ เคธเคฎเคฐเฅเคฅเคฟเคค เคนเฅ",
|
1807 |
+
"Russian": "ะ ัััะบะธะน ะฟะพะดะดะตัะถะธะฒะฐะตััั ัะพะปัะบะพ Edge-TTS",
|
1808 |
+
"Chinese": "ไธญๆไป
ๆฏๆEdge-TTS"
|
1809 |
+
}
|
1810 |
+
info_text = language_info.get(language, f"{language} is only supported by Edge-TTS")
|
1811 |
+
|
1812 |
+
return gr.Radio(
|
1813 |
+
choices=["Edge-TTS"],
|
1814 |
+
value="Edge-TTS",
|
1815 |
+
label="TTS Engine",
|
1816 |
+
info=info_text,
|
1817 |
+
interactive=False
|
1818 |
+
)
|
1819 |
+
else:
|
1820 |
+
return gr.Radio(
|
1821 |
+
choices=["Edge-TTS", "Spark-TTS", "MeloTTS"],
|
1822 |
+
value="Edge-TTS",
|
1823 |
+
label="TTS Engine",
|
1824 |
+
info="Edge-TTS: Cloud-based, natural voices | Spark-TTS: Local AI model | MeloTTS: Local, requires GPU",
|
1825 |
+
interactive=True
|
1826 |
+
)
|
1827 |
+
|
1828 |
+
|
1829 |
+
def toggle_input_visibility(input_type):
|
1830 |
+
"""Toggle visibility of URL input, file upload, and keyword input based on input type"""
|
1831 |
+
if input_type == "URL":
|
1832 |
+
return gr.update(visible=True), gr.update(visible=False), gr.update(visible=False)
|
1833 |
+
elif input_type == "PDF":
|
1834 |
+
return gr.update(visible=False), gr.update(visible=True), gr.update(visible=False)
|
1835 |
+
else: # Keyword
|
1836 |
+
return gr.update(visible=False), gr.update(visible=False), gr.update(visible=True)
|
1837 |
+
|
1838 |
+
|
1839 |
+
# ๋ชจ๋ธ ์ด๊ธฐํ (์ฑ ์์ ์)
|
1840 |
+
if LLAMA_CPP_AVAILABLE:
|
1841 |
+
try:
|
1842 |
+
model_path = hf_hub_download(
|
1843 |
+
repo_id=converter.config.local_model_repo,
|
1844 |
+
filename=converter.config.local_model_name,
|
1845 |
+
local_dir="./models"
|
1846 |
+
)
|
1847 |
+
print(f"Model downloaded to: {model_path}")
|
1848 |
+
except Exception as e:
|
1849 |
+
print(f"Failed to download model at startup: {e}")
|
1850 |
+
|
1851 |
+
|
1852 |
+
# Gradio Interface - ๊ฐ์ ๋ ๋ค๊ตญ์ด ๋ ์ด์์
|
1853 |
+
with gr.Blocks(theme='soft', title="AI Podcast Generator", css="""
|
1854 |
+
.container {max-width: 1200px; margin: auto; padding: 20px;}
|
1855 |
+
.header-text {text-align: center; margin-bottom: 30px;}
|
1856 |
+
.input-group {background: #f7f7f7; padding: 20px; border-radius: 10px; margin-bottom: 20px;}
|
1857 |
+
.output-group {background: #f0f0f0; padding: 20px; border-radius: 10px;}
|
1858 |
+
.status-box {background: #e8f4f8; padding: 15px; border-radius: 8px; margin-top: 10px;}
|
1859 |
+
""") as demo:
|
1860 |
+
with gr.Column(elem_classes="container"):
|
1861 |
+
# ํค๋
|
1862 |
+
with gr.Row(elem_classes="header-text"):
|
1863 |
+
gr.Markdown("""
|
1864 |
+
# ๐๏ธ AI Podcast Generator - Professional Multi-Language Edition
|
1865 |
+
### Convert any article, blog, PDF document, or topic into an engaging professional podcast conversation in 24+ languages!
|
1866 |
+
""")
|
1867 |
+
|
1868 |
+
with gr.Row(elem_classes="discord-badge"):
|
1869 |
+
gr.HTML("""
|
1870 |
+
<p style="text-align: center;">
|
1871 |
+
<a href="https://discord.gg/openfreeai" target="_blank">
|
1872 |
+
<img src="https://img.shields.io/static/v1?label=Discord&message=Openfree%20AI&color=%230000ff&labelColor=%23800080&logo=discord&logoColor=white&style=for-the-badge" alt="badge">
|
1873 |
+
</a>
|
1874 |
+
</p>
|
1875 |
+
""")
|
1876 |
+
|
1877 |
+
# ์ํ ํ์ ์น์
|
1878 |
+
with gr.Row():
|
1879 |
+
with gr.Column(scale=1):
|
1880 |
+
gr.Markdown(f"""
|
1881 |
+
#### ๐ค System Status
|
1882 |
+
- **LLM**: {converter.config.local_model_name.split('.')[0]}
|
1883 |
+
- **Fallback**: {converter.config.api_model_name.split('/')[-1]}
|
1884 |
+
- **Llama CPP**: {"โ
Ready" if LLAMA_CPP_AVAILABLE else "โ Not Available"}
|
1885 |
+
- **Search**: {"โ
Brave API" if BRAVE_KEY else "โ No API"}
|
1886 |
+
""")
|
1887 |
+
with gr.Column(scale=1):
|
1888 |
+
gr.Markdown("""
|
1889 |
+
#### ๐ Multi-Language Support
|
1890 |
+
- **24+ Languages**: Korean, Japanese, French, German, Spanish, Italian, etc.
|
1891 |
+
- **Native Voices**: Optimized for each language
|
1892 |
+
- **Professional Style**: Expert discussions with data & insights
|
1893 |
+
- **Auto-TTS Selection**: Best engine per language
|
1894 |
+
""")
|
1895 |
+
|
1896 |
+
# ๋ฉ์ธ ์
๋ ฅ ์น์
|
1897 |
+
with gr.Group(elem_classes="input-group"):
|
1898 |
+
with gr.Row():
|
1899 |
+
# ์ผ์ชฝ: ์
๋ ฅ ์ต์
๋ค
|
1900 |
+
with gr.Column(scale=2):
|
1901 |
+
# ์
๋ ฅ ํ์
์ ํ
|
1902 |
+
input_type_selector = gr.Radio(
|
1903 |
+
choices=["URL", "PDF", "Keyword"],
|
1904 |
+
value="URL",
|
1905 |
+
label="๐ฅ Input Type",
|
1906 |
+
info="Choose your content source"
|
1907 |
+
)
|
1908 |
+
|
1909 |
+
# URL ์
๋ ฅ
|
1910 |
+
url_input = gr.Textbox(
|
1911 |
+
label="๐ Article URL",
|
1912 |
+
placeholder="Enter the article URL here...",
|
1913 |
+
value="",
|
1914 |
+
visible=True,
|
1915 |
+
lines=2
|
1916 |
+
)
|
1917 |
+
|
1918 |
+
# PDF ์
๋ก๋
|
1919 |
+
pdf_input = gr.File(
|
1920 |
+
label="๐ Upload PDF",
|
1921 |
+
file_types=[".pdf"],
|
1922 |
+
visible=False
|
1923 |
+
)
|
1924 |
+
|
1925 |
+
# ํค์๋ ์
๋ ฅ
|
1926 |
+
keyword_input = gr.Textbox(
|
1927 |
+
label="๐ Topic/Keyword",
|
1928 |
+
placeholder="Enter a topic (e.g., 'AI trends 2024', '์ธ๊ณต์ง๋ฅ', 'IA tendances', 'KI Trends')",
|
1929 |
+
value="",
|
1930 |
+
visible=False,
|
1931 |
+
info="System will search and compile latest information",
|
1932 |
+
lines=2
|
1933 |
+
)
|
1934 |
+
|
1935 |
+
# ์ค๋ฅธ์ชฝ: ์ค์ ์ต์
๋ค
|
1936 |
+
with gr.Column(scale=1):
|
1937 |
+
# ์ธ์ด ์ ํ
|
1938 |
+
language_selector = gr.Radio(
|
1939 |
+
choices=[
|
1940 |
+
"English", "Korean", "Japanese", "French", "German",
|
1941 |
+
"Spanish", "Italian", "Portuguese", "Dutch", "Thai",
|
1942 |
+
"Vietnamese", "Arabic", "Hebrew", "Indonesian", "Hindi",
|
1943 |
+
"Russian", "Chinese", "Norwegian", "Swedish", "Finnish",
|
1944 |
+
"Danish", "Polish", "Turkish", "Greek", "Czech"
|
1945 |
+
],
|
1946 |
+
value="English",
|
1947 |
+
label="๐ Language / ์ธ์ด / ่ฏญ่จ",
|
1948 |
+
info="Select podcast language"
|
1949 |
+
)
|
1950 |
+
|
1951 |
+
# ์ฒ๋ฆฌ ๋ชจ๋
|
1952 |
+
mode_selector = gr.Radio(
|
1953 |
+
choices=["Local", "API"],
|
1954 |
+
value="Local",
|
1955 |
+
label="โ๏ธ Processing Mode",
|
1956 |
+
info="Local: On-device | API: Cloud"
|
1957 |
+
)
|
1958 |
+
|
1959 |
+
# TTS ์์ง
|
1960 |
+
tts_selector = gr.Radio(
|
1961 |
+
choices=["Edge-TTS", "Spark-TTS", "MeloTTS"],
|
1962 |
+
value="Edge-TTS",
|
1963 |
+
label="๐ TTS Engine",
|
1964 |
+
info="Voice synthesis engine"
|
1965 |
+
)
|
1966 |
+
|
1967 |
+
# ์์ฑ ๋ฒํผ
|
1968 |
+
with gr.Row():
|
1969 |
+
convert_btn = gr.Button(
|
1970 |
+
"๐ฏ Generate Professional Conversation",
|
1971 |
+
variant="primary",
|
1972 |
+
size="lg",
|
1973 |
+
scale=1
|
1974 |
+
)
|
1975 |
+
|
1976 |
+
# ์ถ๋ ฅ ์น์
|
1977 |
+
with gr.Group(elem_classes="output-group"):
|
1978 |
+
with gr.Row():
|
1979 |
+
# ์ผ์ชฝ: ๋ํ ํ
์คํธ
|
1980 |
+
with gr.Column(scale=3):
|
1981 |
+
conversation_output = gr.Textbox(
|
1982 |
+
label="๐ฌ Generated Professional Conversation (Editable)",
|
1983 |
+
lines=25,
|
1984 |
+
max_lines=50,
|
1985 |
+
interactive=True,
|
1986 |
+
placeholder="Professional podcast conversation will appear here...\n์ ๋ฌธ ํ์บ์คํธ ๋ํ๊ฐ ์ฌ๊ธฐ์ ํ์๋ฉ๋๋ค...\nLa conversation professionnelle du podcast apparaรฎtra ici...",
|
1987 |
+
info="Edit the conversation as needed. Format: 'Speaker Name: Text'"
|
1988 |
+
)
|
1989 |
+
|
1990 |
+
# ์ค๋์ค ์์ฑ ๋ฒํผ
|
1991 |
+
with gr.Row():
|
1992 |
+
generate_audio_btn = gr.Button(
|
1993 |
+
"๐๏ธ Generate Audio from Text",
|
1994 |
+
variant="secondary",
|
1995 |
+
size="lg"
|
1996 |
+
)
|
1997 |
+
|
1998 |
+
# ์ค๋ฅธ์ชฝ: ์ค๋์ค ์ถ๋ ฅ ๋ฐ ์ํ
|
1999 |
+
with gr.Column(scale=2):
|
2000 |
+
audio_output = gr.Audio(
|
2001 |
+
label="๐ง Professional Podcast Audio",
|
2002 |
+
type="filepath",
|
2003 |
+
interactive=False
|
2004 |
+
)
|
2005 |
+
|
2006 |
+
status_output = gr.Textbox(
|
2007 |
+
label="๐ Status",
|
2008 |
+
interactive=False,
|
2009 |
+
lines=3,
|
2010 |
+
elem_classes="status-box"
|
2011 |
+
)
|
2012 |
+
|
2013 |
+
# ๋์๋ง
|
2014 |
+
gr.Markdown("""
|
2015 |
+
#### ๐ก Quick Tips:
|
2016 |
+
- **URL**: Paste any article link
|
2017 |
+
- **PDF**: Upload documents directly
|
2018 |
+
- **Keyword**: Enter topics for AI research
|
2019 |
+
- **24+ Languages** fully supported
|
2020 |
+
- Edit conversation before audio generation
|
2021 |
+
- Auto TTS engine selection per language
|
2022 |
+
""")
|
2023 |
+
|
2024 |
+
# ์์ ์น์
|
2025 |
+
with gr.Accordion("๐ Multi-Language Examples", open=False):
|
2026 |
+
gr.Examples(
|
2027 |
+
examples=[
|
2028 |
+
["https://huggingface.co/blog/openfreeai/cycle-navigator", "URL", "Local", "Edge-TTS", "English"],
|
2029 |
+
["quantum computing breakthroughs", "Keyword", "Local", "Edge-TTS", "English"],
|
2030 |
+
["์ธ๊ณต์ง๋ฅ ์ค๋ฆฌ์ ๊ท์ ", "Keyword", "Local", "Edge-TTS", "Korean"],
|
2031 |
+
["https://huggingface.co/papers/2505.14810", "URL", "Local", "Edge-TTS", "Japanese"],
|
2032 |
+
["intelligence artificielle tendances", "Keyword", "Local", "Edge-TTS", "French"],
|
2033 |
+
["kรผnstliche intelligenz entwicklung", "Keyword", "Local", "Edge-TTS", "German"],
|
2034 |
+
["inteligencia artificial avances", "Keyword", "Local", "Edge-TTS", "Spanish"],
|
2035 |
+
],
|
2036 |
+
inputs=[url_input, input_type_selector, mode_selector, tts_selector, language_selector],
|
2037 |
+
outputs=[conversation_output, status_output],
|
2038 |
+
fn=synthesize_sync,
|
2039 |
+
cache_examples=False,
|
2040 |
+
)
|
2041 |
+
|
2042 |
+
# Input type change handler
|
2043 |
+
input_type_selector.change(
|
2044 |
+
fn=toggle_input_visibility,
|
2045 |
+
inputs=[input_type_selector],
|
2046 |
+
outputs=[url_input, pdf_input, keyword_input]
|
2047 |
+
)
|
2048 |
+
|
2049 |
+
# ์ธ์ด ๋ณ๊ฒฝ ์ TTS ์์ง ์ต์
์
๋ฐ์ดํธ
|
2050 |
+
language_selector.change(
|
2051 |
+
fn=update_tts_engine_for_language,
|
2052 |
+
inputs=[language_selector],
|
2053 |
+
outputs=[tts_selector]
|
2054 |
+
)
|
2055 |
+
|
2056 |
+
# ์ด๋ฒคํธ ์ฐ๊ฒฐ
|
2057 |
+
def get_article_input(input_type, url_input, pdf_input, keyword_input):
|
2058 |
+
"""Get the appropriate input based on input type"""
|
2059 |
+
if input_type == "URL":
|
2060 |
+
return url_input
|
2061 |
+
elif input_type == "PDF":
|
2062 |
+
return pdf_input
|
2063 |
+
else: # Keyword
|
2064 |
+
return keyword_input
|
2065 |
+
|
2066 |
+
convert_btn.click(
|
2067 |
+
fn=lambda input_type, url_input, pdf_input, keyword_input, mode, tts, lang: synthesize_sync(
|
2068 |
+
get_article_input(input_type, url_input, pdf_input, keyword_input), input_type, mode, tts, lang
|
2069 |
+
),
|
2070 |
+
inputs=[input_type_selector, url_input, pdf_input, keyword_input, mode_selector, tts_selector, language_selector],
|
2071 |
+
outputs=[conversation_output, status_output]
|
2072 |
+
)
|
2073 |
+
|
2074 |
+
generate_audio_btn.click(
|
2075 |
+
fn=regenerate_audio_sync,
|
2076 |
+
inputs=[conversation_output, tts_selector, language_selector],
|
2077 |
+
outputs=[status_output, audio_output]
|
2078 |
+
)
|
2079 |
+
|
2080 |
+
|
2081 |
+
# Launch the app
|
2082 |
+
if __name__ == "__main__":
|
2083 |
+
demo.queue(api_open=True, default_concurrency_limit=10).launch(
|
2084 |
+
show_api=True,
|
2085 |
+
share=False,
|
2086 |
+
server_name="0.0.0.0",
|
2087 |
+
server_port=7860
|
2088 |
+
)
|