mirror of
https://github.com/Viren070/MediaFusion.git
synced 2025-12-01 23:21:11 +01:00
078635a172
Update configurations and logging Refactor IMDB movie retrieval Update deployment settings Remove unused deployment Update resource limits for dramatiq worker
328 lines
11 KiB
Python
328 lines
11 KiB
Python
import asyncio
|
|
import math
|
|
import re
|
|
|
|
import requests
|
|
from imdb import Cinemagoer
|
|
|
|
from db.config import settings
|
|
from db.models import TorrentStreams, MediaFusionTVMetaData
|
|
from db.schemas import Stream, UserData
|
|
from streaming_providers.alldebrid.utils import (
|
|
update_ad_cache_status,
|
|
fetch_downloaded_info_hashes_from_ad,
|
|
)
|
|
from streaming_providers.debridlink.utils import (
|
|
update_dl_cache_status,
|
|
fetch_downloaded_info_hashes_from_dl,
|
|
)
|
|
from streaming_providers.offcloud.utils import (
|
|
update_oc_cache_status,
|
|
fetch_downloaded_info_hashes_from_oc,
|
|
)
|
|
from streaming_providers.pikpak.utils import (
|
|
update_pikpak_cache_status,
|
|
fetch_downloaded_info_hashes_from_pikpak,
|
|
)
|
|
from streaming_providers.premiumize.utils import (
|
|
update_pm_cache_status,
|
|
fetch_downloaded_info_hashes_from_premiumize,
|
|
)
|
|
from streaming_providers.realdebrid.utils import (
|
|
update_rd_cache_status,
|
|
fetch_downloaded_info_hashes_from_rd,
|
|
)
|
|
from streaming_providers.seedr.utils import (
|
|
update_seedr_cache_status,
|
|
fetch_downloaded_info_hashes_from_seedr,
|
|
)
|
|
from streaming_providers.torbox.utils import (
|
|
update_torbox_cache_status,
|
|
fetch_downloaded_info_hashes_from_torbox,
|
|
)
|
|
|
|
ia = Cinemagoer()
|
|
|
|
|
|
async def filter_and_sort_streams(
|
|
streams: list[TorrentStreams], user_data: UserData
|
|
) -> list[TorrentStreams]:
|
|
# Filter streams by selected catalogs and resolutions
|
|
filtered_streams = [
|
|
stream
|
|
for stream in streams
|
|
if any(catalog in stream.catalog for catalog in user_data.selected_catalogs)
|
|
and stream.resolution in user_data.selected_resolutions
|
|
and stream.size <= user_data.max_size
|
|
]
|
|
|
|
if not filtered_streams:
|
|
return []
|
|
|
|
# Define provider-specific cache update functions
|
|
cache_update_functions = {
|
|
"alldebrid": update_ad_cache_status,
|
|
"debridlink": update_dl_cache_status,
|
|
"offcloud": update_oc_cache_status,
|
|
"pikpak": update_pikpak_cache_status,
|
|
"realdebrid": update_rd_cache_status,
|
|
"seedr": update_seedr_cache_status,
|
|
"torbox": update_torbox_cache_status,
|
|
"premiumize": update_pm_cache_status,
|
|
}
|
|
|
|
# Update cache status based on provider
|
|
if user_data.streaming_provider:
|
|
if cache_update_function := cache_update_functions.get(
|
|
user_data.streaming_provider.service
|
|
):
|
|
if asyncio.iscoroutinefunction(cache_update_function):
|
|
await cache_update_function(streams, user_data)
|
|
else:
|
|
await asyncio.to_thread(cache_update_function, streams, user_data)
|
|
|
|
# Sort streams by cache status, creation date, and size
|
|
return sorted(
|
|
filtered_streams, key=lambda x: (x.cached, x.size, x.created_at), reverse=True
|
|
)
|
|
|
|
|
|
async def parse_stream_data(
|
|
streams: list[TorrentStreams],
|
|
user_data: UserData,
|
|
secret_str: str,
|
|
season: int = None,
|
|
episode: int = None,
|
|
) -> list[Stream]:
|
|
stream_list = []
|
|
|
|
streams = await filter_and_sort_streams(streams, user_data)
|
|
|
|
for stream_data in streams:
|
|
quality_detail = " - ".join(
|
|
filter(
|
|
None,
|
|
[
|
|
stream_data.quality,
|
|
stream_data.resolution,
|
|
stream_data.codec,
|
|
stream_data.audio,
|
|
],
|
|
)
|
|
)
|
|
|
|
episode_data = stream_data.get_episode(season, episode)
|
|
|
|
if user_data.streaming_provider:
|
|
streaming_provider = user_data.streaming_provider.service.title()
|
|
if stream_data.cached:
|
|
streaming_provider += " ⚡️"
|
|
else:
|
|
streaming_provider += " ⏳"
|
|
else:
|
|
streaming_provider = "Torrent ⏳"
|
|
|
|
seeders = f"👤 {stream_data.seeders}" if stream_data.seeders else None
|
|
|
|
description_parts = [
|
|
quality_detail,
|
|
convert_bytes_to_readable(
|
|
episode_data.size or stream_data.size
|
|
if episode_data
|
|
else stream_data.size
|
|
),
|
|
seeders,
|
|
" + ".join(stream_data.languages),
|
|
stream_data.source,
|
|
]
|
|
description = ", ".join(filter(lambda x: bool(x), description_parts))
|
|
|
|
stream_details = {
|
|
"name": f"MediaFusion {streaming_provider}",
|
|
"description": description,
|
|
"infoHash": stream_data.id,
|
|
"fileIdx": episode_data.file_index
|
|
if episode_data
|
|
else stream_data.file_index,
|
|
"behaviorHints": {"bingeGroup": f"MediaFusion-{quality_detail}"},
|
|
}
|
|
|
|
if user_data.streaming_provider:
|
|
base_proxy_url = f"{settings.host_url}/{secret_str}/streaming_provider?info_hash={stream_data.id}"
|
|
if episode_data:
|
|
base_proxy_url += f"&season={season}&episode={episode}"
|
|
stream_details["url"] = base_proxy_url
|
|
stream_details.pop("infoHash")
|
|
stream_details.pop("fileIdx")
|
|
stream_details["behaviorHints"]["notWebReady"] = True
|
|
|
|
stream_list.append(Stream(**stream_details))
|
|
|
|
return stream_list
|
|
|
|
|
|
def convert_bytes_to_readable(size_bytes: int) -> str:
|
|
"""
|
|
Convert a size in bytes into a more human-readable format.
|
|
"""
|
|
if not size_bytes:
|
|
return "0B"
|
|
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
|
i = int(math.floor(math.log(size_bytes, 1024)))
|
|
p = math.pow(1024, i)
|
|
s = round(size_bytes / p, 2)
|
|
return f"💾 {s} {size_name[i]}"
|
|
|
|
|
|
def convert_size_to_bytes(size_str: str) -> int:
|
|
"""Convert size string to bytes."""
|
|
match = re.match(r"(\d+(?:\.\d+)?)\s*(GB|MB)", size_str, re.IGNORECASE)
|
|
if match:
|
|
size, unit = match.groups()
|
|
size = float(size)
|
|
return int(size * 1024**3) if "GB" in unit.upper() else int(size * 1024**2)
|
|
return 0
|
|
|
|
|
|
def get_catalogs(catalog: str, languages: list[str]) -> list[str]:
|
|
base_catalogs = ["hdrip", "tcrip", "dubbed", "series"]
|
|
base_catalog = catalog.split("_")[1]
|
|
|
|
if base_catalog not in base_catalogs:
|
|
return [catalog]
|
|
|
|
# Generate the catalog for each supported language
|
|
return [f"{lang.lower()}_{base_catalog}" for lang in languages]
|
|
|
|
|
|
def search_imdb(title: str, year: int, retry: int = 5) -> dict:
|
|
try:
|
|
result = ia.search_movie(f"{title} {year}")
|
|
except Exception:
|
|
return search_imdb(title, year, retry - 1) if retry > 0 else {}
|
|
for movie in result:
|
|
if movie.get("year") == year and movie.get("title").lower() in title.lower():
|
|
imdb_id = f"tt{movie.movieID}"
|
|
poster = f"https://live.metahub.space/poster/small/{imdb_id}/img"
|
|
if requests.get(poster).status_code == 200:
|
|
return {
|
|
"imdb_id": imdb_id,
|
|
"poster": poster.replace("small", "medium"),
|
|
"background": f"https://live.metahub.space/background/medium/{imdb_id}/img",
|
|
"title": movie.myTitle,
|
|
}
|
|
poster = movie.get("full-size cover url")
|
|
return {
|
|
"imdb_id": imdb_id,
|
|
"poster": poster,
|
|
"background": poster,
|
|
"title": movie.myTitle,
|
|
}
|
|
return {}
|
|
|
|
|
|
def get_imdb_data(video_id: str) -> tuple[str, str]:
|
|
try:
|
|
movie = ia.get_movie(video_id.removeprefix("tt"), info="main")
|
|
except Exception:
|
|
return "", ""
|
|
return movie.get("title"), movie.get("year")
|
|
|
|
|
|
def parse_tv_stream_data(tv_data: MediaFusionTVMetaData) -> list[Stream]:
|
|
stream_list = []
|
|
for stream in tv_data.streams:
|
|
if stream.behaviorHints.get("is_redirect", False):
|
|
response = requests.get(
|
|
stream.url,
|
|
headers=stream.behaviorHints["proxyHeaders"]["request"],
|
|
allow_redirects=False,
|
|
)
|
|
if response.status_code == 302:
|
|
stream.url = response.headers["Location"]
|
|
stream_list.append(
|
|
Stream(
|
|
name="MediaFusion",
|
|
description=f"{stream.name}, {tv_data.tv_language}, {stream.source}",
|
|
url=stream.url,
|
|
ytId=stream.ytId,
|
|
behaviorHints=stream.behaviorHints,
|
|
)
|
|
)
|
|
|
|
return stream_list
|
|
|
|
|
|
async def fetch_downloaded_info_hashes(user_data: UserData) -> list[str]:
|
|
fetch_downloaded_info_hashes_functions = {
|
|
"alldebrid": fetch_downloaded_info_hashes_from_ad,
|
|
"debridlink": fetch_downloaded_info_hashes_from_dl,
|
|
"offcloud": fetch_downloaded_info_hashes_from_oc,
|
|
"pikpak": fetch_downloaded_info_hashes_from_pikpak,
|
|
"realdebrid": fetch_downloaded_info_hashes_from_rd,
|
|
"seedr": fetch_downloaded_info_hashes_from_seedr,
|
|
"torbox": fetch_downloaded_info_hashes_from_torbox,
|
|
"premiumize": fetch_downloaded_info_hashes_from_premiumize,
|
|
}
|
|
|
|
if fetch_downloaded_info_hashes_function := fetch_downloaded_info_hashes_functions.get(
|
|
user_data.streaming_provider.service
|
|
):
|
|
if asyncio.iscoroutinefunction(fetch_downloaded_info_hashes_function):
|
|
downloaded_info_hashes = await fetch_downloaded_info_hashes_function(
|
|
user_data
|
|
)
|
|
else:
|
|
downloaded_info_hashes = await asyncio.to_thread(
|
|
fetch_downloaded_info_hashes_function, user_data
|
|
)
|
|
|
|
return downloaded_info_hashes
|
|
|
|
return []
|
|
|
|
|
|
def generate_manifest(manifest: dict, user_data: UserData) -> dict:
|
|
resources = manifest["resources"]
|
|
if user_data.enable_catalogs:
|
|
manifest["catalogs"] = [
|
|
cat
|
|
for cat in manifest["catalogs"]
|
|
if cat["id"] in user_data.selected_catalogs
|
|
]
|
|
else:
|
|
manifest["catalogs"] = []
|
|
resources = [
|
|
{
|
|
"name": "stream",
|
|
"types": ["movie", "series", "tv"],
|
|
"idPrefixes": ["tt", "mf"],
|
|
}
|
|
]
|
|
|
|
if user_data.streaming_provider:
|
|
provider_name = user_data.streaming_provider.service.title()
|
|
manifest["name"] += f" {provider_name}"
|
|
manifest["id"] += f".{provider_name.lower()}"
|
|
|
|
if user_data.streaming_provider.enable_watchlist_catalogs:
|
|
watchlist_catalogs = [
|
|
{
|
|
"id": f"{provider_name.lower()}_watchlist_movies",
|
|
"name": f"{provider_name} Watchlist",
|
|
"type": "movie",
|
|
"extra": [{"name": "skip", "isRequired": False}],
|
|
},
|
|
{
|
|
"id": f"{provider_name.lower()}_watchlist_series",
|
|
"name": f"{provider_name} Watchlist",
|
|
"type": "series",
|
|
"extra": [{"name": "skip", "isRequired": False}],
|
|
},
|
|
]
|
|
manifest["catalogs"] = watchlist_catalogs + manifest["catalogs"]
|
|
resources = manifest["resources"]
|
|
|
|
manifest["resources"] = resources
|
|
return manifest
|