Spaces:
Running
Running
| import gradio as gr | |
| # from dotenv import load_dotenv | |
| import os | |
| from huggingface_hub import hf_hub_download | |
| import pandas as pd | |
| import sqlite3 | |
| # load_dotenv() | |
| DB_DATASET_ID = os.getenv("DB_DATASET_ID") | |
| DB_NAME = os.getenv("DB_NAME") | |
| cache_path = hf_hub_download(repo_id=DB_DATASET_ID, repo_type='dataset', filename=DB_NAME, token=os.getenv("HF_TOKEN")) | |
| # Model name mappings and metadata | |
| closed_source = [ | |
| 'ElevenLabs', | |
| 'Play.HT 2.0', | |
| 'Play.HT 3.0 Mini', | |
| 'PlayDialog', | |
| 'Papla P1', | |
| 'Hume Octave' | |
| ] | |
| # Model name mapping, can include models that users cannot vote on | |
| model_names = { | |
| 'styletts2': 'StyleTTS 2', | |
| 'tacotron': 'Tacotron', | |
| 'tacotronph': 'Tacotron Phoneme', | |
| 'tacotrondca': 'Tacotron DCA', | |
| 'speedyspeech': 'Speedy Speech', | |
| 'overflow': 'Overflow TTS', | |
| 'anonymoussparkle': 'Anonymous Sparkle', | |
| 'vits': 'VITS', | |
| 'vitsneon': 'VITS Neon', | |
| 'neuralhmm': 'Neural HMM', | |
| 'glow': 'Glow TTS', | |
| 'fastpitch': 'FastPitch', | |
| 'jenny': 'Jenny', | |
| 'tortoise': 'Tortoise TTS', | |
| 'xtts2': 'Coqui XTTSv2', | |
| 'xtts': 'Coqui XTTS', | |
| 'openvoice': 'MyShell OpenVoice', | |
| 'elevenlabs': 'ElevenLabs', | |
| 'openai': 'OpenAI', | |
| 'hierspeech': 'HierSpeech++', | |
| 'pheme': 'PolyAI Pheme', | |
| 'speecht5': 'SpeechT5', | |
| 'metavoice': 'MetaVoice-1B', | |
| } | |
| model_links = { | |
| 'ElevenLabs': 'https://elevenlabs.io/', | |
| 'Play.HT 2.0': 'https://play.ht/', | |
| 'Play.HT 3.0 Mini': 'https://play.ht/', | |
| 'XTTSv2': 'https://huggingface.co/coqui/XTTS-v2', | |
| 'MeloTTS': 'https://github.com/myshell-ai/MeloTTS', | |
| 'StyleTTS 2': 'https://github.com/yl4579/StyleTTS2', | |
| 'Parler TTS Large': 'https://github.com/huggingface/parler-tts', | |
| 'Parler TTS': 'https://github.com/huggingface/parler-tts', | |
| 'Fish Speech v1.5': 'https://github.com/fishaudio/fish-speech', | |
| 'Fish Speech v1.4': 'https://github.com/fishaudio/fish-speech', | |
| 'GPT-SoVITS': 'https://github.com/RVC-Boss/GPT-SoVITS', | |
| 'WhisperSpeech': 'https://github.com/WhisperSpeech/WhisperSpeech', | |
| 'VoiceCraft 2.0': 'https://github.com/jasonppy/VoiceCraft', | |
| 'PlayDialog': 'https://play.ht/', | |
| 'Kokoro v0.19': 'https://huggingface.co/hexgrad/Kokoro-82M', | |
| 'Kokoro v1.0': 'https://huggingface.co/hexgrad/Kokoro-82M', | |
| 'CosyVoice 2.0': 'https://github.com/FunAudioLLM/CosyVoice', | |
| 'MetaVoice': 'https://github.com/metavoiceio/metavoice-src', | |
| 'OpenVoice': 'https://github.com/myshell-ai/OpenVoice', | |
| 'OpenVoice V2': 'https://github.com/myshell-ai/OpenVoice', | |
| 'Pheme': 'https://github.com/PolyAI-LDN/pheme', | |
| 'Vokan TTS': 'https://huggingface.co/ShoukanLabs/Vokan', | |
| 'Papla P1': 'https://papla.media', | |
| 'Hume Octave': 'https://www.hume.ai' | |
| } | |
| def get_db(): | |
| conn = sqlite3.connect(cache_path) | |
| return conn | |
| def get_leaderboard(reveal_prelim=False, hide_battle_votes=False, sort_by_elo=True, hide_proprietary=False): | |
| conn = get_db() | |
| cursor = conn.cursor() | |
| if hide_battle_votes: | |
| sql = ''' | |
| SELECT m.name, | |
| SUM(CASE WHEN v.username NOT LIKE '%_battle' AND v.vote = 1 THEN 1 ELSE 0 END) as upvote, | |
| SUM(CASE WHEN v.username NOT LIKE '%_battle' AND v.vote = -1 THEN 1 ELSE 0 END) as downvote | |
| FROM model m | |
| LEFT JOIN vote v ON m.name = v.model | |
| GROUP BY m.name | |
| ''' | |
| else: | |
| sql = ''' | |
| SELECT name, | |
| SUM(CASE WHEN vote = 1 THEN 1 ELSE 0 END) as upvote, | |
| SUM(CASE WHEN vote = -1 THEN 1 ELSE 0 END) as downvote | |
| FROM model | |
| LEFT JOIN vote ON model.name = vote.model | |
| GROUP BY name | |
| ''' | |
| cursor.execute(sql) | |
| data = cursor.fetchall() | |
| df = pd.DataFrame(data, columns=['name', 'upvote', 'downvote']) | |
| df['name'] = df['name'].replace(model_names).replace('Anonymous Sparkle', 'Fish Speech v1.5') | |
| # Calculate total votes and win rate | |
| df['votes'] = df['upvote'] + df['downvote'] | |
| df['win_rate'] = (df['upvote'] / df['votes'] * 100).round(1) | |
| # Remove models with no votes | |
| df = df[df['votes'] > 0] | |
| # Filter out rows with insufficient votes if not revealing preliminary results | |
| if not reveal_prelim: | |
| df = df[df['votes'] > 500] | |
| ## Calculate ELO SCORE (kept as secondary metric) | |
| df['elo'] = 1200 | |
| for i in range(len(df)): | |
| for j in range(len(df)): | |
| if i != j: | |
| try: | |
| expected_a = 1 / (1 + 10 ** ((df['elo'].iloc[j] - df['elo'].iloc[i]) / 400)) | |
| expected_b = 1 / (1 + 10 ** ((df['elo'].iloc[i] - df['elo'].iloc[j]) / 400)) | |
| actual_a = df['upvote'].iloc[i] / df['votes'].iloc[i] if df['votes'].iloc[i] > 0 else 0.5 | |
| actual_b = df['upvote'].iloc[j] / df['votes'].iloc[j] if df['votes'].iloc[j] > 0 else 0.5 | |
| df.iloc[i, df.columns.get_loc('elo')] += 32 * (actual_a - expected_a) | |
| df.iloc[j, df.columns.get_loc('elo')] += 32 * (actual_b - expected_b) | |
| except Exception as e: | |
| print(f"Error in ELO calculation for rows {i} and {j}: {str(e)}") | |
| continue | |
| df['elo'] = round(df['elo']) | |
| # Sort based on user preference | |
| sort_column = 'elo' if sort_by_elo else 'win_rate' | |
| df = df.sort_values(by=sort_column, ascending=False) | |
| df['order'] = ['#' + str(i + 1) for i in range(len(df))] | |
| # Select and order columns for display | |
| df = df[['order', 'name', 'win_rate', 'votes', 'elo']] | |
| # Remove proprietary models if filter is enabled | |
| if hide_proprietary: | |
| df = df[~df['name'].isin(closed_source)] | |
| # Convert DataFrame to markdown table with CSS styling | |
| markdown_table = """ | |
| <style> | |
| /* Reset any Gradio table styles */ | |
| .leaderboard-table, | |
| .leaderboard-table th, | |
| .leaderboard-table td { | |
| border: none !important; | |
| border-collapse: separate !important; | |
| border-spacing: 0 !important; | |
| } | |
| .leaderboard-container { | |
| background: var(--background-fill-primary); | |
| border: 1px solid var(--border-color-primary); | |
| border-radius: 12px; | |
| padding: 4px; | |
| margin: 10px 0; | |
| width: 100%; | |
| overflow-x: auto; /* Enable horizontal scroll */ | |
| } | |
| .leaderboard-scroll { | |
| max-height: 600px; | |
| overflow-y: auto; | |
| border-radius: 8px; | |
| } | |
| .leaderboard-table { | |
| width: 100%; | |
| border-spacing: 0; | |
| border-collapse: separate; | |
| font-size: 15px; | |
| line-height: 1.5; | |
| table-layout: auto; /* Allow flexible column widths */ | |
| } | |
| .leaderboard-table th { | |
| background: var(--background-fill-secondary); | |
| color: var(--body-text-color); | |
| font-weight: 600; | |
| text-align: left; | |
| padding: 12px 16px; | |
| position: sticky; | |
| top: 0; | |
| z-index: 1; | |
| } | |
| .leaderboard-table th:after { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| bottom: 0; | |
| width: 100%; | |
| border-bottom: 1px solid var(--border-color-primary); | |
| } | |
| .leaderboard-table td { | |
| padding: 12px 16px; | |
| color: var(--body-text-color); | |
| } | |
| .leaderboard-table tr td { | |
| border-bottom: 1px solid var(--border-color-primary); | |
| } | |
| .leaderboard-table tr:last-child td { | |
| border-bottom: none; | |
| } | |
| .leaderboard-table tr:hover td { | |
| background: var(--background-fill-secondary); | |
| } | |
| /* Column-specific styles */ | |
| .leaderboard-table .col-rank { | |
| width: 70px; | |
| min-width: 70px; /* Prevent rank from shrinking */ | |
| } | |
| .leaderboard-table .col-model { | |
| min-width: 200px; /* Minimum width before scrolling */ | |
| } | |
| .leaderboard-table .col-winrate { | |
| width: 100px; | |
| min-width: 100px; /* Prevent win rate from shrinking */ | |
| } | |
| .leaderboard-table .col-votes { | |
| width: 100px; | |
| min-width: 100px; /* Prevent votes from shrinking */ | |
| } | |
| .leaderboard-table .col-arena { | |
| width: 100px; | |
| min-width: 100px; /* Prevent arena score from shrinking */ | |
| } | |
| .win-rate { | |
| display: inline-block; | |
| font-weight: 600; | |
| padding: 4px 8px; | |
| border-radius: 6px; | |
| min-width: 65px; | |
| text-align: center; | |
| } | |
| .win-rate-excellent { | |
| background-color: var(--color-accent); | |
| color: var(--color-accent-foreground); | |
| } | |
| .win-rate-good { | |
| background-color: var(--color-accent-soft); | |
| color: var(--body-text-color); | |
| } | |
| .win-rate-average { | |
| background-color: var(--background-fill-secondary); | |
| color: var(--body-text-color); | |
| border: 1px solid var(--border-color-primary); | |
| } | |
| .win-rate-below { | |
| background-color: var(--error-background-fill); | |
| color: var(--body-text-color); | |
| } | |
| .model-link { | |
| color: var(--body-text-color) !important; | |
| text-decoration: none !important; | |
| border-bottom: 2px dashed rgba(128, 128, 128, 0.3); | |
| } | |
| .model-link:hover { | |
| color: var(--color-accent) !important; | |
| border-bottom-color: var(--color-accent) !important; | |
| } | |
| .proprietary-badge { | |
| display: inline-block; | |
| font-size: 12px; | |
| padding: 2px 6px; | |
| border-radius: 4px; | |
| background-color: var(--background-fill-secondary); | |
| color: var(--body-text-color); | |
| margin-left: 6px; | |
| border: 1px solid var(--border-color-primary); | |
| } | |
| /* New Arena V2 Pointer */ | |
| .arena-v2-pointer { | |
| display: block; | |
| margin: 20px auto; | |
| padding: 20px; | |
| text-align: center; | |
| border-radius: 12px; | |
| font-size: 20px; | |
| font-weight: bold; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| position: relative; | |
| overflow: hidden; | |
| text-decoration: none !important; | |
| box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); | |
| max-width: 800px; | |
| background: linear-gradient(135deg, #FF7B00, #FF5500); | |
| color: white !important; | |
| border: none; | |
| } | |
| /* Dark mode adjustments */ | |
| @media (prefers-color-scheme: dark) { | |
| .arena-v2-pointer { | |
| box-shadow: 0 4px 20px rgba(255, 123, 0, 0.3); | |
| } | |
| } | |
| .arena-v2-pointer:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 7px 25px rgba(255, 123, 0, 0.4); | |
| filter: brightness(1.05); | |
| color: white !important; | |
| text-decoration: none !important; | |
| } | |
| .arena-v2-pointer::after { | |
| content: "→"; | |
| font-size: 24px; | |
| margin-left: 10px; | |
| display: inline-block; | |
| transition: transform 0.3s ease; | |
| } | |
| .arena-v2-pointer:hover::after { | |
| transform: translateX(5px); | |
| } | |
| </style> | |
| <a href="https://huggingface.co/spaces/TTS-AGI/TTS-Arena-V2" class="arena-v2-pointer" target="_blank"> | |
| Visit the new TTS Arena V2 to vote on the latest models! | |
| </a> | |
| <div class="leaderboard-container"> | |
| <div class="leaderboard-scroll"> | |
| <table class="leaderboard-table"> | |
| <thead> | |
| <tr> | |
| <th class="col-rank">Rank</th> | |
| <th class="col-model">Model</th> | |
| <th class="col-winrate">Win Rate</th> | |
| <th class="col-votes">Votes</th> | |
| """ + ("""<th class="col-arena">Arena Score</th>""" if sort_by_elo else "") + """ | |
| </tr> | |
| </thead> | |
| <tbody> | |
| """ | |
| def get_win_rate_class(win_rate): | |
| if win_rate >= 60: | |
| return "win-rate-excellent" | |
| elif win_rate >= 55: | |
| return "win-rate-good" | |
| elif win_rate >= 45: | |
| return "win-rate-average" | |
| else: | |
| return "win-rate-below" | |
| for _, row in df.iterrows(): | |
| win_rate_class = get_win_rate_class(row['win_rate']) | |
| win_rate_html = f'<span class="win-rate {win_rate_class}">{row["win_rate"]}%</span>' | |
| # Add link to model name if available and proprietary badge if closed source | |
| model_name = row['name'] | |
| original_model_name = model_name | |
| if model_name in model_links: | |
| model_name = f'<a href="{model_links[model_name]}" target="_blank" class="model-link">{model_name}</a>' | |
| if original_model_name in closed_source: | |
| model_name += '<span class="proprietary-badge">Proprietary</span>' | |
| markdown_table += f'''<tr> | |
| <td class="col-rank">{row['order']}</td> | |
| <td class="col-model">{model_name}</td> | |
| <td class="col-winrate">{win_rate_html}</td> | |
| <td class="col-votes">{row['votes']:,}</td>''' + ( | |
| f'''<td class="col-arena">{int(row['elo'])}</td>''' if sort_by_elo else "" | |
| ) + "</tr>\n" | |
| markdown_table += "</tbody></table></div></div>" | |
| return markdown_table | |
| ABOUT = """ | |
| # TTS Arena (Legacy) | |
| This is the legacy read-only leaderboard for TTS Arena V1. No new votes are being accepted. | |
| **Please visit the new [TTS Arena](https://huggingface.co/spaces/TTS-AGI/TTS-Arena-V2) to vote!** | |
| """ | |
| CITATION_TEXT = """@misc{tts-arena, | |
| title = {Text to Speech Arena}, | |
| author = {mrfakename and Srivastav, Vaibhav and Fourrier, Clémentine and Pouget, Lucain and Lacombe, Yoach and main and Gandhi, Sanchit}, | |
| year = 2024, | |
| publisher = {Hugging Face}, | |
| howpublished = "\\url{https://huggingface.co/spaces/TTS-AGI/TTS-Arena}" | |
| }""" | |
| FOOTER = f""" | |
| If you reference the Arena in your work, please cite it as follows: | |
| ```bibtex | |
| {CITATION_TEXT} | |
| ``` | |
| """ | |
| with gr.Blocks() as demo: | |
| gr.Markdown(ABOUT) | |
| with gr.Row(): | |
| with gr.Column(): | |
| reveal_prelim = gr.Checkbox(label="Show preliminary results (< 500 votes)", value=False) | |
| hide_battle_votes = gr.Checkbox(label="Exclude battle votes", value=False) | |
| with gr.Column(): | |
| sort_by_elo = gr.Checkbox(label="Sort by Arena Score instead of Win Rate", value=True) | |
| hide_proprietary = gr.Checkbox(label="Hide proprietary models", value=False) | |
| leaderboard_html = gr.HTML(get_leaderboard()) | |
| # Update leaderboard when filters change | |
| for control in [reveal_prelim, hide_battle_votes, sort_by_elo, hide_proprietary]: | |
| control.change( | |
| fn=get_leaderboard, | |
| inputs=[reveal_prelim, hide_battle_votes, sort_by_elo, hide_proprietary], | |
| outputs=leaderboard_html | |
| ) | |
| gr.Markdown(FOOTER) | |
| demo.launch() |