chawitzoon's picture
Update app.py
d33dfcc verified
Raw
History Blame Contribute Delete
14.2 kB
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()