feat(opds): serve comic archives in native format with correct mime types
The OPDS device catalog previously hard-coded every acquisition link as application/epub+zip and always offered kepub/pdf alternate links, which is wrong for comic archives (cbz/cbr/cb7/cbt) and other non-epub media. - Resolve the acquisition mime type from the media item's stored mime_type (falling back to format_mimetype, then epub) instead of assuming epub - Only offer reflowable conversions (kepub for kobo, pdf) for ebooks; comic archives are served as-is in their native format - Derive the native format label (epub/pdf/cbz/cbr/...) from the file path in ListFormats rather than always reporting epub - Add resolveMimeType, isComicArchive, and formatLabelFromPath helpers
This commit is contained in:
+67
-12
@@ -67,6 +67,55 @@ func appendToken(url, token string) string {
|
||||
return url + "?token=" + token
|
||||
}
|
||||
|
||||
// resolveMimeType returns the mime type for a media item, preferring the stored
|
||||
// mime_type, then format_mimetype, and finally falling back to EPUB.
|
||||
func resolveMimeType(mime, formatMime pgtype.Text) string {
|
||||
if mime.Valid && mime.String != "" {
|
||||
return mime.String
|
||||
}
|
||||
if formatMime.Valid && formatMime.String != "" {
|
||||
return formatMime.String
|
||||
}
|
||||
return "application/epub+zip"
|
||||
}
|
||||
|
||||
// isComicArchive reports whether a format group represents a comic/manga
|
||||
// archive (cbz/cbr/cb7/cbt). Comic archives are served in their native format
|
||||
// and should not be offered as EPUB/KEPUB/PDF conversions.
|
||||
func isComicArchive(formatGroup string) bool {
|
||||
return strings.EqualFold(formatGroup, "comic_archive")
|
||||
}
|
||||
|
||||
// formatLabelFromPath derives a short format label (e.g. "epub", "cbz") from a
|
||||
// file path's extension, defaulting to "epub" when it cannot be determined.
|
||||
func formatLabelFromPath(path string) string {
|
||||
ext := strings.ToLower(filepath.Ext(path))
|
||||
switch ext {
|
||||
case ".epub":
|
||||
return "epub"
|
||||
case ".pdf":
|
||||
return "pdf"
|
||||
case ".cbz":
|
||||
return "cbz"
|
||||
case ".cbr":
|
||||
return "cbr"
|
||||
case ".cb7":
|
||||
return "cb7"
|
||||
case ".cbt":
|
||||
return "cbt"
|
||||
case ".mobi":
|
||||
return "mobi"
|
||||
case ".azw", ".azw3":
|
||||
return "azw3"
|
||||
case ".txt":
|
||||
return "txt"
|
||||
case "":
|
||||
return "epub"
|
||||
default:
|
||||
return strings.TrimPrefix(ext, ".")
|
||||
}
|
||||
}
|
||||
|
||||
// GetDeviceCatalog returns the OPDS catalog feed for a device
|
||||
func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error {
|
||||
deviceID := c.Param("deviceId")
|
||||
@@ -189,18 +238,22 @@ func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error {
|
||||
entry.SetSummary(item.Description.String)
|
||||
}
|
||||
|
||||
// Add acquisition links
|
||||
// Add acquisition link using the item's real mime type
|
||||
downloadURL := appendToken(fmt.Sprintf("%s/devices/%s/download/%s", opdsBaseURL, deviceID, bookUUID), token)
|
||||
entry.AddAcquisitionLink(downloadURL, "application/epub+zip")
|
||||
entry.AddAcquisitionLink(downloadURL, resolveMimeType(item.MimeType, item.FormatMimetype))
|
||||
|
||||
if device.DeviceType == "kobo" {
|
||||
kepubURL := downloadURL + "&format=kepub"
|
||||
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")
|
||||
// Only offer reflowable conversions (kepub/pdf) for ebooks; comic
|
||||
// archives are served as-is in their native format.
|
||||
if !isComicArchive(item.FormatGroup) {
|
||||
if device.DeviceType == "kobo" {
|
||||
kepubURL := downloadURL + "&format=kepub"
|
||||
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")
|
||||
}
|
||||
|
||||
pdfURL := downloadURL + "&format=pdf"
|
||||
entry.AddAlternateLink(pdfURL, "application/pdf")
|
||||
}
|
||||
|
||||
pdfURL := downloadURL + "&format=pdf"
|
||||
entry.AddAlternateLink(pdfURL, "application/pdf")
|
||||
|
||||
// Add canonical identifier
|
||||
entry.SetIdentifier(bookUUID)
|
||||
|
||||
@@ -318,9 +371,11 @@ func (h *OPDSHandler) SearchDeviceCatalog(c *echo.Context) error {
|
||||
}
|
||||
|
||||
downloadURL := appendToken(fmt.Sprintf("%s/devices/%s/download/%s", opdsBaseURL, deviceID, bookUUID), token)
|
||||
entry.AddAcquisitionLink(downloadURL, "application/epub+zip")
|
||||
entry.AddAcquisitionLink(downloadURL, resolveMimeType(item.MimeType, item.FormatMimetype))
|
||||
|
||||
if device.DeviceType == "kobo" {
|
||||
// Only offer kepub conversion for ebooks; comic archives are served
|
||||
// as-is in their native format.
|
||||
if !isComicArchive(item.FormatGroup) && device.DeviceType == "kobo" {
|
||||
kepubURL := downloadURL + "&format=kepub"
|
||||
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")
|
||||
}
|
||||
@@ -705,14 +760,14 @@ func (h *OPDSHandler) ListFormats(c *echo.Context) error {
|
||||
|
||||
formatList := []FormatInfo{}
|
||||
|
||||
// Add EPUB format (always available if media item exists)
|
||||
// Add the primary/native format (always available if media item exists)
|
||||
fileSize := int64(0)
|
||||
if mediaItem.FileSize.Valid {
|
||||
fileSize = mediaItem.FileSize.Int64
|
||||
}
|
||||
|
||||
formatList = append(formatList, FormatInfo{
|
||||
FormatType: "epub",
|
||||
FormatType: formatLabelFromPath(mediaItem.FilePath),
|
||||
FilePath: mediaItem.FilePath,
|
||||
FileSha256: func() string {
|
||||
if mediaItem.FileSha256.Valid {
|
||||
|
||||
Reference in New Issue
Block a user