mirror of
https://github.com/Readarr/Readarr
synced 2025-12-25 17:54:14 +01:00
New: Allow retagging book files with calibre
This commit is contained in:
parent
e29b0c318e
commit
7072b913a6
23 changed files with 16476 additions and 100 deletions
|
|
@ -258,12 +258,12 @@ class AuthorDetails extends Component {
|
|||
onPress={this.onOrganizePress}
|
||||
/>
|
||||
|
||||
{/* <PageToolbarButton */}
|
||||
{/* label="Preview Retag" */}
|
||||
{/* iconName={icons.RETAG} */}
|
||||
{/* isDisabled={!hasBookFiles} */}
|
||||
{/* onPress={this.onRetagPress} */}
|
||||
{/* /> */}
|
||||
<PageToolbarButton
|
||||
label="Preview Retag"
|
||||
iconName={icons.RETAG}
|
||||
isDisabled={!hasBookFiles}
|
||||
onPress={this.onRetagPress}
|
||||
/>
|
||||
|
||||
<PageToolbarButton
|
||||
label="Manual Import"
|
||||
|
|
|
|||
|
|
@ -6,3 +6,25 @@
|
|||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.searchForNewBookLabelContainer {
|
||||
display: flex;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.searchForNewBookLabel {
|
||||
margin-right: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.searchForNewBookContainer {
|
||||
composes: container from '~Components/Form/CheckInput.css';
|
||||
|
||||
flex: 0 1 0;
|
||||
}
|
||||
|
||||
.searchForNewBookInput {
|
||||
composes: input from '~Components/Form/CheckInput.css';
|
||||
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
|
|
@ -10,58 +11,114 @@ import ModalHeader from 'Components/Modal/ModalHeader';
|
|||
import { icons, kinds } from 'Helpers/Props';
|
||||
import styles from './RetagAuthorModalContent.css';
|
||||
|
||||
function RetagAuthorModalContent(props) {
|
||||
const {
|
||||
authorNames,
|
||||
onModalClose,
|
||||
onRetagAuthorPress
|
||||
} = props;
|
||||
class RetagAuthorModalContent extends Component {
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Retag Selected Author
|
||||
</ModalHeader>
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
<ModalBody>
|
||||
<Alert>
|
||||
Tip: To preview the tags that will be written... select "Cancel" then click any author name and use the
|
||||
<Icon
|
||||
className={styles.retagIcon}
|
||||
name={icons.RETAG}
|
||||
/>
|
||||
</Alert>
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
<div className={styles.message}>
|
||||
Are you sure you want to re-tag all files in the {authorNames.length} selected author?
|
||||
</div>
|
||||
<ul>
|
||||
{
|
||||
authorNames.map((authorName) => {
|
||||
return (
|
||||
<li key={authorName}>
|
||||
{authorName}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</ModalBody>
|
||||
this.state = {
|
||||
updateCovers: false,
|
||||
embedMetadata: false
|
||||
};
|
||||
}
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
//
|
||||
// Listeners
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={onRetagAuthorPress}
|
||||
>
|
||||
Retag
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
onCheckInputChange = ({ name, value }) => {
|
||||
this.setState({ [name]: value });
|
||||
}
|
||||
|
||||
onRetagAuthorPress = () => {
|
||||
this.props.onRetagAuthorPress(this.state.updateCovers, this.state.embedMetadata);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
authorNames,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Retag Selected Author
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Alert>
|
||||
Tip: To preview the tags that will be written... select "Cancel" then click any author name and use the
|
||||
<Icon
|
||||
className={styles.retagIcon}
|
||||
name={icons.RETAG}
|
||||
/>
|
||||
</Alert>
|
||||
|
||||
<div className={styles.message}>
|
||||
Are you sure you want to re-tag all files in the {authorNames.length} selected author?
|
||||
</div>
|
||||
<ul>
|
||||
{
|
||||
authorNames.map((authorName) => {
|
||||
return (
|
||||
<li key={authorName}>
|
||||
{authorName}
|
||||
</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<label className={styles.searchForNewBookLabelContainer}>
|
||||
<span className={styles.searchForNewBookLabel}>
|
||||
Update Covers
|
||||
</span>
|
||||
|
||||
<CheckInput
|
||||
containerClassName={styles.searchForNewBookContainer}
|
||||
className={styles.searchForNewBookInput}
|
||||
name="updateCovers"
|
||||
value={this.state.updateCovers}
|
||||
onChange={this.onCheckInputChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className={styles.searchForNewBookLabelContainer}>
|
||||
<span className={styles.searchForNewBookLabel}>
|
||||
Embed Metadata
|
||||
</span>
|
||||
|
||||
<CheckInput
|
||||
containerClassName={styles.searchForNewBookContainer}
|
||||
className={styles.searchForNewBookInput}
|
||||
name="embedMetadata"
|
||||
value={this.state.embedMetadata}
|
||||
onChange={this.onCheckInputChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Button onPress={onModalClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
kind={kinds.DANGER}
|
||||
onPress={this.onRetagAuthorPress}
|
||||
>
|
||||
Retag
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
RetagAuthorModalContent.propTypes = {
|
||||
|
|
|
|||
|
|
@ -36,10 +36,12 @@ class RetagAuthorModalContentConnector extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onRetagAuthorPress = () => {
|
||||
onRetagAuthorPress = (updateCovers, embedMetadata) => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.RETAG_AUTHOR,
|
||||
authorIds: this.props.authorIds
|
||||
authorIds: this.props.authorIds,
|
||||
updateCovers,
|
||||
embedMetadata
|
||||
});
|
||||
|
||||
this.props.onModalClose(true);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import TextTruncate from 'react-text-truncate';
|
|||
import AuthorHistoryTable from 'Author/History/AuthorHistoryTable';
|
||||
import BookCover from 'Book/BookCover';
|
||||
import DeleteBookModal from 'Book/Delete/DeleteBookModal';
|
||||
// import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
|
||||
import EditBookModalConnector from 'Book/Edit/EditBookModalConnector';
|
||||
import BookFileEditorTable from 'BookFile/Editor/BookFileEditorTable';
|
||||
import HeartRating from 'Components/HeartRating';
|
||||
|
|
@ -27,6 +26,7 @@ import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
|
|||
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
|
||||
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
|
||||
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
|
||||
import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
|
||||
import fonts from 'Styles/Variables/fonts';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import stripHtml from 'Utilities/String/stripHtml';
|
||||
|
|
@ -138,7 +138,7 @@ class BookDetails extends Component {
|
|||
|
||||
const {
|
||||
isOrganizeModalOpen,
|
||||
// isRetagModalOpen,
|
||||
isRetagModalOpen,
|
||||
isEditBookModalOpen,
|
||||
isDeleteBookModalOpen,
|
||||
selectedTabIndex
|
||||
|
|
@ -445,12 +445,12 @@ class BookDetails extends Component {
|
|||
onModalClose={this.onOrganizeModalClose}
|
||||
/>
|
||||
|
||||
{/* <RetagPreviewModalConnector */}
|
||||
{/* isOpen={isRetagModalOpen} */}
|
||||
{/* authorId={author.id} */}
|
||||
{/* bookId={id} */}
|
||||
{/* onModalClose={this.onRetagModalClose} */}
|
||||
{/* /> */}
|
||||
<RetagPreviewModalConnector
|
||||
isOpen={isRetagModalOpen}
|
||||
authorId={author.id}
|
||||
bookId={id}
|
||||
onModalClose={this.onRetagModalClose}
|
||||
/>
|
||||
|
||||
<EditBookModalConnector
|
||||
isOpen={isEditBookModalOpen}
|
||||
|
|
|
|||
|
|
@ -22,3 +22,25 @@
|
|||
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.searchForNewBookLabelContainer {
|
||||
display: flex;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.searchForNewBookLabel {
|
||||
margin-right: 8px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.searchForNewBookContainer {
|
||||
composes: container from '~Components/Form/CheckInput.css';
|
||||
|
||||
flex: 0 1 0;
|
||||
}
|
||||
|
||||
.searchForNewBookInput {
|
||||
composes: input from '~Components/Form/CheckInput.css';
|
||||
|
||||
margin-top: 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Alert from 'Components/Alert';
|
||||
import CheckInput from 'Components/Form/CheckInput';
|
||||
import Button from 'Components/Link/Button';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
|
|
@ -37,7 +36,9 @@ class RetagPreviewModalContent extends Component {
|
|||
allSelected: false,
|
||||
allUnselected: false,
|
||||
lastToggled: null,
|
||||
selectedState: {}
|
||||
selectedState: {},
|
||||
updateCovers: false,
|
||||
embedMetadata: false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -61,8 +62,12 @@ class RetagPreviewModalContent extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
onCheckInputChange = ({ name, value }) => {
|
||||
this.setState({ [name]: value });
|
||||
}
|
||||
|
||||
onRetagPress = () => {
|
||||
this.props.onRetagPress(this.getSelectedIds());
|
||||
this.props.onRetagPress(this.getSelectedIds(), this.state.updateCovers, this.state.embedMetadata);
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -110,12 +115,6 @@ class RetagPreviewModalContent extends Component {
|
|||
{
|
||||
!isFetching && isPopulated && !!items.length &&
|
||||
<div>
|
||||
<Alert>
|
||||
<div>
|
||||
MusicBrainz identifiers will also be added to the files; these are not shown below.
|
||||
</div>
|
||||
</Alert>
|
||||
|
||||
<div className={styles.previews}>
|
||||
{
|
||||
items.map((item) => {
|
||||
|
|
@ -148,6 +147,34 @@ class RetagPreviewModalContent extends Component {
|
|||
/>
|
||||
}
|
||||
|
||||
<label className={styles.searchForNewBookLabelContainer}>
|
||||
<span className={styles.searchForNewBookLabel}>
|
||||
Update Covers
|
||||
</span>
|
||||
|
||||
<CheckInput
|
||||
containerClassName={styles.searchForNewBookContainer}
|
||||
className={styles.searchForNewBookInput}
|
||||
name="updateCovers"
|
||||
value={this.state.updateCovers}
|
||||
onChange={this.onCheckInputChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label className={styles.searchForNewBookLabelContainer}>
|
||||
<span className={styles.searchForNewBookLabel}>
|
||||
Embed Metadata
|
||||
</span>
|
||||
|
||||
<CheckInput
|
||||
containerClassName={styles.searchForNewBookContainer}
|
||||
className={styles.searchForNewBookInput}
|
||||
name="embedMetadata"
|
||||
value={this.state.embedMetadata}
|
||||
onChange={this.onCheckInputChange}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -49,10 +49,12 @@ class RetagPreviewModalContentConnector extends Component {
|
|||
//
|
||||
// Listeners
|
||||
|
||||
onRetagPress = (files) => {
|
||||
onRetagPress = (files, updateCovers, embedMetadata) => {
|
||||
this.props.executeCommand({
|
||||
name: commandNames.RETAG_FILES,
|
||||
authorId: this.props.authorId,
|
||||
updateCovers,
|
||||
embedMetadata,
|
||||
files
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,107 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NzbDrone.Core.Books.Calibre
|
||||
{
|
||||
public class CalibreBook
|
||||
{
|
||||
[JsonProperty("format_metadata")]
|
||||
public Dictionary<string, CalibreBookFormat> Formats { get; set; }
|
||||
[JsonProperty("application_id")]
|
||||
public int Id { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
|
||||
public List<string> Authors { get; set; }
|
||||
|
||||
[JsonProperty("author_sort")]
|
||||
public string AuthorSort { get; set; }
|
||||
|
||||
public string Title { get; set; }
|
||||
[JsonConverter(typeof(CalibreDateConverter))]
|
||||
public DateTime? PubDate { get; set; }
|
||||
|
||||
public string Publisher { get; set; }
|
||||
|
||||
public List<string> Languages { get; set; }
|
||||
|
||||
public string Comments { get; set; }
|
||||
|
||||
public double Rating { get; set; }
|
||||
|
||||
public Dictionary<string, string> Identifiers { get; set; }
|
||||
|
||||
public string Series { get; set; }
|
||||
|
||||
[JsonProperty("series_index")]
|
||||
public string Position { get; set; }
|
||||
public double? Position { get; set; }
|
||||
|
||||
public Dictionary<string, string> Identifiers { get; set; }
|
||||
[JsonProperty("format_metadata")]
|
||||
public Dictionary<string, CalibreBookFormat> Formats { get; set; }
|
||||
|
||||
public Dictionary<string, Tuple<string, string>> Diff(CalibreBook other)
|
||||
{
|
||||
var output = new Dictionary<string, Tuple<string, string>>();
|
||||
|
||||
if (Title != other.Title)
|
||||
{
|
||||
output.Add("Title", Tuple.Create(Title, other.Title));
|
||||
}
|
||||
|
||||
if (!Authors.SequenceEqual(other.Authors))
|
||||
{
|
||||
var oldValue = Authors.Any() ? string.Join(" / ", Authors) : null;
|
||||
var newValue = other.Authors.Any() ? string.Join(" / ", other.Authors) : null;
|
||||
|
||||
output.Add("Author", Tuple.Create(oldValue, newValue));
|
||||
}
|
||||
|
||||
var oldDate = PubDate.HasValue ? PubDate.Value.ToString("MMM-yyyy") : null;
|
||||
var newDate = other.PubDate.HasValue ? other.PubDate.Value.ToString("MMM-yyyy") : null;
|
||||
if (oldDate != newDate)
|
||||
{
|
||||
output.Add("PubDate", Tuple.Create(oldDate, newDate));
|
||||
}
|
||||
|
||||
if (Publisher != other.Publisher)
|
||||
{
|
||||
output.Add("Publisher", Tuple.Create(Publisher, other.Publisher));
|
||||
}
|
||||
|
||||
if (!Languages.OrderBy(x => x).SequenceEqual(other.Languages.OrderBy(x => x)))
|
||||
{
|
||||
output.Add("Languages", Tuple.Create(string.Join(" / ", Languages), string.Join(" / ", other.Languages)));
|
||||
}
|
||||
|
||||
if (Comments != other.Comments)
|
||||
{
|
||||
output.Add("Comments", Tuple.Create(Comments, other.Comments));
|
||||
}
|
||||
|
||||
if (Rating != other.Rating)
|
||||
{
|
||||
output.Add("Rating", Tuple.Create(Rating.ToString(), other.Rating.ToString()));
|
||||
}
|
||||
|
||||
if (!Identifiers.Where(x => x.Value != null).OrderBy(x => x.Key).SequenceEqual(
|
||||
other.Identifiers.Where(x => x.Value != null).OrderBy(x => x.Key)))
|
||||
{
|
||||
output.Add("Identifiers", Tuple.Create(
|
||||
string.Join(" / ", Identifiers.Where(x => x.Value != null).OrderBy(x => x.Key)),
|
||||
string.Join(" / ", other.Identifiers.Where(x => x.Value != null).OrderBy(x => x.Key))));
|
||||
}
|
||||
|
||||
if (Series != other.Series)
|
||||
{
|
||||
output.Add("Series", Tuple.Create(Series, other.Series));
|
||||
}
|
||||
|
||||
if (Position != other.Position)
|
||||
{
|
||||
output.Add("Series Index", Tuple.Create(Position.ToString(), other.Position.ToString()));
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
public class CalibreBookFormat
|
||||
|
|
|
|||
24
src/NzbDrone.Core/Books/Calibre/CalibreDateConverter.cs
Normal file
24
src/NzbDrone.Core/Books/Calibre/CalibreDateConverter.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace NzbDrone.Core.Books.Calibre
|
||||
{
|
||||
public class CalibreDateConverter : IsoDateTimeConverter
|
||||
{
|
||||
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (reader.Value as string == "None")
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return base.ReadJson(reader, objectType, existingValue, serializer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -25,11 +25,12 @@ public interface ICalibreProxy
|
|||
void DeleteBook(BookFile book, CalibreSettings settings);
|
||||
void AddFormat(BookFile file, CalibreSettings settings);
|
||||
void RemoveFormats(int calibreId, IEnumerable<string> formats, CalibreSettings settings);
|
||||
void SetFields(BookFile file, CalibreSettings settings);
|
||||
void SetFields(BookFile file, CalibreSettings settings, bool updateCover = true, bool embed = false);
|
||||
CalibreBookData GetBookData(int calibreId, CalibreSettings settings);
|
||||
long ConvertBook(int calibreId, CalibreConversionOptions options, CalibreSettings settings);
|
||||
List<string> GetAllBookFilePaths(CalibreSettings settings);
|
||||
CalibreBook GetBook(int calibreId, CalibreSettings settings);
|
||||
List<CalibreBook> GetBooks(List<int> calibreId, CalibreSettings settings);
|
||||
void Test(CalibreSettings settings);
|
||||
}
|
||||
|
||||
|
|
@ -41,7 +42,6 @@ public class CalibreProxy : ICalibreProxy
|
|||
private readonly IMapCoversToLocal _mediaCoverService;
|
||||
private readonly IRemotePathMappingService _pathMapper;
|
||||
private readonly Logger _logger;
|
||||
private readonly ICached<CalibreBook> _bookCache;
|
||||
|
||||
public CalibreProxy(IHttpClient httpClient,
|
||||
IMapCoversToLocal mediaCoverService,
|
||||
|
|
@ -52,7 +52,6 @@ public CalibreProxy(IHttpClient httpClient,
|
|||
_httpClient = httpClient;
|
||||
_mediaCoverService = mediaCoverService;
|
||||
_pathMapper = pathMapper;
|
||||
_bookCache = cacheManager.GetCache<CalibreBook>(GetType());
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -140,11 +139,11 @@ public void RemoveFormats(int calibreId, IEnumerable<string> formats, CalibreSet
|
|||
ExecuteSetFields(calibreId, payload, settings);
|
||||
}
|
||||
|
||||
public void SetFields(BookFile file, CalibreSettings settings)
|
||||
public void SetFields(BookFile file, CalibreSettings settings, bool updateCover = true, bool embed = false)
|
||||
{
|
||||
var edition = file.Edition.Value;
|
||||
var book = edition.Book.Value;
|
||||
var serieslink = book.SeriesLinks.Value.FirstOrDefault();
|
||||
var serieslink = book.SeriesLinks.Value.FirstOrDefault(x => x.Series.Value.Title.IsNotNullOrWhiteSpace());
|
||||
|
||||
var series = serieslink?.Series.Value;
|
||||
double? seriesIndex = null;
|
||||
|
|
@ -176,12 +175,12 @@ public void SetFields(BookFile file, CalibreSettings settings)
|
|||
{
|
||||
Title = edition.Title,
|
||||
Authors = new List<string> { file.Author.Value.Name },
|
||||
Cover = image,
|
||||
Cover = updateCover ? image : null,
|
||||
PubDate = book.ReleaseDate,
|
||||
Publisher = edition.Publisher,
|
||||
Languages = edition.Language,
|
||||
Languages = edition.Language.CanonicalizeLanguage(),
|
||||
Comments = edition.Overview,
|
||||
Rating = edition.Ratings.Value * 2,
|
||||
Rating = (int)(edition.Ratings.Value * 2),
|
||||
Identifiers = new Dictionary<string, string>
|
||||
{
|
||||
{ "isbn", edition.Isbn13 },
|
||||
|
|
@ -194,6 +193,11 @@ public void SetFields(BookFile file, CalibreSettings settings)
|
|||
};
|
||||
|
||||
ExecuteSetFields(file.CalibreId, payload, settings);
|
||||
|
||||
if (embed)
|
||||
{
|
||||
EmbedMetadata(file.CalibreId, settings);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExecuteSetFields(int id, CalibreChangesPayload payload, CalibreSettings settings)
|
||||
|
|
@ -208,6 +212,18 @@ private void ExecuteSetFields(int id, CalibreChangesPayload payload, CalibreSett
|
|||
_httpClient.Execute(request);
|
||||
}
|
||||
|
||||
private void EmbedMetadata(int id, CalibreSettings settings)
|
||||
{
|
||||
var request = GetBuilder($"cdb/cmd/embed_metadata", settings)
|
||||
.AddQueryParam("library_id", settings.Library)
|
||||
.Post()
|
||||
.SetHeader("Content-Type", "application/json")
|
||||
.Build();
|
||||
|
||||
request.SetContent($"[{id}, null]");
|
||||
_httpClient.Execute(request);
|
||||
}
|
||||
|
||||
public CalibreBookData GetBookData(int calibreId, CalibreSettings settings)
|
||||
{
|
||||
try
|
||||
|
|
@ -268,10 +284,37 @@ public CalibreBook GetBook(int calibreId, CalibreSettings settings)
|
|||
}
|
||||
}
|
||||
|
||||
public List<CalibreBook> GetBooks(List<int> calibreIds, CalibreSettings settings)
|
||||
{
|
||||
var builder = GetBuilder($"ajax/books/{settings.Library}", settings);
|
||||
builder.LogResponseContent = false;
|
||||
builder.AddQueryParam("ids", calibreIds.ConcatToString(","));
|
||||
|
||||
var request = builder.Build();
|
||||
|
||||
try
|
||||
{
|
||||
var response = _httpClient.Get<Dictionary<int, CalibreBook>>(request);
|
||||
var result = response.Resource.Values.ToList();
|
||||
|
||||
foreach (var book in result)
|
||||
{
|
||||
foreach (var format in book.Formats.Values)
|
||||
{
|
||||
format.Path = _pathMapper.RemapRemoteToLocal(settings.Host, new OsPath(format.Path)).FullPath;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (HttpException ex)
|
||||
{
|
||||
throw new CalibreException("Unable to connect to Calibre library: {0}", ex, ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> GetAllBookFilePaths(CalibreSettings settings)
|
||||
{
|
||||
_bookCache.Clear();
|
||||
|
||||
var ids = GetAllBookIds(settings);
|
||||
var result = new List<string>();
|
||||
|
||||
|
|
@ -297,8 +340,6 @@ public List<string> GetAllBookFilePaths(CalibreSettings settings)
|
|||
|
||||
var localPath = _pathMapper.RemapRemoteToLocal(settings.Host, new OsPath(remotePath)).FullPath;
|
||||
result.Add(localPath);
|
||||
|
||||
_bookCache.Set(localPath, book, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
}
|
||||
catch (HttpException ex)
|
||||
|
|
|
|||
78
src/NzbDrone.Core/Books/Calibre/Extensions.cs
Normal file
78
src/NzbDrone.Core/Books/Calibre/Extensions.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using NzbDrone.Common.Extensions;
|
||||
|
||||
namespace NzbDrone.Core.Books.Calibre
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
private static readonly Dictionary<string, string> TwoToThree;
|
||||
private static readonly Dictionary<string, string> ByThree;
|
||||
private static readonly Dictionary<string, string> NameMap;
|
||||
|
||||
static Extensions()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
TwoToThree = InitializeDictionary(assembly, "2to3.json");
|
||||
ByThree = InitializeDictionary(assembly, "by3.json");
|
||||
NameMap = InitializeDictionary(assembly, "name_map.json");
|
||||
}
|
||||
|
||||
private static Dictionary<string, string> InitializeDictionary(Assembly assembly, string resource)
|
||||
{
|
||||
var resources = assembly.GetManifestResourceNames();
|
||||
var stream = assembly.GetManifestResourceStream(resources.Single(x => x.EndsWith(resource)));
|
||||
|
||||
string data;
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
data = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
return JsonSerializer.Deserialize<Dictionary<string, string>>(data);
|
||||
}
|
||||
|
||||
// Translated from https://github.com/kovidgoyal/calibre/blob/ba06b7452228cfde9114e4735fb8d5785fba4955/src/calibre/utils/localization.py#L430
|
||||
public static string CanonicalizeLanguage(this string raw)
|
||||
{
|
||||
if (raw.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
raw = raw.ToLowerInvariant().Trim();
|
||||
|
||||
if (raw.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
raw = raw.Replace('_', '-').Split('-', 2)[0].Trim();
|
||||
|
||||
if (raw.IsNullOrWhiteSpace())
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (raw.Length == 2)
|
||||
{
|
||||
if (TwoToThree.TryGetValue(raw, out var lang))
|
||||
{
|
||||
return lang;
|
||||
}
|
||||
}
|
||||
else if (raw.Length == 3)
|
||||
{
|
||||
if (ByThree.ContainsKey(raw))
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
return NameMap.TryGetValue(raw, out var langByName) ? langByName : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
186
src/NzbDrone.Core/Books/Calibre/Languages/2to3.json
Normal file
186
src/NzbDrone.Core/Books/Calibre/Languages/2to3.json
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
{
|
||||
"aa": "aar",
|
||||
"ab": "abk",
|
||||
"af": "afr",
|
||||
"ak": "aka",
|
||||
"am": "amh",
|
||||
"ar": "ara",
|
||||
"an": "arg",
|
||||
"as": "asm",
|
||||
"av": "ava",
|
||||
"ae": "ave",
|
||||
"ay": "aym",
|
||||
"az": "aze",
|
||||
"ba": "bak",
|
||||
"bm": "bam",
|
||||
"be": "bel",
|
||||
"bn": "ben",
|
||||
"bi": "bis",
|
||||
"bo": "bod",
|
||||
"bs": "bos",
|
||||
"br": "bre",
|
||||
"bg": "bul",
|
||||
"ca": "cat",
|
||||
"cs": "ces",
|
||||
"ch": "cha",
|
||||
"ce": "che",
|
||||
"cu": "chu",
|
||||
"cv": "chv",
|
||||
"kw": "cor",
|
||||
"co": "cos",
|
||||
"cr": "cre",
|
||||
"cy": "cym",
|
||||
"da": "dan",
|
||||
"de": "deu",
|
||||
"dv": "div",
|
||||
"dz": "dzo",
|
||||
"el": "ell",
|
||||
"en": "eng",
|
||||
"eo": "epo",
|
||||
"et": "est",
|
||||
"eu": "eus",
|
||||
"ee": "ewe",
|
||||
"fo": "fao",
|
||||
"fa": "fas",
|
||||
"fj": "fij",
|
||||
"fi": "fin",
|
||||
"fr": "fra",
|
||||
"fy": "fry",
|
||||
"ff": "ful",
|
||||
"gd": "gla",
|
||||
"ga": "gle",
|
||||
"gl": "glg",
|
||||
"gv": "glv",
|
||||
"gn": "grn",
|
||||
"gu": "guj",
|
||||
"ht": "hat",
|
||||
"ha": "hau",
|
||||
"sh": "hbs",
|
||||
"he": "heb",
|
||||
"hz": "her",
|
||||
"hi": "hin",
|
||||
"ho": "hmo",
|
||||
"hr": "hrv",
|
||||
"hu": "hun",
|
||||
"hy": "hye",
|
||||
"ig": "ibo",
|
||||
"io": "ido",
|
||||
"ii": "iii",
|
||||
"iu": "iku",
|
||||
"ie": "ile",
|
||||
"ia": "ina",
|
||||
"id": "ind",
|
||||
"ik": "ipk",
|
||||
"is": "isl",
|
||||
"it": "ita",
|
||||
"jv": "jav",
|
||||
"ja": "jpn",
|
||||
"kl": "kal",
|
||||
"kn": "kan",
|
||||
"ks": "kas",
|
||||
"ka": "kat",
|
||||
"kr": "kau",
|
||||
"kk": "kaz",
|
||||
"km": "khm",
|
||||
"ki": "kik",
|
||||
"rw": "kin",
|
||||
"ky": "kir",
|
||||
"kv": "kom",
|
||||
"kg": "kon",
|
||||
"ko": "kor",
|
||||
"kj": "kua",
|
||||
"ku": "kur",
|
||||
"lo": "lao",
|
||||
"la": "lat",
|
||||
"lv": "lav",
|
||||
"li": "lim",
|
||||
"ln": "lin",
|
||||
"lt": "lit",
|
||||
"lb": "ltz",
|
||||
"lu": "lub",
|
||||
"lg": "lug",
|
||||
"mh": "mah",
|
||||
"ml": "mal",
|
||||
"mr": "mar",
|
||||
"mk": "mkd",
|
||||
"mg": "mlg",
|
||||
"mt": "mlt",
|
||||
"mn": "mon",
|
||||
"mi": "mri",
|
||||
"ms": "msa",
|
||||
"my": "mya",
|
||||
"na": "nau",
|
||||
"nv": "nav",
|
||||
"nr": "nbl",
|
||||
"nd": "nde",
|
||||
"ng": "ndo",
|
||||
"ne": "nep",
|
||||
"nl": "nld",
|
||||
"nn": "nno",
|
||||
"nb": "nob",
|
||||
"no": "nor",
|
||||
"ny": "nya",
|
||||
"oc": "oci",
|
||||
"oj": "oji",
|
||||
"or": "ori",
|
||||
"om": "orm",
|
||||
"os": "oss",
|
||||
"pa": "pan",
|
||||
"pi": "pli",
|
||||
"pl": "pol",
|
||||
"pt": "por",
|
||||
"ps": "pus",
|
||||
"qu": "que",
|
||||
"rm": "roh",
|
||||
"ro": "ron",
|
||||
"rn": "run",
|
||||
"ru": "rus",
|
||||
"sg": "sag",
|
||||
"sa": "san",
|
||||
"si": "sin",
|
||||
"sk": "slk",
|
||||
"sl": "slv",
|
||||
"se": "sme",
|
||||
"sm": "smo",
|
||||
"sn": "sna",
|
||||
"sd": "snd",
|
||||
"so": "som",
|
||||
"st": "sot",
|
||||
"es": "spa",
|
||||
"sq": "sqi",
|
||||
"sc": "srd",
|
||||
"sr": "srp",
|
||||
"ss": "ssw",
|
||||
"su": "sun",
|
||||
"sw": "swa",
|
||||
"sv": "swe",
|
||||
"ty": "tah",
|
||||
"ta": "tam",
|
||||
"tt": "tat",
|
||||
"te": "tel",
|
||||
"tg": "tgk",
|
||||
"tl": "tgl",
|
||||
"th": "tha",
|
||||
"ti": "tir",
|
||||
"to": "ton",
|
||||
"tn": "tsn",
|
||||
"ts": "tso",
|
||||
"tk": "tuk",
|
||||
"tr": "tur",
|
||||
"tw": "twi",
|
||||
"ug": "uig",
|
||||
"uk": "ukr",
|
||||
"ur": "urd",
|
||||
"uz": "uzb",
|
||||
"ve": "ven",
|
||||
"vi": "vie",
|
||||
"vo": "vol",
|
||||
"wa": "wln",
|
||||
"wo": "wol",
|
||||
"xh": "xho",
|
||||
"yi": "yid",
|
||||
"yo": "yor",
|
||||
"za": "zha",
|
||||
"zh": "zho",
|
||||
"zu": "zul"
|
||||
}
|
||||
7840
src/NzbDrone.Core/Books/Calibre/Languages/by3.json
Normal file
7840
src/NzbDrone.Core/Books/Calibre/Languages/by3.json
Normal file
File diff suppressed because it is too large
Load diff
7840
src/NzbDrone.Core/Books/Calibre/Languages/name_map.json
Normal file
7840
src/NzbDrone.Core/Books/Calibre/Languages/name_map.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -23,6 +23,7 @@ public class CalibreChanges
|
|||
public string Comments { get; set; }
|
||||
public decimal Rating { get; set; }
|
||||
public Dictionary<string, string> Identifiers { get; set; }
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Include)]
|
||||
public string Series { get; set; }
|
||||
[JsonProperty("series_index")]
|
||||
public double? SeriesIndex { get; set; }
|
||||
|
|
|
|||
|
|
@ -237,7 +237,7 @@ public void Execute(RetagAuthorCommand message)
|
|||
foreach (var author in authorToRename)
|
||||
{
|
||||
var bookFiles = _mediaFileService.GetFilesByAuthor(author.Id);
|
||||
_logger.ProgressInfo("Re-tagging all files in author: {0}", author.Name);
|
||||
_logger.ProgressInfo("Re-tagging all files for author: {0}", author.Name);
|
||||
foreach (var file in bookFiles)
|
||||
{
|
||||
WriteTags(file, false, force: true);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ namespace NzbDrone.Core.MediaFiles.Commands
|
|||
public class RetagAuthorCommand : Command
|
||||
{
|
||||
public List<int> AuthorIds { get; set; }
|
||||
public bool UpdateCovers { get; set; }
|
||||
public bool EmbedMetadata { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
public override bool RequiresDiskAccess => true;
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ public class RetagFilesCommand : Command
|
|||
{
|
||||
public int AuthorId { get; set; }
|
||||
public List<int> Files { get; set; }
|
||||
public bool UpdateCovers { get; set; }
|
||||
public bool EmbedMetadata { get; set; }
|
||||
|
||||
public override bool SendUpdatesToClient => true;
|
||||
public override bool RequiresDiskAccess => true;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,19 @@
|
|||
using System.IO;
|
||||
using System.IO.Abstractions;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using NLog;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Common.Instrumentation.Extensions;
|
||||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.Books.Calibre;
|
||||
using NzbDrone.Core.MediaFiles.Azw;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
using PdfSharpCore.Pdf.IO;
|
||||
using VersOne.Epub;
|
||||
using VersOne.Epub.Schema;
|
||||
|
|
@ -17,14 +25,31 @@ namespace NzbDrone.Core.MediaFiles
|
|||
public interface IEBookTagService
|
||||
{
|
||||
ParsedTrackInfo ReadTags(IFileInfo file);
|
||||
List<RetagBookFilePreview> GetRetagPreviewsByAuthor(int authorId);
|
||||
List<RetagBookFilePreview> GetRetagPreviewsByBook(int authorId);
|
||||
}
|
||||
|
||||
public class EBookTagService : IEBookTagService
|
||||
public class EBookTagService : IEBookTagService,
|
||||
IExecute<RetagFilesCommand>,
|
||||
IExecute<RetagAuthorCommand>
|
||||
{
|
||||
private readonly IAuthorService _authorService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly ICalibreProxy _calibre;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public EBookTagService(Logger logger)
|
||||
public EBookTagService(IAuthorService authorService,
|
||||
IMediaFileService mediaFileService,
|
||||
IRootFolderService rootFolderService,
|
||||
ICalibreProxy calibre,
|
||||
Logger logger)
|
||||
{
|
||||
_authorService = authorService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_rootFolderService = rootFolderService;
|
||||
_calibre = calibre;
|
||||
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +72,126 @@ public ParsedTrackInfo ReadTags(IFileInfo file)
|
|||
}
|
||||
}
|
||||
|
||||
public List<RetagBookFilePreview> GetRetagPreviewsByAuthor(int authorId)
|
||||
{
|
||||
var files = _mediaFileService.GetFilesByAuthor(authorId);
|
||||
|
||||
return GetPreviews(files).ToList();
|
||||
}
|
||||
|
||||
public List<RetagBookFilePreview> GetRetagPreviewsByBook(int bookId)
|
||||
{
|
||||
var files = _mediaFileService.GetFilesByBook(bookId);
|
||||
|
||||
return GetPreviews(files).ToList();
|
||||
}
|
||||
|
||||
public void Execute(RetagFilesCommand message)
|
||||
{
|
||||
var author = _authorService.GetAuthor(message.AuthorId);
|
||||
var files = _mediaFileService.Get(message.Files);
|
||||
|
||||
_logger.ProgressInfo("Re-tagging {0} files for {1}", files.Count, author.Name);
|
||||
|
||||
foreach (var file in files.Where(x => x.CalibreId != 0))
|
||||
{
|
||||
var rootFolder = _rootFolderService.GetBestRootFolder(file.Path);
|
||||
_calibre.SetFields(file, rootFolder.CalibreSettings, message.UpdateCovers, message.EmbedMetadata);
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("Selected files re-tagged for {0}", author.Name);
|
||||
}
|
||||
|
||||
public void Execute(RetagAuthorCommand message)
|
||||
{
|
||||
_logger.Debug("Re-tagging all files for selected authors");
|
||||
var authorsToRename = _authorService.GetAuthors(message.AuthorIds);
|
||||
|
||||
foreach (var author in authorsToRename)
|
||||
{
|
||||
var files = _mediaFileService.GetFilesByAuthor(author.Id);
|
||||
|
||||
_logger.ProgressInfo("Re-tagging all files for author: {0}", author.Name);
|
||||
|
||||
foreach (var file in files.Where(x => x.CalibreId != 0))
|
||||
{
|
||||
var rootFolder = _rootFolderService.GetBestRootFolder(file.Path);
|
||||
_calibre.SetFields(file, rootFolder.CalibreSettings, message.UpdateCovers, message.EmbedMetadata);
|
||||
}
|
||||
|
||||
_logger.ProgressInfo("All files re-tagged for {0}", author.Name);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<RetagBookFilePreview> GetPreviews(List<BookFile> files)
|
||||
{
|
||||
var calibreFiles = files.Where(x => x.CalibreId > 0).OrderBy(x => x.Edition.Value.Title).ToList();
|
||||
|
||||
var rootFolderPairs = calibreFiles.Select(x => Tuple.Create(x, _rootFolderService.GetBestRootFolder(x.Path)));
|
||||
|
||||
var rootFolderGroups = rootFolderPairs.GroupBy(x => x.Item2.Path);
|
||||
|
||||
var calibreBooks = new List<CalibreBook>();
|
||||
foreach (var group in rootFolderGroups)
|
||||
{
|
||||
var rootFolder = group.First().Item2;
|
||||
var books = _calibre.GetBooks(group.Select(x => x.Item1.CalibreId).ToList(), rootFolder.CalibreSettings);
|
||||
calibreBooks.AddRange(books);
|
||||
}
|
||||
|
||||
var dict = calibreBooks.ToDictionary(x => x.Id);
|
||||
|
||||
foreach (var file in calibreFiles)
|
||||
{
|
||||
var edition = file.Edition.Value;
|
||||
var book = edition.Book.Value;
|
||||
var serieslink = book.SeriesLinks.Value.FirstOrDefault(x => x.Series.Value.Title.IsNotNullOrWhiteSpace());
|
||||
|
||||
var series = serieslink?.Series.Value;
|
||||
double? seriesIndex = null;
|
||||
if (double.TryParse(serieslink?.Position, out var index))
|
||||
{
|
||||
_logger.Trace($"Parsed {serieslink?.Position} as {index}");
|
||||
seriesIndex = index;
|
||||
}
|
||||
|
||||
var oldTags = dict[file.CalibreId];
|
||||
|
||||
var newTags = new CalibreBook
|
||||
{
|
||||
Title = edition.Title,
|
||||
Authors = new List<string> { file.Author.Value.Name },
|
||||
PubDate = book.ReleaseDate,
|
||||
Publisher = edition.Publisher,
|
||||
Languages = new List<string> { edition.Language.CanonicalizeLanguage() },
|
||||
Comments = edition.Overview,
|
||||
Rating = (int)(edition.Ratings.Value * 2) / 2.0,
|
||||
Identifiers = new Dictionary<string, string>
|
||||
{
|
||||
{ "isbn", edition.Isbn13 },
|
||||
{ "asin", edition.Asin },
|
||||
{ "goodreads", edition.ForeignEditionId }
|
||||
},
|
||||
Series = series?.Title,
|
||||
Position = seriesIndex
|
||||
};
|
||||
|
||||
var diff = oldTags.Diff(newTags);
|
||||
|
||||
if (diff.Any())
|
||||
{
|
||||
yield return new RetagBookFilePreview
|
||||
{
|
||||
AuthorId = file.Author.Value.Id,
|
||||
BookId = file.Edition.Value.Id,
|
||||
BookFileId = file.Id,
|
||||
Path = file.Path,
|
||||
Changes = diff
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ParsedTrackInfo ReadEpub(string file)
|
||||
{
|
||||
_logger.Trace($"Reading {file}");
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ public class RetagBookFilePreview
|
|||
{
|
||||
public int AuthorId { get; set; }
|
||||
public int BookId { get; set; }
|
||||
public List<int> TrackNumbers { get; set; }
|
||||
public List<int> TrackNumbers { get; set; } = new List<int>();
|
||||
public int BookFileId { get; set; }
|
||||
public string Path { get; set; }
|
||||
public Dictionary<string, Tuple<string, string>> Changes { get; set; }
|
||||
|
|
|
|||
|
|
@ -31,5 +31,8 @@
|
|||
<EmbeddedResource Include="..\..\Logo\64.png">
|
||||
<Link>Resources\Logo\64.png</Link>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Books\Calibre\Languages\2to3.json" />
|
||||
<EmbeddedResource Include="Books\Calibre\Languages\by3.json" />
|
||||
<EmbeddedResource Include="Books\Calibre\Languages\name_map.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ namespace Readarr.Api.V1.Books
|
|||
[V1ApiController("retag")]
|
||||
public class RetagBookController : Controller
|
||||
{
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IEBookTagService _eBookTagService;
|
||||
|
||||
public RetagBookController(IAudioTagService audioTagService)
|
||||
public RetagBookController(IEBookTagService eBookTagService)
|
||||
{
|
||||
_audioTagService = audioTagService;
|
||||
_eBookTagService = eBookTagService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
|
@ -22,11 +22,11 @@ public List<RetagBookResource> GetBooks(int? authorId, int? bookId)
|
|||
{
|
||||
if (bookId.HasValue)
|
||||
{
|
||||
return _audioTagService.GetRetagPreviewsByBook(bookId.Value).Where(x => x.Changes.Any()).ToResource();
|
||||
return _eBookTagService.GetRetagPreviewsByBook(bookId.Value).Where(x => x.Changes.Any()).ToResource();
|
||||
}
|
||||
else if (authorId.HasValue)
|
||||
{
|
||||
return _audioTagService.GetRetagPreviewsByAuthor(authorId.Value).Where(x => x.Changes.Any()).ToResource();
|
||||
return _eBookTagService.GetRetagPreviewsByAuthor(authorId.Value).Where(x => x.Changes.Any()).ToResource();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue