Skip to content

Commit

Permalink
Add support for RPDB posters & Support fallback posters via MediaFusi…
Browse files Browse the repository at this point in the history
…on posters (#284)
  • Loading branch information
mhdzumair authored Sep 12, 2024
1 parent dedeb31 commit d169b28
Show file tree
Hide file tree
Showing 7 changed files with 227 additions and 30 deletions.
79 changes: 62 additions & 17 deletions api/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from kodi.routes import kodi_router
from metrics.routes import metrics_router
from scrapers.routes import router as scrapers_router
from scrapers.rpdb import update_rpdb_posters, update_rpdb_poster
from streaming_providers import mapper
from streaming_providers.routes import router as streaming_provider_router
from utils import const, crypto, get_json_data, poster, torrent, wrappers
Expand Down Expand Up @@ -261,33 +262,78 @@ async def get_catalog(
genre: str = None,
user_data: schemas.UserData = Depends(get_user_data),
):
skip, genre = parse_genre_and_skip(genre, skip)
cache_key, is_watchlist_catalog = get_cache_key(
catalog_type, catalog_id, skip, genre, user_data
)

if cache_key:
response.headers.update(const.CACHE_HEADERS)
if cached_data := await REDIS_ASYNC_CLIENT.get(cache_key):
return await update_rpdb_posters(
schemas.Metas.model_validate_json(cached_data), user_data, catalog_type
)
else:
response.headers.update(const.NO_CACHE_HEADERS)

metas = await fetch_metas(
catalog_type, catalog_id, genre, skip, user_data, request, is_watchlist_catalog
)

if cache_key:
await REDIS_ASYNC_CLIENT.set(
cache_key,
metas.model_dump_json(exclude_none=True, by_alias=True),
ex=settings.meta_cache_ttl,
)

return await update_rpdb_posters(metas, user_data, catalog_type)


def parse_genre_and_skip(genre: str, skip: int) -> tuple[int, str]:
if genre and "&" in genre:
genre, skip = genre.split("&")
skip = skip.split("=")[1] if "=" in skip else "0"
skip = int(skip) if skip and skip.isdigit() else 0
return skip, genre


def get_cache_key(
catalog_type: str,
catalog_id: str,
skip: int,
genre: str,
user_data: schemas.UserData,
) -> tuple[str, bool]:
cache_key = f"{catalog_type}_{catalog_id}_{skip}_{genre}_catalog"
is_watchlist_catalog = False

if user_data.streaming_provider and catalog_id.startswith(
user_data.streaming_provider.service
):
response.headers.update(const.NO_CACHE_HEADERS)
cache_key = None
is_watchlist_catalog = True
elif catalog_type == "events":
response.headers.update(const.NO_CACHE_HEADERS)
cache_key = None
elif catalog_type in ["movie", "series"]:
cache_key += "_" + "_".join(
user_data.nudity_filter + user_data.certification_filter
)

# Try retrieving the cached data
if cache_key:
if cached_data := await REDIS_ASYNC_CLIENT.get(cache_key):
return json.loads(cached_data)
return cache_key, is_watchlist_catalog


async def fetch_metas(
catalog_type: str,
catalog_id: str,
genre: str,
skip: int,
user_data: schemas.UserData,
request: Request,
is_watchlist_catalog: bool,
) -> schemas.Metas:
metas = schemas.Metas()

if catalog_type == "tv":
metas.metas.extend(
await crud.get_tv_meta_list(
Expand All @@ -309,6 +355,7 @@ async def get_catalog(
genre=genre,
)
)

if (
is_watchlist_catalog
and catalog_type == "movie"
Expand All @@ -323,13 +370,6 @@ async def get_catalog(
)
metas.metas.insert(0, delete_all_meta)

if cache_key:
await REDIS_ASYNC_CLIENT.set(
cache_key,
metas.model_dump_json(exclude_none=True, by_alias=True),
ex=settings.meta_cache_ttl,
)

return metas


Expand Down Expand Up @@ -366,7 +406,10 @@ async def search_meta(
search_query, namespace=get_request_namespace(request)
)

return await crud.process_search_query(search_query, catalog_type, user_data)
metadata = await crud.process_search_query(search_query, catalog_type, user_data)
return await update_rpdb_posters(
schemas.Metas.model_validate(metadata), user_data, catalog_type
)


@app.get(
Expand Down Expand Up @@ -399,10 +442,10 @@ async def get_meta(
# Try retrieving the cached data
cached_data = await REDIS_ASYNC_CLIENT.get(cache_key)
if cached_data:
meta_data = json.loads(cached_data)
meta_data = schemas.MetaItem.model_validate_json(cached_data)
if not meta_data:
raise HTTPException(status_code=404, detail="Meta ID not found.")
return meta_data
return await update_rpdb_poster(meta_data, user_data, catalog_type)

if catalog_type == "movie":
if meta_id.startswith("dl"):
Expand All @@ -425,7 +468,9 @@ async def get_meta(
if not data:
raise HTTPException(status_code=404, detail="Meta ID not found.")

return data
return await update_rpdb_poster(
schemas.MetaItem.model_validate(data), user_data, catalog_type
)


@app.get(
Expand Down
14 changes: 3 additions & 11 deletions db/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,8 @@ async def get_series_meta(meta_id: str, user_data: schemas.UserData):
"$nin": get_filter_certification_values(user_data)
}

poster_path = f"{settings.poster_host_url}/poster/series/{meta_id}.jpg"

# Define the aggregation pipeline
pipeline = [
{"$match": match_filter},
Expand Down Expand Up @@ -561,7 +563,6 @@ async def get_series_meta(meta_id: str, user_data: schemas.UserData):
"id": "$videos.id",
"meta_id": "$_id",
"series_title": "$series_title", # Store series title
"poster": "$poster",
"background": "$background",
},
"video": {"$first": "$videos"},
Expand All @@ -575,7 +576,6 @@ async def get_series_meta(meta_id: str, user_data: schemas.UserData):
"episode": "$video.episode",
"meta_id": "$_id.meta_id",
"series_title": "$_id.series_title",
"poster": "$_id.poster",
"background": "$_id.background",
"video": "$video",
}
Expand All @@ -586,7 +586,6 @@ async def get_series_meta(meta_id: str, user_data: schemas.UserData):
"$group": {
"_id": "$meta_id",
"title": {"$first": "$series_title"},
"poster": {"$first": "$poster"},
"background": {"$first": "$background"},
"videos": {"$push": "$video"},
}
Expand All @@ -598,14 +597,7 @@ async def get_series_meta(meta_id: str, user_data: schemas.UserData):
"_id": "$_id",
"type": {"$literal": "series"},
"title": "$title",
"poster": {
"$concat": [
settings.poster_host_url,
"/poster/series/",
{"$toString": "$_id"},
".jpg",
]
},
"poster": {"$literal": poster_path},
"background": {"$ifNull": ["$background", "$poster"]},
"videos": "$videos",
},
Expand Down
9 changes: 9 additions & 0 deletions db/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ class Config:
populate_by_name = True


class RPDBConfig(BaseModel):
api_key: str = Field(alias="ak")

class Config:
extra = "ignore"
populate_by_name = True


class StreamingProvider(BaseModel):
service: Literal[
"realdebrid",
Expand Down Expand Up @@ -186,6 +194,7 @@ class UserData(BaseModel):
default=list(const.QUALITY_GROUPS.keys()), alias="qf"
)
mediaflow_config: MediaFlowConfig | None = Field(default=None, alias="mfc")
rpdb_config: RPDBConfig | None = Field(default=None, alias="rpc")

@field_validator("selected_resolutions", mode="after")
def validate_selected_resolutions(cls, v):
Expand Down
41 changes: 41 additions & 0 deletions resources/html/configure.html
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,11 @@ <h6>MediaFlow Configuration</h6>
</label>
</div>
<div id="mediaflow_config" style="display: {% if user_data.mediaflow_config %}block{% else %}none{% endif %};">
<div class="mb-2">
<a href="https://github.com/mhdzumair/mediaflow-proxy?tab=readme-ov-file#mediaflow-proxy" target="_blank" rel="noopener">
MediaFlow Setup Guide
</a>
</div>
<div class="mb-2">
<label for="mediaflow_proxy_url">MediaFlow Proxy URL:</label>
<input type="text" class="form-control" id="mediaflow_proxy_url" name="mediaflow_proxy_url"
Expand Down Expand Up @@ -519,6 +524,42 @@ <h6>MediaFlow Configuration</h6>
</div>
</div>
</div>

<div class="mb-3">
<h6>RPDB (RatingPosterDB) Configuration</h6>
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="enable_rpdb" name="enable_rpdb"
{% if user_data.rpdb_config %}checked{% endif %}>
<label class="form-check-label" for="enable_rpdb">
Enable RPDB Posters
<i class="bi bi-question-circle" data-bs-toggle="tooltip" data-bs-placement="top"
title="Enable to use RPDB for rating posters. Poster configurations can be managed in your RPDB account settings."></i>
</label>
</div>
<div id="rpdb_config" style="display: {% if user_data.rpdb_config %}block{% else %}none{% endif %};">
<div class="mb-2">
<a href="https://ratingposterdb.com/api-key/" target="_blank" rel="noopener">Get RPDB API Key</a>
<span class="mx-2">|</span>
<a href="https://manager.ratingposterdb.com" target="_blank" rel="noopener">Configure RPDB Posters Settings</a>
</div>
<p class="text-secondary">
<small>RPDB is an optional <b>Freemium</b> third-party service for enhanced rating posters. By default, MediaFusion generates rating posters using IMDb ratings.</small>
</p>
<div class="mb-2">
<label for="rpdb_api_key">RPDB API Key:</label>
<div class="input-group">
<input type="password" class="form-control" id="rpdb_api_key" name="rpdb_api_key"
value="{{ user_data.rpdb_config.api_key if user_data.rpdb_config else '' }}">
<button class="btn btn-outline-secondary" type="button" id="toggleRPDBApiKey">
<i id="toggleRPDBApiKeyIcon" class="bi bi-eye"></i>
</button>
</div>
<div class="invalid-feedback">
Please enter a valid RPDB API Key.
</div>
</div>
</div>
</div>
</div>

{% if authentication_required %}
Expand Down
17 changes: 16 additions & 1 deletion resources/js/config_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,14 @@ function getUserData() {
validateInput('mediaflow_api_password', mediaflowConfig.api_password.trim() !== '');
}

let rpdbConfig = null;
if (document.getElementById('enable_rpdb').checked) {
rpdbConfig = {
api_key: document.getElementById('rpdb_api_key').value,
};
validateInput('rpdb_api_key', rpdbConfig.api_key.trim() !== '');
}

// Collect and validate other user data
const maxSizeSlider = document.getElementById('max_size_slider');
const maxSizeValue = maxSizeSlider.value;
Expand Down Expand Up @@ -367,6 +375,7 @@ function getUserData() {
quality_filter: selectedQualityFilters,
api_password: apiPassword,
mediaflow_config: mediaflowConfig,
rpdb_config: rpdbConfig,
};
}

Expand Down Expand Up @@ -456,6 +465,11 @@ document.getElementById('enable_mediaflow').addEventListener('change', function
setElementDisplay('mediaflow_config', this.checked ? 'block' : 'none');
});

document.getElementById('enable_rpdb').addEventListener('change', function () {
setElementDisplay('rpdb_config', this.checked ? 'block' : 'none');
});


// Event listener for the slider
document.getElementById('max_size_slider').addEventListener('input', updateSizeOutput);

Expand Down Expand Up @@ -544,6 +558,7 @@ document.addEventListener('DOMContentLoaded', function () {
setupPasswordToggle('qbittorrent_password', 'toggleQbittorrentPassword', 'toggleQbittorrentPasswordIcon');
setupPasswordToggle('webdav_password', 'toggleWebdavPassword', 'toggleWebdavPasswordIcon');
setupPasswordToggle('mediaflow_api_password', 'toggleMediaFlowPassword', 'toggleMediaFlowPasswordIcon');
setupPasswordToggle('rpdb_api_key', 'toggleRPDBApiKey', 'toggleRPDBApiKeyIcon');
});


Expand Down Expand Up @@ -583,7 +598,7 @@ document.addEventListener('DOMContentLoaded', function () {
});


document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('DOMContentLoaded', function () {
const kodiSetupBtn = document.getElementById('kodiSetupBtn');
if (kodiSetupBtn) {
kodiSetupBtn.addEventListener('click', initiateKodiSetup);
Expand Down
Loading

0 comments on commit d169b28

Please sign in to comment.