Spaces:
Runtime error
Runtime error
| import gradio as gr | |
| import os | |
| from transformers import pipeline | |
| from pypinyin import pinyin, Style | |
| from janome.tokenizer import Tokenizer | |
| import re | |
| from pytube import Search | |
| import requests | |
| from chinese_english_lookup import Dictionary as CEDict | |
| from jamdict import Jamdict | |
| import jieba | |
| import json | |
| # Hugging Face pipelines | |
| translation_pipeline = pipeline("translation", model="Helsinki-NLP/opus-mt-mul-en") | |
| lang_detection_pipeline = pipeline("text-classification", model="papluca/xlm-roberta-base-language-detection") | |
| # Initialize the dictionary libraries | |
| cedict = CEDict() | |
| jam = Jamdict() | |
| # Pinyin Romanization for Chinese | |
| def get_pinyin_from_chinese(lyrics): | |
| pinyin_result = pinyin(lyrics, style=Style.TONE) | |
| return " ".join(["".join(word) for word in pinyin_result]) | |
| # Romanization for Japanese (Romaji) | |
| def get_romaji_from_japanese(lyrics): | |
| t = Tokenizer() | |
| romaji_lyrics = [] | |
| for token in t.tokenize(lyrics): | |
| romaji_lyrics.append(token.reading.lower().replace("*", "")) | |
| return " ".join(romaji_lyrics) | |
| # Function to search YouTube and generate embed code | |
| def search_and_embed_video(song_name, artist_name): | |
| query = f"{song_name} {artist_name} official lyrics" | |
| try: | |
| s = Search(query) | |
| video_id = s.results[0].video_id | |
| youtube_url = f"https://www.youtube.com/embed/{video_id}" | |
| return f'<iframe width="560" height="315" src="{youtube_url}" frameborder="0" allowfullscreen></iframe>' | |
| except Exception as e: | |
| return "<p>Could not find a YouTube video for this song.</p>" | |
| # Lookup functions for each language | |
| def lookup_english_word(word): | |
| api_url = f"https://api.dictionaryapi.dev/api/v2/entries/en/{word}" | |
| try: | |
| response = requests.get(api_url) | |
| response.raise_for_status() | |
| data = response.json() | |
| html_output = f"<h3>{data[0]['word']}</h3>" | |
| for meaning in data[0]['meanings']: | |
| part_of_speech = meaning['partOfSpeech'] | |
| html_output += f"<h4>({part_of_speech})</h4>" | |
| for definition in meaning['definitions']: | |
| def_text = definition['definition'] | |
| html_output += f"<p>• {def_text}</p>" | |
| if 'example' in definition: | |
| example_text = definition['example'] | |
| html_output += f"<p class='example'>Example: <i>{example_text}</i></p>" | |
| return html_output | |
| except requests.exceptions.HTTPError as e: | |
| return f"<p style='color:red;'>Could not find a definition for '{word}'.</p>" | |
| except Exception as e: | |
| return f"<p style='color:red;'>An error occurred: {e}</p>" | |
| def lookup_chinese_word(word): | |
| try: | |
| result = cedict.lookup(word) | |
| if not result: | |
| return f"<p style='color:red;'>Could not find a definition for '{word}'.</p>" | |
| html_output = "" | |
| for entry in result: | |
| html_output += f"<h3>{entry.simp} ({entry.trad})</h3>" | |
| for def_entry in entry.definition_entries: | |
| pinyin_text = def_entry.pinyin | |
| definitions = " / ".join(def_entry.definitions) | |
| html_output += f"<h4>{pinyin_text}</h4>" | |
| html_output += f"<p>• {definitions}</p>" | |
| return html_output | |
| except Exception as e: | |
| return f"<p style='color:red;'>An error occurred during Chinese lookup: {e}</p>" | |
| def lookup_japanese_word(word): | |
| try: | |
| result = jam.lookup(word) | |
| if not result.entries and not result.kanji: | |
| return f"<p style='color:red;'>Could not find a definition for '{word}'.</p>" | |
| html_output = "" | |
| for entry in result.entries: | |
| html_output += f"<h3>{entry.text}</h3>" | |
| if entry.kana_forms: | |
| html_output += f"<h4>({entry.kana_forms[0].text})</h4>" | |
| for gloss in entry.senses: | |
| html_output += f"<p>• {', '.join(gloss.glosses)}</p>" | |
| for k in result.kanji: | |
| html_output += f"<h3>Kanji: {k.text}</h3>" | |
| if k.meanings: | |
| html_output += f"<p>• Meaning: {', '.join(k.meanings)}</p>" | |
| if k.readings: | |
| html_output += f"<p>• Readings: {', '.join(k.readings)}</p>" | |
| return html_output | |
| except Exception as e: | |
| return f"<p style='color:red;'>An error occurred during Japanese lookup: {e}</p>" | |
| # The main dictionary lookup function, which acts as a dispatcher | |
| def get_dictionary_output(json_data: str): | |
| if not json_data: | |
| return "Click on a word to get its definition." | |
| try: | |
| data = json.loads(json_data) | |
| word = data.get('word', '') | |
| language = data.get('lang', '') | |
| if language == 'en': | |
| return lookup_english_word(word) | |
| elif language == 'zh': | |
| return lookup_chinese_word(word) | |
| elif language == 'ja': | |
| return lookup_japanese_word(word) | |
| else: | |
| return f"<p style='color:red;'>Dictionary lookup for language '{language}' is not yet supported.</p>" | |
| except json.JSONDecodeError: | |
| return "Error: Invalid data received for dictionary lookup." | |
| # Helper function to generate the HTML for display | |
| def create_html_display(original_lyrics, translated_lyrics, romanized_lyrics=None, language="en"): | |
| # This is the most important part of the fix. We are embedding the JS `onclick` | |
| # handler directly and ensuring it's a simple, reliable call. | |
| original_lines = original_lyrics.strip().split('\n') | |
| translated_lines = translated_lyrics.strip().split('\n') | |
| num_lines = max(len(original_lines), len(translated_lines)) | |
| original_lines.extend([""] * (num_lines - len(original_lines))) | |
| translated_lines.extend([""] * (num_lines - len(translated_lines))) | |
| romanized_lines = [] | |
| if romanized_lyrics: | |
| romanized_lines = romanized_lyrics.strip().split('\n') | |
| romanized_lines.extend([""] * (num_lines - len(romanized_lines))) | |
| html_content = "" | |
| for i in range(num_lines): | |
| if language == 'zh': | |
| words_in_line = jieba.cut(original_lines[i]) | |
| elif language == 'ja': | |
| words_in_line = [char for char in original_lines[i] if char] | |
| else: | |
| words_in_line = original_lines[i].split() | |
| # # The onclick handler now directly sets the hidden textbox's value. | |
| # original_line_html = " ".join([ | |
| # f"<span class='clickable-word' onclick='document.getElementById(\"hidden_json_input\").value = JSON.stringify({{word: this.textContent, lang: \"{language}\"}});'>{word}</span>" | |
| # for word in words_in_line | |
| # ]) | |
| # We no longer need the onclick handler here. | |
| original_line_html = " ".join([ | |
| f"<span class='clickable-word' data-word='{word}' data-lang='{language}'>{word}</span>" | |
| for word in words_in_line | |
| ]) | |
| # original_line_html = " ".join([ | |
| # f"<span class='clickable-word' onclick=\"" | |
| # f"const data = {{ word: this.textContent, lang: '{language}' }};" | |
| # f"const jsonStr = JSON.stringify(data);" | |
| # f"const hiddenInput = document.getElementById('hidden_json_input');" | |
| # f"hiddenInput.value = jsonStr;" | |
| # f"hiddenInput.dispatchEvent(new Event('input', {{ bubbles: true }}));\"" | |
| # f">{word}</span>" | |
| # for word in words_in_line | |
| # ]) | |
| orig_line = f"<p>{original_line_html}</p>" | |
| if romanized_lines: | |
| rom_line = f"<p class='romaji'>{romanized_lines[i]}</p>" | |
| trans_line = f"<p class='translation'>{translated_lines[i]}</p>" | |
| html_content += f""" | |
| <div class='line-container'> | |
| <div class='original-text'>{orig_line}{rom_line}</div> | |
| <div class='translated-text'>{trans_line}</div> | |
| </div> | |
| """ | |
| else: | |
| trans_line = f"<p>{translated_lines[i]}</p>" | |
| html_content += f""" | |
| <div class='line-container'> | |
| <div class='original-text'>{orig_line}</div> | |
| <div class='translated-text'>{trans_line}</div> | |
| </div> | |
| """ | |
| css = """ | |
| <style> | |
| .line-container { | |
| display: flex; | |
| justify-content: space-between; | |
| margin-bottom: 15px; | |
| padding-bottom: 10px; | |
| border-bottom: 1px solid #eee; | |
| } | |
| .original-text, .translated-text { | |
| flex: 1; | |
| padding: 0 10px; | |
| } | |
| .romaji, .translation { | |
| color: #888; | |
| font-size: 0.9em; | |
| } | |
| .original-text p, .translated-text p { | |
| margin: 0; | |
| padding: 0; | |
| } | |
| .clickable-word { | |
| cursor: pointer; | |
| font-weight: bold; | |
| } | |
| .clickable-word:hover { | |
| text-decoration: underline; | |
| color: #007bff; | |
| } | |
| .example { | |
| font-size: 0.9em; | |
| margin-left: 10px; | |
| color: #555; | |
| } | |
| </style> | |
| """ | |
| # We return the HTML with the language as a data attribute, as before. | |
| return f"{css}<div id='lyrics-container' data-lang='{language}'>{html_content}</div>" | |
| # The main function to handle all the logic | |
| def process_lyrics(lyrics: str, song_name: str = "", artist_name: str = ""): | |
| if not lyrics: | |
| return "", "", "Please paste some lyrics." | |
| original_lines = [line.strip() for line in lyrics.strip().split('\n') if line.strip()] | |
| language = "unknown" | |
| if original_lines: | |
| try: | |
| prediction = lang_detection_pipeline(original_lines[0])[0] | |
| language = prediction['label'] | |
| except Exception: | |
| language = "unknown" | |
| is_chinese = language.startswith("zh") if language else False | |
| translated_lines = [] | |
| for line in original_lines: | |
| try: | |
| if not line: | |
| translated_lines.append("") | |
| continue | |
| if language != "en": | |
| src_lang_tag = "" | |
| if is_chinese: | |
| src_lang_tag = ">>zh<<" | |
| elif language == "ja": | |
| src_lang_tag = ">>ja<<" | |
| translation_result = translation_pipeline(line, src_lang=language, tgt_lang="en")[0]['translation_text'] | |
| translated_lines.append(translation_result) | |
| else: | |
| translated_lines.append(line) | |
| except Exception as e: | |
| translated_lines.append(f"Translation Error: {e}") | |
| romanized_lines = None | |
| if is_chinese: | |
| romanized_lines = [get_pinyin_from_chinese(line) for line in original_lines] | |
| elif language == "ja": | |
| romanized_lines = [get_romaji_from_japanese(line) for line in original_lines] | |
| original_text = "\n".join(original_lines) | |
| translated_text = "\n".join(translated_lines) | |
| romanized_text = "\n".join(romanized_lines) if romanized_lines else None | |
| display_html = create_html_display(original_text, translated_text, romanized_text, language) | |
| youtube_embed = search_and_embed_video(song_name, artist_name) | |
| return display_html, youtube_embed, "Click on a word to get its definition." | |
| def welcome(name): | |
| return f"Welcome to Gradio, {name}!" | |
| js = """ | |
| function createGradioAnimation() { | |
| var container = document.createElement('div'); | |
| container.id = 'gradio-animation'; | |
| container.style.fontSize = '2em'; | |
| container.style.fontWeight = 'bold'; | |
| container.style.textAlign = 'center'; | |
| container.style.marginBottom = '20px'; | |
| var text = 'Welcome to Gradio!'; | |
| for (var i = 0; i < text.length; i++) { | |
| (function(i){ | |
| setTimeout(function(){ | |
| var letter = document.createElement('span'); | |
| letter.style.opacity = '0'; | |
| letter.style.transition = 'opacity 0.5s'; | |
| letter.innerText = text[i]; | |
| container.appendChild(letter); | |
| setTimeout(function() { | |
| letter.style.opacity = '1'; | |
| }, 50); | |
| }, i * 250); | |
| })(i); | |
| } | |
| var gradioContainer = document.querySelector('.gradio-container'); | |
| gradioContainer.insertBefore(container, gradioContainer.firstChild); | |
| return 'Animation created'; | |
| } | |
| """ | |
| # Define the Gradio interface | |
| with gr.Blocks(js=js) as demo: | |
| inp = gr.Textbox(placeholder="What is your name?") | |
| out = gr.Textbox() | |
| inp.change(welcome, inp, out) | |
| # This is the hidden component that our JavaScript will update. | |
| # The `elem_id` is crucial for the JS to find it. | |
| hidden_json_input = gr.Textbox(visible=False, elem_id="hidden_json_input") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| lyrics_input = gr.Textbox(label="Paste Lyrics Here", lines=10, placeholder="Paste your lyrics here...") | |
| song_input = gr.Textbox(label="Enter Song Name (for Youtube)", placeholder="e.g., 月亮代表我的心") | |
| artist_input = gr.Textbox(label="Enter Artist Name (Optional)", placeholder="e.g., Teresa Teng") | |
| process_button = gr.Button("Process") | |
| with gr.Column(scale=2): | |
| lyrics_html_output = gr.HTML(label="Lyrics and Translation") | |
| video_embed_output = gr.HTML(label="YouTube Video") | |
| with gr.Column(scale=1): | |
| dictionary_output = gr.HTML(label="Word Definition") | |
| process_button.click( | |
| fn=process_lyrics, | |
| inputs=[lyrics_input, song_input, artist_input], | |
| outputs=[lyrics_html_output, video_embed_output, dictionary_output] | |
| ).success( | |
| fn=None, | |
| inputs=None, | |
| outputs=None, | |
| js="attach_click_listeners()" | |
| ) | |
| # We listen for a change to the hidden textbox to trigger our lookup | |
| hidden_json_input.change( | |
| fn=get_dictionary_output, | |
| inputs=[hidden_json_input], | |
| outputs=[dictionary_output] | |
| ) | |
| # hidden_json_input.input( | |
| # fn=get_dictionary_output, | |
| # inputs=[hidden_json_input], | |
| # outputs=[dictionary_output] | |
| # ) | |
| # Launch the Gradio app | |
| if __name__ == "__main__": | |
| demo.launch() |