mirror of
https://github.com/ReviveMii/revivetube
synced 2025-04-29 12:39:25 -04:00
Update revivetube.py
This commit is contained in:
parent
64180e18a5
commit
630cbae7b0
1 changed files with 111 additions and 185 deletions
296
revivetube.py
296
revivetube.py
|
@ -18,21 +18,13 @@ import os
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import asyncio
|
import asyncio
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from quart import Quart, request, render_template_string, send_file, Response, abort, jsonify
|
from quart import Quart, request, render_template_string, send_file, Response, abort, jsonify
|
||||||
from helper import (
|
|
||||||
read_file,
|
|
||||||
get_video_duration_from_file,
|
|
||||||
format_duration,
|
|
||||||
get_file_size,
|
|
||||||
get_range,
|
|
||||||
get_api_key
|
|
||||||
)
|
|
||||||
app = Quart(__name__)
|
app = Quart(__name__)
|
||||||
|
|
||||||
VIDEO_FOLDER = "sigma/videos"
|
VIDEO_FOLDER = "sigma/videos"
|
||||||
|
@ -89,7 +81,6 @@ def get_folder_size(path):
|
||||||
@app.route("/thumbnail/<video_id>")
|
@app.route("/thumbnail/<video_id>")
|
||||||
async def get_thumbnail(video_id):
|
async def get_thumbnail(video_id):
|
||||||
thumbnail_url = f"https://img.youtube.com/vi/{video_id}/hqdefault.jpg"
|
thumbnail_url = f"https://img.youtube.com/vi/{video_id}/hqdefault.jpg"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(thumbnail_url) as response:
|
async with session.get(thumbnail_url) as response:
|
||||||
|
@ -99,14 +90,12 @@ async def get_thumbnail(video_id):
|
||||||
mimetype=response.headers.get("Content-Type", "image/jpeg"),
|
mimetype=response.headers.get("Content-Type", "image/jpeg"),
|
||||||
as_attachment=False,
|
as_attachment=False,
|
||||||
)
|
)
|
||||||
else:
|
return f"Failed to fetch thumbnail. Status: {response.status}", 500
|
||||||
return f"Failed to fetch thumbnail. Status: {response.status}", 500
|
|
||||||
except aiohttp.ClientError as e:
|
except aiohttp.ClientError as e:
|
||||||
return f"Error fetching thumbnail: {str(e)}", 500
|
return f"Error fetching thumbnail: {str(e)}", 500
|
||||||
|
|
||||||
async def get_video_comments(video_id, max_results=20):
|
async def get_video_comments(video_id, max_results=20):
|
||||||
api_key = await helper.get_api_key()
|
api_key = await get_api_key()
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"part": "snippet",
|
"part": "snippet",
|
||||||
"videoId": video_id,
|
"videoId": video_id,
|
||||||
|
@ -114,13 +103,11 @@ async def get_video_comments(video_id, max_results=20):
|
||||||
"maxResults": max_results,
|
"maxResults": max_results,
|
||||||
"order": "relevance"
|
"order": "relevance"
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get("https://www.googleapis.com/youtube/v3/commentThreads", params=params, timeout=3) as response:
|
async with session.get("https://www.googleapis.com/youtube/v3/commentThreads", params=params, timeout=3) as response:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
|
|
||||||
comments = []
|
comments = []
|
||||||
if "items" in data:
|
if "items" in data:
|
||||||
for item in data["items"]:
|
for item in data["items"]:
|
||||||
|
@ -131,9 +118,7 @@ async def get_video_comments(video_id, max_results=20):
|
||||||
"likeCount": snippet.get("likeCount", 0),
|
"likeCount": snippet.get("likeCount", 0),
|
||||||
"publishedAt": snippet["publishedAt"]
|
"publishedAt": snippet["publishedAt"]
|
||||||
})
|
})
|
||||||
|
|
||||||
return comments
|
return comments
|
||||||
|
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
||||||
print(f"Can't fetch Comments: {str(e)}")
|
print(f"Can't fetch Comments: {str(e)}")
|
||||||
return []
|
return []
|
||||||
|
@ -154,61 +139,54 @@ async def nohup():
|
||||||
async def index():
|
async def index():
|
||||||
query = request.args.get("query")
|
query = request.args.get("query")
|
||||||
results = None
|
results = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
if query:
|
url = f"https://invidious.materialio.us/api/v1/search?q={query}" if query else "https://invidious.materialio.us/api/v1/trending"
|
||||||
url = f"https://invidious.materialio.us/api/v1/search?q={query}"
|
|
||||||
else:
|
|
||||||
url = "https://invidious.materialio.us/api/v1/trending"
|
|
||||||
|
|
||||||
async with session.get(url, timeout=3) as response:
|
async with session.get(url, timeout=3) as response:
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
|
if response.status == 200 and isinstance(data, list):
|
||||||
|
if query:
|
||||||
|
results = []
|
||||||
|
for entry in data:
|
||||||
|
if entry.get("type") == "video":
|
||||||
|
results.append({
|
||||||
|
"type": "video",
|
||||||
|
"id": entry.get("videoId"),
|
||||||
|
"title": entry.get("title"),
|
||||||
|
"uploader": entry.get("author", "Unknown"),
|
||||||
|
"thumbnail": f"/thumbnail/{entry['videoId']}",
|
||||||
|
"viewCount": entry.get("viewCountText", "Unknown"),
|
||||||
|
"published": entry.get("publishedText", "Unknown"),
|
||||||
|
"duration": await format_duration(entry.get("lengthSeconds", 0))
|
||||||
|
})
|
||||||
|
elif entry.get("type") == "channel":
|
||||||
|
results.append({
|
||||||
|
"type": "channel",
|
||||||
|
"id": entry.get("authorId"),
|
||||||
|
"title": entry.get("author"),
|
||||||
|
"thumbnail": entry.get("authorThumbnails")[-1]["url"] if entry.get("authorThumbnails") else "/static/default_channel_thumbnail.jpg",
|
||||||
|
"subCount": entry.get("subCount", "Unknown"),
|
||||||
|
"videoCount": entry.get("videoCount", "Unknown")
|
||||||
|
})
|
||||||
|
return await render_template_string(SEARCH_TEMPLATE, results=results)
|
||||||
|
else:
|
||||||
|
results = [
|
||||||
|
{
|
||||||
|
"id": entry.get("videoId"),
|
||||||
|
"title": entry.get("title"),
|
||||||
|
"uploader": entry.get("author", "Unknown"),
|
||||||
|
"thumbnail": f"/thumbnail/{entry['videoId']}",
|
||||||
|
"viewCount": entry.get("viewCountText", "Unknown"),
|
||||||
|
"published": entry.get("publishedText", "Unknown"),
|
||||||
|
"duration": await format_duration(entry.get("lengthSeconds", 0))
|
||||||
|
}
|
||||||
|
for entry in data
|
||||||
|
if entry.get("videoId")
|
||||||
|
]
|
||||||
|
return await render_template_string(INDEX_TEMPLATE, results=results)
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError, ValueError) as e:
|
except (aiohttp.ClientError, asyncio.TimeoutError, ValueError) as e:
|
||||||
return "Can't parse Data. If this Issue persists, report it in the Discord Server.", 500
|
return "Can't parse Data. If this Issue persists, report it in the Discord Server.", 500
|
||||||
|
return "No Results or Error in the API.", 404
|
||||||
if response.status == 200 and isinstance(data, list):
|
|
||||||
if query:
|
|
||||||
results = []
|
|
||||||
for entry in data:
|
|
||||||
if entry.get("type") == "video":
|
|
||||||
results.append({
|
|
||||||
"type": "video",
|
|
||||||
"id": entry.get("videoId"),
|
|
||||||
"title": entry.get("title"),
|
|
||||||
"uploader": entry.get("author", "Unknown"),
|
|
||||||
"thumbnail": f"/thumbnail/{entry['videoId']}",
|
|
||||||
"viewCount": entry.get("viewCountText", "Unknown"),
|
|
||||||
"published": entry.get("publishedText", "Unknown"),
|
|
||||||
"duration": await helper.format_duration(entry.get("lengthSeconds", 0))
|
|
||||||
})
|
|
||||||
elif entry.get("type") == "channel":
|
|
||||||
results.append({
|
|
||||||
"type": "channel",
|
|
||||||
"id": entry.get("authorId"),
|
|
||||||
"title": entry.get("author"),
|
|
||||||
"thumbnail": entry.get("authorThumbnails")[-1]["url"] if entry.get("authorThumbnails") else "/static/default_channel_thumbnail.jpg",
|
|
||||||
"subCount": entry.get("subCount", "Unknown"),
|
|
||||||
"videoCount": entry.get("videoCount", "Unknown")
|
|
||||||
})
|
|
||||||
return await render_template_string(SEARCH_TEMPLATE, results=results)
|
|
||||||
else:
|
|
||||||
results = [
|
|
||||||
{
|
|
||||||
"id": entry.get("videoId"),
|
|
||||||
"title": entry.get("title"),
|
|
||||||
"uploader": entry.get("author", "Unknown"),
|
|
||||||
"thumbnail": f"/thumbnail/{entry['videoId']}",
|
|
||||||
"viewCount": entry.get("viewCountText", "Unknown"),
|
|
||||||
"published": entry.get("publishedText", "Unknown"),
|
|
||||||
"duration": await helper.format_duration(entry.get("lengthSeconds", 0))
|
|
||||||
}
|
|
||||||
for entry in data
|
|
||||||
if entry.get("videoId")
|
|
||||||
]
|
|
||||||
return await render_template_string(INDEX_TEMPLATE, results=results)
|
|
||||||
else:
|
|
||||||
return "No Results or Error in the API.", 404
|
|
||||||
|
|
||||||
@app.route("/watch", methods=["GET"])
|
@app.route("/watch", methods=["GET"])
|
||||||
async def watch():
|
async def watch():
|
||||||
|
@ -235,30 +213,21 @@ async def watch():
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
||||||
return f"Can't connect to Metadata-API: {str(e)}", 500
|
return f"Can't connect to Metadata-API: {str(e)}", 500
|
||||||
|
|
||||||
comments = []
|
comments = await get_video_comments(video_id)
|
||||||
try:
|
|
||||||
comments = await get_video_comments(video_id)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Video-Comments Error: {str(e)}")
|
|
||||||
comments = []
|
|
||||||
|
|
||||||
channel_logo_url = ""
|
channel_logo_url = ""
|
||||||
subscriber_count = "Unbekannt"
|
subscriber_count = "Unbekannt"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
channel_id = metadata['channelId']
|
channel_id = metadata['channelId']
|
||||||
api_url = f"https://api-superplaycounts.onrender.com/api/youtube-channel-counter/user/{channel_id}"
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(api_url, timeout=5) as channel_response:
|
async with session.get(f"https://api-superplaycounts.onrender.com/api/youtube-channel-counter/user/{channel_id}", timeout=5) as channel_response:
|
||||||
if channel_response.status == 200:
|
if channel_response.status == 200:
|
||||||
channel_data = await channel_response.json()
|
channel_data = await channel_response.json()
|
||||||
|
|
||||||
for stat in channel_data.get("statistics", []):
|
for stat in channel_data.get("statistics", []):
|
||||||
for count in stat.get("counts", []):
|
for count in stat.get("counts", []):
|
||||||
if count.get("value") == "subscribers":
|
if count.get("value") == "subscribers":
|
||||||
subscriber_count = count.get("count", "Unbekannt")
|
subscriber_count = count.get("count", "Unbekannt")
|
||||||
break
|
break
|
||||||
|
|
||||||
for stat in channel_data.get("statistics", []):
|
|
||||||
for user_info in stat.get("user", []):
|
for user_info in stat.get("user", []):
|
||||||
if user_info.get("value") == "pfp":
|
if user_info.get("value") == "pfp":
|
||||||
channel_logo_url = user_info.get("count", "").replace("https://", "http://")
|
channel_logo_url = user_info.get("count", "").replace("https://", "http://")
|
||||||
|
@ -269,33 +238,13 @@ async def watch():
|
||||||
comment_count = len(comments)
|
comment_count = len(comments)
|
||||||
|
|
||||||
if os.path.exists(video_mp4_path):
|
if os.path.exists(video_mp4_path):
|
||||||
video_duration = await helper.get_video_duration_from_file(video_flv_path)
|
video_duration = await get_video_duration_from_file(video_flv_path)
|
||||||
alert_script = ""
|
alert_script = ""
|
||||||
if video_duration > 420:
|
if video_duration > 420:
|
||||||
alert_script = """
|
alert_script = """<script type="text/javascript">alert("This Video is long. There is a chance that the Wii will not play the Video. Try a Video under 5 minutes.");</script>"""
|
||||||
<script type="text/javascript">
|
|
||||||
alert("This Video is long. There is a chance that the Wii will not play the Video. Try a Video under 5 minutes.");
|
|
||||||
</script>
|
|
||||||
"""
|
|
||||||
|
|
||||||
if is_wii and os.path.exists(video_flv_path):
|
if is_wii and os.path.exists(video_flv_path):
|
||||||
return await render_template_string(WATCH_WII_TEMPLATE + alert_script,
|
return await render_template_string(WATCH_WII_TEMPLATE + alert_script,
|
||||||
title=metadata['title'],
|
|
||||||
uploader=metadata['uploader'],
|
|
||||||
channelId=metadata['channelId'],
|
|
||||||
description=metadata['description'].replace("\n", "<br>"),
|
|
||||||
viewCount=metadata['viewCount'],
|
|
||||||
likeCount=metadata['likeCount'],
|
|
||||||
publishedAt=metadata['publishedAt'],
|
|
||||||
comments=comments,
|
|
||||||
commentCount=comment_count,
|
|
||||||
channel_logo_url=channel_logo_url,
|
|
||||||
subscriberCount=subscriber_count,
|
|
||||||
video_id=video_id,
|
|
||||||
video_flv=f"/sigma/videos/{video_id}.flv",
|
|
||||||
alert_message="")
|
|
||||||
|
|
||||||
return await render_template_string(WATCH_WII_TEMPLATE,
|
|
||||||
title=metadata['title'],
|
title=metadata['title'],
|
||||||
uploader=metadata['uploader'],
|
uploader=metadata['uploader'],
|
||||||
channelId=metadata['channelId'],
|
channelId=metadata['channelId'],
|
||||||
|
@ -311,6 +260,22 @@ async def watch():
|
||||||
video_flv=f"/sigma/videos/{video_id}.flv",
|
video_flv=f"/sigma/videos/{video_id}.flv",
|
||||||
alert_message="")
|
alert_message="")
|
||||||
|
|
||||||
|
return await render_template_string(WATCH_WII_TEMPLATE,
|
||||||
|
title=metadata['title'],
|
||||||
|
uploader=metadata['uploader'],
|
||||||
|
channelId=metadata['channelId'],
|
||||||
|
description=metadata['description'].replace("\n", "<br>"),
|
||||||
|
viewCount=metadata['viewCount'],
|
||||||
|
likeCount=metadata['likeCount'],
|
||||||
|
publishedAt=metadata['publishedAt'],
|
||||||
|
comments=comments,
|
||||||
|
commentCount=comment_count,
|
||||||
|
channel_logo_url=channel_logo_url,
|
||||||
|
subscriberCount=subscriber_count,
|
||||||
|
video_id=video_id,
|
||||||
|
video_flv=f"/sigma/videos/{video_id}.flv",
|
||||||
|
alert_message="")
|
||||||
|
|
||||||
if not os.path.exists(video_mp4_path):
|
if not os.path.exists(video_mp4_path):
|
||||||
if video_status[video_id]["status"] == "processing":
|
if video_status[video_id]["status"] == "processing":
|
||||||
asyncio.create_task(process_video(video_id))
|
asyncio.create_task(process_video(video_id))
|
||||||
|
@ -321,18 +286,16 @@ async def process_video(video_id):
|
||||||
video_flv_path = os.path.join(VIDEO_FOLDER, f"{video_id}.flv")
|
video_flv_path = os.path.join(VIDEO_FOLDER, f"{video_id}.flv")
|
||||||
try:
|
try:
|
||||||
video_status[video_id] = {"status": "downloading"}
|
video_status[video_id] = {"status": "downloading"}
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
temp_video_path = os.path.join(temp_dir, f"{video_id}.%(ext)s")
|
temp_video_path = os.path.join(temp_dir, f"{video_id}.%(ext)s")
|
||||||
command = [
|
subprocess.run([
|
||||||
"yt-dlp",
|
"yt-dlp",
|
||||||
"-o", temp_video_path,
|
"-o", temp_video_path,
|
||||||
"--cookies", "cookies.txt",
|
"--cookies", "cookies.txt",
|
||||||
"--proxy", "http://localhost:4000",
|
"--proxy", "http://localhost:4000",
|
||||||
"-f", "worstvideo+worstaudio",
|
"-f", "worstvideo+worstaudio",
|
||||||
f"https://youtube.com/watch?v={video_id}"
|
f"https://youtube.com/watch?v={video_id}"
|
||||||
]
|
], check=True)
|
||||||
subprocess.run(command, check=True)
|
|
||||||
|
|
||||||
downloaded_files = [f for f in os.listdir(temp_dir) if video_id in f]
|
downloaded_files = [f for f in os.listdir(temp_dir) if video_id in f]
|
||||||
if not downloaded_files:
|
if not downloaded_files:
|
||||||
|
@ -340,49 +303,42 @@ async def process_video(video_id):
|
||||||
return
|
return
|
||||||
|
|
||||||
downloaded_file = os.path.join(temp_dir, downloaded_files[0])
|
downloaded_file = os.path.join(temp_dir, downloaded_files[0])
|
||||||
|
|
||||||
if not downloaded_file.endswith(".mp4"):
|
if not downloaded_file.endswith(".mp4"):
|
||||||
video_status[video_id] = {"status": "converting"}
|
video_status[video_id] = {"status": "converting"}
|
||||||
subprocess.run(
|
subprocess.run([
|
||||||
[
|
"ffmpeg",
|
||||||
"ffmpeg",
|
"-y",
|
||||||
"-y",
|
"-i", downloaded_file,
|
||||||
"-i", downloaded_file,
|
"-c:v", "libx264",
|
||||||
"-c:v", "libx264",
|
"-crf", "51",
|
||||||
"-crf", "51",
|
"-c:a", "aac",
|
||||||
"-c:a", "aac",
|
"-strict", "experimental",
|
||||||
"-strict", "experimental",
|
"-preset", "ultrafast",
|
||||||
"-preset", "ultrafast",
|
"-b:a", "64k",
|
||||||
"-b:a", "64k",
|
"-movflags", "+faststart",
|
||||||
"-movflags", "+faststart",
|
"-vf", "scale=854:480",
|
||||||
"-vf", "scale=854:480",
|
video_mp4_path
|
||||||
video_mp4_path
|
], check=True)
|
||||||
],
|
|
||||||
check=True
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
shutil.copy(downloaded_file, video_mp4_path)
|
shutil.copy(downloaded_file, video_mp4_path)
|
||||||
|
|
||||||
if not os.path.exists(video_flv_path):
|
if not os.path.exists(video_flv_path):
|
||||||
video_status[video_id] = {"status": "converting for Wii"}
|
video_status[video_id] = {"status": "converting for Wii"}
|
||||||
subprocess.run(
|
subprocess.run([
|
||||||
[
|
"ffmpeg",
|
||||||
"ffmpeg",
|
"-y",
|
||||||
"-y",
|
"-i", video_mp4_path,
|
||||||
"-i", video_mp4_path,
|
"-ar", "22050",
|
||||||
"-ar", "22050",
|
"-f", "flv",
|
||||||
"-f", "flv",
|
"-s", "320x240",
|
||||||
"-s", "320x240",
|
"-ab", "32k",
|
||||||
"-ab", "32k",
|
"-preset", "ultrafast",
|
||||||
"-preset", "ultrafast",
|
"-crf", "51",
|
||||||
"-crf", "51",
|
"-filter:v", "fps=fps=15",
|
||||||
"-filter:v", "fps=fps=15",
|
video_flv_path
|
||||||
video_flv_path
|
], check=True)
|
||||||
],
|
|
||||||
check=True)
|
|
||||||
|
|
||||||
video_status[video_id] = {"status": "complete", "url": f"/sigma/videos/{video_id}.mp4"}
|
video_status[video_id] = {"status": "complete", "url": f"/sigma/videos/{video_id}.mp4"}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
video_status[video_id] = {"status": "error", "message": str(e)}
|
video_status[video_id] = {"status": "error", "message": str(e)}
|
||||||
|
|
||||||
|
@ -392,56 +348,40 @@ async def check_status(video_id):
|
||||||
|
|
||||||
@app.route("/video_metadata/<video_id>")
|
@app.route("/video_metadata/<video_id>")
|
||||||
async def video_metadata(video_id):
|
async def video_metadata(video_id):
|
||||||
api_key = await helper.get_api_key()
|
api_key = await get_api_key()
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"part": "snippet,statistics",
|
"part": "snippet,statistics",
|
||||||
"id": video_id,
|
"id": video_id,
|
||||||
"key": api_key
|
"key": api_key
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.get(YOUTUBE_API_URL, params=params, timeout=1) as response:
|
async with session.get(YOUTUBE_API_URL, params=params, timeout=1) as response:
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
data = await response.json()
|
data = await response.json()
|
||||||
|
|
||||||
if "items" not in data or len(data["items"]) == 0:
|
if "items" not in data or len(data["items"]) == 0:
|
||||||
return f"The Video with ID {video_id} was not found.", 404
|
return f"The Video with ID {video_id} was not found.", 404
|
||||||
|
|
||||||
video_data = data["items"][0]
|
video_data = data["items"][0]
|
||||||
title = video_data["snippet"]["title"]
|
|
||||||
description = video_data["snippet"]["description"]
|
|
||||||
uploader = video_data["snippet"]["channelTitle"]
|
|
||||||
channel_id = video_data["snippet"]["channelId"]
|
|
||||||
view_count = video_data["statistics"].get("viewCount", "Unknown")
|
|
||||||
like_count = video_data["statistics"].get("likeCount", "Unknown")
|
|
||||||
dislike_count = video_data["statistics"].get("dislikeCount", "Unknown")
|
|
||||||
published_at = video_data["snippet"].get("publishedAt", "Unknown")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": title,
|
"title": video_data["snippet"]["title"],
|
||||||
"uploader": uploader,
|
"uploader": video_data["snippet"]["channelTitle"],
|
||||||
"channelId": channel_id,
|
"channelId": video_data["snippet"]["channelId"],
|
||||||
"description": description,
|
"description": video_data["snippet"]["description"],
|
||||||
"viewCount": view_count,
|
"viewCount": video_data["statistics"].get("viewCount", "Unknown"),
|
||||||
"likeCount": like_count,
|
"likeCount": video_data["statistics"].get("likeCount", "Unknown"),
|
||||||
"dislikeCount": dislike_count,
|
"dislikeCount": video_data["statistics"].get("dislikeCount", "Unknown"),
|
||||||
"publishedAt": published_at
|
"publishedAt": video_data["snippet"].get("publishedAt", "Unknown")
|
||||||
}
|
}
|
||||||
|
|
||||||
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
except (aiohttp.ClientError, asyncio.TimeoutError) as e:
|
||||||
return f"API Error: {str(e)}", 500
|
return f"API Error: {str(e)}", 500
|
||||||
|
|
||||||
@app.route("/<path:filename>")
|
@app.route("/<path:filename>")
|
||||||
async def serve_video(filename):
|
async def serve_video(filename):
|
||||||
file_path = os.path.join(filename)
|
file_path = os.path.join(filename)
|
||||||
|
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
return "File not found.", 404
|
return "File not found.", 404
|
||||||
|
|
||||||
file_size = await helper.get_file_size(file_path)
|
file_size = await get_file_size(file_path)
|
||||||
|
|
||||||
range_header = request.headers.get('Range', None)
|
range_header = request.headers.get('Range', None)
|
||||||
if range_header:
|
if range_header:
|
||||||
byte_range = range_header.strip().split('=')[1]
|
byte_range = range_header.strip().split('=')[1]
|
||||||
|
@ -452,7 +392,7 @@ async def serve_video(filename):
|
||||||
if start_byte >= file_size or end_byte >= file_size:
|
if start_byte >= file_size or end_byte >= file_size:
|
||||||
abort(416)
|
abort(416)
|
||||||
|
|
||||||
data = await helper.get_range(file_path, (start_byte, end_byte))
|
data = await get_range(file_path, (start_byte, end_byte))
|
||||||
content_range = f"bytes {start_byte}-{end_byte}/{file_size}"
|
content_range = f"bytes {start_byte}-{end_byte}/{file_size}"
|
||||||
|
|
||||||
response = Response(
|
response = Response(
|
||||||
|
@ -470,7 +410,6 @@ async def serve_video(filename):
|
||||||
@app.route('/channel', methods=['GET'])
|
@app.route('/channel', methods=['GET'])
|
||||||
async def channel_m():
|
async def channel_m():
|
||||||
channel_id = request.args.get('channel_id', None)
|
channel_id = request.args.get('channel_id', None)
|
||||||
|
|
||||||
if not channel_id:
|
if not channel_id:
|
||||||
return "Channel ID is required.", 400
|
return "Channel ID is required.", 400
|
||||||
|
|
||||||
|
@ -479,35 +418,24 @@ async def channel_m():
|
||||||
'extract_flat': True,
|
'extract_flat': True,
|
||||||
'playlistend': 20,
|
'playlistend': 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
url = f"https://www.youtube.com/channel/{channel_id}/videos"
|
info = ydl.extract_info(f"https://www.youtube.com/channel/{channel_id}/videos", download=False)
|
||||||
info = ydl.extract_info(url, download=False)
|
|
||||||
|
|
||||||
if 'entries' not in info:
|
if 'entries' not in info:
|
||||||
return "No videos found.", 404
|
return "No videos found.", 404
|
||||||
|
|
||||||
channel_name = info.get('uploader', 'Unknown')
|
channel_name = info.get('uploader', 'Unknown')
|
||||||
|
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
invidious_url = f"https://invidious.materialio.us/channel/{channel_id}"
|
async with session.get(f"https://invidious.materialio.us/channel/{channel_id}", timeout=10) as response:
|
||||||
async with session.get(invidious_url, timeout=10) as response:
|
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
return "Failed to fetch channel page.", 500
|
return "Failed to fetch channel page.", 500
|
||||||
|
soup = BeautifulSoup(await response.text(), "html.parser")
|
||||||
html = await response.text()
|
|
||||||
soup = BeautifulSoup(html, "html.parser")
|
|
||||||
profile_div = soup.find(class_="channel-profile")
|
profile_div = soup.find(class_="channel-profile")
|
||||||
|
channel_picture = ""
|
||||||
if profile_div:
|
if profile_div:
|
||||||
img_tag = profile_div.find("img")
|
img_tag = profile_div.find("img")
|
||||||
if img_tag and "src" in img_tag.attrs:
|
if img_tag and "src" in img_tag.attrs:
|
||||||
channel_picture = "http://api.allorigins.win/raw?url=http://invidious.materialio.us" + img_tag["src"]
|
channel_picture = f"http://api.allorigins.win/raw?url=http://invidious.materialio.us{img_tag['src']}"
|
||||||
else:
|
|
||||||
channel_picture = ""
|
|
||||||
else:
|
|
||||||
channel_picture = ""
|
|
||||||
|
|
||||||
results = [
|
results = [
|
||||||
{
|
{
|
||||||
|
@ -519,14 +447,12 @@ async def channel_m():
|
||||||
}
|
}
|
||||||
for video in info['entries']
|
for video in info['entries']
|
||||||
]
|
]
|
||||||
|
|
||||||
return await render_template_string(
|
return await render_template_string(
|
||||||
CHANNEL_TEMPLATE,
|
CHANNEL_TEMPLATE,
|
||||||
results=results,
|
results=results,
|
||||||
channel_name=channel_name,
|
channel_name=channel_name,
|
||||||
channel_picture=channel_picture
|
channel_picture=channel_picture
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"An error occurred: {str(e)}", 500
|
return f"An error occurred: {str(e)}", 500
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue