diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..edb9d07 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +token.txt +.venv +.idea +__pycache__ \ No newline at end of file diff --git a/check-folder-revivetube.py b/check-folder-revivetube.py index ed407ba..57ea802 100644 --- a/check-folder-revivetube.py +++ b/check-folder-revivetube.py @@ -1,6 +1,7 @@ import os -import time import subprocess +import time + def get_folder_size(folder_path): total_size = 0 @@ -11,6 +12,7 @@ def get_folder_size(folder_path): total_size += os.path.getsize(filepath) return total_size + def delete_files(folder_path, extensions): os.system('sudo pkill -f revivetube.py') process = subprocess.Popen(['sudo', 'nohup', 'python3', 'revivetube.py']) @@ -23,6 +25,7 @@ def delete_files(folder_path, extensions): except: print("ERROR") + def monitor_folder(folder_path, size_limit_gb, check_interval): size_limit_bytes = size_limit_gb * 1024 * 1024 * 1024 while True: @@ -31,6 +34,7 @@ def monitor_folder(folder_path, size_limit_gb, check_interval): delete_files(folder_path, [".flv", ".mp4"]) time.sleep(check_interval) + if __name__ == "__main__": folder_to_monitor = "./sigma/videos/" size_limit = 7 diff --git a/generateRequirements.sh b/generateRequirements.sh new file mode 100644 index 0000000..a182d84 --- /dev/null +++ b/generateRequirements.sh @@ -0,0 +1 @@ +pipreqs . --force --ignore .venv \ No newline at end of file diff --git a/helper.py b/helper.py new file mode 100644 index 0000000..a4a5d25 --- /dev/null +++ b/helper.py @@ -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") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..76a77cb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +Flask==3.1.0 +Requests==2.32.3 +yt_dlp==2024.12.23 diff --git a/revivetube.py b/revivetube.py index c49a344..3f68e7d 100644 --- a/revivetube.py +++ b/revivetube.py @@ -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/ """ -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 shutil +import subprocess +import tempfile import threading -from threading import Thread 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__) + def check_and_create_folder(): while True: folder_path = './sigma/videos' if not os.path.exists(folder_path): os.makedirs(folder_path) - print(f"Ordner {folder_path} wurde erstellt.") + print(f"Folder {folder_path} got created.") time.sleep(10) + def start_folder_check(): thread = Thread(target=check_and_create_folder) 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" video_status = {} -LOADING_TEMPLATE = """ - - - - - - Loading... - - - -

Loading

- Loading... -

Fetching Info...

- -
- Loading Screen will NOT work in Dolphin Emulator.

Long Video = Longer Download and Converting.

For videos longer than 7 minutes, there is a chance that they won’t play.
- - -""" -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) -MAX_VIDEO_SIZE = 1 * 1024 * 1024 * 1024 -MAX_FOLDER_SIZE = 5 * 1024 * 1024 * 1024 +MAX_VIDEO_SIZE = 1 * 1024 * 1024 * 1024 +MAX_FOLDER_SIZE = 5 * 1024 * 1024 * 1024 + def get_folder_size(path): total_size = 0 @@ -169,7 +70,10 @@ def get_folder_size(path): total_size += os.path.getsize(file_path) return total_size + """ +[UNUSED IN THE CURRENT VERSION] + def delete_videos_periodically(): while True: time.sleep(86400) @@ -182,244 +86,13 @@ def delete_videos_periodically(): threading.Thread(target=delete_videos_periodically, daemon=True).start() """ -INDEX_TEMPLATE = """ - - - - - - ReviveTube by ReviveMii - - - -

ReviveTube by ReviveMii

-

A YouTube App for the Wii

-
- - -
- {% if results %} -

Search Results

-
- {% for video in results %} -
- - {{ video['title'] }} -
{{ video['title'] }}
-
By: {{ video['uploader'] }}
-
Duration: {{ video['duration'] }}
-
-
- {% endfor %} -
- {% endif %} -

Visit ReviveMii

-

\/ Scroll down \/

-

We are NOT affiliated with Nintendo or YouTube. This app uses code from Wiinet.xyz. For more information, scroll down to Open Source Software.

-

It's recommended to bookmark this page. Some sites may take longer to load.

- Terms of Service and Privacy Policy (Last Updated: 7. Dec 2024 12:41 CET)

- Source Code

- Discord Server [Use a Compatible Device] -

Version: v2 Beta (Sometimes I forget to update the Version Number)

