feat: faster than ever, better results

This commit is contained in:
Goldy
2024-08-31 16:32:14 +02:00
parent e5cc4250da
commit 19f9cc48ae
12 changed files with 238 additions and 188 deletions
+3 -13
View File
@@ -1,3 +1,4 @@
import PTT
import RTN
from fastapi import APIRouter, Request
@@ -25,20 +26,9 @@ indexers = settings.INDEXER_MANAGER_INDEXERS
web_config = {
"indexers": [indexer.replace(" ", "_").lower() for indexer in indexers],
"languages": [
language.replace(" ", "_")
for language in RTN.patterns.language_code_mapping.keys()
],
"resolutions": [
"360p",
"480p",
"576p",
"720p",
"1080p",
"1440p",
"2160p",
"4K",
"Unknown",
language for language in PTT.parse.LANGUAGES_TRANSLATION_TABLE.values()
],
"resolutions": [resolution.value for resolution in RTN.models.Resolution],
"resultFormat": ["Title", "Metadata", "Size", "Tracker", "Languages"],
}
+22 -23
View File
@@ -79,9 +79,12 @@ async def stream(request: Request, b64config: str, type: str, id: str):
)
metadata = await get_metadata.json()
element = metadata["d"][
0 if metadata["d"][0]["id"] not in ["/imdbpicks/summer-watch-guide", "/emmys"] else 1
0
if metadata["d"][0]["id"]
not in ["/imdbpicks/summer-watch-guide", "/emmys"]
else 1
]
for element in metadata["d"]:
if element["id"] == id:
break
@@ -166,7 +169,7 @@ async def stream(request: Request, b64config: str, type: str, id: str):
data = hash_data["data"]
results.append(
{
"name": f"[{debrid_extension}⚡] Comet {data['resolution'][0] if data['resolution'] != [] else 'Unknown'}",
"name": f"[{debrid_extension}⚡] Comet {data['resolution']}",
"title": format_title(data, config),
"torrentTitle": (
data["torrent_title"]
@@ -241,7 +244,7 @@ async def stream(request: Request, b64config: str, type: str, id: str):
)
else:
logger.info(
f"No indexer {'manager ' if not indexer_manager_type else ' '}{'selected by user' if indexer_manager_type else 'defined'} for {log_name}"
f"No indexer {'manager ' if not indexer_manager_type else ''}{'selected by user' if indexer_manager_type else 'defined'} for {log_name}"
)
if settings.ZILEAN_URL:
@@ -339,23 +342,24 @@ async def stream(request: Request, b64config: str, type: str, id: str):
ranked_files = set()
for hash in files:
# try:
ranked_file = rtn.rank(
files[hash]["title"],
hash, # , correct_title=name, remove_trash=True
)
# except:
# continue
try:
ranked_file = rtn.rank(
files[hash]["title"],
hash, # , correct_title=name, remove_trash=True
)
except:
pass
ranked_files.add(ranked_file)
sorted_ranked_files = sort_torrents(ranked_files)
len_sorted_ranked_files = len(sorted_ranked_files)
logger.info(
f"{len(sorted_ranked_files)} cached files found on {config['debridService']} for {log_name}"
f"{len_sorted_ranked_files} cached files found on {config['debridService']} for {log_name}"
)
if len(sorted_ranked_files) == 0:
if len_sorted_ranked_files == 0:
return {"streams": []}
sorted_ranked_files = {
@@ -409,7 +413,7 @@ async def stream(request: Request, b64config: str, type: str, id: str):
data = hash_data["data"]
results.append(
{
"name": f"[{debrid_extension}⚡] Comet {data['resolution'][0] if data['resolution'] != [] else 'Unknown'}",
"name": f"[{debrid_extension}⚡] Comet {data['resolution']}",
"title": format_title(data, config),
"torrentTitle": data["torrent_title"],
"torrentSize": data["torrent_size"],
@@ -453,19 +457,14 @@ async def playback(request: Request, b64config: str, hash: str, index: str):
config = config_check(b64config)
if not config:
return FileResponse("comet/assets/invalidconfig.mp4")
if (
settings.PROXY_DEBRID_STREAM
and settings.PROXY_DEBRID_STREAM_PASSWORD
== config["debridStreamProxyPassword"]
and settings.PROXY_DEBRID_STREAM_PASSWORD == config["debridStreamProxyPassword"]
and config["debridApiKey"] == ""
):
config["debridService"] = (
settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE
)
config["debridApiKey"] = (
settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY
)
config["debridService"] = settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_SERVICE
config["debridApiKey"] = settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY
async with aiohttp.ClientSession() as session:
# Check for cached download link
+3 -3
View File
@@ -82,14 +82,14 @@ class AllDebrid:
continue
filename_parsed = parse(filename)
if episode not in filename_parsed.episode:
if episode not in filename_parsed.episodes:
continue
if kitsu:
if filename_parsed.season:
if filename_parsed.seasons:
continue
else:
if season not in filename_parsed.season:
if season not in filename_parsed.seasons:
continue
files[magnet["hash"]] = {
+3 -3
View File
@@ -70,14 +70,14 @@ class DebridLink:
continue
filename_parsed = parse(filename)
if episode not in filename_parsed.episode:
if episode not in filename_parsed.episodes:
continue
if kitsu:
if filename_parsed.season:
if filename_parsed.seasons:
continue
else:
if season not in filename_parsed.season:
if season not in filename_parsed.seasons:
continue
files[hash] = {
+5 -5
View File
@@ -90,14 +90,14 @@ class Premiumize:
filename = filenames[index]
filename_parsed = parse(filename)
if episode not in filename_parsed.episode:
if episode not in filename_parsed.episodes:
continue
if kitsu:
if filename_parsed.season:
if filename_parsed.seasons:
continue
else:
if season not in filename_parsed.season:
if season not in filename_parsed.seasons:
continue
files[hashes[index]] = {
@@ -156,8 +156,8 @@ class Premiumize:
if season is not None:
filename_parsed = parse(filename)
if (
season in filename_parsed.season
and episode in filename_parsed.episode
season in filename_parsed.seasons
and episode in filename_parsed.episodes
):
return file["link"]
+3 -3
View File
@@ -75,14 +75,14 @@ class RealDebrid:
continue
filename_parsed = parse(filename)
if episode not in filename_parsed.episode:
if episode not in filename_parsed.episodes:
continue
if kitsu:
if filename_parsed.season:
if filename_parsed.seasons:
continue
else:
if season not in filename_parsed.season:
if season not in filename_parsed.seasons:
continue
files[hash] = {
+3 -3
View File
@@ -73,14 +73,14 @@ class TorBox:
continue
filename_parsed = parse(filename)
if episode not in filename_parsed.episode:
if episode not in filename_parsed.episodes:
continue
if kitsu:
if filename_parsed.season:
if filename_parsed.seasons:
continue
else:
if season not in filename_parsed.season:
if season not in filename_parsed.seasons:
continue
files[torrent["hash"]] = {
+2 -3
View File
@@ -129,12 +129,11 @@ def start_log():
"COMET",
f"Indexer Manager: {settings.INDEXER_MANAGER_TYPE}|{settings.INDEXER_MANAGER_URL} - Timeout: {settings.INDEXER_MANAGER_TIMEOUT}s",
)
logger.log("COMET", f"Indexers: {', '.join(settings.INDEXER_MANAGER_INDEXERS)}")
logger.log("COMET", f"Get Torrent Timeout: {settings.GET_TORRENT_TIMEOUT}s")
else:
logger.log("COMET", "Indexer Manager: False")
logger.log("COMET", f"Indexers: {', '.join(settings.INDEXER_MANAGER_INDEXERS)}")
logger.log("COMET", f"Get Torrent Timeout: {settings.GET_TORRENT_TIMEOUT}s")
if settings.ZILEAN_URL:
logger.log(
"COMET",
+62 -2
View File
@@ -593,6 +593,55 @@
</sl-alert>
<script type="module">
const languagesEmojis = {
"English": "🇬🇧",
"Japanese": "🇯🇵",
"Chinese": "🇨🇳",
"Russian": "🇷🇺",
"Arabic": "🇸🇦",
"Portuguese": "🇵🇹",
"Spanish": "🇪🇸",
"French": "🇫🇷",
"German": "🇩🇪",
"Italian": "🇮🇹",
"Korean": "🇰🇷",
"Hindi": "🇮🇳",
"Bengali": "🇧🇩",
"Punjabi": "🇵🇰",
"Marathi": "🇮🇳",
"Gujarati": "🇮🇳",
"Tamil": "🇮🇳",
"Telugu": "🇮🇳",
"Kannada": "🇮🇳",
"Malayalam": "🇮🇳",
"Thai": "🇹🇭",
"Vietnamese": "🇻🇳",
"Indonesian": "🇮🇩",
"Turkish": "🇹🇷",
"Hebrew": "🇮🇱",
"Persian": "🇮🇷",
"Ukrainian": "🇺🇦",
"Greek": "🇬🇷",
"Lithuanian": "🇱🇹",
"Latvian": "🇱🇻",
"Estonian": "🇪🇪",
"Polish": "🇵🇱",
"Czech": "🇨🇿",
"Slovak": "🇸🇰",
"Hungarian": "🇭🇺",
"Romanian": "🇷🇴",
"Bulgarian": "🇧🇬",
"Serbian": "🇷🇸",
"Croatian": "🇭🇷",
"Slovenian": "🇸🇮",
"Dutch": "🇳🇱",
"Danish": "🇩🇰",
"Finnish": "🇫🇮",
"Swedish": "🇸🇪",
"Norwegian": "🇳🇴",
"Malay": "🇲🇾",
};
let defaultLanguages = [];
let defaultResolutions = [];
let defaultResultFormat = [];
@@ -632,10 +681,21 @@
options.forEach(option => {
const optionElement = document.createElement("sl-option");
optionElement.value = option;
optionElement.textContent = option;
if (selectId === "languages") {
// For languages, prepend the flag emoji if it exists
const flag = languagesEmojis[option] || '';
optionElement.textContent = `${flag} ${option}`;
} else {
// For other selects, just use the option text as is
optionElement.textContent = option;
}
selectElement.appendChild(optionElement);
});
selectElement.value = options;
// Set the default value
selectElement.value = options; // Assuming first option as default
}
const installButton = document.querySelector("#install");
+128 -106
View File
@@ -4,6 +4,8 @@ import json
import re
import aiohttp
import bencodepy
import PTT
import asyncio
from RTN import parse, title_match
from curl_cffi import requests
@@ -12,57 +14,58 @@ from comet.utils.logger import logger
from comet.utils.models import settings, ConfigModel
languages_emojis = {
"multi_subs": "🌐",
"multi_audio": "🌎",
"dual_audio": "🔉",
"english": "🇬🇧",
"japanese": "🇯🇵",
"korean": "🇰🇷",
"taiwanese": "🇹🇼",
"chinese": "🇨🇳",
"french": "🇫🇷",
"latino": "💃🏻",
"spanish": "🇪🇸",
"portuguese": "🇵🇹",
"italian": "🇮🇹",
"greek": "🇬🇷",
"german": "🇩🇪",
"russian": "🇷🇺",
"ukrainian": "🇺🇦",
"hindi": "🇮🇳",
"telugu": "🇮🇳",
"tamil": "🇮🇳",
"lithuanian": "🇱🇹",
"latvian": "🇱🇻",
"estonian": "🇪🇪",
"polish": "🇵🇱",
"czech": "🇨🇿",
"slovakian": "🇸🇰",
"hungarian": "🇭🇺",
"romanian": "🇷🇴",
"bulgarian": "🇧🇬",
"serbian": "🇷🇸",
"croatian": "🇭🇷",
"slovenian": "🇸🇮",
"dutch": "🇳🇱",
"danish": "🇩🇰",
"finnish": "🇫🇮",
"swedish": "🇸🇪",
"norwegian": "🇳🇴",
"arabic": "🇸🇦",
"turkish": "🇹🇷",
"vietnamese": "🇻🇳",
"indonesian": "🇮🇩",
"thai": "🇹🇭",
"malay": "🇲🇾",
"hebrew": "🇮🇱",
"persian": "🇮🇷",
"bengali": "🇧🇩",
"dubbed": "🌎",
"en": "🇬🇧", # English
"ja": "🇯🇵", # Japanese
"zh": "🇨🇳", # Chinese
"ru": "🇷🇺", # Russian
"ar": "🇸🇦", # Arabic
"pt": "🇵🇹", # Portuguese
"es": "🇪🇸", # Spanish
"fr": "🇫🇷", # French
"de": "🇩🇪", # German
"it": "🇮🇹", # Italian
"ko": "🇰🇷", # Korean
"hi": "🇮🇳", # Hindi
"bn": "🇧🇩", # Bengali
"pa": "🇵🇰", # Punjabi
"mr": "🇮🇳", # Marathi
"gu": "🇮🇳", # Gujarati
"ta": "🇮🇳", # Tamil
"te": "🇮🇳", # Telugu
"kn": "🇮🇳", # Kannada
"ml": "🇮🇳", # Malayalam
"th": "🇹🇭", # Thai
"vi": "🇻🇳", # Vietnamese
"id": "🇮🇩", # Indonesian
"tr": "🇹🇷", # Turkish
"he": "🇮🇱", # Hebrew
"fa": "🇮🇷", # Persian
"uk": "🇺🇦", # Ukrainian
"el": "🇬🇷", # Greek
"lt": "🇱🇹", # Lithuanian
"lv": "🇱🇻", # Latvian
"et": "🇪🇪", # Estonian
"pl": "🇵🇱", # Polish
"cs": "🇨🇿", # Czech
"sk": "🇸🇰", # Slovak
"hu": "🇭🇺", # Hungarian
"ro": "🇷🇴", # Romanian
"bg": "🇧🇬", # Bulgarian
"sr": "🇷🇸", # Serbian
"hr": "🇭🇷", # Croatian
"sl": "🇸🇮", # Slovenian
"nl": "🇳🇱", # Dutch
"da": "🇩🇰", # Danish
"fi": "🇫🇮", # Finnish
"sv": "🇸🇪", # Swedish
"no": "🇳🇴", # Norwegian
"ms": "🇲🇾", # Malay
}
def get_language_emoji(language: str):
language_formatted = language.replace(" ", "_").lower()
language_formatted = language.lower()
return (
languages_emojis[language_formatted]
if language_formatted in languages_emojis
@@ -275,19 +278,36 @@ async def get_indexer_manager(
results = []
try:
indexers = [indexer.replace("_", " ") for indexer in indexers]
timeout = aiohttp.ClientTimeout(total=settings.INDEXER_MANAGER_TIMEOUT)
if indexer_manager_type == "jackett":
response = await session.get(
f"{settings.INDEXER_MANAGER_URL}/api/v2.0/indexers/all/results?apikey={settings.INDEXER_MANAGER_API_KEY}&Query={query}&Tracker[]={'&Tracker[]='.join(indexer for indexer in indexers)}",
timeout=timeout,
)
response = await response.json()
for result in response["Results"]:
results.append(result)
async def fetch_jackett_results(
session: aiohttp.ClientSession, indexer: str, query: str
):
try:
async with session.get(
f"{settings.INDEXER_MANAGER_URL}/api/v2.0/indexers/all/results?apikey={settings.INDEXER_MANAGER_API_KEY}&Query={query}&Tracker[]={indexer}",
timeout=aiohttp.ClientTimeout(
total=settings.INDEXER_MANAGER_TIMEOUT
),
) as response:
response_json = await response.json()
return response_json.get("Results", [])
except Exception as e:
logger.warning(
f"Exception while fetching Jackett results for indexer {indexer}: {e}"
)
return []
if indexer_manager_type == "prowlarr":
tasks = [
fetch_jackett_results(session, indexer, query) for indexer in indexers
]
all_results = await asyncio.gather(*tasks)
for result_set in all_results:
results.extend(result_set)
elif indexer_manager_type == "prowlarr":
get_indexers = await session.get(
f"{settings.INDEXER_MANAGER_URL}/api/v1/indexer",
headers={"X-Api-Key": settings.INDEXER_MANAGER_API_KEY},
@@ -433,7 +453,7 @@ async def filter(torrents: list, name: str, year: int):
results.append((index, False))
continue
if year and parsed.year != 0 and year != parsed.year:
if year and parsed.year and year != parsed.year:
results.append((index, False))
continue
@@ -480,16 +500,19 @@ async def get_torrent_hash(session: aiohttp.ClientSession, torrent: tuple):
def get_balanced_hashes(hashes: dict, config: dict):
max_results = config["maxResults"]
max_size = config["maxSize"]
config_resolutions = config["resolutions"]
config_languages = {
language.replace("_", " ").capitalize() for language in config["languages"]
}
include_all_languages = "All" in config_languages
include_all_resolutions = "All" in config_resolutions
include_unknown_resolution = (
include_all_resolutions or "Unknown" in config_resolutions
)
config_resolutions = [resolution.lower() for resolution in config["resolutions"]]
include_all_resolutions = "all" in config_resolutions
languages = [language.lower() for language in config["languages"]]
include_all_languages = "all" in languages
if not include_all_languages:
config_languages = [
code
for code, name in PTT.parse.LANGUAGES_TRANSLATION_TABLE.items()
if name.lower() in languages
]
hashes_by_resolution = {}
for hash, hash_data in hashes.items():
@@ -498,26 +521,18 @@ def get_balanced_hashes(hashes: dict, config: dict):
if max_size != 0 and hash_info["size"] > max_size:
continue
if (
not include_all_languages
and not hash_info["is_multi_audio"]
and not any(lang in hash_info["language"] for lang in config_languages)
if not include_all_languages and not any(
lang in hash_info["languages"] for lang in config_languages
):
continue
resolution = hash_info["resolution"]
if not resolution:
if not include_unknown_resolution:
continue
resolution_key = "Unknown"
else:
resolution_key = resolution[0]
if not include_all_resolutions and resolution_key not in config_resolutions:
continue
if not include_all_resolutions and resolution not in config_resolutions:
continue
if resolution_key not in hashes_by_resolution:
hashes_by_resolution[resolution_key] = []
hashes_by_resolution[resolution_key].append(hash)
if resolution not in hashes_by_resolution:
hashes_by_resolution[resolution] = []
hashes_by_resolution[resolution].append(hash)
total_resolutions = len(hashes_by_resolution)
if max_results == 0 or total_resolutions == 0:
@@ -549,49 +564,56 @@ def get_balanced_hashes(hashes: dict, config: dict):
def format_metadata(data: dict):
extras = []
if data["hdr"] != "":
extras.append(data["hdr"] if data["hdr"] != "DV" else "Dolby Vision")
if data["remux"]:
extras.append("Remux")
if data["proper"]:
extras.append("Proper")
if data["repack"]:
extras.append("Repack")
if data["upscaled"]:
extras.append("Upscaled")
if data["remastered"]:
extras.append("Remastered")
if data["directorsCut"]:
extras.append("Director's Cut")
if data["extended"]:
extras.append("Extended")
return " | ".join(extras)
if data["quality"]:
extras.append(data["quality"])
if data["hdr"]:
extras.extend(data["hdr"])
if data["codec"]:
extras.append(data["codec"])
if data["audio"]:
extras.extend(data["audio"])
if data["channels"]:
extras.extend(data["channels"])
if data["bit_depth"]:
extras.append(data["bit_depth"])
if data["network"]:
extras.append(data["network"])
if data["group"]:
extras.append(data["group"])
return "|".join(extras)
def format_title(data: dict, config: dict):
title = ""
if "Title" in config["resultFormat"] or "All" in config["resultFormat"]:
if "All" in config["resultFormat"] or "Title" in config["resultFormat"]:
title += f"{data['title']}\n"
if "Metadata" in config["resultFormat"] or "All" in config["resultFormat"]:
if "All" in config["resultFormat"] or "Metadata" in config["resultFormat"]:
metadata = format_metadata(data)
if metadata != "":
title += f"💿 {metadata}\n"
if "Size" in config["resultFormat"] or "All" in config["resultFormat"]:
if "All" in config["resultFormat"] or "Size" in config["resultFormat"]:
title += f"💾 {bytes_to_size(data['size'])} "
if "Tracker" in config["resultFormat"] or "All" in config["resultFormat"]:
if "All" in config["resultFormat"] or "Tracker" in config["resultFormat"]:
title += f"🔎 {data['tracker'] if 'tracker' in data else '?'}"
if "Languages" in config["resultFormat"] or "All" in config["resultFormat"]:
languages = data["language"]
if "All" in config["resultFormat"] or "Languages" in config["resultFormat"]:
languages = data["languages"]
formatted_languages = (
"/".join(get_language_emoji(language) for language in languages)
if languages
else get_language_emoji("multi_audio")
if data["is_multi_audio"]
else get_language_emoji("dubbed")
if data["dubbed"]
else None
)
languages_str = "\n" + formatted_languages if formatted_languages else ""
title += f"{languages_str}"
if title == "":
# Without this, Streamio shows SD as the result, which is confusing
title = "Empty result format configuration"
return title
+2 -23
View File
@@ -6,7 +6,7 @@ from typing import List, Optional
from databases import Database
from pydantic import BaseModel, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict
from RTN import RTN, BaseRankingModel, SettingsModel
from RTN import RTN, BestRanking, SettingsModel
class AppSettings(BaseSettings):
@@ -107,29 +107,8 @@ class ConfigModel(BaseModel):
return v
class BestOverallRanking(BaseRankingModel):
uhd: int = 100
fhd: int = 90
hd: int = 80
sd: int = 70
dolby_video: int = 100
hdr: int = 80
hdr10: int = 90
dts_x: int = 100
dts_hd: int = 80
dts_hd_ma: int = 90
atmos: int = 90
truehd: int = 60
ddplus: int = 40
aac: int = 30
ac3: int = 20
remux: int = 150
bluray: int = 120
webdl: int = 90
rtn_settings = SettingsModel()
rtn_ranking = BestOverallRanking()
rtn_ranking = BestRanking()
# For use anywhere
rtn = RTN(settings=rtn_settings, ranking_model=rtn_ranking)
+2 -1
View File
@@ -23,7 +23,8 @@ orjson = "*"
asyncpg = "*"
aiosqlite = "*"
jinja2 = "*"
rank-torrent-name = "^0.2.23"
rank-torrent-name = "*"
parsett = "*"
[tool.poetry.group.dev.dependencies]