mirror of
https://github.com/ReviveMii/revivetube
synced 2025-04-29 12:39:25 -04:00
revivetube update
This commit is contained in:
parent
e9c12a7ef6
commit
9bef2e5f6f
6 changed files with 349 additions and 26 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -4,3 +4,4 @@ token.txt
|
||||||
__pycache__
|
__pycache__
|
||||||
sigma
|
sigma
|
||||||
nohup.out
|
nohup.out
|
||||||
|
cookies.txt
|
|
@ -1,3 +1,4 @@
|
||||||
Flask
|
Flask
|
||||||
Requests
|
Requests
|
||||||
yt_dlp
|
yt_dlp
|
||||||
|
bs4
|
105
revivetube.py
105
revivetube.py
|
@ -22,6 +22,8 @@ import threading
|
||||||
import time
|
import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
from flask import Flask, request, render_template_string, send_file, Response, abort, jsonify
|
from flask import Flask, request, render_template_string, send_file, Response, abort, jsonify
|
||||||
|
@ -53,6 +55,8 @@ video_status = {}
|
||||||
FILE_SEPARATOR = os.sep
|
FILE_SEPARATOR = os.sep
|
||||||
|
|
||||||
LOADING_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}loading_template.html")
|
LOADING_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}loading_template.html")
|
||||||
|
CHANNEL_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}channel_template.html")
|
||||||
|
SEARCH_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}search_template.html")
|
||||||
|
|
||||||
|
|
||||||
os.makedirs(VIDEO_FOLDER, exist_ok=True)
|
os.makedirs(VIDEO_FOLDER, exist_ok=True)
|
||||||
|
@ -139,29 +143,59 @@ def index():
|
||||||
|
|
||||||
if query:
|
if query:
|
||||||
response = requests.get(f"https://invidious.materialio.us/api/v1/search?q={query}", timeout=3)
|
response = requests.get(f"https://invidious.materialio.us/api/v1/search?q={query}", timeout=3)
|
||||||
try:
|
else:
|
||||||
data = response.json()
|
response = requests.get("https://invidious.materialio.us/api/v1/trending", timeout=3)
|
||||||
except ValueError:
|
|
||||||
return "Can't parse Data. If this Issue persist, report it in the Discord Server.", 500
|
|
||||||
|
|
||||||
if response.status_code == 200 and isinstance(data, list):
|
try:
|
||||||
|
data = response.json()
|
||||||
|
except ValueError:
|
||||||
|
return "Can't parse Data. If this Issue persists, report it in the Discord Server.", 500
|
||||||
|
|
||||||
|
if response.status_code == 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": 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 render_template_string(SEARCH_TEMPLATE, results=results)
|
||||||
|
else:
|
||||||
results = [
|
results = [
|
||||||
{
|
{
|
||||||
"id": entry.get("videoId"),
|
"id": entry.get("videoId"),
|
||||||
"title": entry.get("title"),
|
"title": entry.get("title"),
|
||||||
"uploader": entry.get("author", "Unbekannt"),
|
"uploader": entry.get("author", "Unknown"),
|
||||||
"thumbnail": f"/thumbnail/{entry['videoId']}",
|
"thumbnail": f"/thumbnail/{entry['videoId']}",
|
||||||
"viewCount": entry.get("viewCountText", "Unbekannt"),
|
"viewCount": entry.get("viewCountText", "Unknown"),
|
||||||
"published": entry.get("publishedText", "Unbekannt"),
|
"published": entry.get("publishedText", "Unknown"),
|
||||||
"duration": helper.format_duration(entry.get("lengthSeconds", 0))
|
"duration": helper.format_duration(entry.get("lengthSeconds", 0))
|
||||||
}
|
}
|
||||||
for entry in data
|
for entry in data
|
||||||
if entry.get("videoId")
|
if entry.get("videoId")
|
||||||
]
|
]
|
||||||
else:
|
return render_template_string(INDEX_TEMPLATE, results=results)
|
||||||
return "No Results or Error in the API.", 404
|
else:
|
||||||
|
return "No Results or Error in the API.", 404
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return render_template_string(INDEX_TEMPLATE, results=results)
|
|
||||||
|
|
||||||
@app.route("/watch", methods=["GET"])
|
@app.route("/watch", methods=["GET"])
|
||||||
def watch():
|
def watch():
|
||||||
|
@ -397,6 +431,7 @@ def serve_video(filename):
|
||||||
return send_file(file_path)
|
return send_file(file_path)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/channel', methods=['GET'])
|
@app.route('/channel', methods=['GET'])
|
||||||
def channel_m():
|
def channel_m():
|
||||||
channel_id = request.args.get('channel_id', None)
|
channel_id = request.args.get('channel_id', None)
|
||||||
|
@ -418,22 +453,48 @@ def channel_m():
|
||||||
if 'entries' not in info:
|
if 'entries' not in info:
|
||||||
return "No videos found.", 404
|
return "No videos found.", 404
|
||||||
|
|
||||||
results = [
|
channel_name = info.get('uploader', 'Unknown')
|
||||||
{
|
|
||||||
'id': video['id'],
|
|
||||||
'duration': 'Duration not available on Channel View',
|
|
||||||
'title': video['title'],
|
|
||||||
'uploader': info.get('uploader', 'Unknown'),
|
|
||||||
'thumbnail': f"http://yt.old.errexe.xyz/thumbnail/{video['id']}"
|
|
||||||
}
|
|
||||||
for video in info['entries']
|
|
||||||
]
|
|
||||||
|
|
||||||
return render_template_string(INDEX_TEMPLATE, results=results)
|
invidious_url = f"https://invidious.materialio.us/channel/{channel_id}"
|
||||||
|
response = requests.get(invidious_url, timeout=10)
|
||||||
|
|
||||||
|
if response.status_code != 200:
|
||||||
|
return "Failed to fetch channel page.", 500
|
||||||
|
|
||||||
|
soup = BeautifulSoup(response.text, "html.parser")
|
||||||
|
profile_div = soup.find(class_="channel-profile")
|
||||||
|
|
||||||
|
if profile_div:
|
||||||
|
img_tag = profile_div.find("img")
|
||||||
|
if img_tag and "src" in img_tag.attrs:
|
||||||
|
channel_picture = "http://api.allorigins.win/raw?url=http://invidious.materialio.us" + img_tag["src"]
|
||||||
|
else:
|
||||||
|
channel_picture = ""
|
||||||
|
else:
|
||||||
|
channel_picture = ""
|
||||||
|
|
||||||
|
results = [
|
||||||
|
{
|
||||||
|
'id': video['id'],
|
||||||
|
'duration': 'Duration not available on Channel View',
|
||||||
|
'title': video['title'],
|
||||||
|
'uploader': channel_name,
|
||||||
|
'thumbnail': f"http://yt.old.errexe.xyz/thumbnail/{video['id']}"
|
||||||
|
}
|
||||||
|
for video in info['entries']
|
||||||
|
]
|
||||||
|
|
||||||
|
return render_template_string(
|
||||||
|
CHANNEL_TEMPLATE,
|
||||||
|
results=results,
|
||||||
|
channel_name=channel_name,
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app.run(host="0.0.0.0", debug=True, port=5000)
|
app.run(host="0.0.0.0", debug=True, port=5000)
|
||||||
|
|
132
site_storage/channel_template.html
Normal file
132
site_storage/channel_template.html
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<title>ReviveTube - A YouTube App for the Wii</title>
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
color: #3ea6ff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #46bbff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #181818;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #202020;
|
||||||
|
border-bottom:2px solid #2c2c2c;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fe0000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.search-container {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.search-bar {
|
||||||
|
width: 400px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #313131;
|
||||||
|
background-color: #121212;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.search-bar:hover {
|
||||||
|
border: 1px solid #268ee9;
|
||||||
|
}
|
||||||
|
.search-button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #222222;
|
||||||
|
border:1px solid #3d3d3d;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.search-button:hover {
|
||||||
|
border: 1px solid #268ee9;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.video-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.video-item {
|
||||||
|
width: 320px;
|
||||||
|
margin: 10px;
|
||||||
|
background-color: #222;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.video-item img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.video-item-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.video-item-uploader, .video-item-duration {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="../favicon.ico" style="width:32px; height:32px; display:inline; position:relative; top:3px; right:3px; padding-right:2px;"><span style="position:relative; top:-4px; padding-left:5px; border-left:1px solid #323232;">
|
||||||
|
ReviveTube</span></div>
|
||||||
|
<div class="search-container">
|
||||||
|
<form action="/" method="get">
|
||||||
|
<input class="search-bar" placeholder="Search YouTube" name="query" type="text">
|
||||||
|
<input type="submit" class="search-button" value="Search">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
{% if results %}
|
||||||
|
<div>
|
||||||
|
<img src="{{ channel_picture if channel_picture else 'default_profile.png' }}"
|
||||||
|
alt="Channel Profile Picture"
|
||||||
|
style="width: 48px; height: 48px; border-radius: 50%; object-fit: cover; display: inline-block; vertical-align: middle; margin-right: 10px;">
|
||||||
|
<h2 style="display: inline-block; vertical-align: middle; margin: 0; color: white;">{{ channel_name }}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="video-grid">
|
||||||
|
{% for video in results %}
|
||||||
|
<div class="video-item">
|
||||||
|
<a href="/watch?video_id={{ video['id'] }}">
|
||||||
|
<img alt="{{ video['title'] }}" src="{{ video['thumbnail'] }}">
|
||||||
|
</a>
|
||||||
|
<div class="video-item-title">{{ video['title'] }}</div>
|
||||||
|
<div class="video-item-uploader">By: {{ video['uploader'] }}</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<p style="color: red; text-align: center;">ReviveTube - A YouTube App for the Wii</p>
|
||||||
|
<p style="text-align: center;"><a href="http://revivemii.xyz" target="_blank">Visit the ReviveMii Project</a></p>
|
||||||
|
<p style="font-size: 12px; text-align: center;">We are NOT affiliated with Nintendo or YouTube.</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="https://github.com/ReviveMii/revivetube/" target="_blank">Source Code</a> |
|
||||||
|
<a href="https://revivemii.errexe.xyz/discord-redirect.html">Discord Server</a> |
|
||||||
|
<a href="mailto:theerrorexe@gmail.com">Contact</a> |
|
||||||
|
<a href="/site_storage/credits.html">Credits</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -98,7 +98,6 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{% if results %}
|
{% if results %}
|
||||||
<h2>Search Results:</h2>
|
|
||||||
<div class="video-grid">
|
<div class="video-grid">
|
||||||
{% for video in results %}
|
{% for video in results %}
|
||||||
<div class="video-item">
|
<div class="video-item">
|
||||||
|
|
129
site_storage/search_template.html
Normal file
129
site_storage/search_template.html
Normal file
|
@ -0,0 +1,129 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<title>ReviveTube - A YouTube App for the Wii</title>
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
color: #3ea6ff;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #46bbff;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
color: #fff;
|
||||||
|
background-color: #181818;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
background-color: #202020;
|
||||||
|
border-bottom:2px solid #2c2c2c;
|
||||||
|
padding: 10px 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #fe0000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.search-container {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.search-bar {
|
||||||
|
width: 400px;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
border: 1px solid #313131;
|
||||||
|
background-color: #121212;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.search-bar:hover {
|
||||||
|
border: 1px solid #268ee9;
|
||||||
|
}
|
||||||
|
.search-button {
|
||||||
|
padding: 8px 15px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #222222;
|
||||||
|
border:1px solid #3d3d3d;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.search-button:hover {
|
||||||
|
border: 1px solid #268ee9;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
.video-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.video-item {
|
||||||
|
width: 320px;
|
||||||
|
margin: 10px;
|
||||||
|
background-color: #222;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.video-item img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.video-item-title {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.video-item-uploader, .video-item-duration {
|
||||||
|
color: #aaa;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="header">
|
||||||
|
<div class="logo"><img src="../favicon.ico" style="width:32px; height:32px; display:inline; position:relative; top:3px; right:3px; padding-right:2px;"><span style="position:relative; top:-4px; padding-left:5px; border-left:1px solid #323232;">
|
||||||
|
ReviveTube</span></div>
|
||||||
|
<div class="search-container">
|
||||||
|
<form action="/" method="get">
|
||||||
|
<input class="search-bar" placeholder="Search YouTube" name="query" type="text">
|
||||||
|
<input type="submit" class="search-button" value="Search">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
{% if results %}
|
||||||
|
<div class="video-grid">
|
||||||
|
{% for result in results %}
|
||||||
|
<div class="video-item">
|
||||||
|
<a href="{% if result.type == 'video' %}/watch?video_id={{ result.id }}{% else %}/channel?channel_id={{ result.id }}{% endif %}">
|
||||||
|
<img alt="{{ result.title }}" src="{{ result.thumbnail }}">
|
||||||
|
</a>
|
||||||
|
<div class="video-item-title">{{ result.title }}</div>
|
||||||
|
{% if result.type == 'video' %}
|
||||||
|
<div class="video-item-uploader">By: {{ result.uploader }}</div>
|
||||||
|
<div class="video-item-duration">Duration: {{ result.duration }}</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="video-item-uploader">Subscribers: {{ result.subCount }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<p style="color: red; text-align: center;">ReviveTube - A YouTube App for the Wii</p>
|
||||||
|
<p style="text-align: center;"><a href="http://revivemii.xyz" target="_blank">Visit the ReviveMii Project</a></p>
|
||||||
|
<p style="font-size: 12px; text-align: center;">We are NOT affiliated with Nintendo or YouTube.</p>
|
||||||
|
<p style="text-align: center;">
|
||||||
|
<a href="https://github.com/ReviveMii/revivetube/" target="_blank">Source Code</a> |
|
||||||
|
<a href="https://revivemii.errexe.xyz/discord-redirect.html">Discord Server</a> |
|
||||||
|
<a href="mailto:theerrorexe@gmail.com">Contact</a> |
|
||||||
|
<a href="/site_storage/credits.html">Credits</a>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Add table
Reference in a new issue