import streamlit as st import pyphen import re from typing import List, Tuple import string import os # Initialize pyphen for syllable splitting dic = pyphen.Pyphen(lang='en') # Configure Streamlit page st.set_page_config( page_title="Text Pronunciation Analyzer", page_icon="๐Ÿ—ฃ๏ธ", layout="wide", initial_sidebar_state="collapsed" ) # Custom CSS for styling st.markdown(""" """, unsafe_allow_html=True) class PronunciationAnalyzer: def __init__(self): self.dic = pyphen.Pyphen(lang='en') self.color_classes = [ 'color-1', 'color-2', 'color-3', 'color-4', 'color-5', 'color-6', 'color-7', 'color-8' ] def get_syllables(self, word: str) -> List[str]: """Get syllables for a word using pyphen""" # Remove punctuation and convert to lowercase clean_word = word.lower().strip(string.punctuation) if not clean_word: return [word] # Use pyphen to split into syllables syllables = self.dic.inserted(clean_word).split('-') # If pyphen couldn't split (returns original word), try basic vowel-based splitting if len(syllables) == 1 and len(clean_word) > 3: syllables = self._basic_syllable_split(clean_word) return syllables if syllables else [word] def _basic_syllable_split(self, word: str) -> List[str]: """Basic vowel-based syllable splitting as fallback""" vowels = 'aeiouy' syllables = [] current_syllable = '' for i, char in enumerate(word): current_syllable += char # Look ahead for vowel patterns if i < len(word) - 1: if char in vowels and word[i + 1] not in vowels: # Vowel followed by consonant - potential syllable break if len(current_syllable) >= 2: syllables.append(current_syllable) current_syllable = '' if current_syllable: syllables.append(current_syllable) return syllables if syllables else [word] def tokenize_text(self, text: str) -> List[str]: """Tokenize text into words while preserving punctuation""" # Enhanced regex-based tokenization # This pattern matches: # - Words (including contractions like "don't") # - Numbers # - Punctuation marks # - Preserves spacing # Split text into tokens while preserving structure pattern = r"(?:\w+(?:'\w+)?|\d+|[^\w\s])" tokens = re.findall(pattern, text) # Add spaces back where needed result = [] text_pos = 0 for token in tokens: # Find the token's position in the original text token_pos = text.find(token, text_pos) # Add any whitespace before the token if token_pos > text_pos: whitespace = text[text_pos:token_pos] if whitespace.strip() == '': # Only add if it's pure whitespace result.extend(list(whitespace)) result.append(token) text_pos = token_pos + len(token) # Filter out empty strings and normalize return [token for token in result if token.strip()] def analyze_text(self, text: str) -> Tuple[List[Tuple[str, List[str]]], dict]: """Analyze text and return word-syllable pairs and statistics""" if not text.strip(): return [], {} # Tokenize the text words = self.tokenize_text(text) # Filter out pure punctuation tokens for analysis content_words = [word for word in words if any(c.isalnum() for c in word)] # Get syllables for each word word_syllables = [] total_syllables = 0 for word in words: if any(c.isalnum() for c in word): # Only analyze words with alphanumeric characters syllables = self.get_syllables(word) word_syllables.append((word, syllables)) total_syllables += len(syllables) else: word_syllables.append((word, [word])) # Keep punctuation as-is # Calculate statistics stats = { 'total_words': len(content_words), 'total_syllables': total_syllables, 'avg_syllables': round(total_syllables / len(content_words), 2) if content_words else 0, 'longest_word': max(content_words, key=len) if content_words else '', 'most_syllables': max(content_words, key=lambda w: len(self.get_syllables(w))) if content_words else '' } return word_syllables, stats def render_highlighted_text(word_syllables: List[Tuple[str, List[str]]], analyzer: PronunciationAnalyzer): """Render original text with word highlighting""" html_parts = [] word_index = 0 for word, syllables in word_syllables: if any(c.isalnum() for c in word): color_class = analyzer.color_classes[word_index % len(analyzer.color_classes)] html_parts.append(f'{word}') word_index += 1 else: html_parts.append(word) # Add space after word (except for punctuation that shouldn't have spaces) if word not in '.,!?;:': html_parts.append(' ') return ''.join(html_parts) def render_pronunciation(word_syllables: List[Tuple[str, List[str]]], analyzer: PronunciationAnalyzer): """Render pronunciation with syllable breakdown""" html_parts = [] word_index = 0 for word, syllables in word_syllables: if any(c.isalnum() for c in word): color_class = analyzer.color_classes[word_index % len(analyzer.color_classes)] # Join syllables with dots syllable_text = 'ยท'.join(syllables) html_parts.append(f'{syllable_text}') word_index += 1 else: html_parts.append(f'{word}') # Add space after word (except for punctuation that shouldn't have spaces) if word not in '.,!?;:': html_parts.append(' ') return ''.join(html_parts) def main(): # Initialize analyzer analyzer = PronunciationAnalyzer() # Header st.markdown('

