Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| import requests | |
| import urllib.parse | |
| # 页面配置 | |
| st.set_page_config( | |
| page_title="视频搜索 - 轻松找视频", | |
| page_icon="🎬", | |
| layout="wide" | |
| ) | |
| # 自定义CSS - 老年人友好设计 | |
| st.markdown(""" | |
| <style> | |
| /* 整体字体放大 */ | |
| .stApp { | |
| font-size: 20px !important; | |
| } | |
| /* 标题样式 */ | |
| h1 { | |
| font-size: 42px !important; | |
| color: #d97706 !important; | |
| text-align: center; | |
| padding: 20px 0; | |
| } | |
| h2 { | |
| font-size: 28px !important; | |
| color: #92400e !important; | |
| } | |
| h3 { | |
| font-size: 24px !important; | |
| color: #b45309 !important; | |
| } | |
| /* 输入框样式 */ | |
| .stTextInput > div > div > input { | |
| font-size: 24px !important; | |
| padding: 15px !important; | |
| border-radius: 15px !important; | |
| border: 3px solid #fbbf24 !important; | |
| } | |
| .stTextInput > div > div > input:focus { | |
| border-color: #d97706 !important; | |
| box-shadow: 0 0 10px rgba(217, 119, 6, 0.3) !important; | |
| } | |
| /* 按钮样式 */ | |
| .stButton > button { | |
| font-size: 22px !important; | |
| padding: 15px 40px !important; | |
| border-radius: 15px !important; | |
| background-color: #f59e0b !important; | |
| color: white !important; | |
| border: none !important; | |
| min-height: 60px !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .stButton > button:hover { | |
| background-color: #d97706 !important; | |
| transform: scale(1.02) !important; | |
| } | |
| /* 视频卡片样式 */ | |
| .video-card { | |
| background: linear-gradient(135deg, #fffbeb 0%, #fef3c7 100%); | |
| border-radius: 20px; | |
| padding: 20px; | |
| margin: 15px 0; | |
| border: 2px solid #fbbf24; | |
| box-shadow: 0 4px 15px rgba(251, 191, 36, 0.2); | |
| transition: all 0.3s ease; | |
| } | |
| .video-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 8px 25px rgba(251, 191, 36, 0.3); | |
| } | |
| .video-title { | |
| font-size: 22px !important; | |
| font-weight: bold; | |
| color: #92400e; | |
| margin-bottom: 10px; | |
| line-height: 1.4; | |
| } | |
| .video-channel { | |
| font-size: 18px; | |
| color: #b45309; | |
| margin-bottom: 8px; | |
| } | |
| .video-stats { | |
| font-size: 16px; | |
| color: #78716c; | |
| } | |
| /* 快捷按钮样式 */ | |
| .quick-btn { | |
| display: inline-block; | |
| padding: 12px 20px; | |
| margin: 5px; | |
| background-color: #fef3c7; | |
| border: 2px solid #fbbf24; | |
| border-radius: 25px; | |
| font-size: 18px; | |
| cursor: pointer; | |
| transition: all 0.3s ease; | |
| } | |
| .quick-btn:hover { | |
| background-color: #fde68a; | |
| } | |
| /* 提示文字 */ | |
| .tip-text { | |
| font-size: 18px; | |
| color: #78716c; | |
| text-align: center; | |
| padding: 20px; | |
| background-color: #fffbeb; | |
| border-radius: 15px; | |
| margin: 20px 0; | |
| } | |
| /* 链接样式 */ | |
| a { | |
| color: #d97706 !important; | |
| text-decoration: none !important; | |
| font-weight: bold; | |
| } | |
| a:hover { | |
| color: #b45309 !important; | |
| text-decoration: underline !important; | |
| } | |
| /* 页脚 */ | |
| .footer { | |
| text-align: center; | |
| padding: 30px; | |
| color: #78716c; | |
| font-size: 16px; | |
| } | |
| /* 搜索结果数量 */ | |
| .result-count { | |
| font-size: 20px; | |
| color: #92400e; | |
| text-align: center; | |
| padding: 15px; | |
| background-color: #fef3c7; | |
| border-radius: 10px; | |
| margin: 20px 0; | |
| } | |
| /* 平台选择器 */ | |
| .stSelectbox > div > div { | |
| font-size: 20px !important; | |
| } | |
| /* 分隔线 */ | |
| hr { | |
| border: none; | |
| height: 3px; | |
| background: linear-gradient(90deg, #fbbf24, #f59e0b, #fbbf24); | |
| margin: 30px 0; | |
| border-radius: 2px; | |
| } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # 标题 | |
| st.markdown("# 🎬 视频搜索") | |
| st.markdown("<p style='text-align: center; font-size: 22px; color: #78716c;'>输入想看的内容,帮您找到好看的视频!</p>", unsafe_allow_html=True) | |
| st.markdown("<p style='text-align: center; font-size: 14px;'><a href='https://huggingface.co/spaces/akhaliq/anycoder' target='_blank'>Built with anycoder</a></p>", unsafe_allow_html=True) | |
| st.markdown("---") | |
| # 初始化session state | |
| if 'search_query' not in st.session_state: | |
| st.session_state.search_query = "" | |
| if 'search_results' not in st.session_state: | |
| st.session_state.search_results = [] | |
| if 'searched' not in st.session_state: | |
| st.session_state.searched = False | |
| # 搜索函数 - 使用Invidious API (YouTube的开源前端) | |
| def search_videos(query, max_results=12): | |
| """搜索视频""" | |
| if not query or not query.strip(): | |
| return [] | |
| # 使用多个Invidious实例作为备选 | |
| invidious_instances = [ | |
| "https://vid.puffyan.us", | |
| "https://invidious.snopyta.org", | |
| "https://yewtu.be", | |
| "https://invidious.kavin.rocks", | |
| ] | |
| results = [] | |
| for instance in invidious_instances: | |
| try: | |
| # 搜索API | |
| search_url = f"{instance}/api/v1/search" | |
| params = { | |
| "q": query, | |
| "type": "video", | |
| "sort_by": "relevance" | |
| } | |
| response = requests.get(search_url, params=params, timeout=10) | |
| if response.status_code == 200: | |
| data = response.json() | |
| for item in data[:max_results]: | |
| if item.get("type") == "video": | |
| video_id = item.get("videoId", "") | |
| # 格式化观看次数 | |
| view_count = item.get("viewCount", 0) | |
| if view_count >= 100000000: | |
| views_str = f"{view_count // 100000000}亿次观看" | |
| elif view_count >= 10000: | |
| views_str = f"{view_count // 10000}万次观看" | |
| else: | |
| views_str = f"{view_count}次观看" | |
| # 格式化时长 | |
| length_seconds = item.get("lengthSeconds", 0) | |
| if length_seconds >= 3600: | |
| hours = length_seconds // 3600 | |
| minutes = (length_seconds % 3600) // 60 | |
| duration_str = f"{hours}小时{minutes}分钟" | |
| else: | |
| minutes = length_seconds // 60 | |
| seconds = length_seconds % 60 | |
| duration_str = f"{minutes}分{seconds}秒" | |
| results.append({ | |
| "title": item.get("title", "未知标题"), | |
| "video_id": video_id, | |
| "channel": item.get("author", "未知频道"), | |
| "views": views_str, | |
| "duration": duration_str, | |
| "thumbnail": f"https://img.youtube.com/vi/{video_id}/mqdefault.jpg", | |
| "url": f"https://www.youtube.com/watch?v={video_id}", | |
| "embed_url": f"https://www.youtube.com/embed/{video_id}" | |
| }) | |
| if results: | |
| return results | |
| except Exception as e: | |
| continue | |
| return results | |
| # 生成模拟搜索结果(当API不可用时的备选方案) | |
| def generate_search_links(query): | |
| """生成各平台的搜索链接""" | |
| encoded_query = urllib.parse.quote(query) | |
| return { | |
| "YouTube": f"https://www.youtube.com/results?search_query={encoded_query}", | |
| "哔哩哔哩": f"https://search.bilibili.com/all?keyword={encoded_query}", | |
| "抖音": f"https://www.douyin.com/search/{encoded_query}", | |
| "西瓜视频": f"https://www.ixigua.com/search/{encoded_query}", | |
| "腾讯视频": f"https://v.qq.com/x/search/?q={encoded_query}", | |
| "优酷": f"https://so.youku.com/search_video/q_{encoded_query}", | |
| "爱奇艺": f"https://so.iqiyi.com/so/q_{encoded_query}" | |
| } | |
| # 搜索区域 | |
| col1, col2, col3 = st.columns([1, 4, 1]) | |
| with col2: | |
| # 搜索输入框 | |
| search_input = st.text_input( | |
| "🔍 输入您想看的内容", | |
| value=st.session_state.search_query, | |
| placeholder="比如:太极拳教学、京剧、健康养生...", | |
| key="search_input" | |
| ) | |
| # 搜索按钮 | |
| col_btn1, col_btn2, col_btn3 = st.columns([1, 2, 1]) | |
| with col_btn2: | |
| search_clicked = st.button("🔍 开始搜索", use_container_width=True, type="primary") | |
| # 快捷搜索标签 | |
| st.markdown("### 💡 热门推荐(点击即可搜索)") | |
| # 快捷搜索选项 | |
| quick_searches = [ | |
| ("🎭 京剧表演", "京剧经典唱段"), | |
| ("🥋 太极拳", "太极拳教学"), | |
| ("🌿 养生保健", "老年人养生保健"), | |
| ("🍳 家常菜", "家常菜做法教程"), | |
| ("🎵 老歌经典", "经典老歌怀旧"), | |
| ("💃 广场舞", "广场舞教学"), | |
| ("🎨 书法教学", "书法入门教程"), | |
| ("🌸 园艺种花", "阳台种花教程"), | |
| ("♟️ 象棋教学", "中国象棋教程"), | |
| ("📺 相声小品", "经典相声小品"), | |
| ("🧘 健身操", "中老年健身操"), | |
| ("🎹 戏曲欣赏", "经典戏曲合集") | |
| ] | |
| # 创建快捷搜索按钮网格 | |
| cols = st.columns(4) | |
| for idx, (label, query) in enumerate(quick_searches): | |
| with cols[idx % 4]: | |
| if st.button(label, key=f"quick_{idx}", use_container_width=True): | |
| st.session_state.search_query = query | |
| st.session_state.searched = True | |
| st.rerun() | |
| st.markdown("---") | |
| # 执行搜索 | |
| if search_clicked and search_input: | |
| st.session_state.search_query = search_input | |
| st.session_state.searched = True | |
| # 显示搜索结果 | |
| if st.session_state.searched and st.session_state.search_query: | |
| query = st.session_state.search_query | |
| st.markdown(f"## 🎯 "{query}" 的搜索结果") | |
| # 显示加载状态 | |
| with st.spinner("🔍 正在为您搜索视频,请稍候..."): | |
| results = search_videos(query) | |
| if results: | |
| st.markdown(f"<div class='result-count'>✨ 找到 {len(results)} 个相关视频</div>", unsafe_allow_html=True) | |
| # 显示视频结果 | |
| for i in range(0, len(results), 3): | |
| cols = st.columns(3) | |
| for j, col in enumerate(cols): | |
| if i + j < len(results): | |
| video = results[i + j] | |
| with col: | |
| st.markdown(f""" | |
| <div class='video-card'> | |
| <img src="{video['thumbnail']}" style="width: 100%; border-radius: 10px; margin-bottom: 10px;"> | |
| <div class='video-title'>{video['title'][:50]}{'...' if len(video['title']) > 50 else ''}</div> | |
| <div class='video-channel'>📺 {video['channel']}</div> | |
| <div class='video-stats'>👁️ {video['views']} | ⏱️ {video['duration']}</div> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| if st.button(f"▶️ 观看视频", key=f"watch_{i+j}", use_container_width=True): | |
| st.markdown(f"[点击这里打开视频]({video['url']})") | |
| # 嵌入视频播放器 | |
| st.video(video['url']) | |
| # 显示各平台搜索链接 | |
| st.markdown("---") | |
| st.markdown("### 🌐 在其他平台搜索") | |
| st.markdown("<p style='font-size: 18px; color: #78716c;'>点击下方按钮,在对应平台搜索更多视频</p>", unsafe_allow_html=True) | |
| search_links = generate_search_links(query) | |
| platform_cols = st.columns(4) | |
| platforms = list(search_links.items()) | |
| for idx, (platform, url) in enumerate(platforms): | |
| with platform_cols[idx % 4]: | |
| st.markdown(f""" | |
| <a href="{url}" target="_blank" style=" | |
| display: block; | |
| text-align: center; | |
| padding: 15px; | |
| margin: 10px 0; | |
| background: linear-gradient(135deg, #fef3c7, #fde68a); | |
| border-radius: 15px; | |
| border: 2px solid #fbbf24; | |
| color: #92400e !important; | |
| font-size: 20px; | |
| font-weight: bold; | |
| text-decoration: none; | |
| transition: all 0.3s ease; | |
| "> | |
| 🔗 {platform} | |
| </a> | |
| """, unsafe_allow_html=True) | |
| else: | |
| # 未搜索时显示提示 | |
| st.markdown(""" | |
| <div class='tip-text'> | |
| <p style='font-size: 24px; margin-bottom: 15px;'>👆 在上方输入框中输入您想看的内容</p> | |
| <p style='font-size: 20px; margin-bottom: 10px;'>或者点击"热门推荐"中的标签快速搜索</p> | |
| <p style='font-size: 18px; color: #a8a29e;'>例如:太极拳、京剧、养生保健、广场舞...</p> | |
| </div> | |
| """, unsafe_allow_html=True) | |
| # 显示使用说明 | |
| with st.expander("📖 使用说明(点击展开)", expanded=False): | |
| st.markdown(""" | |
| ### 🎯 如何使用视频搜索? | |
| **方法一:直接搜索** | |
| 1. 在搜索框中输入您想看的内容 | |
| 2. 点击"开始搜索"按钮 | |
| 3. 等待搜索结果出现 | |
| 4. 点击视频卡片下方的"观看视频"按钮 | |
| **方法二:快捷搜索** | |
| 1. 在"热门推荐"中找到感兴趣的标签 | |
| 2. 直接点击标签即可搜索 | |
| ### 💡 搜索小技巧 | |
| - 搜索词越具体,结果越准确 | |
| - 可以搜索:教学、表演、讲解、合集等 | |
| - 如果找不到想要的,试试换个说法 | |
| ### 🌐 支持的视频平台 | |
| - YouTube(油管) | |
| - 哔哩哔哩(B站) | |
| - 抖音 | |
| - 西瓜视频 | |
| - 腾讯视频 | |
| - 优酷 | |
| - 爱奇艺 | |
| """) | |
| # 页脚 | |
| st.markdown("---") | |
| st.markdown(""" | |
| <div class='footer'> | |
| <p>🎬 视频搜索 - 轻松找到您想看的视频</p> | |
| <p style='font-size: 14px; color: #a8a29e;'>温馨提示:观看视频时注意休息眼睛,每看30分钟休息5分钟 👀</p> | |
| <p style='font-size: 14px;'><a href='https://huggingface.co/spaces/akhaliq/anycoder' target='_blank'>Built with anycoder</a></p> | |
| </div> | |
| """, unsafe_allow_html=True) |