import os import time from tqdm import tqdm import torf from dotenv import load_dotenv # import requests from bs4 import BeautifulSoup import subprocess import json from lib.ScreenShot import ScreenShot from urllib.parse import urljoin import asyncio import aiohttp from urllib.parse import urlparse,parse_qs from lib.logging_data import logger import discord from themoviedb import aioTMDb as TMDb from themoviedb import Movie, TV, SizeType class qBittorrent: def __init__(self, url='http://media-server.lan:8080',console:logger=None): self.url = url load_dotenv(".env") self.username = os.getenv('qbit_username') self.password = os.getenv('qbit_password') # self.session = requests.Session() self.session = aiohttp.ClientSession() self.console = console or logger(app_name="qBittorrent",log_dir="./log") async def login(self): """Login to qBittorrent WebUI""" payload = { 'username': self.username, 'password': self.password } # response = await self.session.post(f"{self.url}/api/v2/auth/login", data=payload) async with self.session.post(f"{self.url}/api/v2/auth/login", data=payload) as response: if response.status == 200: await response.text() if response.status == 200: self.console.log("✅ Successfully logged in to qBittorrent") else: self.console.error("❌ Failed to log in to qBittorrent") async def add_torrent(self, torrent_file_path, save_path=None, category="SeFree",rename=None): """Add a torrent file to qBittorrent""" await self.login() # files = {'torrents': (os.path.basename(torrent_file_path), open(torrent_file_path, 'rb'), 'application/x-bittorrent')} form = aiohttp.FormData() form.add_field('torrents', open(torrent_file_path, 'rb'), filename=os.path.basename(torrent_file_path), content_type='application/x-bittorrent') # payload = {'contentLayout':"Original"} form.add_field('contentLayout', 'Original') if save_path: form.add_field('savepath', save_path) if category: form.add_field('category', category) if rename: form.add_field('rename', rename) # response = self.session.post(f"{self.url}/api/v2/torrents/add", files=files, data=payload) count = 0 while True: async with self.session.post(f"{self.url}/api/v2/torrents/add", data=form) as response: # print(response.status_code) # print(response.text) if response.status == 200: self.console.log(f"✅ Successfully added torrent: {torrent_file_path}") break else: self.console.warn(f"❌ Failed to add torrent: {torrent_file_path} - {await response.text()}") count += 1 if count >= 5: self.console.error("❌ Max retry attempts reached. Exiting.") exit(1) await asyncio.sleep(5) async def stop_torrent(self,search_string,tmdb_id=None,tmdb_type='tv') -> aiohttp.ClientResponse | None: # self.session.post async with self.session.get(f"{self.url}/api/v2/torrents/info") as torrents: torrents = await torrents.json() if tmdb_id: info=await tmdb_info(tmdb_id,tmdb_type,'en-US') search_string=info.name torrent = next((t for t in torrents if t['name'] in search_string or search_string in t['name']),None) # print(f"Removing: {t['name']}") async with self.session.post(f"{self.url}/api/v2/torrents/stop", data={"hashes": torrent["hash"],}) as response: if response.status == 200: self.console.log("✅ Successfully stop torrent") return response else: self.console.error("❌ Failed to stop torrent") return async def stop_previous_episode_torrent(self,tmdb_id,entry,title_config,prv_count=1) -> None: season=int(entry['season']) if title_config['absolute_season']: season=int(title_config['season'])+int(title_config['absolute_season']) episode=int(entry['episode']) if title_config['absolute']: episode=int(entry['episode'])+int(title_config['absolute']) previous_episode=episode-prv_count ssep="S{:02d}E{:02d}".format(season, episode) pv_ssep="S{:02d}E{:02d}".format(season, previous_episode) info=await tmdb_info(tmdb_id,'tv','en-US') async with self.session.get(f"{self.url}/api/v2/torrents/info") as torrents: torrents = await torrents.json() # torrent = next((t for t in torrents if info.name in t['name'] and ssep in t['name'] ),None) result=[] for torrent in torrents: if not( info.name in torrent['name'] and (ssep in torrent['name'] or pv_ssep in torrent['name'])): continue async with self.session.post(f"{self.url}/api/v2/torrents/stop", data={"hashes": torrent["hash"],}) as response: if response.status == 200: self.console.log("✅ Successfully stop torrent :", torrent['name']) result.append(True) else: self.console.error("❌ Failed to stop torrent :" , torrent['name']) result.append(False) # return return all(result) async def set_super_seed(self, rename): for _ in range(5): async with self.session.get(f"{self.url}/api/v2/torrents/info") as resp: torrents = await resp.json() torrent = next((t for t in torrents if rename in t["name"]), None) if torrent is None: self.console.warn(f"Not found {rename} in qBittorrent. Wait for 5 seconds") await asyncio.sleep(5) continue async with self.session.post( f"{self.url}/api/v2/torrents/setSuperSeeding", data={"hashes": torrent["hash"], "value": True}, ) as response: if response.status == 200: self.console.log("✅ Successfully set super seed torrent:", torrent["name"]) return True else: self.console.error("❌ Failed to set super seed torrent:", torrent["name"]) return False self.console.warn(f"Not found {rename} in qBittorrent. Super seed will not set") return False async def set_limit_ratio(self,rename,ratioLimit=-1,seedingTimeLimit=-1,inactiveSeedingTimeLimit=-1): for _ in range(5): async with self.session.get(f"{self.url}/api/v2/torrents/info") as resp: torrents = await resp.json() torrent = next((t for t in torrents if rename in t["name"]), None) if torrent is None: self.console.warn(f"Not found {rename} in qBittorrent. Wait for 5 seconds") await asyncio.sleep(5) continue data = { 'hashes': torrent["hash"], 'ratioLimit': ratioLimit, 'seedingTimeLimit': seedingTimeLimit, 'inactiveSeedingTimeLimit': inactiveSeedingTimeLimit, } async with self.session.post( f"{self.url}/api/v2/torrents/setShareLimits", data=data, ) as response: if response.status == 200: self.console.log("✅ Successfully set limit ratio torrent:", torrent["name"]) return True else: self.console.error("❌ Failed to set limit ratio torrent:", torrent["name"]) return False self.console.warn(f"Not found {rename} in qBittorrent. limit ratio will not set") return False class BearBit: """Class to interact with BearBit API""" def __init__(self,console:logger=None): load_dotenv(".env") self.id = os.getenv('bb_id') self.password = os.getenv('bb_pass') self.baseurl = "https://bearbit.org" self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0" # self.session = requests.Session() self.session = aiohttp.ClientSession() self.TRACKER_BEARBIT =None self.console = console or logger(app_name="BearBit",log_dir="./log") async def login(self): """Login to BearBit""" url = f"{self.baseurl}/takelogin.php" payload = f'username={self.id}&password={self.password}&returnto=%2F' headers = { 'User-Agent': self.user_agent, 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': f'{self.baseurl}/login.php?returnto=%2F' } # response = await self.session.post(url, headers=headers, data=payload, verify=False) async with self.session.post(url, headers=headers, data=payload, verify_ssl=False) as response: # await response.text() if 300 <= response.status < 400: return False # Login Failed else: return True async def check_login(self): """Check login status""" url = f"{self.baseurl}/" headers = { 'User-Agent': self.user_agent } response = None try: # response = await self.retry(self.session.get, url, headers=headers, verify=False) async with self.session.get(url, headers=headers, verify_ssl=False) as response: await response.text() if response.status == 200: pass else: return False # Not logged in except Exception: return True soup = BeautifulSoup(response.text, 'html.parser') title = soup.find('title').text return 'login' not in title.lower() async def get_tracker(self): """get current tracker url""" if not await self.check_login(): await self.login() # response = await self.retry(self.session.get, url, headers=headers, verify=False) url = f"{self.baseurl}/toserv.php" headers = { 'User-Agent': self.user_agent } async with self.session.get(url, headers=headers, verify_ssl=False) as response: # await response.text() if response.status != 200: raise Exception(f"Failed to get tracker: {response.status}") text = await response.read() soup = BeautifulSoup(text, 'html.parser') passkey_field = soup.find('input', {'name': 'passk'}) self.TRACKER_BEARBIT = passkey_field.get('value') # return TRACKER_BEARBIT # self.TRACKER_BEARBIT = await self.retry(get_tracker_bb) async def upload_torrent(self,name:str,sdescr:str,poster_url:str,category:str,imdburl:str,source_type:str,codec:str,standard:str,country:str,source:str,original_platform:int,tracktype:str,torrent_file_path,description:str): """Upload a torrent file to BearBit""" CATEGORY_MAP = { "Anime": 10, "Western Series": 52, "Korean Series": 5, "Japanese Series": 51, "Chinese Series": 54, "Thai Series": 53, "Other Series": 57, "Hi-Def Movie": 92, "4K": 90, "สารคดี": 17, "Documentary": 17, } SOURCE_TYPE_MAP = { '4K': 42, 'Blu-ray': 1, 'CD': 9, 'DVD5': 7, 'DVD9': 8, 'Encode': 4, 'HD DVD': 2, 'HDTV': 6, 'MiniBD': 5, 'Remux': 3, 'Track': 10, 'WEB-DL': 31, 'Image': 27 } CODEC_MAP = { 'AC-3': 19, 'APE': 17, 'DTS': 18, 'FLAC': 16, 'H264': 11, 'HEVC': 30, 'MPEG-1': 14, 'MPEG-2': 15, 'Other': 20, 'VC-1': 12, 'Xvid': 13, 'Image/PDF': 28 } STANDARD_MAP = { '1080i': 22, '1080p': 21, '720p': 23, 'DVD': 25, '480p': 24, 'Other': 29 } COUNTRY_MAP = { 'Thai': 'ไทย', 'Western': 'ฝรั่ง', 'Korean': 'เกาหลี', 'Japanese': 'ญี่ปุ่น', 'Chinese': 'จีน', 'Other': 'อื่นๆ' } SOURCE_MAP = { 'Master': 'Master', 'Zoom': 'หนังซูม', 'V2D': 'V2D From Master', 'TV': 'From TV', 'HD-TV': 'From HD-TV', 'Hi-def': 'Hi-def rip from Master', ##for Anime 'From Master/DVD': 'V2D From Master/DVD Modified', 'Rip TV': 'อัดจาก TV', 'Rip Master': 'rip from Master', 'scan': 'Scan' } ORIGINAL_PLATFORM_MAP = { 'DVD': 1, 'Hi-def': 5, 'TV': 2, 'Books': 3, 'Other': 4, 'Netflix': 6 } TRACKTYPE_MAP = { 'Thai Audio': 'พากย์ไทย', 'Thai Audio Thai Sub': 'พากย์ไทย บรรยายไทย', 'Thai Sub': 'Soundtrack บรรยายไทย', 'Thai Sub T': 'Soundtrack บรรยายไทย (แปล)', 'No Sub': 'Soundtrack ไม่มีบรรยาย', 'ENG Sub': 'Soundtrack Sub ENG' } # await self.login() # await self.get_tracker() # name = f"[SeFree] {name}" # description=f"[size=6]{name}[/size] \n\n [img]{poster_url}[/img] \n\n [size=6]อัพโหลดอัตโนมัติผ่านสคริป[/size] \n\n [size=4]ชื่อไฟล์ | ซีซัน อัตโนมัติจาก Sonarr/Radarr โดยอิงจาก TVDB[/size] \n\n [size=6]มีปัญหาอะไรสามารถ DM | Comment แจ้งได้[/size]" payload = { 'passk': self.TRACKER_BEARBIT, 'name': name.encode('iso8859_11', errors='ignore'), 'sdescr': sdescr.encode('iso8859_11', errors='ignore'), 'ss': poster_url, 'type': CATEGORY_MAP[category], 'imdb': imdburl, # 'sc1031': SOURCE_TYPE_MAP[source_type], # 'sc1032': CODEC_MAP[codec], # 'sc1033': STANDARD_MAP[standard], 'mtype': SOURCE_MAP[source].encode('iso8859_11', errors='ignore') if source else None, 'mcoun': COUNTRY_MAP[country].encode('iso8859_11', errors='ignore') if country else None, 'mtv': ORIGINAL_PLATFORM_MAP[original_platform] if original_platform else None, 'msub': TRACKTYPE_MAP[tracktype].encode('iso8859_11', errors='ignore') if tracktype else None, 'color': 0, 'font': 0, 'size': 0, 'descr': description.encode('iso8859_11', errors='ignore') } if CATEGORY_MAP[category] in [52, 5, 51, 54, 53, 57]: # payload.update({"scs31": SOURCE_TYPE_MAP[source_type]}) if source_type else None # payload.update({"scs32": CODEC_MAP[codec]}) if codec else None # payload.update({"scs33": STANDARD_MAP[standard]}) if standard else None payload['scs31']= SOURCE_TYPE_MAP[source_type] if source_type else None payload['scs32']= CODEC_MAP[codec] if codec else None payload['scs33']= STANDARD_MAP.get(standard,29) if standard else None elif CATEGORY_MAP[category] in [10]: # payload.update({"sc1031": SOURCE_TYPE_MAP[source_type]}) if source_type else None # payload.update({"sc1032": CODEC_MAP[codec]}) if codec else None # payload.update({"sc1033": STANDARD_MAP[standard]}) if standard else None payload['sc1031']= SOURCE_TYPE_MAP[source_type] if source_type else None payload['sc1032']= CODEC_MAP[codec] if codec else None payload['sc1033']= STANDARD_MAP.get(standard,29) if standard else None elif CATEGORY_MAP[category] in [92]: # For 4K and Hi-Def Movie, we can add more specific fields if needed payload['sc9131'] = SOURCE_TYPE_MAP[source_type] if source_type else None payload['sc9132'] = CODEC_MAP[codec] if codec else None payload['sc9133'] = STANDARD_MAP.get(standard,29) if standard else None elif CATEGORY_MAP[category] in [17]: # For 4K, we can add more specific fields if needed payload['sc1731'] = SOURCE_TYPE_MAP[source_type] if source_type else None payload['sc1732'] = CODEC_MAP[codec] if codec else None payload['sc1733'] = STANDARD_MAP.get(standard,29) if standard else None if not await self.check_login(): await self.login() self.console.log(f"Uploading torrent with payload: {payload}") form = aiohttp.FormData() for key, value in payload.items(): if isinstance(value, bytes): value = value.decode('iso8859_11', errors='ignore') form.add_field(name=key, value=value,content_type='application/x-www-form-urlencoded; charset=iso-8859-11') elif not isinstance(value, str): value = str(value) # 🔧 Convert int, float, etc. to str form.add_field(name=key, value=value) else: form.add_field(name=key, value=value) form.add_field('file', open(torrent_file_path, 'rb'), filename=os.path.basename(torrent_file_path), content_type='application/octet-stream') self.console.log(f"Uploading torrent with form data: {form._gen_form_data()}") url = f"{self.baseurl}/taketoserv.php" headers = { 'User-Agent': self.user_agent, 'Referer': f'{self.baseurl}/toserv.php' } try: async with self.session.post(url, headers=headers, data=form, allow_redirects=False, verify_ssl=False) as response: raw = await response.read() text = raw.decode('iso8859_11', errors='ignore') self.console.log(f"Response status code: {response.status}") # response.encoding = "iso8859_11" if response.status == 302 and 'Location' in response.headers: redirect_url:str = "https://bearbit.org/"+response.headers['Location'] redirect_id=parse_qs(urlparse(redirect_url).query)["id"][0] redirect_url= "https://bearbit.org/details.php?id={id}".format(id=redirect_id) self.console.log(f"Redirected to: {redirect_url}") return {"success": True, "url": redirect_url, "message": "Torrent uploaded successfully"} else: self.console.error(f"Upload failed with status code: {response.status}") return {"success": False, "url": None, "message": text} except Exception as e: return {"success": False, "url": None, "message": e} async def retry(self,func, *args, max_attempts=3, delay_seconds=5, **kwargs): attempts = 0 while attempts < max_attempts: try: return await func(*args, **kwargs) except Exception as e: self.console.warn(f"Error: {e}. Retrying...") attempts += 1 await asyncio.sleep(delay_seconds) # If using asyncio, replace with await asyncio.sleep(delay_seconds) self.console.error("Max retry attempts reached.") raise Exception("Max retry attempts reached.") class TorrentDD: """Class to interact with TorrentDD API""" category_dict = { # "พระมหากษัตริย์": 1, "Anime": 2, # "Game/PSP/ND/Mobile": 3, # "Game/Pc/Ps/Xbox": 4, # "สื่อเรียนรู้/แม่และเด็ก": 5, # "MV/karaoke": 6, # "เพลง": 7, # "OS Windows/Office": 8, # "AntiVirus/Antispyware": 9, # "ซอฟแวร์ มือถือ/ธีม": 10, # "ซอฟแวร์ แม็ค/ลินุกซ์": 11, # "คอนเสิร์ต": 13, # "ทอล์คโชว์/ตลก/วิทยุ": 14, # "ทั่วไป": 15, # "ธรรมะ": 16, # "font/icon/source": 17, "4K": 18, "Hi-Def Movie": 19, # "ภาพยนตร์ DVD": 20, # "ภาพยนตร์ VCD": 21, # "XviD/DivX/AVI/RM": 22, # "รายการทีวี/วาไรตี้": 23, # "กีฬา/ฟุตบอล": 24, # "รูปภาพ/วอลเปเปอร์": 25, "สารคดี": 26, # "สื่อเรียนรู้/หนังสือ/Ebook": 27, "Korean Series": 28, "Japanese Series": 29, "Chinese Series": 30, "Western Series": 31, "Thai Series": 32, "Other Series": 40, # "คลิปทั่วไป": 33, # "XXX (uncensor)": 34, # "XXX (censor)": 35, # "XXX Clip": 36, # "XXX cartoon/book/pic": 37, # "XXX Gay/Lesbian": 38 } def __init__(self,console:logger=None): load_dotenv(".env") self.id = os.getenv('dd_id') self.password = os.getenv('dd_pass') self.baseurl = "https://www.torrentdd.com" self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0" # self.session= requests.Session() self.session = aiohttp.ClientSession() self.console = console or logger(app_name="TorrentDD",log_dir="./log") async def login(self): """Login to TorrentDD""" url = urljoin(self.baseurl, '/takelogin.php') payload = { 'username': self.id, 'password': self.password } headers = { 'User-Agent': self.user_agent, } # response = await self.session.post(url, headers=headers, data=payload, allow_redirects=False) async with self.session.post(url, headers=headers, data=payload, allow_redirects=False) as response: await response.text() if response.status == 200: return False # Login Failed else: return True async def check_login(self): """Check login status""" url = "https://www.torrentdd.com/home.php" headers = { 'User-Agent': self.user_agent } # response = await self.retry(self.session.get, url, headers=headers, allow_redirects=False) async with self.session.get(url, headers=headers, allow_redirects=False) as response: await response.text() if 300 <= response.status < 400: return False else: return True async def retry(self, func, *args, max_attempts=3, delay_seconds=5, **kwargs) -> any: attempts = 0 while attempts < max_attempts: try: return await func(*args, **kwargs) except Exception as e: self.console.warn(f"Error: {e}. Retrying...") attempts += 1 # time.sleep(delay_seconds) await asyncio.sleep(delay_seconds) raise Exception("Max retry attempts reached.") async def upload_torrent(self ,category ,torrent_file_path, name, poster_url, description): """Upload torrent to TorrentDD""" if not os.path.exists(torrent_file_path): raise FileNotFoundError(f"Torrent file not found: {torrent_file_path}") if not await self.check_login(): await self.login() url = urljoin(self.baseurl, '/takeupload.php') # payload = { # 'type': self.category_dict[category], # 'name': name, # 'poster': poster_url, # 'descr': description, # 'countdown': 12000 - len(description), # 'cf1': 'on', # 'cf2': 'on' # } # print(f"Uploading torrent with payload: {payload}") # files = [ # ('file',(os.path.basename(torrent_file_path) ,open(torrent_file_path, 'rb'),'application/octet-stream')) # ] form = aiohttp.FormData() form.add_field('type', str(self.category_dict[category])) form.add_field('name', name) form.add_field('poster', poster_url) form.add_field('descr', description) form.add_field('countdown', str(12000 - len(description))) form.add_field('cf1', 'on') form.add_field('cf2', 'on') form.add_field('file', open(torrent_file_path, 'rb'), filename=os.path.basename(torrent_file_path), content_type='application/octet-stream') self.console.log(f"Uploading torrent with form data: {form}") headers = { 'User-Agent': self.user_agent, 'Referer': f'{urljoin(self.baseurl, '/upload.php')}', } response = None try: # response = await self.session.post(url, headers=headers, data=payload, files=files, allow_redirects=False) async with self.session.post(url, headers=headers, data=form, allow_redirects=False) as response: await response.text() self.console.log(f"Response status code: {response.status}") # print(response.status_code) if response.status == 302 and 'Location' in response.headers: return { "success": True, "url": f"{self.baseurl}/" + response.headers['Location'].lstrip("./").replace('&uploaded=1', ''), "raw_url": response.headers['Location'].lstrip("./").replace('&uploaded=1', ''), "message": "Success!" } else: extracted_text = BeautifulSoup(await response.text(), 'html.parser').find('div', class_='alert alert-warning').get_text(strip=True) return {"success": False, "url":None, "message":extracted_text} # response = None except Exception as e: return {"success": False, "url":None, "message":e} class Torrent_File: def __init__(self, torrent_path:dict[str, str]=None, ss_url:list[str]=None, metadata:list[dict[str,str]]=None): self.torrent_path:dict[str, str] = torrent_path self.ss_url:list[str] = ss_url self.metadata:list[dict[str,str]] = metadata class TorrentCreator: """Class to create torrents for files and folders""" CODEC_VIDEO_MAP = { "hevc": "H.265", "h264": "H.264", "av1" : "AV1", "vp9": "VP9", } CODEC_AUDIO_MAP = { "aac": "AAC", "eac3": "DDP" } # # torrent_files = {"torrent_file":{"bearbit": [], "torrentdd": [], "all": []},"ss_url":[],"metadata":[]} def __init__(self,console:logger=None): load_dotenv(".env") self.total_created = 0 self.start_time = None self.current_pbar = None # self.bb_id = dotenv_values('.env')['bb_id'] # self.bb_pass = dotenv_values('.env')['bb_pass'] # self.bb_baseurl =f"https://bearbit.org" # self.bb_user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0" # self.bb_session = requests.Session() self.TRACKER_BEARBIT = None self.TRACKER_TORRENTDD = os.getenv('TRACKER_TORRENTDD') # self.bb_login() # self.bb_get_tracker() # self.TRACKER_SETS = [ # ('bearbit', [self.TRACKER_BEARBIT]), # ('torrentdd', [self.TRACKER_TORRENTDD]), # ('both', [self.TRACKER_BEARBIT, self.TRACKER_TORRENTDD]), # ] self.TRACKER_SETS = [] self.comment="[SeFree] Auto-generated torrent" self.console = console or logger(app_name="torrent_uploader",log_dir="./log") # self.ss_url = [] async def create_single_file_torrent(self, file_path, trackers, output_path) -> str: """Create torrent for a single file""" output_path=output_path.replace('#', '') file_name = os.path.basename(file_path) self.console.log(f"\n🔹 Creating torrent for: {file_name}") torrent = torf.Torrent( path=file_path, trackers=trackers, private=True, created_by="[SeFree]", # piece_size=1048576, # 1 MiB # piece_size_max=16777216, # 16 MiB piece_size_min=1048576, # 1 MiB comment=self.comment, ) file_size = os.path.getsize(file_path) self.current_pbar = tqdm(total=file_size, unit='B', unit_scale=True, desc="Generating hashes") def progress_callback(torrent, path, pieces_done, pieces_total): if self.current_pbar: position = (pieces_done / pieces_total) * file_size self.current_pbar.update(position - self.current_pbar.n) # torrent.generate(callback=progress_callback) await asyncio.to_thread(torrent.generate, callback=progress_callback) self.current_pbar.close() self.current_pbar = None torrent.write(output_path) self.console.log(f"✅ Created: {os.path.basename(output_path)}") self.total_created += 1 return output_path async def create_multi_file_torrent(self, folder_path, trackers, output_path) -> str: """Create torrent for a folder (multiple files)""" output_path=output_path.replace('#', '') folder_name = os.path.basename(folder_path) self.console.log(f"\n📁 Creating torrent for folder: {folder_name}") torrent = torf.Torrent( path=folder_path, trackers=trackers, private=True, created_by="[SeFree]", # piece_size=1048576, # 1 MiB # piece_size_max=16777216, # 16 MiB piece_size_min=1048576, # 1 MiB comment=self.comment, ) total_size = 0 for root, _, files in os.walk(folder_path): for file in files: # print(f"🔹 Adding file: {file}") if not file.endswith('.torrent') : total_size += os.path.getsize(os.path.join(root, file)) self.current_pbar = tqdm(total=total_size, unit='B', unit_scale=True, desc="Generating hashes") def progress_callback(torrent, path, pieces_done, pieces_total): if self.current_pbar: position = (pieces_done / pieces_total) * total_size self.current_pbar.update(position - self.current_pbar.n) # torrent.generate(callback=progress_callback) await asyncio.to_thread(torrent.generate, callback=progress_callback) self.current_pbar.close() self.current_pbar = None torrent.write(output_path) self.console.log(f"✅ Created: {os.path.basename(output_path)}") self.total_created += 1 return output_path async def create_torrents_for_directory(self, directory_path,pack=False,rename:str=None) -> list[Torrent_File]: """Create torrents for all files and folders in the given directory""" self.screenshot = ScreenShot() self.console.log(f"\n🚀 Starting torrent creation in: {directory_path}") # print("📡 Trackers: BearBit, TorrentDD, All") self.start_time = time.time() items = [] if not pack: for item in sorted(os.listdir(directory_path)): item_path = os.path.join(directory_path, item) if os.path.isfile(item_path) and not item.endswith('.torrent') and not item.endswith('.jpg') and not item.endswith('.png'): items.append(('file', item_path)) elif os.path.isdir(item_path): items.append(('folder', item_path)) else: items.append(('folder', directory_path)) if not items: self.console.error("❌ No files or folders found to create torrents") return # total_items = len(items) * len(self.TRACKER_SETS) current_item = 0 # torrent_files = {"torrent_file":{"bearbit": [], "torrentdd": [], "all": []},"ss_url":[],"metadata":[]} torrent_files=[] await self.screenshot.login() for item_type, item_path in items: torrent_file=Torrent_File() current_item += 1 base_name = os.path.splitext(os.path.basename(item_path))[0] if item_type == 'file' else os.path.basename(item_path) if not rename else rename ss_output=None # ss_url=None # metadata=None if item_type == 'file': ss_output=await self.screenshot.run(item_path) torrent_file.ss_url= [await self.screenshot.upload_to_imgbb(ss_output)] # torrent_files['ss_url'].append(ss_url) # ss_urls.append(ss_url) duration, video_codec, audio_codec, audio_channels, resolution, size_mb, audio_lang, subtitle_lang, json_metadata = self.get_metadata(item_path) torrent_file.metadata=[{ "path": item_path, "sub_path": item_path, "duration": duration, "video_codec": video_codec, "audio_codec": audio_codec, "audio_channels": audio_channels, "resolution": resolution, "size_mb": size_mb, "audio_lang": audio_lang, "subtitle_lang": subtitle_lang, "json_metadata": json_metadata }] # torrent_files['metadata'].append({ # "path": item_path, # "duration": duration, # "video_codec": video_codec, # "audio_codec": audio_codec, # "audio_channels": audio_channels, # "resolution": resolution, # "size_mb": size_mb, # "audio_lang": audio_lang, # "subtitle_lang": subtitle_lang, # "json_metadata": json_metadata # }) # self.ss_url.append({item_path.replace("#", ""):self.screenshot.upload_to_imgbb(ss_output)}) else: # stop= False torrent_file.ss_url=[] torrent_file.metadata=[] for root, _, files in sorted(os.walk(item_path)): base_name = base_name + " " + os.path.splitext(os.path.basename(item_path))[0] if rename and not pack else base_name # ss_url=None # if stop and pack: # break for i in sorted(files): # torrent_file=Torrent_File() if i.endswith('.torrent') or i.endswith('.jpg') or i.endswith('.png'): continue ss_output=await self.screenshot.run(os.path.join(root, i)) torrent_file.ss_url.append(await self.screenshot.upload_to_imgbb(ss_output)) # ss_urls.append(ss_url) # self.ss_url.append({root.replace("#", ""):self.screenshot.upload_to_imgbb(ss_output)}) duration, video_codec, audio_codec, audio_channels, resolution, size_mb, audio_lang, subtitle_lang, json_metadata = self.get_metadata(os.path.join(root,i)) torrent_file.metadata.append({ "path": item_path, "sub_path": os.path.join(root, i), "duration": duration, "video_codec": video_codec, "audio_codec": audio_codec, "audio_channels": audio_channels, "resolution": resolution, "size_mb": size_mb, "audio_lang": audio_lang, "subtitle_lang": subtitle_lang, "json_metadata": json_metadata }) # torrent_files['metadata'].append({ # "path": item_path, # "duration": duration, # "video_codec": video_codec, # "audio_codec": audio_codec, # "audio_channels": audio_channels, # "resolution": resolution, # "size_mb": size_mb, # "audio_lang": audio_lang, # "subtitle_lang": subtitle_lang, # "json_metadata": json_metadata # }) # stop = True break # Only take the first file's metadata for the folders # torrent_file.metadatas=metadatas # torrent_file.ss_urls=ss_urls # torrent_files['metadata'].append(metadatas) # torrent_files['ss_url'].append(ss_urls) torrent_file.torrent_path={} for label, trackers in self.TRACKER_SETS: output_name = f"{base_name}.{label}.torrent" # os.makedirs(os.path.join(directory_path, "torrent"), exist_ok=True) output_path = os.path.join(directory_path, output_name) # if item_type != 'file' else os.path.join(os.path.dirname(directory_path), output_name) self.console.log(f"\n📌 Processing item {current_item}/{len(items)} ({label})") try: if not pack: if item_type == 'file': torrent_file.torrent_path[label]= await self.create_single_file_torrent(item_path, trackers, output_path) # torrent_files["torrent_file"][label].extend(await self.create_single_file_torrent(item_path, trackers, output_path)) else: torrent_file.torrent_path[label]= await self.create_multi_file_torrent(item_path, trackers, output_path) # torrent_files["torrent_file"][label].extend(await self.create_multi_file_torrent(item_path, trackers, output_path)) else: # torrent_files["torrent_file"][label].extend(await self.create_single_file_torrent(item_path, trackers,os.path.join(os.path.dirname(item_path),output_name))) torrent_file.torrent_path[label]= await self.create_single_file_torrent(item_path, trackers,os.path.join(os.path.dirname(item_path),output_name)) except Exception as e: self.console.error(f"❌ Error creating torrent for {item_path}: {str(e)}") continue torrent_files.append(torrent_file) elapsed_time = time.time() - self.start_time self.console.log(f"\n🎉 Torrent creation completed! Created {self.total_created} torrent(s) in {elapsed_time:.2f} seconds") return torrent_files def get_metadata(self, file_path): def ffprobe_entry(stream, entry,stream_type="stream"): cmd = [ "ffprobe", "-v", "error", "-select_streams", stream, "-show_entries", f"{stream_type}={entry}", "-of", "default=noprint_wrappers=1:nokey=1", file_path ] result = subprocess.run(cmd, stdout=subprocess.PIPE) return result.stdout.decode().strip() def get_json_metadata(): """Get json metadata for the file using ffprobe""" cmd = [ "ffprobe", "-v", "error", "-print_format", "json","-show_format", "-show_streams", "-show_chapters", file_path ] result = subprocess.run(cmd, stdout=subprocess.PIPE) if result.returncode != 0: raise Exception(f"ffprobe error: {result.stderr.decode().strip()}") return json.loads(result.stdout.decode()) def ffprobe_tag_languages(stream_type): cmd = [ "ffprobe", "-v", "error", "-select_streams", stream_type, "-show_entries", "stream_tags=language", "-of", "csv=p=0", file_path ] # print(" ".join(cmd)) result = subprocess.run(cmd, stdout=subprocess.PIPE) # Get unique non-empty language tags langs = list(set([lang.strip() for lang in result.stdout.decode().splitlines() if lang.strip()])) return ",".join(langs) if langs else "und" duration = float(self.get_duration(file_path)) video_codec = ffprobe_entry("v:0", "codec_name").splitlines()[0] + " " + ffprobe_entry("v:0", "profile").splitlines()[0] audio_codec = ffprobe_entry("a:0", "codec_name").splitlines()[0] + (" DDP" if "Dolby Digital Plus" in ffprobe_entry("a:0", "profile").splitlines()[0] else " DD" if "Dolby Digital" in ffprobe_entry("a:0", "profile").splitlines()[0] else "") + (" Atmos" if "Atmos" in ffprobe_entry("a:0", "profile").splitlines()[0] else "") audio_channels = ffprobe_entry("a:0", "channel_layout").splitlines()[0] width = ffprobe_entry("v:0", "width").splitlines()[0] height = ffprobe_entry("v:0", "height").splitlines()[0] resolution = f"{width}x{height}" size_mb = os.path.getsize(file_path) / (1024 * 1024) audio_lang = ffprobe_tag_languages("a").upper() subtitle_lang = ffprobe_tag_languages("s").upper() json_metadata = dict(get_json_metadata()) return duration, video_codec.upper().strip(), audio_codec.upper().strip(), audio_channels, resolution, size_mb, audio_lang, subtitle_lang, json_metadata @staticmethod def get_duration(filename): result = subprocess.run( ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", filename], stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) return float(result.stdout) class TorrentUpload(TorrentCreator): def __init__(self, qbit=None, bb=None, dd=None,console:logger=None): super().__init__(console=console) self.qbit = qbit or qBittorrent(console=console) self.bb = bb or BearBit(console=console) self.dd = dd or TorrentDD(console=console) # self.console = console or logger(app_name="torrent_uploader",log_dir="./log") async def upload_torrent(self,entry:dict,qbit_category="SeFree",super_seed=False,ratioLimit=-1,seedingTimeLimit=-1,inactiveSeedingTimeLimit=-1): file_path = entry['file_path'] imdb_id = entry['imdb_id'] tmdb_id = entry.get('tmdb_id', None) # Optional TMDB ID category = entry['category'] source_type = entry['source_type'] source = entry['source'] country = entry['country'] original_platform = entry['original_platform'] is_subdir = entry['is_subdir'] bearbit = entry['bearbit'] torrentdd = entry['torrentdd'] is_movie = entry['is_movie'] pack = entry['pack'] await self.bb.login() await self.bb.get_tracker() if tmdb_id: tmdb_response = await tmdb_info(tmdb_id, type='movie' if is_movie else 'tv') tmdb_response_eng = await tmdb_info(tmdb_id, type='movie' if is_movie else 'tv', language='en-US') trailers=await tmdb_trailer(tmdb_response,type='movie' if is_movie else 'tv') self.TRACKER_SETS = [] imdburl= f'https://www.imdb.com/title/{imdb_id}/' if bearbit: self.TRACKER_SETS.append(('bearbit', [self.bb.TRACKER_BEARBIT])) if torrentdd: self.TRACKER_SETS.append(('torrentdd', [self.TRACKER_TORRENTDD])) all_list=[] for i in self.TRACKER_SETS: # print(f"Using tracker: {i[0]} - {i[1]}") all_list.extend(i[1]) self.TRACKER_SETS.append(('all', all_list)) torrent_files = [] # name_short = await rename(m, torrent, i, is_movie,pack=pack,tmdb_response=tmdb_response_eng,short=True) # print(torrent) # print(name_short) # exit() if os.path.isdir(file_path): # if dir: if not pack: if is_subdir: for root, directories, _ in os.walk(file_path): for directory in sorted(directories): torrent_files+= await self.create_torrents_for_directory(os.path.join(root, directory)) # torrent_files = {k: torrents[k] + torrent_files[k] for k in torrents} else: name_short = await rename({"path":file_path}, None, is_movie,pack=pack,tmdb_response=tmdb_response_eng,short=True) torrent_files = await self.create_torrents_for_directory(file_path,rename=name_short) else: name_short = await rename({"path":file_path}, None, is_movie,pack=pack,tmdb_response=tmdb_response_eng,short=True) torrent_files = await self.create_torrents_for_directory(file_path,rename=name_short, pack=True) elif os.path.isfile(file_path): torrent_file=Torrent_File() duration, video_codec, audio_codec, audio_channels, resolution, size_mb, audio_lang, subtitle_lang, json_metadata = self.get_metadata(file_path) torrent_file.metadata=[{ "path": file_path, "sub_path": file_path, "duration": duration, "video_codec": video_codec, "audio_codec": audio_codec, "audio_channels": audio_channels, "resolution": resolution, "size_mb": size_mb, "audio_lang": audio_lang, "subtitle_lang": subtitle_lang, "json_metadata": json_metadata }] torrent_file.torrent_path={} for label, trackers in self.TRACKER_SETS: output_name = f"{os.path.splitext(os.path.basename(file_path))[0]}.{label}.torrent" output_path = os.path.join(os.path.dirname(os.path.dirname(file_path)), output_name) if not is_movie else os.path.join(os.path.dirname(file_path), output_name) # os.makedirs(os.path.dirname(output_path), exist_ok=True) torrent_file.torrent_path[label] = await self.create_single_file_torrent(file_path, trackers, output_path) screenshot = ScreenShot(console=self.console) await screenshot.login() ss_output = await screenshot.run(file_path,is_movie) ss_url= await screenshot.upload_to_imgbb(ss_output) # creator.ss_url.append({os.path.join(os.path.dirname(file_path), os.path.splitext(os.path.basename(file_path))[0]).replace("#", ""):creator.screenshot.upload_to_imgbb(ss_output)}) torrent_file.ss_url=[ss_url] torrent_files.append(torrent_file) # print("\nGenerated torrent files:") # with open("torrent_files.json", "w") as f: # json.dump([item.__dict__ for item in torrent_files], f, indent=4) # exit() # status={"bearbit": False, "torrentdd": False} self.console.debug("torrent status : ",[item.__dict__ for item in torrent_files]) status={"bearbit": [], "torrentdd": []} # for label, files in torrent_files.items(): # print(f"\n{label.capitalize()} Torrents:") # for torrent_file in files: # print(f" - {torrent_file}") embed = discord.Embed( title="Torrent Creation Completed", description="The torrent files have been created successfully.", color=discord.Color.green() ) embed.add_field(name="Torrent Files", value="\n".join([i for item in torrent_files for i in item.torrent_path["all"]]), inline=False) embed.set_footer(text="Now uploading to BearBit and TorrentDD...") # await channel.send(embed=embed) for torrent in torrent_files: # meta= torrent_files['metadata'][i] if i < len(torrent_files['metadata']) else None # for m in meta: # for torrent_all in torrent.torrent_path["all"]: name = await rename(torrent.metadata[0], torrent.torrent_path["all"], is_movie,pack=pack,tmdb_response=tmdb_response_eng) await self.qbit.add_torrent(torrent.torrent_path["all"], save_path=os.path.dirname(torrent.metadata[0]['path']),category=qbit_category, rename=name) # await asyncio.sleep(5) self.console.debug(f"qBittorrent name : {name}") if super_seed: await self.qbit.set_super_seed(name.strip()) # await asyncio.sleep(1) # Sleep to avoid overwhelming the qBittorrent API await self.qbit.set_limit_ratio(name.strip(),ratioLimit=ratioLimit,seedingTimeLimit=seedingTimeLimit,inactiveSeedingTimeLimit=inactiveSeedingTimeLimit) # for i,torrent in enumerate(sorted(torrent_files['all'])): # print(f"Adding torrent for: {torrent_files['metadata'][i]['path']}") # qbit.add_torrent(torrent, save_path=os.path.dirname(torrent_files['metadata'][i]['path'])) # time.sleep(1) # Sleep to avoid overwhelming the qBittorrent API # return for torrent in torrent_files: poster_url = torrent.ss_url meta= torrent.metadata # # name = "[SeFree] " + name # name = rename(torrent_files, torrent, i, is_movie,pack=pack) # print(f"Uploading {name}...") name = None # continue # await qbit.add_torrent(torrent, save_path=os.path.dirname(torrent_files['metadata'][i]['path']), rename=name) # continue # if "THA" in torrent_files['metadata'][i]['audio_lang'] and "THA" in torrent_files['metadata'][i]['subtitle_lang']: # tracktype = 'Thai Audio Thai Sub' # elif "THA" not in torrent_files['metadata'][i]['audio_lang'] and "THA" in torrent_files['metadata'][i]['subtitle_lang']: # tracktype = "Thai Sub" # elif "THA" in torrent_files['metadata'][i]['audio_lang'] and "THA" not in torrent_files['metadata'][i]['subtitle_lang']: # tracktype = 'Thai Audio' tracktype = None # for url in poster_url: # if url: # description += f"[img]{url}[/img]\n\n" # description += f"[img]{poster_url}[/img]\n\n" for m in meta: if name is None: name = await rename(m, torrent.torrent_path["all"], is_movie,pack=pack,tmdb_response=tmdb_response_eng) sdescr = tmdb_response.name + " | " + tmdb_response_eng.name # description = f"[color=Red][b][size=6]{name}[/size][/b][/color]\n\n" description = "[size=5]พูดคุย ติชม แจ้งปัญหา ขอไฟล์ได้ที่ [color=Red]Discord[/color][/size]\n" description += "[size=5]https://discord.gg/cdN69PMbnV[/size]\n\n" description += f"[color=Red][b][size=6]{tmdb_response_eng.name} ({tmdb_response_eng.year})[/size][/b][/color]\n" description += f"[color=Red][b][size=6]{tmdb_response.name}[/size][/b][/color]\n\n" description += f"[img]{tmdb_response.poster_url(SizeType.w500)}[/img]\n\n" for trailer in trailers: description += f"[youtube]{trailer["key"]}[/youtube]\n" break # description += f"[youtube]{trailers[0]["key"]}[/youtube]\n" description += f"[quote][color=red][b][size=4] Overview:[/size][/b][/color]\n[size=3]{tmdb_response.overview}\n\n{tmdb_response_eng.overview}[/size][/quote]\n\n" if poster_url: for url in poster_url: description += f"[img]{url}[/img]\n\n" # description += f"[size=4][b]{os.path.basename(m['sub_path'])}[/b][/size]\n" description += ffprobe_streams_to_bbcode(m['json_metadata'], os.path.basename(m['sub_path'])) if "TH" in m['audio_lang'] and "TH" in m['subtitle_lang']: tracktype = 'Thai Audio Thai Sub' elif "TH" not in m['audio_lang'] and "TH" in m['subtitle_lang']: tracktype = "Thai Sub" elif "TH" in m['audio_lang'] and "TH" not in m['subtitle_lang']: tracktype = 'Thai Audio' # description += f"[color=Red][size=3]ปล่อยผ่าน IPv6 ตรง | IPv4 Port forward[/size][/color]\n" # description += f"[color=Red][size=3]อยากโหลดเร็ว เกาะไว แนะนำเปิด IPv6 ครับ[/size][/color]\n\n" description += "[color=Red][size=3]อัพโหลดอัตโนมัติผ่านสคริป[/size][/color]\n\n" description += "[size=2]ทุกไฟล์โหลดตรงจาก Source ไม่ได้ผ่านการ Transcode codec[/size]\n" description += "[size=2]หากแต่บางทีจะมีการ Remux File เช่น BLBL ไม่มีพากย์ไทย ก็จะไปเอาของ TID มาใส่[/size]\n\n" # description += f"[size=4]ชื่อไฟล์ | ซีซัน อัตโนมัติจาก Sonarr/Radarr โดยอิงจาก TVDB[/size]\n\n" # description += f"[size=5]มีปัญหาอะไรสามารถ DM | Comment แจ้งได้[/size]" description += "[size=5]พูดคุย ติชม แจ้งปัญหา ขอไฟล์ได้ที่ [color=Red]Discord[/color][/size]\n" description += "[size=5]https://discord.gg/cdN69PMbnV[/size]\n\n" self.console.debug("Name :",name) self.console.debug("description :",description) # print(f"Uploading torrent: {name} with description:\n{description}") # print(f"Uploading torrent: {name}") # print(f"JSON Metadata: {torrent_files['metadata'][i]['json_metadata']}") # print(ffprobe_streams_to_bbcode(torrent_files['metadata'][i]['json_metadata']))3 # print(sorted(torrent_files['bearbit'])) # print(i) # print(sorted(torrent_files['bearbit'])[i]) # continue if bearbit: result_bb = await self.bb.upload_torrent( name=name, sdescr=sdescr, # poster_url=poster_url[0], poster_url=tmdb_response.poster_url(SizeType.w500), imdburl=imdburl, category=category, source_type=source_type, codec=meta[0]['video_codec'].split(" ")[0] if meta[0]['video_codec'].split(" ")[0] in ['H264','HEVC'] else "Other", standard=meta[0]['resolution'].split('x')[1] + "p", country=country, source=source, original_platform=original_platform, tracktype=tracktype, torrent_file_path=torrent.torrent_path["bearbit"], description=description ) if result_bb is not None: if result_bb.get('success', False): self.console.log(f"Upload result BearBit: {result_bb['message']}") status['bearbit'].append(({"status":True, "name":name, "path":torrent.torrent_path['bearbit'], "url":result_bb.get('url', "")})) else: self.console.error(f"❌ Failed to upload torrent: {torrent} - {result_bb['message']}") status['bearbit'].append(({"status":False, "name":name, "path":torrent.torrent_path['bearbit'], "url":result_bb.get('url', "")})) else: self.console.error(f"❌ Failed to upload torrent. No response: {torrent}") status['bearbit'].append(({"status":False, "name":name, "path":torrent.torrent_path['bearbit'], "url":result_bb.get('url', "")})) # await asyncio.sleep(5) # Sleep to avoid overwhelming the API if torrentdd: result_dd = await self.dd.upload_torrent( category=category, torrent_file_path=torrent.torrent_path['torrentdd'], name=name, poster_url=poster_url[0], description=description, ) if result_dd is not None: if result_dd.get('success', False): self.console.log(f"Upload result TorrentDD: {result_dd['message']}") status['torrentdd'].append(({"status":True, "name":name, "path":torrent.torrent_path['torrentdd'], "url":result_dd.get('url', "")})) else: self.console.error(f"❌ Failed to upload torrent: {torrent} - {result_dd['message']}") status['torrentdd'].append(({"status":False, "name":name, "path":torrent.torrent_path['torrentdd'], "url":result_dd.get('url', "")})) # continue else: self.console.error(f"❌ Failed to upload torrent. No response: {torrent}") status['torrentdd'].append(({"status":False, "name":name, "path":torrent.torrent_path['torrentdd'], "url":result_dd.get('url', "")})) await asyncio.sleep(5) # Sleep to avoid overwhelming the API if not (any(item['status'] is False for item in status['bearbit']) or any(item['status'] is False for item in status['torrentdd'])): # embed = discord.Embed( # title="Torrent Upload Completed", # description="All torrents have been created and uploaded successfully.", # color=discord.Color.green() # ) # bb_text="" # for staBB in status['bearbit']: # bb_text+=f"{"Uploaded" if staBB["status"] else "Fail Upload"} : {staBB["name"]}" # bb_text+="\n" # embed.add_field(name="BearBit", value=bb_text, inline=False) # dd_text="" # for staDD in status['torrentdd']: # dd_text+=f"{"Uploaded" if staDD["status"] else "Fail Upload"} : {staDD["name"]}" # dd_text+="\n" # embed.add_field(name="TorrentDD", value=dd_text, inline=False) # # if status['bearbit'] or status['torrentdd']: # # for i,torrent in enumerate(torrent_files['all']): # # embed.add_field(name="Title", value=os.path.basename(torrent).replace(".all.torrent",""), inline=False) # # embed.add_field(name="BearBit", value="[Click here to visit BearBit](https://bearbit.org/)", inline=False) # # embed.add_field(name="TorrentDD", value="[Click here to visit TorrentDD](https://torrentdd.com/)", inline=False) # embed.set_footer(text="Thank you for visiting!") # console.debug(status) self.console.log('Torrent Upload Completed') # await channel.send(embed=embed) return status else: # elif not status['bearbit'] or not status['torrentdd']: # embed = discord.Embed( # title="Torrent Upload Failed", # description="Some or all torrents were fail to uploaded. Please check the logs for details.", # color=discord.Color.red() if (any(item['status'] is False for item in status['bearbit']) and any(item['status'] is False for item in status['torrentdd'])) else discord.Color.orange() # ) # bb_text="" # torrent_name=None # for staBB in status['bearbit']: # bb_text+=f"{"Uploaded" if staBB["status"] else "Fail Upload"} : {staBB["name"]}" # bb_text+="\n" # embed.add_field(name="BearBit", value=bb_text, inline=False) # dd_text="" # for staDD in status['torrentdd']: # dd_text+=f"{"Uploaded" if staDD["status"] else "Fail Upload"} : {staDD["name"]}" # dd_text+="\n" # embed.add_field(name="TorrentDD", value=dd_text, inline=False) # embed.set_footer(text="Please check the logs for more details.") self.console.debug(status) self.console.error('Torrent Upload Failed :',status) return status async def tmdb_info(tmdb_id,type='movie',language='th-TH'): load_dotenv(".env") tmdb = TMDb(key=os.getenv('tmdb_api'), language=language, region="TH") if type == 'movie': response = await tmdb.movie(movie_id=tmdb_id).details(append_to_response="videos") response.name = response.__str__() elif type == 'tv': response = await tmdb.tv(tv_id=tmdb_id).details(append_to_response="videos") else: raise ValueError("Invalid type specified. Use 'movie' or 'tv'.") return response async def tmdb_trailer(response: Movie | TV, type='movie'): tmdb_trailer = await tmdb_info(response.id, type=type, language=response.original_language) trailers=[] for video in tmdb_trailer.videos.results: if not video.official or video.site !="YouTube" or video.type!="Trailer": continue # print(video.official) # print(video.site) # print(video.name) # print("https://www.youtube.com/watch?v="+video.key) trailers.append({ "name":video.name, "site":video.site, "key": video.key }) return trailers async def rename(metadata,torrent,is_movie=False,pack=False,short=False,tmdb_response:Movie | TV=None): import re # i=Number if not pack: if os.path.isdir(metadata['path']) and torrent : name = tmdb_response.name + " " + os.path.basename(metadata['path']) name += f" ({tmdb_response.year})" if hasattr(tmdb_response, 'year') else '' else: name = tmdb_response.name #+" "+ os.path.dirname(torrent_files['path']).split('/')[-1] if is_movie: name += f" ({tmdb_response.year})" if hasattr(tmdb_response, 'year') else '' else: name = tmdb_response.name match = re.search(r"S\d{2}", os.path.basename(metadata['path'])) if match: name += " " +match.group(0) name += f" ({tmdb_response.year})" if hasattr(tmdb_response, 'year') else '' if short: return name match = re.search(r"S\d{2}E\d{2}", os.path.basename(torrent)) if match: name += ' | ' + match.group(0) name += " | " + metadata['video_codec'] name += " | " + metadata['audio_codec'] + " " + metadata['audio_channels'] name += " | " + metadata['resolution'].split('x')[1] + "p" name += " | " + "Audio : " + metadata['audio_lang'] + " | " + "Sub : " + metadata['subtitle_lang'] + " | " return "[SeFree] " + name def ffprobe_streams_to_bbcode(ffprobe_data,file_name): output = [] output.append("[size=3][b]File Name: {}[/b][/size]".format(file_name)) for stream in ffprobe_data.get('streams', []): if stream.get('codec_type') not in ["video","audio","subtitle"]: continue output.append("[b]Stream Index:[/b] {}".format(stream.get('index'))) type_=stream.get('codec_type') for key, value in stream.items(): if type_=="video" and key not in ["codec_name","profile","codec_type","width","height","display_aspect_ratio","pix_fmt","color_range", "color_space","r_frame_rate","bits_per_raw_sample","tags"]: continue if type_=="audio" and key not in ["codec_name","profile","codec_type","sample_rate","channels","channel_layout","tags"]: continue if type_=="subtitle" and key not in ["codec_name","codec_type","extradata_size","tags"]: continue if isinstance(value, dict): for subkey, subval in value.items(): if type_=="video" and subkey not in ["title","BPS","DURATION","NUMBER_OF_FRAMES","NUMBER_OF_BYTES"]: continue if type_=="audio" and subkey not in ["title","language","BPS","DURATION","NUMBER_OF_FRAMES","NUMBER_OF_BYTES"]: continue if type_=="subtitle" and subkey not in ["title","language","BPS","DURATION","NUMBER_OF_FRAMES","NUMBER_OF_BYTES"]: continue output.append(" [b]{}:[/b] {}".format(subkey, subval)) # try: # if int(subval) ==0: # continue # output.append(" [b]{}:[/b] {}".format(subkey, subval)) # except (ValueError, TypeError): # output.append(" [b]{}:[/b] {}".format(subkey, subval)) else: try: if int(value) ==0: continue output.append(" [b]{}:[/b] {}".format(key, value)) except (ValueError, TypeError): output.append(" [b]{}:[/b] {}".format(key, value)) output.append("") # blank line between streams for i,chapter in enumerate(ffprobe_data.get('chapters', [])): output.append("[b]Chapter : {}[/b]".format(chapter.get("tags",{}).get("title",{}))) for key, value in chapter.items(): if key not in ["start_time","end_time"]: continue try: if int(value) ==0: continue output.append(" [b]{}:[/b] {}".format(key, value)) except (ValueError, TypeError): output.append(" [b]{}:[/b] {}".format(key, value)) # output.append("") return f"[quote]{"\n".join(output)}[/quote]\n\n" # return f"[detail][quote]{"\n".join(output)}[/quote][/detail]\n\n" if __name__ == "__main__": async def main(): # entry={'title': 'SHIBOYUGI: Playing Death Games to Put Food on the Table (Thai Dub)', 'season': 1, 'episode': 5, 'sonarr_id': 1086, 'air_time': '2235', 'day_of_week': 'Wednesday', 'offset': 0, 'start_timestamp': 1773761700} # title_config={'ID': 1086, 'Service': 'CR', 'Title': 'SHIBOYUGI: Playing Death Games to Put Food on the Table (Thai Dub)', 'url': 'GT00365787', 'audio_lang': 'ja,th', 'sub_lang': 'th,en', 'quality': 1080, 'codec': '264', 'range': 'SDR', 'audio_channel': None, 'title_lang': None, 'season': 1, 'if_dub': 0, 'org_lang': None, 'url_org': None, 'absolute_season': None, 'absolute': None, 'worst': 0, 'video_bitrate': None, 'proxy': None} qbit=qBittorrent() await qbit.login() # await qbit.stop_previous_episode_torrent("263330",entry=entry,title_config=title_config) asyncio.run(main()) # cli()