stream-fusion-old/stream_fusion/utils/torrent/torrent_smart_container.py
LimeDrive 6fe4fe55f3 init
2024-07-09 04:13:29 +02:00

212 lines
No EOL
10 KiB
Python

import threading
from typing import List, Dict
from RTN import parse
from stream_fusion.utils.debrid.alldebrid import AllDebrid
from stream_fusion.utils.debrid.premiumize import Premiumize
from stream_fusion.utils.debrid.realdebrid import RealDebrid
from stream_fusion.utils.torrent.torrent_item import TorrentItem
from stream_fusion.utils.cache.cache import cache_public
from stream_fusion.utils.general import season_episode_in_filename
from stream_fusion.logging_config import logger
class TorrentSmartContainer:
def __init__(self, torrent_items: List[TorrentItem], media):
self.logger = logger
self.logger.info(f"Initializing TorrentSmartContainer with {len(torrent_items)} items")
self.__itemsDict: Dict[TorrentItem] = self.__build_items_dict_by_infohash(torrent_items)
self.__media = media
def get_hashes(self):
hashes = list(self.__itemsDict.keys())
self.logger.debug(f"Retrieved {len(hashes)} hashes")
return hashes
def get_items(self):
items = list(self.__itemsDict.values())
self.logger.debug(f"Retrieved {len(items)} items")
return items
def get_direct_torrentable(self):
self.logger.info("Retrieving direct torrentable items")
direct_torrentable_items = []
for torrent_item in self.__itemsDict.values():
if torrent_item.privacy == "public" and torrent_item.file_index is not None:
direct_torrentable_items.append(torrent_item)
self.logger.info(f"Found {len(direct_torrentable_items)} direct torrentable items")
return direct_torrentable_items
def get_best_matching(self):
self.logger.info("Finding best matching items")
best_matching = []
self.logger.debug(f"Total items to process: {len(self.__itemsDict)}")
for torrent_item in self.__itemsDict.values():
self.logger.debug(f"Processing item: {torrent_item.raw_title} - Has torrent: {torrent_item.torrent_download is not None}")
if torrent_item.torrent_download is not None:
self.logger.debug(f"Has file index: {torrent_item.file_index is not None}")
if torrent_item.file_index is not None:
best_matching.append(torrent_item)
self.logger.debug("Item added to best matching (has file index)")
else:
best_matching.append(torrent_item)
self.logger.debug("Item added to best matching (magnet link)")
self.logger.info(f"Found {len(best_matching)} best matching items")
return best_matching
def cache_container_items(self):
self.logger.info("Starting cache process for container items")
threading.Thread(target=self.__save_to_cache).start()
def __save_to_cache(self):
self.logger.info("Saving public items to cache")
public_torrents = list(filter(lambda x: x.privacy == "public", self.get_items()))
self.logger.debug(f"Found {len(public_torrents)} public torrents to cache")
cache_public(public_torrents, self.__media)
self.logger.info("Caching process completed")
def update_availability(self, debrid_response, debrid_type, media):
self.logger.info(f"Updating availability for {debrid_type.__name__}")
if debrid_type is RealDebrid:
self.__update_availability_realdebrid(debrid_response, media)
elif debrid_type is AllDebrid:
self.__update_availability_alldebrid(debrid_response, media)
elif debrid_type is Premiumize:
self.__update_availability_premiumize(debrid_response)
else:
self.logger.error(f"Unsupported debrid type: {debrid_type.__name__}")
raise NotImplementedError(f"Debrid type {debrid_type.__name__} not implemented")
def __update_availability_realdebrid(self, response, media):
self.logger.info("Updating availability for RealDebrid")
for info_hash, details in response.items():
if "rd" not in details:
self.logger.debug(f"Skipping hash {info_hash}: no RealDebrid data")
continue
torrent_item: TorrentItem = self.__itemsDict[info_hash]
self.logger.debug(f"Processing {torrent_item.type}: {torrent_item.raw_title}")
files = []
if torrent_item.type == "series":
self.__process_series_files(details, media, torrent_item, files)
else:
self.__process_movie_files(details, files)
self.__update_file_details(torrent_item, files)
self.logger.info("RealDebrid availability update completed")
def __process_series_files(self, details, media, torrent_item, files):
for variants in details["rd"]:
file_found = False
for file_index, file in variants.items():
clean_season = media.season.replace("S", "")
clean_episode = media.episode.replace("E", "")
numeric_season = int(clean_season)
numeric_episode = int(clean_episode)
if season_episode_in_filename(file["filename"], numeric_season, numeric_episode):
self.logger.debug(f"Matching file found: {file['filename']}")
torrent_item.file_index = file_index
torrent_item.file_name = file["filename"]
torrent_item.size = file["filesize"]
torrent_item.availability = True
file_found = True
files.append({
"file_index": file_index,
"title": file["filename"],
"size": file["filesize"]
})
break
if file_found:
break
def __process_movie_files(self, details, files):
for variants in details["rd"]:
for file_index, file in variants.items():
self.logger.debug(f"Adding movie file: {file['filename']}")
files.append({
"file_index": file_index,
"title": file["filename"],
"size": file["filesize"]
})
def __update_availability_alldebrid(self, response, media):
self.logger.info("Updating availability for AllDebrid")
if response["status"] != "success":
self.logger.error(f"AllDebrid API error: {response}")
return
for data in response["data"]["magnets"]:
if not data["instant"]:
self.logger.debug(f"Skipping non-instant magnet: {data['hash']}")
continue
torrent_item: TorrentItem = self.__itemsDict[data["hash"]]
files = []
self.__explore_folders(data["files"], files, 1, torrent_item.type, media.season, media.episode)
self.__update_file_details(torrent_item, files)
self.logger.info("AllDebrid availability update completed")
def __update_availability_premiumize(self, response):
self.logger.info("Updating availability for Premiumize")
if response["status"] != "success":
self.logger.error(f"Premiumize API error: {response}")
return
torrent_items = self.get_items()
for i, is_available in enumerate(response["response"]):
if bool(is_available):
torrent_items[i].availability = response["transcoded"][i]
self.logger.debug(f"Updated availability for item {i}: {torrent_items[i].availability}")
self.logger.info("Premiumize availability update completed")
def __update_file_details(self, torrent_item, files):
if not files:
self.logger.debug(f"No files to update for {torrent_item.raw_title}")
return
file = max(files, key=lambda file: file["size"])
torrent_item.availability = True
torrent_item.file_index = file["file_index"]
torrent_item.file_name = file["title"]
torrent_item.size = file["size"]
self.logger.debug(f"Updated file details for {torrent_item.raw_title}: {file['title']}")
def __build_items_dict_by_infohash(self, items: List[TorrentItem]):
self.logger.info(f"Building items dictionary by infohash ({len(items)} items)")
items_dict = {}
for item in items:
if item.info_hash is not None:
if item.info_hash not in items_dict:
self.logger.debug(f"Adding {item.info_hash} to items dict")
items_dict[item.info_hash] = item
else:
self.logger.debug(f"Skipping duplicate info hash: {item.info_hash}")
self.logger.info(f"Built dictionary with {len(items_dict)} unique items")
return items_dict
def __explore_folders(self, folder, files, file_index, type, season=None, episode=None):
if episode is None or season is None:
return file_index
if type == "series":
for file in folder:
if "e" in file:
file_index = self.__explore_folders(file["e"], files, file_index, type, season, episode)
continue
parsed_file = parse(file["n"])
if season in parsed_file.season and episode in parsed_file.episode:
self.logger.debug(f"Matching series file found: {file['n']}")
files.append({
"file_index": file_index,
"title": file["n"],
"size": file["s"] if "s" in file else 0
})
file_index += 1
elif type == "movie":
file_index = 1
for file in folder:
if "e" in file:
file_index = self.__explore_folders(file["e"], files, file_index, type)
continue
self.logger.debug(f"Adding movie file: {file['n']}")
files.append({
"file_index": file_index,
"title": file["n"],
"size": file["s"] if "s" in file else 0
})
file_index += 1
return file_index