WebSeedCreator: Added support for download progress bar & include static trackers

This commit is contained in:
mhdzumair
2025-01-31 23:17:56 +05:30
parent 946270707b
commit a897aef254
+222 -16
View File
@@ -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",
" <style>\n",
@@ -3121,6 +3317,9 @@
" create_button.disabled = True\n",
" loading_indicator.layout.display = 'block'\n",
"\n",
" # Reset progress\n",
" progress_bar.value = 0\n",
" status_label.value = 'Starting download...'\n",
" with output_area:\n",
" output_area.clear_output()\n",
" print(\"Starting torrent creation process...\")\n",
@@ -3155,8 +3354,10 @@
"\n",
" result = self.create_torrent(\n",
" source_url=url_input.value,\n",
" media_info={'title': torrent_name}, # Pass the complete name\n",
" custom_filename=f\"{torrent_name}\"\n",
" media_info={'title': torrent_name},\n",
" custom_filename=f\"{torrent_name}\",\n",
" progress_widget=progress_bar,\n",
" status_label=status_label\n",
" )\n",
"\n",
" print(\"\\n✅ Torrent created successfully!\")\n",
@@ -3174,6 +3375,9 @@
" print(\"1. Is the URL valid and accessible?\")\n",
" print(\"2. Have you filled in all required fields?\")\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",
" finally:\n",
" # Re-enable button and hide loading\n",
@@ -3218,6 +3422,8 @@
" file_type,\n",
" metadata_section,\n",
" create_button,\n",
" progress_bar,\n",
" status_label,\n",
" loading_indicator,\n",
" output_area,\n",
" download_area\n",