|
|
""" |
|
|
YouTube Tutorial to Step-by-Step Guide Generator |
|
|
Main application file for Hugging Face Space deployment |
|
|
""" |
|
|
import os |
|
|
import logging |
|
|
import time |
|
|
import tempfile |
|
|
from typing import Dict, List, Optional, Any |
|
|
|
|
|
import gradio as gr |
|
|
import numpy as np |
|
|
from huggingface_hub import HfApi, login |
|
|
from dotenv import load_dotenv |
|
|
|
|
|
|
|
|
from smolagent_processor import SmoLAgentProcessor |
|
|
import ui_components |
|
|
import youtube_utils |
|
|
import memory_utils |
|
|
|
|
|
|
|
|
logging.basicConfig( |
|
|
level=logging.INFO, |
|
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' |
|
|
) |
|
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
|
HF_TOKEN = os.getenv("HF_TOKEN") |
|
|
if HF_TOKEN: |
|
|
login(token=HF_TOKEN) |
|
|
logger.info("Logged in to Hugging Face Hub") |
|
|
else: |
|
|
logger.warning("No Hugging Face token found. Some features may be limited.") |
|
|
|
|
|
|
|
|
def process_video(video_url: str, progress=gr.Progress()) -> Dict[str, Any]: |
|
|
""" |
|
|
Process YouTube video and generate step-by-step guide. |
|
|
|
|
|
Args: |
|
|
video_url: YouTube video URL |
|
|
progress: Gradio progress indicator |
|
|
|
|
|
Returns: |
|
|
Dictionary with processed video information and steps |
|
|
""" |
|
|
logger.info(f"Processing video: {video_url}") |
|
|
start_time = time.time() |
|
|
|
|
|
|
|
|
video_id = youtube_utils.extract_video_id(video_url) |
|
|
if not video_id: |
|
|
return {"error": "Invalid YouTube URL. Please provide a valid YouTube video URL."} |
|
|
|
|
|
progress(0.1, "Extracting video information...") |
|
|
|
|
|
|
|
|
video_info = youtube_utils.get_video_info(video_id) |
|
|
if "error" in video_info: |
|
|
return {"error": video_info["error"]} |
|
|
|
|
|
|
|
|
video_info["id"] = video_id |
|
|
|
|
|
progress(0.2, "Getting video transcript...") |
|
|
|
|
|
|
|
|
transcript = youtube_utils.get_transcript(video_id) |
|
|
|
|
|
progress(0.3, "Getting video chapters...") |
|
|
|
|
|
|
|
|
chapters = youtube_utils.get_video_chapters(video_id) |
|
|
|
|
|
progress(0.4, "Processing transcript...") |
|
|
|
|
|
|
|
|
processor = SmoLAgentProcessor() |
|
|
|
|
|
|
|
|
memory_utils.log_memory_usage() |
|
|
|
|
|
|
|
|
steps = processor.process_transcript(transcript, chapters) |
|
|
|
|
|
progress(0.9, "Finalizing results...") |
|
|
|
|
|
|
|
|
memory_utils.log_memory_usage() |
|
|
|
|
|
|
|
|
processing_time = time.time() - start_time |
|
|
logger.info(f"Processing completed in {processing_time:.2f} seconds") |
|
|
|
|
|
|
|
|
return { |
|
|
"video_info": video_info, |
|
|
"chapters": chapters, |
|
|
"steps": steps, |
|
|
"memory_usage": memory_utils.get_memory_usage(), |
|
|
"processing_time": processing_time |
|
|
} |
|
|
|
|
|
def create_interface() -> gr.Blocks: |
|
|
""" |
|
|
Create Gradio interface for the application. |
|
|
|
|
|
Returns: |
|
|
Gradio Blocks interface |
|
|
""" |
|
|
with gr.Blocks(css=ui_components.CUSTOM_CSS) as app: |
|
|
gr.Markdown("# YouTube Tutorial to Step-by-Step Guide") |
|
|
gr.Markdown("Convert any YouTube tutorial into an editable, time-stamped guide with code detection.") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=3): |
|
|
video_url = gr.Textbox( |
|
|
label="YouTube Video URL", |
|
|
placeholder="https://www.youtube.com/watch?v=...", |
|
|
info="Enter the URL of a YouTube tutorial video" |
|
|
) |
|
|
|
|
|
submit_btn = gr.Button("Generate Guide", variant="primary") |
|
|
|
|
|
with gr.Accordion("Advanced Options", open=False): |
|
|
memory_info = gr.Markdown(ui_components.format_memory_usage(memory_utils.get_memory_usage())) |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown(""" |
|
|
## How it works |
|
|
1. Enter a YouTube tutorial URL |
|
|
2. The app extracts the transcript and detects chapters |
|
|
3. It processes the content to identify steps and code snippets |
|
|
4. You get an editable guide with timestamps |
|
|
|
|
|
## Features |
|
|
- Automatic chapter detection |
|
|
- Code snippet identification |
|
|
- Editable steps and code |
|
|
- Export to Markdown |
|
|
""") |
|
|
|
|
|
with gr.Tabs() as tabs: |
|
|
with gr.TabItem("Guide"): |
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
video_info_md = gr.Markdown("Enter a YouTube URL and click 'Generate Guide'") |
|
|
|
|
|
with gr.Column(scale=1): |
|
|
chapters_md = gr.Markdown("") |
|
|
|
|
|
steps_md = gr.Markdown("") |
|
|
|
|
|
with gr.Row(): |
|
|
export_md_btn = gr.Button("Export to Markdown") |
|
|
export_md = gr.Textbox( |
|
|
label="Markdown Export", |
|
|
visible=False, |
|
|
lines=10 |
|
|
) |
|
|
|
|
|
with gr.TabItem("Edit"): |
|
|
steps_df = gr.Dataframe( |
|
|
headers=["Timestamp", "Text", "Is Code", "Code Language", "Code Content"], |
|
|
datatype=["str", "str", "bool", "str", "str"], |
|
|
col_count=(5, "fixed"), |
|
|
interactive=True |
|
|
) |
|
|
|
|
|
update_steps_btn = gr.Button("Update Guide") |
|
|
|
|
|
|
|
|
submit_btn.click( |
|
|
fn=process_video, |
|
|
inputs=[video_url], |
|
|
outputs=[video_info_md, chapters_md, steps_md, steps_df, memory_info] |
|
|
) |
|
|
|
|
|
export_md_btn.click( |
|
|
fn=lambda steps, video_info: ui_components.create_export_markdown(steps, video_info), |
|
|
inputs=[steps_md, video_info_md], |
|
|
outputs=[export_md] |
|
|
).then( |
|
|
fn=lambda: True, |
|
|
outputs=[export_md] |
|
|
) |
|
|
|
|
|
update_steps_btn.click( |
|
|
fn=lambda df_data, video_info: { |
|
|
"steps": ui_components.dataframe_to_steps(df_data), |
|
|
"video_info": video_info |
|
|
}, |
|
|
inputs=[steps_df, video_info_md], |
|
|
outputs=[steps_md] |
|
|
) |
|
|
|
|
|
|
|
|
app.load(lambda: None, None, None, _js=ui_components.YOUTUBE_EMBED_JS) |
|
|
|
|
|
return app |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
app = create_interface() |
|
|
app.launch() |
|
|
|