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)}")