Spaces:
Running
Running
#!/usr/bin/python3 | |
# -*- coding: utf-8 -*- | |
import json | |
import re | |
import gradio as gr | |
from jinja2 import Template | |
import tempfile | |
html_content = """ | |
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>YouTube视频连续播放器</title> | |
<style> | |
body { | |
font-family: Arial, sans-serif; | |
max-width: 1600px; | |
margin: 0 auto; | |
padding: 20px; | |
text-align: center; | |
} | |
#player { | |
width: 1200px; | |
height: 675px; | |
# aspect-ratio: 16/9; | |
} | |
#playlist { | |
text-align: left; | |
margin: 20px 0; | |
} | |
.current { | |
font-weight: bold; | |
color: #ff0000; | |
} | |
</style> | |
</head> | |
<body> | |
<h1>YouTube视频连续播放器</h1> | |
<div id="player"></div> | |
<div id="playlist"></div> | |
<div id="status">准备播放...</div> | |
<script> | |
// 获取所有视频ID | |
const videoIds = {{ video_ids }}; | |
let currentVideoIndex = 0; | |
let player; | |
// 加载YouTube IFrame API | |
function onYouTubeIframeAPIReady() { | |
player = new YT.Player('player', { | |
height: '390', | |
width: '640', | |
events: { | |
'onReady': onPlayerReady, | |
'onStateChange': onPlayerStateChange | |
} | |
}); | |
} | |
// 播放器准备就绪 | |
function onPlayerReady(event) { | |
loadAndPlayVideo(currentVideoIndex); | |
updatePlaylist(); | |
} | |
// 播放器状态变化 | |
function onPlayerStateChange(event) { | |
if (event.data === YT.PlayerState.ENDED) { | |
// 当前视频结束,播放下一个 | |
currentVideoIndex++; | |
if (currentVideoIndex < videoIds.length) { | |
loadAndPlayVideo(currentVideoIndex); | |
updatePlaylist(); | |
} else { | |
document.getElementById('status').textContent = '所有视频播放完毕'; | |
} | |
} | |
} | |
// 加载并播放指定索引的视频 | |
function loadAndPlayVideo(index) { | |
if (index >= 0 && index < videoIds.length) { | |
player.loadVideoById(videoIds[index]); | |
document.getElementById('status').textContent = `正在播放: 视频 ${index + 1}/${videoIds.length}`; | |
} | |
} | |
// 更新播放列表显示 | |
function updatePlaylist() { | |
const playlistElement = document.getElementById('playlist'); | |
playlistElement.innerHTML = '<h3>播放列表:</h3>'; | |
videoIds.forEach((id, index) => { | |
const item = document.createElement('div'); | |
item.textContent = `视频 ${index + 1}: https://youtu.be/${id}`; | |
if (index === currentVideoIndex) { | |
item.className = 'current'; | |
} | |
playlistElement.appendChild(item); | |
}); | |
} | |
// 动态加载YouTube IFrame API | |
const tag = document.createElement('script'); | |
tag.src = "https://www.youtube.com/iframe_api"; | |
const firstScriptTag = document.getElementsByTagName('script')[0]; | |
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag); | |
</script> | |
</body> | |
</html> | |
""" | |
def make_file(content: str): | |
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.html') as tmp: | |
tmp.write(content) | |
tmp_path = tmp.name | |
return tmp_path | |
def get_video_id(url: str): | |
""" | |
从各种YouTube URL格式中提取视频ID | |
支持的格式包括: | |
- 普通URL: https://www.youtube.com/watch?v=dQw4w9WgXcQ | |
- 短链接: https://youtu.be/dQw4w9WgXcQ | |
- 嵌入链接: https://www.youtube.com/embed/dQw4w9WgXcQ | |
- 带时间戳: https://youtu.be/dQw4w9WgXcQ?t=120 | |
- 带其他参数: https://www.youtube.com/watch?v=dQw4w9WgXcQ&feature=share | |
- 移动端链接: https://m.youtube.com/watch?v=dQw4w9WgXcQ | |
- 无协议链接: youtube.com/watch?v=dQw4w9WgXcQ | |
- 仅视频ID: dQw4w9WgXcQ | |
""" | |
pattern = r'(?:youtube\.com\/(?:watch\?v=|embed\/|shorts\/|live\/)|youtu\.be\/)([^"&?\/\s]{11})' | |
match = re.search(pattern, url) | |
if match is None: | |
return None | |
video_id = match.group(1) | |
return video_id | |
def when_click_make_html_button(playlist: str): | |
urls = [url.strip() for url in playlist.split("\n") if url.strip()] | |
video_ids = [get_video_id(url) for url in urls] | |
video_ids = [video_id for video_id in video_ids if video_id is not None] | |
video_ids_ = json.dumps(video_ids, ensure_ascii=False) | |
template = Template(html_content) | |
variables = { | |
"video_ids": video_ids_ | |
} | |
html_content_ = template.render(variables) | |
filename = make_file(html_content_) | |
return filename | |
def get_youtube_player_tab(): | |
with gr.TabItem("youtube_player"): | |
with gr.Row(): | |
with gr.Column(scale=5): | |
yp_playlist = gr.Textbox( | |
label="playlist", | |
value="https://www.youtube.com/watch?v=tvRNE-ULe94\nhttps://www.youtube.com/watch?v=VQmaDgmyIBc\nhttps://www.youtube.com/watch?v=e_sFLaQUN3k\nhttps://www.youtube.com/watch?v=-VDYnk0jM8Q", | |
placeholder="每行输入一个YouTube视频链接\n例如:\nhttps://www.youtube.com/watch?v=dQw4w9WgXcQ\nhttps://youtu.be/9bZkp7q19f0", | |
lines=10, | |
) | |
yp_make_html_button = gr.Button("make_html") | |
with gr.Column(scale=5): | |
yp_video_output = gr.File(label="play_html_file") | |
yp_make_html_button.click( | |
fn=when_click_make_html_button, | |
inputs=[yp_playlist], | |
outputs=[yp_video_output] | |
) | |
return locals() | |
if __name__ == "__main__": | |
with gr.Blocks() as block: | |
fs_components = get_youtube_player_tab() | |
block.launch() | |