From a897aef25450aa0658b4c428c2614374b2ac6e62 Mon Sep 17 00:00:00 2001 From: mhdzumair Date: Fri, 31 Jan 2025 23:17:56 +0530 Subject: [PATCH] WebSeedCreator: Added support for download progress bar & include static trackers --- docs/TorrentWebCreator.ipynb | 238 ++++++++++++++++++++++++++++++++--- 1 file changed, 222 insertions(+), 16 deletions(-) diff --git a/docs/TorrentWebCreator.ipynb b/docs/TorrentWebCreator.ipynb index e861150..b05bc28 100644 --- a/docs/TorrentWebCreator.ipynb +++ b/docs/TorrentWebCreator.ipynb @@ -2674,14 +2674,17 @@ { "cell_type": "code", "source": [ + "import base64\n", "import os\n", "import subprocess\n", - "import base64\n", + "import threading\n", "from typing import Dict, Optional, List\n", "from urllib.parse import urlparse, unquote\n", - "import requests\n", + "\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", "class MediaTorrentCreator:\n", " # Constants for media information\n", @@ -2704,6 +2707,81 @@ " 'Arabic': 'ARA', 'Thai': 'THA', 'Vietnamese': 'VIE', 'Indonesian': 'IND'\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", " \"\"\"Initialize the creator with local storage paths\"\"\"\n", " self.base_path = base_path\n", @@ -2725,11 +2803,11 @@ " timeout=30\n", " )\n", " if response.status_code == 200:\n", - " return [tracker.strip() for tracker in response.text.split(\"\\n\") if tracker.strip()]\n", - " return []\n", + " best_trackers = {tracker.strip() for tracker in response.text.split(\"\\n\") if tracker.strip()}\n", + " return list(best_trackers.union(self.TRACKERS))\n", " except Exception as e:\n", " print(f\"Warning: Failed to fetch trackers: {str(e)}\")\n", - " return []\n", + " return list(self.TRACKERS)\n", "\n", " def _get_filename_from_url(self, url: str, custom_filename: Optional[str] = None) -> str:\n", " \"\"\"Get filename from URL or custom input\"\"\"\n", @@ -2818,12 +2896,97 @@ "\n", " return '.'.join(parts)\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", " self,\n", " source_url: str,\n", " media_info: Dict,\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", " \"\"\"Create a torrent with web seeding for content\"\"\"\n", " try:\n", @@ -2842,23 +3005,40 @@ " print(f\"Downloading using aria2c to: {safe_download_filename}\")\n", " aria2c_command = [\n", " \"aria2c\",\n", - " \"-x\", \"16\",\n", - " \"-s\", \"16\",\n", - " \"-k\", \"1M\",\n", + " \"-x\", \"16\", # Maximum connection per server\n", + " \"-s\", \"16\", # Split file into 16 parts\n", + " \"-k\", \"1M\", # Minimum split size\n", " \"-o\", safe_download_filename,\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", " ]\n", "\n", - " process = subprocess.run(\n", + " process = subprocess.Popen(\n", " aria2c_command,\n", " stdout=subprocess.PIPE,\n", " stderr=subprocess.PIPE,\n", - " text=True\n", + " text=True,\n", + " bufsize=1,\n", + " universal_newlines=True\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", - " raise Exception(f\"Failed to download file: {process.stderr}\")\n", + " raise Exception(f\"Failed to download file: {stderr}\")\n", "\n", " if not os.path.exists(local_file_path):\n", " raise Exception(f\"Downloaded file not found: {local_file_path}\")\n", @@ -2908,7 +3088,7 @@ " \"filename\": safe_download_filename,\n", " \"torrent_name\": torrent_name,\n", " \"trackers\": trackers,\n", - " \"command_output\": process.stdout\n", + " \"command_output\": stdout\n", " }\n", "\n", " except Exception as e:\n", @@ -3061,6 +3241,22 @@ " layout=widgets.Layout(display='none')\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", " display(HTML(\"\"\"\n", "