youtube / app.py
Juna190825's picture
Update app.py
a69db60 verified
Raw
History Blame Contribute Delete
14.6 kB
import gradio as gr
import logging
import json
import subprocess
import sys
import tempfile
import os
from urllib.parse import urlparse, parse_qs
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Try to import docker, but handle gracefully if not available
try:
import docker
DOCKER_AVAILABLE = True
docker_client = docker.from_env()
docker_client.ping()
logger.info("โœ… Docker client connected successfully")
except ImportError:
DOCKER_AVAILABLE = False
docker_client = None
logger.warning("โš ๏ธ Docker module not available, using subprocess fallback")
except Exception as e:
DOCKER_AVAILABLE = False
docker_client = None
logger.warning(f"โš ๏ธ Docker connection failed: {e}")
# Fallback: Use subprocess with yt-dlp and yt-search-python installed directly
def search_youtube_subprocess(query, max_results=15):
"""Search YouTube using subprocess with yt-search-python"""
try:
logger.info(f"๐Ÿ” Subprocess search: {query}")
# Create Python script for searching
search_script = f'''
import json
from yt_search import YouTubeSearch
import sys
try:
results = YouTubeSearch('{query}', max_results={max_results}).to_dict()
videos = []
for video in results:
videos.append({{
'title': video.get('title', 'No Title'),
'url': 'https://youtube.com' + video.get('url_suffix', ''),
'thumbnail': video.get('thumbnails', [{{}}])[0].get('url', ''),
'channel': video.get('channel', 'Unknown'),
'duration': video.get('duration', 'N/A'),
'views': video.get('views', 'N/A')
}})
print(json.dumps(videos))
except Exception as e:
print(f"ERROR: {{e}}")
sys.exit(1)
'''
# Run the script
result = subprocess.run(
[sys.executable, "-c", search_script],
capture_output=True,
text=True,
timeout=30
)
if result.returncode != 0:
logger.error(f"Search error: {result.stderr}")
return [], f"Search error: {result.stderr}"
output = result.stdout.strip()
if output.startswith("ERROR:"):
return [], output
videos = json.loads(output)
logger.info(f"โœ… Found {len(videos)} videos")
return videos, None
except subprocess.TimeoutExpired:
return [], "Search timed out"
except Exception as e:
logger.error(f"โŒ Search error: {e}")
return [], str(e)
def play_youtube_subprocess(url):
"""Play YouTube video using subprocess with yt-dlp"""
if not url or not url.strip():
return "<p>โš ๏ธ Please enter a YouTube URL</p>"
try:
logger.info(f"๐ŸŽฌ Subprocess playback: {url}")
# Create Python script for playing
play_script = f'''
import json
import yt_dlp
import sys
import os
# Disable logging
import logging
logging.disable(logging.CRITICAL)
try:
ydl_opts = {{
'quiet': True,
'no_warnings': True,
'extract_flat': False,
'format': 'best[ext=mp4]',
'no_cache': True,
'ignoreerrors': True,
'geo_bypass': True,
}}
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
info = ydl.extract_info('{url}', download=False)
if info:
result = {{
'title': info.get('title', 'Video'),
'id': info.get('id', ''),
'url': info.get('url', ''),
'thumbnail': info.get('thumbnail', ''),
'duration': info.get('duration', 0),
'formats': []
}}
# Get formats
formats = info.get('formats', [])
video_url = None
# Try to get best quality mp4
for f in formats:
if f.get('ext') == 'mp4':
if not video_url or (f.get('height') and f.get('height') > 720):
video_url = f.get('url')
if f.get('height') and f.get('height') >= 720:
break
if not video_url and info.get('url'):
video_url = info.get('url')
result['video_url'] = video_url
print(json.dumps(result))
else:
print('ERROR: Could not extract video info')
sys.exit(1)
except Exception as e:
print(f"ERROR: {{e}}")
sys.exit(1)
'''
# Run the script
result = subprocess.run(
[sys.executable, "-c", play_script],
capture_output=True,
text=True,
timeout=60
)
if result.returncode != 0:
logger.error(f"Playback error: {result.stderr}")
return f"<p>โŒ Playback error: {result.stderr}</p>"
output = result.stdout.strip()
if output.startswith("ERROR:"):
return f"<p>โŒ {output}</p>"
video_data = json.loads(output)
video_url = video_data.get('video_url')
video_title = video_data.get('title', 'Video')
video_id = video_data.get('id', '')
if video_url:
logger.info(f"โœ… Playing: {video_title}")
logger.info(f"๐Ÿ“Š Video ID: {video_id}")
logger.info(f"๐Ÿ”— URL: {url}")
return f"""
<div id="video-player" style="background:#000;border-radius:8px;padding:15px;">
<h3 style="color:white;margin:0 0 10px 0;">โ–ถ๏ธ {video_title}</h3>
<video
controls
autoplay
style="width:100%;max-height:500px;border-radius:4px;"
src="{video_url}"
type="video/mp4"
>
Your browser does not support the video tag.
</video>
<div style="color:#aaa;margin-top:10px;font-size:12px;">
๐Ÿ“บ Playing: {url}
<br>โฑ๏ธ Duration: {video_data.get('duration', 0)} seconds
</div>
</div>
"""
else:
return f"""
<div style="padding:20px;background:#ffebee;border-radius:8px;">
<p>โŒ Could not get video URL</p>
<p>๐Ÿ’ก Try opening directly: <a href="{url}" target="_blank">{url}</a></p>
</div>
"""
except subprocess.TimeoutExpired:
return "<p>โฑ๏ธ Playback timed out. Try a different video.</p>"
except Exception as e:
logger.error(f"โŒ Playback error: {e}")
return f"""
<div style="padding:20px;background:#ffebee;border-radius:8px;">
<p>โŒ Error: {str(e)}</p>
<p>๐Ÿ’ก Try this URL directly: <a href="{url}" target="_blank">{url}</a></p>
</div>
"""
# Choose method based on availability
if DOCKER_AVAILABLE:
def search_youtube(query, max_results=15):
"""Search using Docker if available"""
try:
videos, error = search_youtube_docker(query, max_results)
if error:
logger.warning(f"Docker search failed, falling back to subprocess: {error}")
return search_youtube_subprocess(query, max_results)
return videos, None
except:
return search_youtube_subprocess(query, max_results)
def play_youtube(url):
"""Play using Docker if available"""
try:
result = play_youtube_docker(url)
# Check if result contains error
if "โŒ" in result and "Docker" in result:
logger.warning("Docker playback failed, falling back to subprocess")
return play_youtube_subprocess(url)
return result
except:
return play_youtube_subprocess(url)
else:
search_youtube = search_youtube_subprocess
play_youtube = play_youtube_subprocess
def create_video_grid(videos):
"""Create HTML grid from video results"""
if not videos:
return "<p>โŒ No videos found. Please try a different search term.</p>"
html = """
<style>
.video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 15px;
padding: 10px;
}
.video-card {
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.video-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
.video-card img {
width: 100%;
height: 150px;
object-fit: cover;
}
.video-card .info {
padding: 10px;
}
.video-card .title {
font-weight: bold;
font-size: 13px;
margin-bottom: 5px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
height: 36px;
}
.video-card .channel {
color: #666;
font-size: 11px;
margin-bottom: 3px;
}
.video-card .meta {
color: #888;
font-size: 10px;
}
.video-card .play-btn {
display: inline-block;
margin-top: 5px;
padding: 3px 10px;
background: #ff0000;
color: white;
border-radius: 4px;
text-decoration: none;
font-size: 11px;
border: none;
cursor: pointer;
}
.video-card .play-btn:hover {
background: #cc0000;
}
</style>
<div class="video-grid">
"""
for video in videos:
video_id = video['url'].split('watch?v=')[-1] if 'watch?v=' in video['url'] else video['url']
html += f"""
<div class="video-card">
<img src="{video['thumbnail']}" alt="{video['title']}" loading="lazy"
onclick="loadVideo('{video['url']}')">
<div class="info">
<div class="title">{video['title'][:50]}{'...' if len(video['title']) > 50 else ''}</div>
<div class="channel">๐Ÿ“บ {video['channel'][:25]}</div>
<div class="meta">โฑ๏ธ {video['duration']} | ๐Ÿ‘๏ธ {video['views']}</div>
<button class="play-btn" onclick="loadVideo('{video['url']}')">
โ–ถ๏ธ Play
</button>
</div>
</div>
"""
html += """
</div>
<script>
function loadVideo(url) {
// Set URL input
const urlInput = document.querySelector('#url_input input');
if (urlInput) {
urlInput.value = url;
urlInput.dispatchEvent(new Event('input', { bubbles: true }));
}
// Click play button
const playBtn = document.querySelector('button[data-testid="play_btn"]');
if (playBtn) {
playBtn.click();
}
// Scroll to player
const player = document.getElementById('video-player');
if (player) {
player.scrollIntoView({ behavior: 'smooth' });
}
}
</script>
"""
return html
def handle_search(query):
"""Handle search request"""
if not query or not query.strip():
return "<p>โš ๏ธ Please enter a search term.</p>"
videos, error = search_youtube(query, max_results=15)
if error:
return f"<p>โŒ Search error: {error}</p>"
return create_video_grid(videos)
# Gradio Interface
with gr.Blocks(title="YouTube Browser", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# ๐ŸŽฌ YouTube Browser
Search YouTube, browse thumbnails, and play videos directly in this Space!
""")
# Status indicator
if DOCKER_AVAILABLE:
gr.Markdown("โœ… **Docker mode**: Using Docker for isolation")
else:
gr.Markdown("โš ๏ธ **Subprocess mode**: Running directly (Docker not available)")
with gr.Row():
with gr.Column(scale=4):
search_input = gr.Textbox(
label="Search YouTube",
placeholder="Enter search term...",
value="",
container=False
)
with gr.Column(scale=1):
search_btn = gr.Button("๐Ÿ” Search", variant="primary", size="lg")
# Search results
results_display = gr.HTML(
label="Search Results",
value="<p>๐Ÿ” Search for videos to see results here!</p>"
)
gr.Markdown("---")
# Player section
gr.Markdown("### ๐ŸŽฅ Video Player")
with gr.Row():
with gr.Column(scale=4):
url_input = gr.Textbox(
label="YouTube URL",
placeholder="https://www.youtube.com/watch?v=...",
container=False,
elem_id="url_input"
)
with gr.Column(scale=1):
play_btn = gr.Button("โ–ถ๏ธ Play Video", variant="primary", size="lg", elem_id="play_btn")
player_display = gr.HTML(
label="Player",
value="<p>๐Ÿ’ก Search for a video or paste a URL above to play.</p>"
)
# Event handlers
search_btn.click(
fn=handle_search,
inputs=search_input,
outputs=results_display
)
search_input.submit(
fn=handle_search,
inputs=search_input,
outputs=results_display
)
play_btn.click(
fn=play_youtube,
inputs=url_input,
outputs=player_display
)
url_input.submit(
fn=play_youtube,
inputs=url_input,
outputs=player_display
)
gr.Markdown("""
### ๐Ÿ’ก How to use:
1. **Search**: Enter a search term and click "Search"
2. **Browse**: Scroll through thumbnails
3. **Watch**: Click "Play" on any video
4. **Direct**: Or paste any YouTube URL directly
### ๐Ÿ“Š Features:
- Search YouTube with thumbnails
- Play videos directly in the Space
- All URLs logged to console
- Click thumbnails to load videos
""")
demo.launch()