Merge pull request #1 from rmilooo/main

This commit is contained in:
TheErrorExe 2025-01-01 22:43:58 +01:00 committed by GitHub
commit 7fe1bf52d7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 459 additions and 402 deletions

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
token.txt
.venv
.idea
__pycache__

View file

@ -1,6 +1,7 @@
import os import os
import time
import subprocess import subprocess
import time
def get_folder_size(folder_path): def get_folder_size(folder_path):
total_size = 0 total_size = 0
@ -11,6 +12,7 @@ def get_folder_size(folder_path):
total_size += os.path.getsize(filepath) total_size += os.path.getsize(filepath)
return total_size return total_size
def delete_files(folder_path, extensions): def delete_files(folder_path, extensions):
os.system('sudo pkill -f revivetube.py') os.system('sudo pkill -f revivetube.py')
process = subprocess.Popen(['sudo', 'nohup', 'python3', 'revivetube.py']) process = subprocess.Popen(['sudo', 'nohup', 'python3', 'revivetube.py'])
@ -23,6 +25,7 @@ def delete_files(folder_path, extensions):
except: except:
print("ERROR") print("ERROR")
def monitor_folder(folder_path, size_limit_gb, check_interval): def monitor_folder(folder_path, size_limit_gb, check_interval):
size_limit_bytes = size_limit_gb * 1024 * 1024 * 1024 size_limit_bytes = size_limit_gb * 1024 * 1024 * 1024
while True: while True:
@ -31,6 +34,7 @@ def monitor_folder(folder_path, size_limit_gb, check_interval):
delete_files(folder_path, [".flv", ".mp4"]) delete_files(folder_path, [".flv", ".mp4"])
time.sleep(check_interval) time.sleep(check_interval)
if __name__ == "__main__": if __name__ == "__main__":
folder_to_monitor = "./sigma/videos/" folder_to_monitor = "./sigma/videos/"
size_limit = 7 size_limit = 7

1
generateRequirements.sh Normal file
View file

@ -0,0 +1 @@
pipreqs . --force --ignore .venv

57
helper.py Normal file
View file

@ -0,0 +1,57 @@
import json
import os
import subprocess
def read_file(path):
assert isinstance(path, str), "Path must be a string"
try:
with open(path, 'r', encoding='utf-8') as file:
content = file.read()
return content
except FileNotFoundError:
return "Error: File not found."
except Exception as e:
return f"Error: {str(e)}"
def get_video_duration_from_file(video_path):
try:
result = subprocess.run(
['ffprobe', '-v', 'error', '-show_format', '-show_streams', '-of', 'json', video_path],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
video_info = json.loads(result.stdout)
duration = float(video_info['format']['duration'])
return duration
except Exception as e:
print(f"Can't fetch Video-Duration: {str(e)}")
return 0
def format_duration(seconds):
minutes = seconds // 60
seconds = seconds % 60
return f"{minutes}:{str(seconds).zfill(2)}"
def get_file_size(file_path):
return os.path.getsize(file_path)
def get_range(file_path, byte_range):
with open(file_path, 'rb') as f:
f.seek(byte_range[0])
return f.read(byte_range[1] - byte_range[0] + 1)
def get_api_key():
try:
with open("token.txt", "r") as f:
return f.read().strip()
except FileNotFoundError:
raise FileNotFoundError("Missing token.txt. Please go to README.md")

3
requirements.txt Normal file
View file

@ -0,0 +1,3 @@
Flask==3.1.0
Requests==2.32.3
yt_dlp==2024.12.23

View file

@ -14,29 +14,32 @@ If you use this Code, you agree to https://revivemii.fr.to/revivetube/t-and-p.ht
ReviveMii Project: https://revivemii.fr.to/ ReviveMii Project: https://revivemii.fr.to/
""" """
import json
import isodate
from flask import Flask, request, render_template_string, send_file, Response, abort, jsonify
import tempfile
import shutil
import yt_dlp
import requests
import subprocess
import os import os
import shutil
import subprocess
import tempfile
import threading import threading
from threading import Thread
import time import time
from threading import Thread
import requests
import yt_dlp
from flask import Flask, request, render_template_string, send_file, Response, abort, jsonify
import helper
app = Flask(__name__) app = Flask(__name__)
def check_and_create_folder(): def check_and_create_folder():
while True: while True:
folder_path = './sigma/videos' folder_path = './sigma/videos'
if not os.path.exists(folder_path): if not os.path.exists(folder_path):
os.makedirs(folder_path) os.makedirs(folder_path)
print(f"Ordner {folder_path} wurde erstellt.") print(f"Folder {folder_path} got created.")
time.sleep(10) time.sleep(10)
def start_folder_check(): def start_folder_check():
thread = Thread(target=check_and_create_folder) thread = Thread(target=check_and_create_folder)
thread.daemon = True thread.daemon = True
@ -48,118 +51,16 @@ API_BASE_URL = "https://y.com.sb/api/v1/"
YOUTUBE_API_URL = "https://www.googleapis.com/youtube/v3/videos" YOUTUBE_API_URL = "https://www.googleapis.com/youtube/v3/videos"
video_status = {} video_status = {}
LOADING_TEMPLATE = """ FILE_SEPARATOR = os.sep
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loading...</title>
<style>
body {
text-align: center;
font-family: Arial, sans-serif;
}
#loadingGif {
width: 50px;
height: 50px;
margin: 20px auto;
}
#goButton {
display: none;
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
background-color: #4caf50;
color: white;
border: none;
cursor: pointer;
}
#goButton:disabled {
background-color: gray;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>Loading</h1>
<img id="loadingGif" src="loading.gif" alt="Loading..." />
<p id="progressText">Fetching Info...</p>
<button id="goButton" onclick="startVideo()">Go</button>
<br>
<small style="color: grey">Loading Screen will NOT work in Dolphin Emulator.<br><br>Long Video = Longer Download and Converting.<br><br>For videos longer than 7 minutes, there is a chance that they wont play.</small>
<script type="text/javascript">
var goButton = document.getElementById('goButton');
var loadingGif = document.getElementById('loadingGif');
var progressText = document.getElementById('progressText');
var videoId = "{{ video_id }}";
function simulateLoading() { LOADING_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}loading_template.html")
setInterval(checkStatus, 1000);
}
function checkStatus() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/status/' + videoId, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var response;
try {
response = eval('(' + xhr.responseText + ')');
} catch (e) {
response = { status: 'error' };
}
updateProgress(response);
}
};
xhr.send();
}
function updateProgress(status) {
if (status.status === 'complete') {
loadingGif.style.display = 'none';
progressText.innerHTML = 'Done!';
goButton.style.display = 'inline';
} else if (status.status === 'downloading') {
progressText.innerHTML = 'The Server is Downloading...';
} else if (status.status === 'converting') {
progressText.innerHTML = 'The Server is Converting video...';
} else if (status.status === 'converting for Wii') {
progressText.innerHTML = 'The Server is Converting for Wii...';
} else {
progressText.innerHTML = 'The Server was unable to process the video! Report the Bug in the Discord Server. <br> Error Details for Developers: {{ video_id }}_unable_1.<br>Discord Server on ReviveMii Homepage Footer';
}
}
function startVideo() {
window.location.href = '/watch?video_id=' + videoId;
}
window.onload = function () {
simulateLoading();
};
</script>
</body>
</html>"""
def get_file_size(file_path):
return os.path.getsize(file_path)
def get_range(file_path, byte_range):
with open(file_path, 'rb') as f:
f.seek(byte_range[0])
return f.read(byte_range[1] - byte_range[0] + 1)
def get_api_key():
try:
with open("token.txt", "r") as f:
return f.read().strip()
except FileNotFoundError:
raise FileNotFoundError("Missing token.txt. Please go to README.md")
os.makedirs(VIDEO_FOLDER, exist_ok=True) os.makedirs(VIDEO_FOLDER, exist_ok=True)
MAX_VIDEO_SIZE = 1 * 1024 * 1024 * 1024 MAX_VIDEO_SIZE = 1 * 1024 * 1024 * 1024
MAX_FOLDER_SIZE = 5 * 1024 * 1024 * 1024 MAX_FOLDER_SIZE = 5 * 1024 * 1024 * 1024
def get_folder_size(path): def get_folder_size(path):
total_size = 0 total_size = 0
@ -169,7 +70,10 @@ def get_folder_size(path):
total_size += os.path.getsize(file_path) total_size += os.path.getsize(file_path)
return total_size return total_size
""" """
[UNUSED IN THE CURRENT VERSION]
def delete_videos_periodically(): def delete_videos_periodically():
while True: while True:
time.sleep(86400) time.sleep(86400)
@ -182,244 +86,13 @@ def delete_videos_periodically():
threading.Thread(target=delete_videos_periodically, daemon=True).start() threading.Thread(target=delete_videos_periodically, daemon=True).start()
""" """
INDEX_TEMPLATE = """ INDEX_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}index_template.html")
<!DOCTYPE html>
<html lang="en"> WATCH_STANDARD_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}watch_standard_template.html")
<head>
<meta charset="UTF-8"> WATCH_WII_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}watch_wii_template.html")
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ReviveTube by ReviveMii</title>
<style>
body {
font-family: 'Arial', sans-serif;
color: #fff;
background-color: #181818;
text-align: center;
}
h1 {
color: #ff0000;
font-size: 28px;
margin-bottom: 20px;
}
p, h2 {
font-size: 16px;
margin-bottom: 10px;
}
.search-bar {
width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
display: block;
margin: 0 auto;
}
button {
padding: 10px 20px;
font-size: 16px;
background-color: #333333;
color: white;
border: none;
cursor: pointer;
border-radius: 4px;
display: block;
margin: 10px auto;
}
.video-item {
margin-bottom: 20px;
text-align: center;
}
.video-item img {
width: 320px;
height: 180px;
border-radius: 8px;
}
.video-item-title {
color: #fff;
font-weight: bold;
font-size: 16px;
text-align: center;
}
.video-item-uploader {
color: #ccc;
font-size: 14px;
text-align: center;
}
.video-item-duration {
color: #ccc;
font-size: 14px;
text-align: center;
margin-top: 5px;
}
.dark-mode {
background-color: #181818;
color: #fff;
}
.dark-mode a {
color: #1e90ff;
}
</style>
</head>
<body class="dark-mode" id="page-body">
<h1>ReviveTube by ReviveMii</h1>
<p>A YouTube App for the Wii</p>
<form action="/" method="get">
<input class="search-bar" type="text" name="query" placeholder="Search YouTube">
<button type="submit">Go</button>
</form>
{% if results %}
<h2>Search Results</h2>
<div>
{% for video in results %}
<div class="video-item">
<a href="/watch?video_id={{ video['id'] }}">
<img src="{{ video['thumbnail'] }}" alt="{{ video['title'] }}">
<div class="video-item-title">{{ video['title'] }}</div>
<div class="video-item-uploader">By: {{ video['uploader'] }}</div>
<div class="video-item-duration">Duration: {{ video['duration'] }}</div>
</a>
</div>
{% endfor %}
</div>
{% endif %}
<p><a href="http://revivemii.errexe.xyz" target="_blank">Visit ReviveMii</a></p>
<p style="color: red;">\/ Scroll down \/</p>
<p style="font-size: 12px;">We are NOT affiliated with Nintendo or YouTube. This app uses code from Wiinet.xyz. For more information, scroll down to Open Source Software.</p>
<p style="color: blue">It's recommended to bookmark this page. Some sites may take longer to load.</p>
<a href="http://revivetube.errexe.xyz/revivetube/t-and-p.html">Terms of Service and Privacy Policy (Last Updated: 7. Dec 2024 12:41 CET)</a><br><br>
<a href="https://github.com/ReviveMii/revivetube/" target="_blank">Source Code</a><br><br>
<a href="http://revivetube.errexe.xyz/discord-redirect.html">Discord Server [Use a Compatible Device]</a>
<p>Version: v2 Beta (Sometimes I forget to update the Version Number)</p>
<a href="/licenses.html">Open Source Software Used in This App</a>
<br>
<a href="mailto:theerrorexe@gmail.com">Contact</a>
<br>
<a href="https://revivemii.errexe.xyz/feedback.html">Report Bugs & Feedback</a>
</body>
</html>
"""
WATCH_STANDARD_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<style>
body { font-family: 'Arial', sans-serif; text-align: center; color: #fff; background-color: #181818; }
video { margin-top: 20px; }
h1 { color: #ff0000; font-size: 24px; }
h3, p { font-size: 16px; }
.comments { text-align: left; margin: 0 auto; width: 80%; font-size: 14px; }
.comment { margin-bottom: 15px; padding: 10px; border-bottom: 1px solid #ddd; }
.comment p { font-size: 14px; }
.dark-mode { background-color: #181818; color: #fff; }
.dark-mode a { color: #1e90ff; }
</style>
</head>
<!-- <body class="dark-mode" id="page-body">
<video width="640" height="360" controls>
<source src="{{ video_mp4 }}" type="video/mp4">
Your browser does not support the video tag.
</video>
<h1>{{ title }}</h1>
<h3>Uploaded by: <a href="/channel?channel_id={{ channelId }}">{{ uploader }}</a></h3>
<p><strong>Views:</strong> {{ viewCount }}</p>
<p><strong>Likes:</strong> {{ likeCount }}</p>
<p><strong>Upload Date:</strong> {{ publishedAt }}</p>
<a href="#comments">Skip Description</a>
<h3>Description:</h3>
<p>{{ description | safe }}</p>
<h3 id="comments" class="comments">Comments:</h3>
<div class="comments">
{% if comments %}
{% for comment in comments %}
<div class="comment">
<p><strong>{{ comment.author }}</strong> posted:</p>
<p>{{ comment.text|safe }}</p>
<p style="color: gray; font-size: 12px;">Likes: {{ comment.likeCount }} | Post date: {{ comment.publishedAt }}</p>
</div>
{% endfor %}
{% else %}
<p>No Comments.</p>
{% endif %}
</div> -->
Please access this Site on a Wii
</body>
</html>
"""
WATCH_WII_TEMPLATE = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ title }}</title>
<style>
body {
font-family: 'Arial', sans-serif;
text-align: center; /* Zentriert den Text */
color: #fff;
background-color: #181818;
}
.dark-mode {
background-color: #181818;
color: #fff;
}
.dark-mode a {
color: #1e90ff;
}
.comments {
margin: 0 auto;
width: 80%;
}
h1 {
color: red;
text-align: center;
}
h3 {
color: white;
text-align: center;
}
</style>
<script src="https://unpkg.com/@ruffle-rs/ruffle"></script>
</head>
<body class="dark-mode" id="page-body">
<div style="width: 100%; background-color: #000; text-align: center;">
<object type="application/x-shockwave-flash" data="/player.swf" width="384" height="256">
<param name="wmode" value="transparent">
<param name="allowFullScreen" value="false">
<param name="flashvars" value="filename={{ video_flv }}">
</object>
</div>
<h1 style="color: red">{{ title }}</h1>
<h3>Uploaded by: <a href="/channel?channel_id={{ channelId }}">{{ uploader }}</a></h3>
<p><strong>Views:</strong> {{ viewCount }}</p>
<p><strong>Likes:</strong> {{ likeCount }}</p>
<p><strong>Upload Date:</strong> {{ publishedAt }}</p>
<a href="#comments">Skip Description</a>
<h3 style="color: red">Description:</h3>
<p>{{ description | safe }}</p>
<h3 id="comments" class="comments" style="color: red">Comments:</h3>
<div class="comments">
{% if comments %}
{% for comment in comments %}
<div class="comment">
<p><strong>{{ comment.author }}</strong> posted:</p>
<p>{{ comment.text|safe }}</p>
<p style="color: gray; font-size: 12px;">Likes: {{ comment.likeCount }} | Post date: {{ comment.publishedAt }}</p>
</div>
{% endfor %}
{% else %}
<p>No Comments.</p>
{% endif %}
</div>
</body>
</html>
"""
@app.route("/thumbnail/<video_id>") @app.route("/thumbnail/<video_id>")
def get_thumbnail(video_id): 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"
@ -439,15 +112,16 @@ def get_thumbnail(video_id):
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
return f"Error fetching thumbnail: {str(e)}", 500 return f"Error fetching thumbnail: {str(e)}", 500
def get_video_comments(video_id, max_results=20): def get_video_comments(video_id, max_results=20):
api_key = get_api_key() api_key = helper.get_api_key()
params = { params = {
"part": "snippet", "part": "snippet",
"videoId": video_id, "videoId": video_id,
"key": api_key, "key": api_key,
"maxResults": max_results, "maxResults": max_results,
"order": "relevance" "order": "relevance"
} }
try: try:
@ -472,6 +146,8 @@ def get_video_comments(video_id, max_results=20):
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
print(f"Fehler beim Abrufen der Kommentare: {str(e)}") print(f"Fehler beim Abrufen der Kommentare: {str(e)}")
return [] return []
@app.route("/switch_wii", methods=["GET"]) @app.route("/switch_wii", methods=["GET"])
def switch_wii(): def switch_wii():
video_id = request.args.get("video_id") video_id = request.args.get("video_id")
@ -489,6 +165,7 @@ def switch_wii():
else: else:
return "Can't start DEBUG Mode.", 500 return "Can't start DEBUG Mode.", 500
@app.route("/switch_n", methods=["GET"]) @app.route("/switch_n", methods=["GET"])
def switch_n(): def switch_n():
video_id = request.args.get("video_id") video_id = request.args.get("video_id")
@ -506,6 +183,7 @@ def switch_n():
else: else:
return "Can't start DEBUG Mode.", 500 return "Can't start DEBUG Mode.", 500
@app.route("/", methods=["GET"]) @app.route("/", methods=["GET"])
def index(): def index():
query = request.args.get("query") query = request.args.get("query")
@ -527,7 +205,7 @@ def index():
"thumbnail": f"/thumbnail/{entry['videoId']}", "thumbnail": f"/thumbnail/{entry['videoId']}",
"viewCount": entry.get("viewCountText", "Unbekannt"), "viewCount": entry.get("viewCountText", "Unbekannt"),
"published": entry.get("publishedText", "Unbekannt"), "published": entry.get("publishedText", "Unbekannt"),
"duration": format_duration(entry.get("lengthSeconds", 0)) # Video Dauer formatiert "duration": helper.format_duration(entry.get("lengthSeconds", 0)) # Video Dauer formatiert
} }
for entry in data for entry in data
if entry.get("videoId") if entry.get("videoId")
@ -537,27 +215,6 @@ def index():
return render_template_string(INDEX_TEMPLATE, results=results) return render_template_string(INDEX_TEMPLATE, results=results)
def format_duration(seconds):
minutes = seconds // 60
seconds = seconds % 60
return f"{minutes}:{str(seconds).zfill(2)}"
def get_video_duration_from_file(video_path):
try:
result = subprocess.run(
['ffprobe', '-v', 'error', '-show_format', '-show_streams', '-of', 'json', video_path],
stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
video_info = json.loads(result.stdout)
duration = float(video_info['format']['duration'])
return duration
except Exception as e:
print(f"Can't fetch Video-Duration: {str(e)}")
return 0
@app.route("/watch", methods=["GET"]) @app.route("/watch", methods=["GET"])
def watch(): def watch():
@ -583,7 +240,6 @@ def watch():
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException 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 = []
try: try:
comments = get_video_comments(video_id) comments = get_video_comments(video_id)
@ -592,7 +248,7 @@ def watch():
comments = [] comments = []
if os.path.exists(video_mp4_path): if os.path.exists(video_mp4_path):
video_duration = get_video_duration_from_file(video_flv_path) video_duration = helper.get_video_duration_from_file(video_flv_path)
alert_script = "" alert_script = ""
if video_duration > 420: if video_duration > 420:
alert_script = """ alert_script = """
@ -600,28 +256,28 @@ def watch():
alert("This Video is long. There is a chance that the Wii will not play the Video. Try a Video under 7 minutes or something like that."); alert("This Video is long. There is a chance that the Wii will not play the Video. Try a Video under 7 minutes or something like that.");
</script> </script>
""" """
if is_wii and os.path.exists(video_flv_path): if is_wii and os.path.exists(video_flv_path):
return render_template_string(WATCH_WII_TEMPLATE + alert_script, return render_template_string(WATCH_WII_TEMPLATE + alert_script,
title=metadata['title'], title=metadata['title'],
uploader=metadata['uploader'], uploader=metadata['uploader'],
channelId=metadata['channelId'], channelId=metadata['channelId'],
description=metadata['description'].replace("\n", "<br>"), description=metadata['description'].replace("\n", "<br>"),
viewCount=metadata['viewCount'], viewCount=metadata['viewCount'],
likeCount=metadata['likeCount'], likeCount=metadata['likeCount'],
publishedAt=metadata['publishedAt'], publishedAt=metadata['publishedAt'],
comments=comments, comments=comments,
video_id=video_id, video_id=video_id,
video_flv=f"/sigma/videos/{video_id}.flv", video_flv=f"/sigma/videos/{video_id}.flv",
alert_message="") alert_message="")
return render_template_string(WATCH_WII_TEMPLATE, return render_template_string(WATCH_WII_TEMPLATE,
title=metadata['title'], title=metadata['title'],
uploader=metadata['uploader'], uploader=metadata['uploader'],
channelId=metadata['channelId'], channelId=metadata['channelId'],
description=metadata['description'].replace("\n", "<br>"), description=metadata['description'].replace("\n", "<br>"),
viewCount=metadata['viewCount'], viewCount=metadata['viewCount'],
likeCount=metadata['likeCount'], likeCount=metadata['likeCount'],
publishedAt=metadata['publishedAt'], publishedAt=metadata['publishedAt'],
comments=comments, comments=comments,
video_id=video_id, video_id=video_id,
@ -633,6 +289,7 @@ def watch():
threading.Thread(target=process_video, args=(video_id,)).start() threading.Thread(target=process_video, args=(video_id,)).start()
return render_template_string(LOADING_TEMPLATE, video_id=video_id) return render_template_string(LOADING_TEMPLATE, video_id=video_id)
def process_video(video_id): def process_video(video_id):
video_mp4_path = os.path.join(VIDEO_FOLDER, f"{video_id}.mp4") video_mp4_path = os.path.join(VIDEO_FOLDER, f"{video_id}.mp4")
video_flv_path = os.path.join(VIDEO_FOLDER, f"{video_id}.flv") video_flv_path = os.path.join(VIDEO_FOLDER, f"{video_id}.flv")
@ -645,7 +302,7 @@ def process_video(video_id):
command = [ command = [
"yt-dlp", "yt-dlp",
"-f worstvideo+worstaudio", "-f worstvideo+worstaudio",
"--proxy", "http://localhost:4000", "http://localhost:4000",
"-o", temp_video_path, "-o", temp_video_path,
f"https://m.youtube.com/watch?v={video_id}" f"https://m.youtube.com/watch?v={video_id}"
] ]
@ -703,13 +360,15 @@ def process_video(video_id):
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)}
@app.route("/status/<video_id>") @app.route("/status/<video_id>")
def check_status(video_id): def check_status(video_id):
return jsonify(video_status.get(video_id, {"status": "pending"})) return jsonify(video_status.get(video_id, {"status": "pending"}))
@app.route("/video_metadata/<video_id>") @app.route("/video_metadata/<video_id>")
def video_metadata(video_id): def video_metadata(video_id):
api_key = get_api_key() api_key = helper.get_api_key()
params = { params = {
"part": "snippet,statistics", "part": "snippet,statistics",
@ -719,7 +378,7 @@ def video_metadata(video_id):
try: try:
response = requests.get(YOUTUBE_API_URL, params=params, timeout=1) response = requests.get(YOUTUBE_API_URL, params=params, timeout=1)
response.raise_for_status() response.raise_for_status()
data = response.json() data = response.json()
@ -750,6 +409,7 @@ def video_metadata(video_id):
except requests.exceptions.RequestException as e: except requests.exceptions.RequestException as e:
return f"Fehler bei der API-Anfrage: {str(e)}", 500 return f"Fehler bei der API-Anfrage: {str(e)}", 500
@app.route("/<path:filename>") @app.route("/<path:filename>")
def serve_video(filename): def serve_video(filename):
file_path = os.path.join(filename) file_path = os.path.join(filename)
@ -757,7 +417,7 @@ def serve_video(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 = get_file_size(file_path) file_size = helper.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:
@ -767,9 +427,9 @@ def serve_video(filename):
end_byte = int(end_byte) if end_byte else file_size - 1 end_byte = int(end_byte) if end_byte else file_size - 1
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 = get_range(file_path, (start_byte, end_byte)) data = helper.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(
@ -785,10 +445,11 @@ 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)
if not channel_id: if not channel_id:
return "Channel ID is required.", 400 return "Channel ID is required.", 400
@ -797,7 +458,7 @@ 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" url = f"https://www.youtube.com/channel/{channel_id}/videos"
@ -805,7 +466,7 @@ def channel_m():
if 'entries' not in info: if 'entries' not in info:
return "No videos found.", 404 return "No videos found.", 404
results = [ results = [
{ {
'id': video['id'], 'id': video['id'],
@ -816,11 +477,12 @@ def channel_m():
} }
for video in info['entries'] for video in info['entries']
] ]
return render_template_string(INDEX_TEMPLATE, results=results) return render_template_string(INDEX_TEMPLATE, results=results)
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)

View file

@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>ReviveTube by ReviveMii</title>
<style>
body {
font-family: 'Arial', sans-serif;
color: #fff;
background-color: #181818;
text-align: center;
}
h1 {
color: #ff0000;
font-size: 28px;
margin-bottom: 20px;
}
p, h2 {
font-size: 16px;
margin-bottom: 10px;
}
.search-bar {
width: 300px;
padding: 10px;
font-size: 16px;
border: 1px solid #ccc;
border-radius: 4px;
display: block;
margin: 0 auto;
}
button {
padding: 10px 20px;
font-size: 16px;
background-color: #333333;
color: white;
border: none;
cursor: pointer;
border-radius: 4px;
display: block;
margin: 10px auto;
}
.video-item {
margin-bottom: 20px;
text-align: center;
}
.video-item img {
width: 320px;
height: 180px;
border-radius: 8px;
}
.video-item-title {
color: #fff;
font-weight: bold;
font-size: 16px;
text-align: center;
}
.video-item-uploader {
color: #ccc;
font-size: 14px;
text-align: center;
}
.video-item-duration {
color: #ccc;
font-size: 14px;
text-align: center;
margin-top: 5px;
}
.dark-mode {
background-color: #181818;
color: #fff;
}
.dark-mode a {
color: #1e90ff;
}
</style>
</head>
<body class="dark-mode" id="page-body">
<h1>ReviveTube by ReviveMii</h1>
<p>A YouTube App for the Wii</p>
<form action="/" method="get">
<input class="search-bar" name="query" placeholder="Search YouTube" type="text">
<button type="submit">Go</button>
</form>
{% if results %}
<h2>Search Results</h2>
<div>
{% for video in results %}
<div class="video-item">
<a href="/watch?video_id={{ video['id'] }}">
<img alt="{{ video['title'] }}" src="{{ video['thumbnail'] }}">
<div class="video-item-title">{{ video['title'] }}</div>
<div class="video-item-uploader">By: {{ video['uploader'] }}</div>
<div class="video-item-duration">Duration: {{ video['duration'] }}</div>
</a>
</div>
{% endfor %}
</div>
{% endif %}
<p><a href="http://revivemii.errexe.xyz" target="_blank">Visit ReviveMii</a></p>
<p style="color: red;">\/ Scroll down \/</p>
<p style="font-size: 12px;">We are NOT affiliated with Nintendo or YouTube. This app uses code from Wiinet.xyz. For more
information, scroll down to Open Source Software.</p>
<p style="color: blue">It's recommended to bookmark this page. Some sites may take longer to load.</p>
<a href="http://revivetube.errexe.xyz/revivetube/t-and-p.html">Terms of Service and Privacy Policy (Last Updated: 7. Dec
2024 12:41 CET)</a><br><br>
<a href="https://github.com/ReviveMii/revivetube/" target="_blank">Source Code</a><br><br>
<a href="http://revivetube.errexe.xyz/discord-redirect.html">Discord Server [Use a Compatible Device]</a>
<p>Version: v2 Beta (Sometimes I forget to update the Version Number)</p>
<a href="/licenses.html">Open Source Software Used in This App</a>
<br>
<a href="mailto:theerrorexe@gmail.com">Contact</a>
<br>
<a href="https://revivemii.errexe.xyz/feedback.html">Report Bugs & Feedback</a>
</body>
</html>

View file

@ -0,0 +1,93 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>Loading...</title>
<style>
body {
text-align: center;
font-family: Arial, sans-serif;
}
#loadingGif {
width: 50px;
height: 50px;
margin: 20px auto;
}
#goButton {
display: none;
margin-top: 20px;
padding: 10px 20px;
font-size: 16px;
background-color: #4caf50;
color: white;
border: none;
cursor: pointer;
}
#goButton:disabled {
background-color: gray;
cursor: not-allowed;
}
</style>
</head>
<body>
<h1>Loading</h1>
<img alt="Loading..." id="loadingGif" src="loading.gif"/>
<p id="progressText">Fetching Info...</p>
<button id="goButton" onclick="startVideo()">Go</button>
<br>
<small style="color: grey">Loading Screen will NOT work in Dolphin Emulator.<br><br>Long Video = Longer Download and
Converting.<br><br>For videos longer than 7 minutes, there is a chance that they wont play.</small>
<script type="text/javascript">
var goButton = document.getElementById('goButton');
var loadingGif = document.getElementById('loadingGif');
var progressText = document.getElementById('progressText');
var videoId = "{{ video_id }}";
function simulateLoading() {
setInterval(checkStatus, 1000);
}
function checkStatus() {
var xhr = new XMLHttpRequest();
xhr.open('GET', '/status/' + videoId, true);
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
var response;
try {
response = eval('(' + xhr.responseText + ')');
} catch (e) {
response = { status: 'error' };
}
updateProgress(response);
}
};
xhr.send();
}
function updateProgress(status) {
if (status.status === 'complete') {
loadingGif.style.display = 'none';
progressText.innerHTML = 'Done!';
goButton.style.display = 'inline';
} else if (status.status === 'downloading') {
progressText.innerHTML = 'The Server is Downloading...';
} else if (status.status === 'converting') {
progressText.innerHTML = 'The Server is Converting video...';
} else if (status.status === 'converting for Wii') {
progressText.innerHTML = 'The Server is Converting for Wii...';
} else {
progressText.innerHTML = 'The Server was unable to process the video! Report the Bug in the Discord Server. <br> Error Details for Developers: {{ video_id }}_unable_1.<br>Discord Server on ReviveMii Homepage Footer';
}
}
function startVideo() {
window.location.href = '/watch?video_id=' + videoId;
}
window.onload = function () {
simulateLoading();
};
</script>
</body>
</html>

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>{{ title }}</title>
<style>
body { font-family: 'Arial', sans-serif; text-align: center; color: #fff; background-color: #181818; }
video { margin-top: 20px; }
h1 { color: #ff0000; font-size: 24px; }
h3, p { font-size: 16px; }
.comments { text-align: left; margin: 0 auto; width: 80%; font-size: 14px; }
.comment { margin-bottom: 15px; padding: 10px; border-bottom: 1px solid #ddd; }
.comment p { font-size: 14px; }
.dark-mode { background-color: #181818; color: #fff; }
.dark-mode a { color: #1e90ff; }
</style>
</head>
<!-- <body class="dark-mode" id="page-body">
<video width="640" height="360" controls>
<source src="{{ video_mp4 }}" type="video/mp4">
Your browser does not support the video tag.
</video>
<h1>{{ title }}</h1>
<h3>Uploaded by: <a href="/channel?channel_id={{ channelId }}">{{ uploader }}</a></h3>
<p><strong>Views:</strong> {{ viewCount }}</p>
<p><strong>Likes:</strong> {{ likeCount }}</p>
<p><strong>Upload Date:</strong> {{ publishedAt }}</p>
<a href="#comments">Skip Description</a>
<h3>Description:</h3>
<p>{{ description | safe }}</p>
<h3 id="comments" class="comments">Comments:</h3>
<div class="comments">
{% if comments %}
{% for comment in comments %}
<div class="comment">
<p><strong>{{ comment.author }}</strong> posted:</p>
<p>{{ comment.text|safe }}</p>
<p style="color: gray; font-size: 12px;">Likes: {{ comment.likeCount }} | Post date: {{ comment.publishedAt }}</p>
</div>
{% endfor %}
{% else %}
<p>No Comments.</p>
{% endif %}
</div> -->
Please access this Site on a Wii
</body>
</html>

View file

@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0" name="viewport">
<title>{{ title }}</title>
<style>
body {
font-family: 'Arial', sans-serif;
text-align: center; /* Zentriert den Text */
color: #fff;
background-color: #181818;
}
.dark-mode {
background-color: #181818;
color: #fff;
}
.dark-mode a {
color: #1e90ff;
}
.comments {
margin: 0 auto;
width: 80%;
}
h1 {
color: red;
text-align: center;
}
h3 {
color: white;
text-align: center;
}
</style>
<script src="https://unpkg.com/@ruffle-rs/ruffle"></script>
</head>
<body class="dark-mode" id="page-body">
<div style="width: 100%; background-color: #000; text-align: center;">
<object data="/player.swf" height="256" type="application/x-shockwave-flash" width="384">
<param name="wmode" value="transparent">
<param name="allowFullScreen" value="false">
<param name="flashvars" value="filename={{ video_flv }}">
</object>
</div>
<h1 style="color: red">{{ title }}</h1>
<h3>Uploaded by: <a href="/channel?channel_id={{ channelId }}">{{ uploader }}</a></h3>
<p><strong>Views:</strong> {{ viewCount }}</p>
<p><strong>Likes:</strong> {{ likeCount }}</p>
<p><strong>Upload Date:</strong> {{ publishedAt }}</p>
<a href="#comments">Skip Description</a>
<h3 style="color: red">Description:</h3>
<p>{{ description | safe }}</p>
<h3 class="comments" id="comments" style="color: red">Comments:</h3>
<div class="comments">
{% if comments %}
{% for comment in comments %}
<div class="comment">
<p><strong>{{ comment.author }}</strong> posted:</p>
<p>{{ comment.text|safe }}</p>
<p style="color: gray; font-size: 12px;">Likes: {{ comment.likeCount }} | Post date: {{ comment.publishedAt
}}</p>
</div>
{% endfor %}
{% else %}
<p>No Comments.</p>
{% endif %}
</div>
</body>
</html>