๐Ÿ—ฃ๏ธ Text Pronunciation Analyzer

', unsafe_allow_html=True) st.markdown('

Enter any text below to see its pronunciation breakdown with advanced syllable detection

', unsafe_allow_html=True) # Input section st.markdown("### ๐Ÿ“ Enter Your Text") # Sample texts sample_texts = [ "Hello world", "Pronunciation analyzer", "Supercalifragilisticexpialidocious", "Linguistics and phonetics", "The quick brown fox jumps over the lazy dog" ] # Sample buttons st.markdown("**Try these samples:**") cols = st.columns(len(sample_texts)) for i, sample in enumerate(sample_texts): if cols[i].button(sample, key=f"sample_{i}"): st.session_state.input_text = sample # Text input text_input = st.text_area( "Text to analyze:", value=st.session_state.get('input_text', ''), height=120, placeholder="Type or paste your text here...", key="text_input" ) # Update session state if text_input: st.session_state.input_text = text_input # Analyze button col1, col2, col3 = st.columns([1, 1, 1]) with col2: analyze_button = st.button("๐Ÿ” Analyze Text", type="primary", use_container_width=True) # Clear button if st.button("๐Ÿ—‘๏ธ Clear"): st.session_state.input_text = "" st.rerun() # Analysis results if analyze_button and text_input.strip(): with st.spinner("Analyzing text..."): word_syllables, stats = analyzer.analyze_text(text_input) if word_syllables: st.markdown("---") st.markdown("## ๐Ÿ“Š Analysis Results") # Statistics st.markdown("### ๐Ÿ“ˆ Text Statistics") col1, col2, col3, col4 = st.columns(4) with col1: st.markdown(f"""
{stats['total_words']}
Words
""", unsafe_allow_html=True) with col2: st.markdown(f"""
{stats['total_syllables']}
Syllables
""", unsafe_allow_html=True) with col3: st.markdown(f"""
{stats['avg_syllables']}
Avg/Word
""", unsafe_allow_html=True) with col4: longest_syllables = len(analyzer.get_syllables(stats['most_syllables'])) st.markdown(f"""
{longest_syllables}
Max Syllables
""", unsafe_allow_html=True) # Original text with highlights st.markdown("### ๐Ÿ“– Original Text") original_html = render_highlighted_text(word_syllables, analyzer) st.markdown(f'
{original_html}
', unsafe_allow_html=True) # Pronunciation breakdown st.markdown("### ๐Ÿ”ค Pronunciation Breakdown") pronunciation_html = render_pronunciation(word_syllables, analyzer) st.markdown(f'
{pronunciation_html}
', unsafe_allow_html=True) # Add JavaScript for hover interaction st.components.v1.html(""" """, height=0) # Word-by-word breakdown st.markdown("### ๐Ÿ“ Word-by-Word Analysis") # Create expandable sections for detailed breakdown content_words = [(word, syllables) for word, syllables in word_syllables if any(c.isalnum() for c in word)] if content_words: # Group words into rows of 3 for i in range(0, len(content_words), 3): cols = st.columns(3) for j, (word, syllables) in enumerate(content_words[i:i+3]): with cols[j]: st.markdown(f"""
{word}
{'ยท'.join(syllables)}
{len(syllables)} syllable{'s' if len(syllables) != 1 else ''}
""", unsafe_allow_html=True) elif analyze_button: st.warning("Please enter some text to analyze.") # Footer st.markdown("---") st.markdown( '
Created by @aghilalb and ai
', unsafe_allow_html=True ) if __name__ == "__main__": main()