- Open Source Software Used in This App -
- Contact -
- Report Bugs & Feedback - - -""" +INDEX_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}index_template.html") + +WATCH_STANDARD_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}watch_standard_template.html") + +WATCH_WII_TEMPLATE = helper.read_file(f"site_storage{FILE_SEPARATOR}watch_wii_template.html") -WATCH_STANDARD_TEMPLATE = """ - - - - - - {{ title }} - - - -Please access this Site on a Wii - - -""" -WATCH_WII_TEMPLATE = """ - - - - - - {{ title }} - - - - -
- - - - - -
-

{{ title }}

-

Uploaded by: {{ uploader }}

-

Views: {{ viewCount }}

-

Likes: {{ likeCount }}

-

Upload Date: {{ publishedAt }}

- Skip Description -

Description:

-

{{ description | safe }}

-

Comments:

-
- {% if comments %} - {% for comment in comments %} -
-

{{ comment.author }} posted:

-

{{ comment.text|safe }}

-

Likes: {{ comment.likeCount }} | Post date: {{ comment.publishedAt }}

-
- {% endfor %} - {% else %} -

No Comments.

- {% endif %} -
- - -""" @app.route("/thumbnail/") def get_thumbnail(video_id): 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: return f"Error fetching thumbnail: {str(e)}", 500 + def get_video_comments(video_id, max_results=20): - api_key = get_api_key() + api_key = helper.get_api_key() params = { "part": "snippet", "videoId": video_id, "key": api_key, "maxResults": max_results, - "order": "relevance" + "order": "relevance" } try: @@ -472,6 +146,8 @@ def get_video_comments(video_id, max_results=20): except requests.exceptions.RequestException as e: print(f"Fehler beim Abrufen der Kommentare: {str(e)}") return [] + + @app.route("/switch_wii", methods=["GET"]) def switch_wii(): video_id = request.args.get("video_id") @@ -489,6 +165,7 @@ def switch_wii(): else: return "Can't start DEBUG Mode.", 500 + @app.route("/switch_n", methods=["GET"]) def switch_n(): video_id = request.args.get("video_id") @@ -506,6 +183,7 @@ def switch_n(): else: return "Can't start DEBUG Mode.", 500 + @app.route("/", methods=["GET"]) def index(): query = request.args.get("query") @@ -527,7 +205,7 @@ def index(): "thumbnail": f"/thumbnail/{entry['videoId']}", "viewCount": entry.get("viewCountText", "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 if entry.get("videoId") @@ -537,27 +215,6 @@ def index(): 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"]) def watch(): @@ -583,7 +240,6 @@ def watch(): except requests.exceptions.RequestException as e: return f"Can't connect to Metadata-API: {str(e)}", 500 - comments = [] try: comments = get_video_comments(video_id) @@ -592,7 +248,7 @@ def watch(): comments = [] 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 = "" if video_duration > 420: 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."); """ - + if is_wii and os.path.exists(video_flv_path): - return render_template_string(WATCH_WII_TEMPLATE + alert_script, - title=metadata['title'], - uploader=metadata['uploader'], + return render_template_string(WATCH_WII_TEMPLATE + alert_script, + title=metadata['title'], + uploader=metadata['uploader'], channelId=metadata['channelId'], description=metadata['description'].replace("\n", "
"), - viewCount=metadata['viewCount'], + viewCount=metadata['viewCount'], likeCount=metadata['likeCount'], publishedAt=metadata['publishedAt'], comments=comments, - video_id=video_id, + video_id=video_id, video_flv=f"/sigma/videos/{video_id}.flv", alert_message="") - return render_template_string(WATCH_WII_TEMPLATE, - title=metadata['title'], - uploader=metadata['uploader'], + return render_template_string(WATCH_WII_TEMPLATE, + title=metadata['title'], + uploader=metadata['uploader'], channelId=metadata['channelId'], description=metadata['description'].replace("\n", "
"), - viewCount=metadata['viewCount'], - likeCount=metadata['likeCount'], + viewCount=metadata['viewCount'], + likeCount=metadata['likeCount'], publishedAt=metadata['publishedAt'], comments=comments, video_id=video_id, @@ -633,6 +289,7 @@ def watch(): threading.Thread(target=process_video, args=(video_id,)).start() return render_template_string(LOADING_TEMPLATE, video_id=video_id) + def process_video(video_id): video_mp4_path = os.path.join(VIDEO_FOLDER, f"{video_id}.mp4") video_flv_path = os.path.join(VIDEO_FOLDER, f"{video_id}.flv") @@ -645,7 +302,7 @@ def process_video(video_id): command = [ "yt-dlp", "-f worstvideo+worstaudio", - "--proxy", "http://localhost:4000", + "http://localhost:4000", "-o", temp_video_path, f"https://m.youtube.com/watch?v={video_id}" ] @@ -703,13 +360,15 @@ def process_video(video_id): except Exception as e: video_status[video_id] = {"status": "error", "message": str(e)} + @app.route("/status/") def check_status(video_id): return jsonify(video_status.get(video_id, {"status": "pending"})) + @app.route("/video_metadata/") def video_metadata(video_id): - api_key = get_api_key() + api_key = helper.get_api_key() params = { "part": "snippet,statistics", @@ -719,7 +378,7 @@ def video_metadata(video_id): try: response = requests.get(YOUTUBE_API_URL, params=params, timeout=1) - response.raise_for_status() + response.raise_for_status() data = response.json() @@ -750,6 +409,7 @@ def video_metadata(video_id): 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) @@ -757,7 +417,7 @@ def serve_video(filename): if not os.path.exists(file_path): 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) if range_header: @@ -767,9 +427,9 @@ def serve_video(filename): end_byte = int(end_byte) if end_byte else file_size - 1 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}" response = Response( @@ -785,10 +445,11 @@ def serve_video(filename): return send_file(file_path) + @app.route('/channel', methods=['GET']) def channel_m(): channel_id = request.args.get('channel_id', None) - + if not channel_id: return "Channel ID is required.", 400 @@ -797,7 +458,7 @@ def channel_m(): 'extract_flat': True, 'playlistend': 20, } - + try: with yt_dlp.YoutubeDL(ydl_opts) as ydl: url = f"https://www.youtube.com/channel/{channel_id}/videos" @@ -805,7 +466,7 @@ def channel_m(): if 'entries' not in info: return "No videos found.", 404 - + results = [ { 'id': video['id'], @@ -816,11 +477,12 @@ def channel_m(): } for video in info['entries'] ] - + return render_template_string(INDEX_TEMPLATE, results=results) - + except Exception as e: return f"An error occurred: {str(e)}", 500 + if __name__ == "__main__": app.run(host="0.0.0.0", debug=True, port=5000) diff --git a/site_storage/index_template.html b/site_storage/index_template.html new file mode 100644 index 0000000..4b1bddd --- /dev/null +++ b/site_storage/index_template.html @@ -0,0 +1,116 @@ + + + + + + ReviveTube by ReviveMii + + + +

ReviveTube by ReviveMii

+

A YouTube App for the Wii

+
+ + +
+{% if results %} +

Search Results

+
+ {% for video in results %} + + {% endfor %} +
+{% endif %} +

Visit ReviveMii

+

\/ Scroll down \/

+

We are NOT affiliated with Nintendo or YouTube. This app uses code from Wiinet.xyz. For more + information, scroll down to Open Source Software.

+

It's recommended to bookmark this page. Some sites may take longer to load.

+Terms of Service and Privacy Policy (Last Updated: 7. Dec + 2024 12:41 CET)

+Source Code

+Discord Server [Use a Compatible Device] +

Version: v2 Beta (Sometimes I forget to update the Version Number)

+Open Source Software Used in This App +
+Contact +
+Report Bugs & Feedback + + \ No newline at end of file diff --git a/site_storage/loading_template.html b/site_storage/loading_template.html new file mode 100644 index 0000000..72e941d --- /dev/null +++ b/site_storage/loading_template.html @@ -0,0 +1,93 @@ + + + + + + Loading... + + + +

Loading

+Loading... +

Fetching Info...

+ +
+Loading Screen will NOT work in Dolphin Emulator.

Long Video = Longer Download and + Converting.

For videos longer than 7 minutes, there is a chance that they won’t play.
+ + + \ No newline at end of file diff --git a/site_storage/watch_standard_template.html b/site_storage/watch_standard_template.html new file mode 100644 index 0000000..abc970a --- /dev/null +++ b/site_storage/watch_standard_template.html @@ -0,0 +1,49 @@ + + + + + + {{ title }} + + + +Please access this Site on a Wii + + \ No newline at end of file diff --git a/site_storage/watch_wii_template.html b/site_storage/watch_wii_template.html new file mode 100644 index 0000000..f65194a --- /dev/null +++ b/site_storage/watch_wii_template.html @@ -0,0 +1,68 @@ + + + + + + {{ title }} + + + + +
+ + + + + +
+

{{ title }}

+

Uploaded by: {{ uploader }}

+

Views: {{ viewCount }}

+

Likes: {{ likeCount }}

+

Upload Date: {{ publishedAt }}

+Skip Description +

Description:

+

{{ description | safe }}

+

Comments:

+
+ {% if comments %} + {% for comment in comments %} +
+

{{ comment.author }} posted:

+

{{ comment.text|safe }}

+

Likes: {{ comment.likeCount }} | Post date: {{ comment.publishedAt + }}

+
+ {% endfor %} + {% else %} +

No Comments.

+ {% endif %} +
+ + \ No newline at end of file