First crack at opds server

pull/3344/head
adechant 8 months ago
parent 0922982d3d
commit 702910f0e4

@ -198,7 +198,27 @@ namespace Readarr.Api.V1.BookFiles
{
if (!_mimeTypeProvider.TryGetContentType(filePath, out var contentType))
{
contentType = string.Format("application/{0}", PathExtensions.GetPathExtension(filePath));
var ext = PathExtensions.GetPathExtension(filePath);
if (ext.Contains("epub"))
{
contentType = "application/epub+zip";
}
else if (ext.Contains("azw"))
{
contentType = "application/vnd.amazon.ebook";
}
else if (ext.Contains("azw"))
{
contentType = "application/x-mobipocket-ebook";
}
else if (ext.Contains("pdf"))
{
contentType = "application/pdf";
}
else
{
contentType = "application/octet-stream";
}
}
return contentType;

@ -0,0 +1,106 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using DryIoc.ImTools;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.StaticFiles;
using NzbDrone.Core.Books;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using Readarr.Http;
namespace Readarr.Api.V1.OPDS
{
[V1ApiController]
public class OPDSController : Controller
{
private readonly IMediaFileService _mediaFileService;
private readonly IAuthorService _authorService;
private readonly IBookService _bookService;
private readonly IEditionService _editionService;
private readonly IContentTypeProvider _mimeTypeProvider;
private readonly IMapCoversToLocal _coverMapper;
public OPDSController(IAuthorService authorService,
IBookService bookService,
IEditionService editionService,
IMapCoversToLocal coverMapper,
IMediaFileService mediaFileService)
{
_authorService = authorService;
_bookService = bookService;
_editionService = editionService;
_mimeTypeProvider = new FileExtensionContentTypeProvider();
_coverMapper = coverMapper;
_mediaFileService = mediaFileService;
}
// /opds
[HttpGet]
public OPDSCatalogResource GetOPDSCatalog([FromQuery] bool availableBooks = true)
{
var metadataTask = Task.Run(() => _authorService.GetAllAuthors());
var books = _bookService.GetAllBooks();
var authors = metadataTask.GetAwaiter().GetResult().ToDictionary(x => x.AuthorMetadataId);
foreach (var book in books)
{
book.Author = authors[book.AuthorMetadataId];
}
var catalog = OPDSResourceMapper.ToOPDSCatalogResource();
catalog.Publications = MapToResource(books, false);
return catalog;
}
// /opds/publications
[HttpGet("publications")]
public OPDSPublicationsResource GetOPDSPublications()
{
var metadataTask = Task.Run(() => _authorService.GetAllAuthors());
var books = _bookService.GetAllBooks();
var authors = metadataTask.GetAwaiter().GetResult().ToDictionary(x => x.AuthorMetadataId);
foreach (var book in books)
{
book.Author = authors[book.AuthorMetadataId];
}
var publications = OPDSResourceMapper.ToOPDSPublicationsResource();
publications.Publications = MapToResource(books, false);
return publications;
}
// /opds/publications/{int:id}
[HttpGet("publications/{id:int}")]
public OPDSPublicationResource GetOPDSPublication(int id)
{
var images = new List<MediaCover>();
var metadataTask = Task.Run(() => _authorService.GetAllAuthors());
var book = _bookService.GetBook(id);
var author = _authorService.GetAuthor(book.AuthorId);
var bookfile = _mediaFileService.GetFilesByBook(book.Id);
_coverMapper.ConvertToLocalUrls(book.Id, MediaCoverEntity.Book, images);
book.Author = author;
return OPDSResourceMapper.ToOPDSPublicationResource(book, bookfile, images);
}
protected List<OPDSPublicationResource> MapToResource(List<Book> books, bool availableBooks)
{
var pubclications = new List<OPDSPublicationResource>();
for (var i = 0; i < books.Count; i++)
{
var images = new List<MediaCover>();
var book = books[i];
var bookfile = _mediaFileService.GetFilesByBook(book.Id);
var edition = _editionService.GetEdition(book.Id);
_coverMapper.ConvertToLocalUrls(book.Id, MediaCoverEntity.Book, images);
var publication = OPDSResourceMapper.ToOPDSPublicationResource(book, bookfile, images);
pubclications.Add(publication);
}
return pubclications;
}
}
}

@ -0,0 +1,234 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NzbDrone.Common.Extensions;
using NzbDrone.Core.Books;
using NzbDrone.Core.Datastore;
using NzbDrone.Core.MediaCover;
using NzbDrone.Core.MediaFiles;
using Readarr.Http.REST;
namespace Readarr.Api.V1.OPDS
{
public class OPDSCatalogMetadataResource : IEmbeddedDocument
{
public string Title { get; set; }
}
public class OPDSLinkResource : IEmbeddedDocument
{
public string Title { get; set; }
public string Rel { get; set; }
public string Href { get; set; }
public string Type { get; set; }
}
public class OPDSPublicationMetadataResource : IEmbeddedDocument
{
public string Title { get; set; }
public string @Type { get; set; }
public string Author { get; set; }
public string Identifier { get; set; }
public string Language { get; set; }
public DateTime Modified { get; set; }
public string Description { get; set; }
}
public class OPDSImageResource : IEmbeddedDocument
{
public string Href { get; set; }
public string Type { get; set; }
public int Height { get; set; }
public int Width { get; set; }
}
public class OPDSPublicationResource : IEmbeddedDocument
{
public OPDSPublicationMetadataResource Metadata { get; set; }
public List<OPDSLinkResource> Links { get; set; }
public List<OPDSImageResource> Images { get; set; }
}
public class OPDSCatalogResource : RestResource
{
public OPDSCatalogMetadataResource Metadata { get; set; }
public List<OPDSLinkResource> Links { get; set; }
public List<OPDSLinkResource> Navigation { get; set; }
public List<OPDSPublicationResource> Publications { get; set; }
}
public class OPDSPublicationsResource : RestResource
{
public OPDSCatalogMetadataResource Metadata { get; set; }
public List<OPDSLinkResource> Links { get; set; }
public List<OPDSPublicationResource> Publications { get; set; }
}
public static class OPDSResourceMapper
{
public static OPDSCatalogResource ToOPDSCatalogResource()
{
var self = new OPDSLinkResource
{
Href = "/opds",
Rel = "self",
Title = "Readarr OPDS Catalog",
Type = "application/opds+json"
};
var links = new List<OPDSLinkResource>();
links.Add(self);
var meta = new OPDSCatalogMetadataResource
{
Title = self.Title
};
return new OPDSCatalogResource
{
Metadata = meta,
Links = links,
Navigation = new List<OPDSLinkResource>(),
Publications = new List<OPDSPublicationResource>()
};
}
public static OPDSPublicationsResource ToOPDSPublicationsResource()
{
var self = new OPDSLinkResource
{
Href = "/opds/publications",
Rel = "self",
Title = "Readarr OPDS Publications",
Type = "application/opds+json"
};
var links = new List<OPDSLinkResource>();
links.Add(self);
var meta = new OPDSCatalogMetadataResource
{
Title = self.Title
};
return new OPDSPublicationsResource
{
Metadata = meta,
Links = links,
Publications = new List<OPDSPublicationResource>()
};
}
public static OPDSPublicationMetadataResource ToOPDSPublicationMetadataResource(Book book)
{
var edition = book.Editions?.Value.Where(x => x.Monitored).SingleOrDefault();
return new OPDSPublicationMetadataResource
{
Title = book.Title,
@Type = "http://schema.org/Book",
Author = book.Author.Value.CleanName,
Identifier = edition.Isbn13,
Language = edition.Language,
Modified = book.ReleaseDate ?? DateTime.Now,
Description = edition.Overview
};
}
public static OPDSImageResource ToOPDSImageResource(MediaCover image)
{
return new OPDSImageResource
{
Href = image.Url,
Type = GetImageContentType(image.Extension)
};
}
public static OPDSPublicationResource ToOPDSPublicationResource(Book book, List<BookFile> files, List<MediaCover> images)
{
var linkResources = new List<OPDSLinkResource>();
var imageResources = new List<OPDSImageResource>();
//Must have link to self
linkResources.Add(new OPDSLinkResource
{
Href = string.Format("/opds/publications/{0}", book.Id),
Rel = "self",
Title = book.Title,
Type = "application/opds-publication+json"
});
//we'll only add the first bookfile (for now)
foreach (var file in files)
{
linkResources.Add(new OPDSLinkResource
{
Href = string.Format("/bookfile/download/{0}", book.Id),
Rel = "http://opds-spec.org/acquisition",
Title = "Readarr OPDS Catalog",
Type = GetContentType(file.Path)
});
break;
}
foreach (var image in images)
{
var imageResource = ToOPDSImageResource(image);
imageResources.Add(imageResource);
}
return new OPDSPublicationResource
{
Metadata = ToOPDSPublicationMetadataResource(book),
Links = linkResources,
Images = imageResources
};
}
private static string GetContentType(string filePath)
{
var contentType = "application/octet-stream";
var ext = PathExtensions.GetPathExtension(filePath);
if (ext.Contains("epub"))
{
contentType = "application/epub+zip";
}
else if (ext.Contains("azw"))
{
contentType = "application/vnd.amazon.ebook";
}
else if (ext.Contains("azw"))
{
contentType = "application/x-mobipocket-ebook";
}
else if (ext.Contains("pdf"))
{
contentType = "application/pdf";
}
return contentType;
}
private static string GetImageContentType(string ext)
{
var contentType = string.Format("application/{0}", ext);
if (ext.Contains("jpg") || ext.Contains("jepg"))
{
contentType = "image/jpeg";
}
else if (ext.Contains("png"))
{
contentType = "image/png";
}
else if (ext.Contains("gif"))
{
contentType = "image/gif";
}
else if (ext.Contains("svg"))
{
contentType = "image/svg+xml";
}
return contentType;
}
}
}
Loading…
Cancel
Save