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)