mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
WebSeedCreator: Added support for download progress bar & include static trackers
This commit is contained in:
+222
-16
@@ -2674,14 +2674,17 @@
|
|||||||
{
|
{
|
||||||
"cell_type": "code",
|
"cell_type": "code",
|
||||||
"source": [
|
"source": [
|
||||||
|
"import base64\n",
|
||||||
"import os\n",
|
"import os\n",
|
||||||
"import subprocess\n",
|
"import subprocess\n",
|
||||||
"import base64\n",
|
"import threading\n",
|
||||||
"from typing import Dict, Optional, List\n",
|
"from typing import Dict, Optional, List\n",
|
||||||
"from urllib.parse import urlparse, unquote\n",
|
"from urllib.parse import urlparse, unquote\n",
|
||||||
"import requests\n",
|
"\n",
|
||||||
"import ipywidgets as widgets\n",
|
"import ipywidgets as widgets\n",
|
||||||
"from IPython.display import display, HTML, Javascript\n",
|
"import requests\n",
|
||||||
|
"from IPython.display import display, HTML\n",
|
||||||
|
"\n",
|
||||||
"\n",
|
"\n",
|
||||||
"class MediaTorrentCreator:\n",
|
"class MediaTorrentCreator:\n",
|
||||||
" # Constants for media information\n",
|
" # Constants for media information\n",
|
||||||
@@ -2704,6 +2707,81 @@
|
|||||||
" 'Arabic': 'ARA', 'Thai': 'THA', 'Vietnamese': 'VIE', 'Indonesian': 'IND'\n",
|
" 'Arabic': 'ARA', 'Thai': 'THA', 'Vietnamese': 'VIE', 'Indonesian': 'IND'\n",
|
||||||
" }\n",
|
" }\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" TRACKERS = {\n",
|
||||||
|
" \"udp://tracker.renfei.net:8080/announce\",\n",
|
||||||
|
" \"udp://fe.dealclub.de:6969/announce\",\n",
|
||||||
|
" \"udp://boysbitte.be:6969/announce\",\n",
|
||||||
|
" \"udp://thouvenin.cloud:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.theoks.net:6969/announce\",\n",
|
||||||
|
" \"udp://wepzone.net:6969/announce\",\n",
|
||||||
|
" \"https://tracker.foreverpirates.co:443/announce\",\n",
|
||||||
|
" \"udp://ipv4.tracker.harry.lu:80/announce\",\n",
|
||||||
|
" \"udp://tracker.dler.org:6969/announce\",\n",
|
||||||
|
" \"udp://camera.lei001.com:6969/announce\",\n",
|
||||||
|
" \"http://tracker.mywaifu.best:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.ddunlimited.net:6969/announce\",\n",
|
||||||
|
" \"http://bt.endpot.com:80/announce\",\n",
|
||||||
|
" \"http://tracker.ipv6tracker.ru:80/announce\",\n",
|
||||||
|
" \"udp://static.54.161.216.95.clients.your-server.de:6969/announce\",\n",
|
||||||
|
" \"http://bt.okmp3.ru:2710/announce\",\n",
|
||||||
|
" \"udp://tracker.bittor.pw:1337/announce\",\n",
|
||||||
|
" \"udp://tracker.jonaslsa.com:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.ccp.ovh:6969/announce\",\n",
|
||||||
|
" \"http://vps-dd0a0715.vps.ovh.net:6969/announce\",\n",
|
||||||
|
" \"http://wepzone.net:6969/announce\",\n",
|
||||||
|
" \"udp://psyco.fr:6969/announce\",\n",
|
||||||
|
" \"udp://9.rarbg.com:2810/announce\",\n",
|
||||||
|
" \"https://opentracker.i2p.rocks:443/announce\",\n",
|
||||||
|
" \"http://1337.abcvg.info:80/announce\",\n",
|
||||||
|
" \"udp://open.tracker.cl:1337/announce\",\n",
|
||||||
|
" \"udp://opentracker.io:6969/announce\",\n",
|
||||||
|
" \"udp://u4.trakx.crim.ist:1337/announce\",\n",
|
||||||
|
" \"http://tracker.opentrackr.org:1337/announce\",\n",
|
||||||
|
" \"udp://mail.artixlinux.org:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.publictracker.xyz:6969/announce\",\n",
|
||||||
|
" \"udp://moonburrow.club:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.moeking.me:6969/announce\",\n",
|
||||||
|
" \"udp://tr.bangumi.moe:6969/announce\",\n",
|
||||||
|
" \"https://tracker.gbitt.info:443/announce\",\n",
|
||||||
|
" \"http://v6-tracker.0g.cx:6969/announce\",\n",
|
||||||
|
" \"udp://public.publictracker.xyz:6969/announce\",\n",
|
||||||
|
" \"udp://www.torrent.eu.org:451/announce\",\n",
|
||||||
|
" \"https://tracker.kuroy.me:443/announce\",\n",
|
||||||
|
" \"https://tr.ready4.icu:443/announce\",\n",
|
||||||
|
" \"https://tracker.lelux.fi:443/announce\",\n",
|
||||||
|
" \"https://torrent-tracker.hama3.net:443/announce\",\n",
|
||||||
|
" \"udp://black-bird.ynh.fr:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.artixlinux.org:6969/announce\",\n",
|
||||||
|
" \"udp://v2.iperson.xyz:6969/announce\",\n",
|
||||||
|
" \"https://tracker.mlsub.net:443/announce\",\n",
|
||||||
|
" \"http://tracker.dler.org:6969/announce\",\n",
|
||||||
|
" \"http://tracker.qu.ax:6969/announce\",\n",
|
||||||
|
" \"udp://tamas3.ynh.fr:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.yangxiaoguozi.cn:6969/announce\",\n",
|
||||||
|
" \"udp://v1046920.hosted-by-vdsina.ru:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.dler.com:6969/announce\",\n",
|
||||||
|
" \"udp://admin.52ywp.com:6969/announce\",\n",
|
||||||
|
" \"http://bt1.letpo.com:80/announce\",\n",
|
||||||
|
" \"http://tracker.lelux.fi:80/announce\",\n",
|
||||||
|
" \"https://1337.abcvg.info:443/announce\",\n",
|
||||||
|
" \"udp://thagoat.rocks:6969/announce\",\n",
|
||||||
|
" \"https://tracker1.520.jp:443/announce\",\n",
|
||||||
|
" \"udp://chouchou.top:8080/announce\",\n",
|
||||||
|
" \"udp://fh2.cmp-gaming.com:6969/announce\",\n",
|
||||||
|
" \"http://parag.rs:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.swateam.org.uk:2710/announce\",\n",
|
||||||
|
" \"udp://trackerb.jonaslsa.com:6969/announce\",\n",
|
||||||
|
" \"udp://open.publictracker.xyz:6969/announce\",\n",
|
||||||
|
" \"udp://open.demonii.com:1337/announce\",\n",
|
||||||
|
" \"http://tracker1.bt.moack.co.kr:80/announce\",\n",
|
||||||
|
" \"udp://epider.me:6969/announce\",\n",
|
||||||
|
" \"udp://tracker.cyberia.is:6969/announce\",\n",
|
||||||
|
" \"udp://run.publictracker.xyz:6969/announce\",\n",
|
||||||
|
" \"udp://run-2.publictracker.xyz:6969/announce\",\n",
|
||||||
|
" \"udp://6ahddutb1ucc3cp.ru:6969/announce\",\n",
|
||||||
|
" \"http://tracker.bt4g.com:2095/announce\"\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
" def __init__(self, base_path: str = \"./media_storage\"):\n",
|
" def __init__(self, base_path: str = \"./media_storage\"):\n",
|
||||||
" \"\"\"Initialize the creator with local storage paths\"\"\"\n",
|
" \"\"\"Initialize the creator with local storage paths\"\"\"\n",
|
||||||
" self.base_path = base_path\n",
|
" self.base_path = base_path\n",
|
||||||
@@ -2725,11 +2803,11 @@
|
|||||||
" timeout=30\n",
|
" timeout=30\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
" if response.status_code == 200:\n",
|
" if response.status_code == 200:\n",
|
||||||
" return [tracker.strip() for tracker in response.text.split(\"\\n\") if tracker.strip()]\n",
|
" best_trackers = {tracker.strip() for tracker in response.text.split(\"\\n\") if tracker.strip()}\n",
|
||||||
" return []\n",
|
" return list(best_trackers.union(self.TRACKERS))\n",
|
||||||
" except Exception as e:\n",
|
" except Exception as e:\n",
|
||||||
" print(f\"Warning: Failed to fetch trackers: {str(e)}\")\n",
|
" print(f\"Warning: Failed to fetch trackers: {str(e)}\")\n",
|
||||||
" return []\n",
|
" return list(self.TRACKERS)\n",
|
||||||
"\n",
|
"\n",
|
||||||
" def _get_filename_from_url(self, url: str, custom_filename: Optional[str] = None) -> str:\n",
|
" def _get_filename_from_url(self, url: str, custom_filename: Optional[str] = None) -> str:\n",
|
||||||
" \"\"\"Get filename from URL or custom input\"\"\"\n",
|
" \"\"\"Get filename from URL or custom input\"\"\"\n",
|
||||||
@@ -2818,12 +2896,97 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" return '.'.join(parts)\n",
|
" return '.'.join(parts)\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" def _monitor_download_progress(self, process, progress_widget, status_label):\n",
|
||||||
|
" \"\"\"Monitor aria2c download progress and update the progress widget\"\"\"\n",
|
||||||
|
" try:\n",
|
||||||
|
" total_size = None\n",
|
||||||
|
" max_total_seen = 0 # Keep track of the largest total size we've seen\n",
|
||||||
|
" last_progress = 0 # Keep track of the last progress value\n",
|
||||||
|
"\n",
|
||||||
|
" for line in iter(process.stdout.readline, ''):\n",
|
||||||
|
" try:\n",
|
||||||
|
" if '[#' in line: # This is a progress line\n",
|
||||||
|
" # Example line: [#7a9390 16MiB/34MiB(47%) CN:1 DL:6.2MiB ETA:2s]\n",
|
||||||
|
" parts = line.strip().split()\n",
|
||||||
|
"\n",
|
||||||
|
" # Find the part containing the size information (e.g., \"16MiB/34MiB\")\n",
|
||||||
|
" size_part = next((part for part in parts if '/' in part and 'iB' in part), None)\n",
|
||||||
|
" if size_part:\n",
|
||||||
|
" current, total = size_part.split('/')\n",
|
||||||
|
" current_mb = self._parse_size(current)\n",
|
||||||
|
" total_mb = self._parse_size(total)\n",
|
||||||
|
"\n",
|
||||||
|
" # Update max total size if this is the largest we've seen\n",
|
||||||
|
" if total_mb > max_total_seen:\n",
|
||||||
|
" max_total_seen = total_mb\n",
|
||||||
|
" total_size = total_mb # Update our fixed total size\n",
|
||||||
|
"\n",
|
||||||
|
" # Calculate progress based on the fixed total size\n",
|
||||||
|
" if total_size:\n",
|
||||||
|
" progress = min((current_mb / total_size) * 100, 100)\n",
|
||||||
|
"\n",
|
||||||
|
" # Only update if progress has increased\n",
|
||||||
|
" if progress > last_progress:\n",
|
||||||
|
" last_progress = progress\n",
|
||||||
|
" progress_widget.value = progress\n",
|
||||||
|
"\n",
|
||||||
|
" # Find download speed\n",
|
||||||
|
" speed_part = next((part for part in parts if 'DL:' in part), None)\n",
|
||||||
|
" if speed_part:\n",
|
||||||
|
" speed = self._parse_size(speed_part.replace('DL:', ''))\n",
|
||||||
|
" speed_text = f\"{speed:.2f}MB/s\"\n",
|
||||||
|
"\n",
|
||||||
|
" # Update status text with fixed total size\n",
|
||||||
|
" status_text = (\n",
|
||||||
|
" f\"Downloaded: {current_mb:.1f}MB / {total_size:.1f}MB \"\n",
|
||||||
|
" f\"({progress:.1f}%) • Speed: {speed_text}\"\n",
|
||||||
|
" )\n",
|
||||||
|
" status_label.value = status_text\n",
|
||||||
|
"\n",
|
||||||
|
" except Exception as e:\n",
|
||||||
|
" continue # Skip any lines we can't parse\n",
|
||||||
|
"\n",
|
||||||
|
" # Ensure we show 100% at completion\n",
|
||||||
|
" progress_widget.value = 100\n",
|
||||||
|
" status_label.value = \"Download complete!\"\n",
|
||||||
|
"\n",
|
||||||
|
" except Exception as e:\n",
|
||||||
|
" print(f\"Error monitoring progress: {str(e)}\")\n",
|
||||||
|
" status_label.value = \"Error monitoring progress\"\n",
|
||||||
|
"\n",
|
||||||
|
" def _parse_size(self, size_str):\n",
|
||||||
|
" \"\"\"Convert aria2c size strings to MB\"\"\"\n",
|
||||||
|
" try:\n",
|
||||||
|
" # Extract numeric part and unit\n",
|
||||||
|
" size_str = size_str.strip()\n",
|
||||||
|
"\n",
|
||||||
|
" # Handle percentage or other non-size strings\n",
|
||||||
|
" if not any(unit in size_str for unit in ['KiB', 'MiB', 'GiB', 'TiB']):\n",
|
||||||
|
" return 0\n",
|
||||||
|
"\n",
|
||||||
|
" number = float(''.join(filter(lambda x: x.isdigit() or x == '.', size_str)))\n",
|
||||||
|
" unit = ''.join(filter(str.isalpha, size_str))\n",
|
||||||
|
"\n",
|
||||||
|
" # Convert to MB based on unit\n",
|
||||||
|
" multipliers = {\n",
|
||||||
|
" 'KiB': 1 / 1024,\n",
|
||||||
|
" 'MiB': 1,\n",
|
||||||
|
" 'GiB': 1024,\n",
|
||||||
|
" 'TiB': 1024 * 1024\n",
|
||||||
|
" }\n",
|
||||||
|
"\n",
|
||||||
|
" return number * multipliers.get(unit, 1)\n",
|
||||||
|
" except Exception:\n",
|
||||||
|
" return 0\n",
|
||||||
|
"\n",
|
||||||
" def create_torrent(\n",
|
" def create_torrent(\n",
|
||||||
" self,\n",
|
" self,\n",
|
||||||
" source_url: str,\n",
|
" source_url: str,\n",
|
||||||
" media_info: Dict,\n",
|
" media_info: Dict,\n",
|
||||||
" custom_filename: Optional[str] = None,\n",
|
" custom_filename: Optional[str] = None,\n",
|
||||||
" cleanup: bool = False\n",
|
" cleanup: bool = False,\n",
|
||||||
|
" progress_widget=None,\n",
|
||||||
|
" status_label=None\n",
|
||||||
" ) -> Dict:\n",
|
" ) -> Dict:\n",
|
||||||
" \"\"\"Create a torrent with web seeding for content\"\"\"\n",
|
" \"\"\"Create a torrent with web seeding for content\"\"\"\n",
|
||||||
" try:\n",
|
" try:\n",
|
||||||
@@ -2842,23 +3005,40 @@
|
|||||||
" print(f\"Downloading using aria2c to: {safe_download_filename}\")\n",
|
" print(f\"Downloading using aria2c to: {safe_download_filename}\")\n",
|
||||||
" aria2c_command = [\n",
|
" aria2c_command = [\n",
|
||||||
" \"aria2c\",\n",
|
" \"aria2c\",\n",
|
||||||
" \"-x\", \"16\",\n",
|
" \"-x\", \"16\", # Maximum connection per server\n",
|
||||||
" \"-s\", \"16\",\n",
|
" \"-s\", \"16\", # Split file into 16 parts\n",
|
||||||
" \"-k\", \"1M\",\n",
|
" \"-k\", \"1M\", # Minimum split size\n",
|
||||||
" \"-o\", safe_download_filename,\n",
|
" \"-o\", safe_download_filename,\n",
|
||||||
" \"-d\", download_dir,\n",
|
" \"-d\", download_dir,\n",
|
||||||
|
" \"--summary-interval=1\", # Update interval in seconds\n",
|
||||||
|
" \"--show-console-readout=true\",\n",
|
||||||
|
" \"--auto-file-renaming=false\",\n",
|
||||||
" source_url\n",
|
" source_url\n",
|
||||||
" ]\n",
|
" ]\n",
|
||||||
"\n",
|
"\n",
|
||||||
" process = subprocess.run(\n",
|
" process = subprocess.Popen(\n",
|
||||||
" aria2c_command,\n",
|
" aria2c_command,\n",
|
||||||
" stdout=subprocess.PIPE,\n",
|
" stdout=subprocess.PIPE,\n",
|
||||||
" stderr=subprocess.PIPE,\n",
|
" stderr=subprocess.PIPE,\n",
|
||||||
" text=True\n",
|
" text=True,\n",
|
||||||
|
" bufsize=1,\n",
|
||||||
|
" universal_newlines=True\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" if progress_widget and status_label:\n",
|
||||||
|
" # Start progress monitoring in a separate thread\n",
|
||||||
|
" monitor_thread = threading.Thread(\n",
|
||||||
|
" target=self._monitor_download_progress,\n",
|
||||||
|
" args=(process, progress_widget, status_label),\n",
|
||||||
|
" daemon=True\n",
|
||||||
|
" )\n",
|
||||||
|
" monitor_thread.start()\n",
|
||||||
|
"\n",
|
||||||
|
" # Wait for download to complete\n",
|
||||||
|
" stdout, stderr = process.communicate()\n",
|
||||||
|
"\n",
|
||||||
" if process.returncode != 0:\n",
|
" if process.returncode != 0:\n",
|
||||||
" raise Exception(f\"Failed to download file: {process.stderr}\")\n",
|
" raise Exception(f\"Failed to download file: {stderr}\")\n",
|
||||||
"\n",
|
"\n",
|
||||||
" if not os.path.exists(local_file_path):\n",
|
" if not os.path.exists(local_file_path):\n",
|
||||||
" raise Exception(f\"Downloaded file not found: {local_file_path}\")\n",
|
" raise Exception(f\"Downloaded file not found: {local_file_path}\")\n",
|
||||||
@@ -2908,7 +3088,7 @@
|
|||||||
" \"filename\": safe_download_filename,\n",
|
" \"filename\": safe_download_filename,\n",
|
||||||
" \"torrent_name\": torrent_name,\n",
|
" \"torrent_name\": torrent_name,\n",
|
||||||
" \"trackers\": trackers,\n",
|
" \"trackers\": trackers,\n",
|
||||||
" \"command_output\": process.stdout\n",
|
" \"command_output\": stdout\n",
|
||||||
" }\n",
|
" }\n",
|
||||||
"\n",
|
"\n",
|
||||||
" except Exception as e:\n",
|
" except Exception as e:\n",
|
||||||
@@ -3061,6 +3241,22 @@
|
|||||||
" layout=widgets.Layout(display='none')\n",
|
" layout=widgets.Layout(display='none')\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" progress_bar = widgets.FloatProgress(\n",
|
||||||
|
" value=0,\n",
|
||||||
|
" min=0,\n",
|
||||||
|
" max=100,\n",
|
||||||
|
" description='Progress:',\n",
|
||||||
|
" bar_style='info',\n",
|
||||||
|
" style={'bar_color': '#2196F3'},\n",
|
||||||
|
" orientation='horizontal',\n",
|
||||||
|
" layout=widgets.Layout(width='80%', margin='10px 0px')\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
|
" status_label = widgets.HTML(\n",
|
||||||
|
" value='Ready to download...',\n",
|
||||||
|
" layout=widgets.Layout(margin='5px 0px')\n",
|
||||||
|
" )\n",
|
||||||
|
"\n",
|
||||||
" # Add loading spinner CSS\n",
|
" # Add loading spinner CSS\n",
|
||||||
" display(HTML(\"\"\"\n",
|
" display(HTML(\"\"\"\n",
|
||||||
" <style>\n",
|
" <style>\n",
|
||||||
@@ -3121,6 +3317,9 @@
|
|||||||
" create_button.disabled = True\n",
|
" create_button.disabled = True\n",
|
||||||
" loading_indicator.layout.display = 'block'\n",
|
" loading_indicator.layout.display = 'block'\n",
|
||||||
"\n",
|
"\n",
|
||||||
|
" # Reset progress\n",
|
||||||
|
" progress_bar.value = 0\n",
|
||||||
|
" status_label.value = 'Starting download...'\n",
|
||||||
" with output_area:\n",
|
" with output_area:\n",
|
||||||
" output_area.clear_output()\n",
|
" output_area.clear_output()\n",
|
||||||
" print(\"Starting torrent creation process...\")\n",
|
" print(\"Starting torrent creation process...\")\n",
|
||||||
@@ -3155,8 +3354,10 @@
|
|||||||
"\n",
|
"\n",
|
||||||
" result = self.create_torrent(\n",
|
" result = self.create_torrent(\n",
|
||||||
" source_url=url_input.value,\n",
|
" source_url=url_input.value,\n",
|
||||||
" media_info={'title': torrent_name}, # Pass the complete name\n",
|
" media_info={'title': torrent_name},\n",
|
||||||
" custom_filename=f\"{torrent_name}\"\n",
|
" custom_filename=f\"{torrent_name}\",\n",
|
||||||
|
" progress_widget=progress_bar,\n",
|
||||||
|
" status_label=status_label\n",
|
||||||
" )\n",
|
" )\n",
|
||||||
"\n",
|
"\n",
|
||||||
" print(\"\\n✅ Torrent created successfully!\")\n",
|
" print(\"\\n✅ Torrent created successfully!\")\n",
|
||||||
@@ -3174,6 +3375,9 @@
|
|||||||
" print(\"1. Is the URL valid and accessible?\")\n",
|
" print(\"1. Is the URL valid and accessible?\")\n",
|
||||||
" print(\"2. Have you filled in all required fields?\")\n",
|
" print(\"2. Have you filled in all required fields?\")\n",
|
||||||
" print(\"3. Do you have write permissions in the output directory?\")\n",
|
" print(\"3. Do you have write permissions in the output directory?\")\n",
|
||||||
|
" # Reset progress on error\n",
|
||||||
|
" progress_bar.value = 0\n",
|
||||||
|
" status_label.value = 'Error occurred during download'\n",
|
||||||
"\n",
|
"\n",
|
||||||
" finally:\n",
|
" finally:\n",
|
||||||
" # Re-enable button and hide loading\n",
|
" # Re-enable button and hide loading\n",
|
||||||
@@ -3218,6 +3422,8 @@
|
|||||||
" file_type,\n",
|
" file_type,\n",
|
||||||
" metadata_section,\n",
|
" metadata_section,\n",
|
||||||
" create_button,\n",
|
" create_button,\n",
|
||||||
|
" progress_bar,\n",
|
||||||
|
" status_label,\n",
|
||||||
" loading_indicator,\n",
|
" loading_indicator,\n",
|
||||||
" output_area,\n",
|
" output_area,\n",
|
||||||
" download_area\n",
|
" download_area\n",
|
||||||
|
|||||||
Reference in New Issue
Block a user