From 7c99592fe0258d5a433482c6a037419a769fa47d Mon Sep 17 00:00:00 2001 From: tcsenpai Date: Wed, 25 Dec 2024 14:24:24 +0100 Subject: [PATCH] modernized webui + added po_token support + added rephrasing of original transcript --- src/main.py | 377 +++++++++++++++++++++++++------------------ src/yt_audiophile.py | 66 +++++--- 2 files changed, 268 insertions(+), 175 deletions(-) diff --git a/src/main.py b/src/main.py index 9058caa..4c0e5c7 100644 --- a/src/main.py +++ b/src/main.py @@ -17,55 +17,142 @@ load_dotenv() st.set_page_config( page_title="YouTube Summarizer by TCSenpai", page_icon="src/assets/subtitles.png", + layout="wide", # This ensures full width ) -# Add this after set_page_config +# Add custom CSS with a modern, clean design st.markdown( """ -""", + """, unsafe_allow_html=True, ) @@ -229,62 +316,28 @@ def summarize_video( def main(): - # Remove the existing title - # st.title("YouTube Video Summarizer") + # Settings section + st.write("## AI Video Summarizer") - # Add input for custom Ollama URL - default_ollama_url = os.getenv("OLLAMA_URL") - ollama_url = st.text_input( - "Ollama URL (optional)", - value=default_ollama_url, - placeholder="Enter custom Ollama URL", - ) - - if not ollama_url: - ollama_url = default_ollama_url - - # Fetch available models using the specified Ollama URL - available_models = get_ollama_models(ollama_url) - default_model = os.getenv("OLLAMA_MODEL") - - if not default_model in available_models: - available_models.append(default_model) - - # Sets whisper options - default_whisper_url = os.getenv("WHISPER_URL") - whisper_url = st.text_input( - "Whisper URL (optional)", - value=default_whisper_url, - placeholder="Enter custom Whisper URL", - ) - if not whisper_url: - whisper_url = default_whisper_url - whisper_model = os.getenv("WHISPER_MODEL") - if not whisper_model: - whisper_model = "Systran/faster-whisper-large-v3" - st.caption(f"Whisper model: {whisper_model}") - - # Create model selection dropdown - # selected_model = st.selectbox( - # "Select Ollama Model", - # options=available_models, - # index=( - # available_models.index(default_model) - # if default_model in available_models - # else 0 - # ), - # ) - - # Use columns for URL and model inputs - col1, col2 = st.columns([2, 1]) - with col1: - video_url = st.text_input( - "Enter the YouTube video URL:", - placeholder="https://www.youtube.com/watch?v=...", + # Ollama Settings - single card + with st.container(): + st.subheader("🎯 Ollama Settings") + default_ollama_url = os.getenv("OLLAMA_URL") + ollama_url = st.text_input( + "Ollama URL", + value=default_ollama_url, + placeholder="Enter Ollama URL", ) - with col2: + if not ollama_url: + ollama_url = default_ollama_url + + available_models = get_ollama_models(ollama_url) + default_model = os.getenv("OLLAMA_MODEL") + if default_model not in available_models: + available_models.append(default_model) + selected_model = st.selectbox( - "Select Ollama Model", + "Model", options=available_models, index=( available_models.index(default_model) @@ -293,89 +346,107 @@ def main(): ), ) - # Group Ollama and Whisper settings - with st.expander("Advanced Settings"): - col1, col2 = st.columns(2) - with col1: - ollama_url = st.text_input( - "Ollama URL", - value=default_ollama_url, - placeholder="Enter custom Ollama URL", - ) - with col2: - whisper_url = st.text_input( - "Whisper URL", - value=default_whisper_url, - placeholder="Enter custom Whisper URL", + # Video URL input section + with st.container(): + url_col, button_col = st.columns([4, 1]) + + with url_col: + video_url = st.text_input( + "🎥 Video URL", + placeholder="https://www.youtube.com/watch?v=...", ) - col1, col2 = st.columns(2) - with col1: + with button_col: + summarize_button = st.button("🚀 Summarize", use_container_width=True) + + # Advanced settings in collapsible sections + with st.expander("⚙️ Advanced Settings"): + # Whisper Settings + st.subheader("🎤 Whisper Settings") + default_whisper_url = os.getenv("WHISPER_URL") + whisper_url = st.text_input( + "Whisper URL", + value=default_whisper_url, + placeholder="Enter Whisper URL", + ) + if not whisper_url: + whisper_url = default_whisper_url + + whisper_model = os.getenv("WHISPER_MODEL") + if not whisper_model: + whisper_model = "Systran/faster-whisper-large-v3" + st.caption(f"Current model: {whisper_model}") + + st.markdown("
", unsafe_allow_html=True) # Add some spacing + + # Whisper Options + adv_col1, adv_col2 = st.columns(2) + with adv_col1: force_whisper = st.checkbox("Force Whisper", value=False) - with col2: + with adv_col2: fallback_to_whisper = st.checkbox("Fallback to Whisper", value=True) - # Support any video that has a valid YouTube ID - if not "https://www.youtube.com/watch?v=" or "https://youtu.be/" in video_url: - if "watch?v=" in video_url: - st.warning( - "This is not a YouTube URL. Might be a privacy-fronted embed. Trying to extract the YouTube ID..." - ) - video_id = video_url.split("watch?v=")[-1] - video_url = f"https://www.youtube.com/watch?v={video_id}" - else: - st.error("Please enter a valid YouTube video URL.") - return - # Support short urls as well - if "https://youtu.be/" in video_url: - video_id = video_url.split("youtu.be/")[-1] - video_url = f"https://www.youtube.com/watch?v={video_id}" + if summarize_button and video_url: + summary = summarize_video( + video_url, + selected_model, + ollama_url, + fallback_to_whisper=fallback_to_whisper, + force_whisper=force_whisper, + ) - if st.button("Summarize"): - if video_url: - summary = summarize_video( - video_url, - selected_model, - ollama_url, - fallback_to_whisper=fallback_to_whisper, - force_whisper=force_whisper, - ) - st.subheader("Video Information:") + # Video Information + st.subheader("📺 Video Information") + info_col1, info_col2 = st.columns(2) + with info_col1: st.write(f"**Title:** {summary['title']}") + with info_col2: st.write(f"**Channel:** {summary['channel']}") - st.subheader("Summary:") - st.write(summary["summary"]) + # Transcript Section + with st.expander("📝 Original Transcript", expanded=False): + col1, col2 = st.columns([3, 1]) + with col1: + st.text_area( + "Raw Transcript", + summary["transcript"], + height=200, + disabled=True, + ) + with col2: + if st.button("🔄 Rephrase"): + with st.spinner("Rephrasing transcript..."): + ollama_client = OllamaClient(ollama_url, selected_model) + prompt = f"Rephrase the following transcript to make it more readable and well-formatted, keeping the main content intact:\n\n{summary['transcript']}" + rephrased = ollama_client.generate(prompt) + st.markdown(rephrased) - st.subheader("Original Transcript:") - st.text_area( - "Full Transcript", summary["transcript"], height=300, disabled=True - ) - - # Share button moved here, after the transcript - if st.button("Share Transcript"): - try: - content = f"""Video Title: {summary['title']} + if st.button("📋 Share"): + try: + content = f"""Video Title: {summary['title']} Channel: {summary['channel']} URL: {video_url} --- Transcript --- {summary['transcript']}""" - - paste_url = create_paste(f"Transcript: {summary['title']}", content) - st.success( - f"Transcript shared successfully! [View here]({paste_url})" - ) - except Exception as e: - if "PASTEBIN_API_KEY" not in os.environ: - st.warning( - "PASTEBIN_API_KEY not found in environment variables" + paste_url = create_paste( + f"Transcript: {summary['title']}", content ) - else: - st.error(f"Error sharing transcript: {str(e)}") - else: - st.error("Please enter a valid YouTube video URL.") + st.success( + f"Transcript shared successfully! [View here]({paste_url})" + ) + except Exception as e: + if "PASTEBIN_API_KEY" not in os.environ: + st.warning( + "PASTEBIN_API_KEY not found in environment variables" + ) + else: + st.error(f"Error sharing transcript: {str(e)}") + + # Summary Section + st.subheader("📊 AI Summary") + st.markdown(summary["summary"]) if __name__ == "__main__": diff --git a/src/yt_audiophile.py b/src/yt_audiophile.py index 3f5fa11..2663b91 100644 --- a/src/yt_audiophile.py +++ b/src/yt_audiophile.py @@ -5,32 +5,54 @@ from pytubefix.cli import on_progress https://www.youtube.com/watch?v=vwTDiLH6mqg """ + def download_audio(url): - yt = YouTube(url, on_progress_callback=on_progress) - audio, video = itags(yt, "1080p") # specify the resolution - yt.streams.get_by_itag(audio).download("downloads","output.m4a") # downloads audio + try: + # Create YouTube object with bot detection bypass + yt = YouTube( + url, + on_progress_callback=on_progress, + use_oauth=True, + allow_oauth_cache=True, + use_po_token=True, # Add this to bypass bot detection + ) + + # Get audio stream + audio_stream = yt.streams.filter(only_audio=True).order_by("abr").desc().first() + if not audio_stream: + raise Exception("No audio stream found") + + # Download audio + audio_stream.download("downloads", "output.m4a") + return True + + except Exception as e: + print(f"Error in download_audio: {str(e)}") + raise Exception(f"Download failed: {str(e)}") def itags(yt: YouTube, resolution="1080p"): - max_audio = 0 - audio_value = 0 - for audio_stream in yt.streams.filter(only_audio=True): - abr = int(audio_stream.abr.replace("kbps", "")) - if abr > max_audio: - max_audio = abr - audio_value = audio_stream.itag - streams = yt.streams try: - video_tag = streams.filter(res=resolution, fps=60)[0].itag - print("60 FPS") - except IndexError: - video_tag = streams.filter(res=resolution, fps=30) - if video_tag: - video_tag = video_tag[0].itag - print("30 FPS") - else: - video_tag = streams.filter(res=resolution, fps=24)[0].itag - print("24 FPS") - return audio_value, video_tag + # Get best audio stream + audio_stream = yt.streams.filter(only_audio=True).order_by("abr").desc().first() + audio_value = audio_stream.itag if audio_stream else None + # Get video stream + video_stream = None + for fps in [60, 30, 24]: + try: + video_stream = yt.streams.filter(res=resolution, fps=fps).first() + if video_stream: + print(f"Found {fps} FPS stream") + break + except IndexError: + continue + if not video_stream: + raise Exception(f"No video stream found for resolution {resolution}") + + return audio_value, video_stream.itag + + except Exception as e: + print(f"Error in itags: {str(e)}") + raise Exception(f"Stream selection failed: {str(e)}")