We are NOT afiliated with 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
Version: v0.0.9 Beta
"""
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 "Fehlende Video-ID.", 400
# Pfade für die Video-Dateien
video_output_path = os.path.join(VIDEO_FOLDER, f"{video_id}.%(ext)s") # Variable Erweiterung
video_downloaded_path = None # Platzhalter für die heruntergeladene Datei
video_mp4_path = os.path.join(VIDEO_FOLDER, f"{video_id}.mp4")
video_flv_path = os.path.join(VIDEO_FOLDER, f"{video_id}.flv")
# 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"Fehler beim Abrufen der Metadaten: {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 herunterladen, falls nicht vorhanden
if not os.path.exists(video_mp4_path):
try:
# Erstelle ein temporäres Verzeichnis für die Konvertierung
with tempfile.TemporaryDirectory() as temp_dir:
temp_video_path = os.path.join(temp_dir, f"{video_id}.%(ext)s")
# yt-dlp-Command zum Herunterladen des Videos
command = [
"yt-dlp",
"-o", temp_video_path,
f"https://m.youtube.com/watch?v={video_id}" # Video-URL
]
subprocess.run(command, check=True)
# Finden der heruntergeladenen Datei
downloaded_files = [f for f in os.listdir(temp_dir) if f.startswith(video_id)]
if downloaded_files:
video_downloaded_path = os.path.join(temp_dir, downloaded_files[0])
else:
return "Heruntergeladene Datei nicht gefunden.", 500
# Prüfen, ob die heruntergeladene Datei bereits im MP4-Format ist
if video_downloaded_path.endswith(".mp4"):
# Wenn die Datei im MP4-Format vorliegt, wird keine Konvertierung durchgeführt
# Kopiere die MP4-Datei in das endgültige Verzeichnis
shutil.copy(video_downloaded_path, video_mp4_path)
else:
# Konvertierung zu MP4 mit modernen Codecs
subprocess.run(
[
"ffmpeg",
"-y",
"-i", video_downloaded_path, # Eingabedatei
"-c:v", "libx264", # H.264 Video-Codec
"-c:a", "aac", # AAC Audio-Codec
"-strict", "experimental",
"-preset", "fast", # Schnelles Encoding
"-movflags", "+faststart", # Optimierung für Streaming
"-vf", "scale=854:480", # Auflösung auf 480p setzen
video_mp4_path # Ziel als MP4
],
check=True
)
# Originaldatei nach der Konvertierung löschen
if video_downloaded_path != video_mp4_path:
os.remove(video_downloaded_path)
# Verschieben der konvertierten Datei ins endgültige Verzeichnis
if os.path.exists(video_mp4_path):
shutil.move(video_mp4_path, os.path.join(VIDEO_FOLDER, f"{video_id}.mp4"))
except subprocess.CalledProcessError as e:
return f"Fehler beim Herunterladen oder der Konvertierung des Videos: {e.stderr}", 500
# Für Wii in FLV umwandeln, falls erforderlich
if is_wii and not os.path.exists(video_flv_path):
try:
subprocess.run(
[
"ffmpeg",
"-y",
"-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"Fehler bei der Konvertierung in FLV: {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)