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
|
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
|
// GetDeviceCatalog returns the OPDS catalog feed for a device
|
||||||
func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error {
|
func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error {
|
||||||
deviceID := c.Param("deviceId")
|
deviceID := c.Param("deviceId")
|
||||||
@@ -189,18 +238,22 @@ func (h *OPDSHandler) GetDeviceCatalog(c *echo.Context) error {
|
|||||||
entry.SetSummary(item.Description.String)
|
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)
|
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 reflowable conversions (kepub/pdf) for ebooks; comic
|
||||||
kepubURL := downloadURL + "&format=kepub"
|
// archives are served as-is in their native format.
|
||||||
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")
|
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
|
// Add canonical identifier
|
||||||
entry.SetIdentifier(bookUUID)
|
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)
|
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"
|
kepubURL := downloadURL + "&format=kepub"
|
||||||
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")
|
entry.AddAlternateLink(kepubURL, "application/vnd.kobo+xml+zip")
|
||||||
}
|
}
|
||||||
@@ -705,14 +760,14 @@ func (h *OPDSHandler) ListFormats(c *echo.Context) error {
|
|||||||
|
|
||||||
formatList := []FormatInfo{}
|
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)
|
fileSize := int64(0)
|
||||||
if mediaItem.FileSize.Valid {
|
if mediaItem.FileSize.Valid {
|
||||||
fileSize = mediaItem.FileSize.Int64
|
fileSize = mediaItem.FileSize.Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
formatList = append(formatList, FormatInfo{
|
formatList = append(formatList, FormatInfo{
|
||||||
FormatType: "epub",
|
FormatType: formatLabelFromPath(mediaItem.FilePath),
|
||||||
FilePath: mediaItem.FilePath,
|
FilePath: mediaItem.FilePath,
|
||||||
FileSha256: func() string {
|
FileSha256: func() string {
|
||||||
if mediaItem.FileSha256.Valid {
|
if mediaItem.FileSha256.Valid {
|
||||||
|
|||||||
Reference in New Issue
Block a user