feat: Enhance Calibre OPDS integration to extract and handle tags, ratings, and publication dates from metadata

This commit is contained in:
JB
2025-11-01 07:27:56 -07:00
parent 2d61674e90
commit d238524cbb
5 changed files with 278 additions and 5 deletions
+52
View File
@@ -2922,6 +2922,16 @@ def calibre_opds_import() -> ResponseReturnValue:
metadata_payload = data.get("metadata") if isinstance(data, Mapping) else None
metadata_overrides: Dict[str, Any] = {}
if isinstance(metadata_payload, Mapping):
def _stringify_metadata_value(value: Any) -> str:
if value is None:
return ""
if isinstance(value, (list, tuple, set)):
parts = [str(item).strip() for item in value if item is not None]
parts = [part for part in parts if part]
return ", ".join(parts)
text = str(value).strip()
return text
raw_series = metadata_payload.get("series") or metadata_payload.get("series_name")
series_name = str(raw_series or "").strip()
if series_name:
@@ -2940,6 +2950,48 @@ def calibre_opds_import() -> ResponseReturnValue:
metadata_overrides.setdefault("series_position", series_index_text)
metadata_overrides.setdefault("series_sequence", series_index_text)
metadata_overrides.setdefault("book_number", series_index_text)
tags_value = metadata_payload.get("tags") or metadata_payload.get("keywords")
if tags_value:
tags_text = _stringify_metadata_value(tags_value)
if tags_text:
metadata_overrides.setdefault("tags", tags_text)
metadata_overrides.setdefault("keywords", tags_text)
metadata_overrides.setdefault("genre", tags_text)
description_value = metadata_payload.get("description") or metadata_payload.get("summary")
if description_value:
description_text = _stringify_metadata_value(description_value)
if description_text:
metadata_overrides.setdefault("description", description_text)
metadata_overrides.setdefault("summary", description_text)
published_value = metadata_payload.get("published") or metadata_payload.get("publication_date")
if published_value:
published_text = _stringify_metadata_value(published_value)
if published_text:
metadata_overrides.setdefault("published", published_text)
metadata_overrides.setdefault("publication_date", published_text)
publication_year = metadata_payload.get("publication_year") or metadata_payload.get("year")
if publication_year:
year_text = _stringify_metadata_value(publication_year)
if year_text:
metadata_overrides.setdefault("publication_year", year_text)
metadata_overrides.setdefault("year", year_text)
rating_value = metadata_payload.get("rating")
if rating_value is not None:
rating_text = _stringify_metadata_value(rating_value)
if rating_text:
metadata_overrides.setdefault("rating", rating_text)
rating_max = metadata_payload.get("rating_max")
if rating_max is not None:
rating_max_text = _stringify_metadata_value(rating_max)
if rating_max_text:
metadata_overrides.setdefault("rating_max", rating_max_text)
for key, value in metadata_payload.items():
if value is None:
continue
text_value = _stringify_metadata_value(value)
if not text_value:
continue
metadata_overrides.setdefault(str(key), text_value)
stored_settings = _stored_integration_config("calibre_opds")
if not stored_settings or not _coerce_bool(stored_settings.get("enabled"), False):
+16
View File
@@ -355,6 +355,22 @@ def _build_audiobookshelf_metadata(job: Job) -> Dict[str, Any]:
"seriesSequence": series_sequence,
"isbn": _first_nonempty(tags.get("isbn"), tags.get("asin")),
}
published_date = _first_nonempty(tags.get("published"), tags.get("publication_date"), tags.get("date"))
if published_date:
data["publishedDate"] = published_date
rating_text = _first_nonempty(tags.get("rating"), tags.get("my_rating"))
if rating_text:
try:
data["rating"] = float(str(rating_text).strip())
except ValueError:
pass
rating_max_text = _first_nonempty(tags.get("rating_max"), tags.get("rating_scale"))
if rating_max_text:
try:
data["ratingMax"] = float(str(rating_max_text).strip())
except ValueError:
pass
# Remove empty values
cleaned: Dict[str, Any] = {}
for key, value in data.items():
+45
View File
@@ -314,6 +314,21 @@ if (modal && browser) {
header.appendChild(seriesMeta);
}
if (entry.rating !== null && entry.rating !== undefined && entry.rating !== '') {
const ratingMeta = document.createElement('p');
ratingMeta.className = 'opds-browser__meta';
const ratingMax = entry.rating_max ?? entry.ratingMax ?? 5;
ratingMeta.textContent = `Rating: ${entry.rating}${ratingMax ? ` / ${ratingMax}` : ''}`;
header.appendChild(ratingMeta);
}
if (Array.isArray(entry.tags) && entry.tags.length > 0) {
const tagsMeta = document.createElement('p');
tagsMeta.className = 'opds-browser__meta';
tagsMeta.textContent = `Tags: ${entry.tags.join(', ')}`;
header.appendChild(tagsMeta);
}
item.appendChild(header);
const summarySource = entry.summary || entry?.alternate?.title || entry?.download?.title || '';
@@ -436,6 +451,36 @@ if (modal && browser) {
metadata.series_position = seriesIndex;
metadata.book_number = seriesIndex;
}
if (Array.isArray(entry.tags) && entry.tags.length > 0) {
const tagsText = entry.tags.join(', ');
metadata.tags = tagsText;
metadata.keywords = tagsText;
metadata.genre = tagsText;
}
if (typeof entry.summary === 'string' && entry.summary.trim()) {
metadata.description = entry.summary;
metadata.summary = entry.summary;
}
if (entry.rating !== null && entry.rating !== undefined && entry.rating !== '') {
metadata.rating = String(entry.rating);
}
if (entry.rating_max !== null && entry.rating_max !== undefined && entry.rating_max !== '') {
metadata.rating_max = String(entry.rating_max);
}
if (entry.published) {
metadata.published = entry.published;
metadata.publication_date = entry.published;
try {
const publishedDate = new Date(entry.published);
if (!Number.isNaN(publishedDate.getTime())) {
const year = String(publishedDate.getUTCFullYear());
metadata.publication_year = year;
metadata.year = year;
}
} catch (error) {
// Ignore invalid date parsing issues
}
}
if (Object.keys(metadata).length > 0) {
requestPayload.metadata = metadata;
}