from flask import Flask, request, render_template_string, send_file, Response, abort import requests import subprocess import os import threading import time # Flask-Setup app = Flask(__name__) # Konstanten RIITUBE_BASE_URL = "https://riitube.rc24.xyz/" VIDEO_FOLDER = "sigma/videos" API_BASE_URL = "https://y.com.sb/api/v1/" YOUTUBE_API_URL = "https://www.googleapis.com/youtube/v3/videos" # Diese Funktion dient dazu, die Dateigröße zu ermitteln def get_file_size(file_path): return os.path.getsize(file_path) # Diese Funktion dient dazu, einen bestimmten Abschnitt einer Datei zurückzugeben 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() # Den API Key zurückgeben except FileNotFoundError: raise FileNotFoundError("Die Datei token.txt wurde nicht gefunden. Bitte stelle sicher, dass sie vorhanden ist.") # Videos-Ordner erstellen, falls nicht vorhanden os.makedirs(VIDEO_FOLDER, exist_ok=True) # Maximum size limits (1 GB and 5 GB) MAX_VIDEO_SIZE = 1 * 1024 * 1024 * 1024 # 1 GB MAX_FOLDER_SIZE = 5 * 1024 * 1024 * 1024 # 5 GB # Helper function to calculate the total size of the folder def get_folder_size(path): total_size = 0 for dirpath, dirnames, filenames in os.walk(path): for f in filenames: file_path = os.path.join(dirpath, f) total_size += os.path.getsize(file_path) return total_size # Function to periodically delete videos every 5 minutes def delete_videos_periodically(): while True: time.sleep(86400) # 24 hours for filename in os.listdir(VIDEO_FOLDER): file_path = os.path.join(VIDEO_FOLDER, filename) if os.path.isfile(file_path): os.remove(file_path) print(f"Deleted: {file_path}") # Start the periodic deletion in a separate thread threading.Thread(target=delete_videos_periodically, daemon=True).start() # HTML-Templates als Strings INDEX_TEMPLATE = """ ReviveTube by ReviveMii

ReviveTube by ReviveMii

{% if results %}

Search Results

{% endif %}
Visit ReviveMii

This app uses the RiiConnect24 WiiMC API. We are NOT afiliated with RiiConnect24, Nintendo or YouTube. This app is using Code from Wiinet.xyz.

It's recommend to bookmark this Page

It's normal that Sites take long to load

""" WATCH_STANDARD_TEMPLATE = """ {{ title }}

{{ title }}

Uploaded by: {{ uploader }}

Description:

{{ description }}

""" WATCH_WII_TEMPLATE = """ {{ title }}

If the video does not play smoothly, restart the Internet Channel by pressing the Home button and then Reset. It's a bug. It happens if you visit too many Sites

{{ title }}

Uploaded by: {{ uploader }}

Description:

{{ description }}

""" @app.route("/thumbnail/") def get_thumbnail(video_id): thumbnail_url = f"https://img.youtube.com/vi/{video_id}/hqdefault.jpg" try: # Thumbnail von YouTube abrufen response = requests.get(thumbnail_url, stream=True, timeout=5) if response.status_code == 200: # Content-Type weiterleiten return send_file( response.raw, mimetype=response.headers.get("Content-Type", "image/jpeg"), as_attachment=False, ) else: return f"Failed to fetch thumbnail. Status: {response.status_code}", 500 except requests.exceptions.RequestException as e: return f"Error fetching thumbnail: {str(e)}", 500 @app.route("/", methods=["GET"]) def index(): query = request.args.get("query") results = None if query: # API-Anfrage an y.com.sb für die Suchergebnisse response = requests.get(f"{API_BASE_URL}search?q={query}", timeout=3) try: data = response.json() # Parst die JSON-Antwort der API except ValueError: return "Fehler beim Parsen der API-Antwort.", 500 # Ergebnisse verarbeiten, falls die API-Antwort erfolgreich und im erwarteten Format ist if response.status_code == 200 and isinstance(data, list): results = [ { "id": entry.get("videoId"), # Die Video-ID "title": entry.get("title"), # Der Titel des Videos "uploader": entry.get("author", "Unbekannt"), # Der Name des Uploaders "thumbnail": f"/thumbnail/{entry['videoId']}", "viewCount": entry.get("viewCountText", "Unbekannt"), # Anzahl der Aufrufe "published": entry.get("publishedText", "Unbekannt") # Veröffentlichungsdatum } for entry in data # Iteriere durch jedes Video if entry.get("videoId") # Sicherstellen, dass ein VideoID vorhanden ist ] else: return "Keine Ergebnisse gefunden oder Fehler in der API-Antwort.", 404 return render_template_string(INDEX_TEMPLATE, results=results) @app.route("/watch", methods=["GET"]) def watch(): video_id = request.args.get("video_id") if not video_id: return "Missing video ID.", 400 # Metadaten abrufen metadata_response = requests.get(f"http://127.0.0.1:5000/video_metadata/{video_id}") if metadata_response.status_code != 200: return f"Failed to fetch video metadata: {metadata_response.text}", 500 metadata = metadata_response.json() # User-Agent prüfen user_agent = request.headers.get("User-Agent", "").lower() is_wii = "wii" in user_agent and "wiiu" not in user_agent # Video-Pfade 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 herunterladen, falls nicht vorhanden if not os.path.exists(video_mp4_path): video_url = f"{RIITUBE_BASE_URL}video/wii/?q={video_id}" try: response = requests.get(video_url, stream=True, timeout=10) if response.status_code != 200: return f"Failed to download video. HTTP Status: {response.status_code}, Reason: {response.reason}", 500 # Check file size during download total_size = 0 with open(video_mp4_path, "wb") as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) total_size += len(chunk) if total_size > MAX_VIDEO_SIZE: os.remove(video_mp4_path) return "Video exceeds 1 GB in size.", 400 except requests.exceptions.RequestException as e: return f"An error occurred while downloading the video: {str(e)}", 500 # Für Wii in FLV umwandeln if is_wii and not os.path.exists(video_flv_path): try: subprocess.run( [ "ffmpeg", "-i", video_mp4_path, "-ar", "22050", "-f", "flv", "-s", "320x240", "-ab", "32k", "-filter:v", "fps=fps=15", video_flv_path ], check=True ) except subprocess.CalledProcessError as e: return f"Failed to convert video to FLV for Wii: {str(e)}", 500 # HTML basierend auf User-Agent rendern if is_wii: return render_template_string(WATCH_WII_TEMPLATE, **metadata, video_flv=f"/sigma/videos/{video_id}.flv") else: return render_template_string(WATCH_STANDARD_TEMPLATE, **metadata, video_mp4=f"/sigma/videos/{video_id}.mp4") # Video-Metadaten zurückgeben (Simulation für Metadaten) @app.route("/video_metadata/") def video_metadata(video_id): api_key = get_api_key() # API-Anfrage an YouTube Data API v3 params = { "part": "snippet,statistics", "id": video_id, "key": api_key } try: response = requests.get(YOUTUBE_API_URL, params=params, timeout=2) response.raise_for_status() # Raise HTTPError für schlechte Antworten data = response.json() # Überprüfen, ob Video-Daten vorhanden sind if "items" not in data or len(data["items"]) == 0: return f"Video mit ID {video_id} wurde nicht gefunden.", 404 # Metadaten extrahieren video_data = data["items"][0] title = video_data["snippet"]["title"] description = video_data["snippet"]["description"] uploader = video_data["snippet"]["channelTitle"] view_count = video_data["statistics"].get("viewCount", "Unknown") like_count = video_data["statistics"].get("likeCount", "Unknown") dislike_count = video_data["statistics"].get("dislikeCount", "Unknown") # Metadaten als JSON zurückgeben return { "title": title, "uploader": uploader, "description": description, "viewCount": view_count, "likeCount": like_count, "dislikeCount": dislike_count } except requests.exceptions.RequestException as e: return f"Fehler bei der API-Anfrage: {str(e)}", 500 @app.route("/") def serve_video(filename): file_path = os.path.join(filename) # Überprüfen, ob die Datei existiert if not os.path.exists(file_path): return "File not found.", 404 file_size = get_file_size(file_path) # Überprüfen, ob ein Range-Header vorhanden ist range_header = request.headers.get('Range', None) if range_header: # Range-Header parsen (Beispiel: 'bytes=0-499') byte_range = range_header.strip().split('=')[1] start_byte, end_byte = byte_range.split('-') start_byte = int(start_byte) end_byte = int(end_byte) if end_byte else file_size - 1 # Wenn der angeforderte Bereich ungültig ist if start_byte >= file_size or end_byte >= file_size: abort(416) # 416 Range Not Satisfiable # Abschnitt der Datei zurückgeben data = get_range(file_path, (start_byte, end_byte)) content_range = f"bytes {start_byte}-{end_byte}/{file_size}" # Antwort mit Status 206 (Partial Content) response = Response( data, status=206, mimetype="video/mp4", content_type="video/mp4", direct_passthrough=True ) response.headers["Content-Range"] = content_range response.headers["Content-Length"] = str(len(data)) return response # Wenn kein Range-Header vorhanden ist, wird die ganze Datei gesendet return send_file(file_path) if __name__ == "__main__": app.run(debug=True)