mirror of
https://github.com/Readarr/Readarr
synced 2026-03-05 12:14:05 +01:00
New: Allow keeping calibre in sync with goodreads
This commit is contained in:
parent
7072b913a6
commit
f584d2d8d2
14 changed files with 152 additions and 57 deletions
|
|
@ -59,6 +59,21 @@ class DevelopmentSettings extends Component {
|
|||
id="developmentSettings"
|
||||
{...otherProps}
|
||||
>
|
||||
<FieldSet legend="Metadata Provider Source">
|
||||
<FormGroup>
|
||||
<FormLabel>Metadata Source</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="metadataSource"
|
||||
helpText="Alternative Metadata Source (Leave blank for default)"
|
||||
helpLink="https://wiki.servarr.com/Readarr_Settings#Metadata"
|
||||
onChange={onInputChange}
|
||||
{...settings.metadataSource}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend="Logging">
|
||||
<FormGroup>
|
||||
<FormLabel>Log Rotation</FormLabel>
|
||||
|
|
|
|||
|
|
@ -8,16 +8,14 @@ import FormLabel from 'Components/Form/FormLabel';
|
|||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
|
||||
const writeAudioTagOptions = [
|
||||
{ key: 'sync', value: 'All files; keep in sync with MusicBrainz' },
|
||||
const writeBookTagOptions = [
|
||||
{ key: 'sync', value: 'All files; keep in sync with Goodreads' },
|
||||
{ key: 'allFiles', value: 'All files; initial import only' },
|
||||
{ key: 'newFiles', value: 'For new downloads only' },
|
||||
{ key: 'no', value: 'Never' }
|
||||
{ key: 'newFiles', value: 'For new downloads only' }
|
||||
];
|
||||
|
||||
function MetadataProvider(props) {
|
||||
const {
|
||||
advancedSettings,
|
||||
isFetching,
|
||||
error,
|
||||
settings,
|
||||
|
|
@ -41,51 +39,42 @@ function MetadataProvider(props) {
|
|||
{
|
||||
hasSettings && !isFetching && !error &&
|
||||
<Form>
|
||||
{
|
||||
advancedSettings &&
|
||||
<FieldSet legend="Metadata Provider Source">
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Metadata Source</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="metadataSource"
|
||||
helpText="Alternative Metadata Source (Leave blank for default)"
|
||||
helpLink="https://wiki.servarr.com/Readarr_Settings#Metadata"
|
||||
onChange={onInputChange}
|
||||
{...settings.metadataSource}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
<FieldSet legend="Write Metadata to Audio Files">
|
||||
<FieldSet legend="Calibre Metadata">
|
||||
<FormGroup>
|
||||
<FormLabel>Tag Audio Files with Metadata</FormLabel>
|
||||
<FormLabel>Send Metadata to Calibre</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="writeAudioTags"
|
||||
name="writeBookTags"
|
||||
helpTextWarning="Selecting 'All files' will alter existing files when they are imported."
|
||||
helpLink="https://wiki.servarr.com/Readarr_Settings#Write_Metadata_to_Audio_Files"
|
||||
values={writeAudioTagOptions}
|
||||
helpLink="https://wiki.servarr.com/Readarr_Settings#Write_Metadata_to_Book_Files"
|
||||
values={writeBookTagOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.writeAudioTags}
|
||||
{...settings.writeBookTags}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Scrub Existing Tags</FormLabel>
|
||||
<FormLabel>Update Covers</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="scrubAudioTags"
|
||||
helpText="Remove existing tags from files, leaving only those added by Readarr."
|
||||
name="updateCovers"
|
||||
helpText="Set book covers in Calibre to match those in Readarr"
|
||||
onChange={onInputChange}
|
||||
{...settings.scrubAudioTags}
|
||||
{...settings.updateCovers}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Embed Metadata in Book Files</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="embedMetadata"
|
||||
helpText="Tell Calibre to write metadata into the actual book file"
|
||||
onChange={onInputChange}
|
||||
{...settings.embedMetadata}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
|
|
@ -98,7 +87,6 @@ function MetadataProvider(props) {
|
|||
}
|
||||
|
||||
MetadataProvider.propTypes = {
|
||||
advancedSettings: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
settings: PropTypes.object.isRequired,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
|||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import MetadatasConnector from './Metadata/MetadatasConnector';
|
||||
// import MetadatasConnector from './Metadata/MetadatasConnector';
|
||||
import MetadataProviderConnector from './MetadataProvider/MetadataProviderConnector';
|
||||
|
||||
class MetadataSettings extends Component {
|
||||
|
|
@ -59,7 +59,7 @@ class MetadataSettings extends Component {
|
|||
onChildMounted={this.onChildMounted}
|
||||
onChildStateChange={this.onChildStateChange}
|
||||
/>
|
||||
<MetadatasConnector />
|
||||
{/* <MetadatasConnector /> */}
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,14 +15,17 @@ public class RefreshEditionService : IRefreshEditionService
|
|||
{
|
||||
private readonly IEditionService _editionService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IEBookTagService _eBookTagService;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public RefreshEditionService(IEditionService editionService,
|
||||
IAudioTagService audioTagService,
|
||||
IEBookTagService eBookTagService,
|
||||
Logger logger)
|
||||
{
|
||||
_editionService = editionService;
|
||||
_audioTagService = audioTagService;
|
||||
_eBookTagService = eBookTagService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
|
|
@ -52,6 +55,7 @@ public bool RefreshEditionInfo(List<Edition> add, List<Edition> update, List<Tup
|
|||
}
|
||||
|
||||
_audioTagService.SyncTags(tagsToUpdate);
|
||||
_eBookTagService.SyncTags(tagsToUpdate);
|
||||
|
||||
return add.Any() || delete.Any() || updateList.Any() || merge.Any();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -290,6 +290,27 @@ public bool ScrubAudioTags
|
|||
set { SetValue("ScrubAudioTags", value); }
|
||||
}
|
||||
|
||||
public WriteBookTagsType WriteBookTags
|
||||
{
|
||||
get { return GetValueEnum("WriteBookTags", WriteBookTagsType.NewFiles); }
|
||||
|
||||
set { SetValue("WriteBookTags", value); }
|
||||
}
|
||||
|
||||
public bool UpdateCovers
|
||||
{
|
||||
get { return GetValueBoolean("UpdateCovers", true); }
|
||||
|
||||
set { SetValue("UpdateCovers", value); }
|
||||
}
|
||||
|
||||
public bool EmbedMetadata
|
||||
{
|
||||
get { return GetValueBoolean("EmbedMetadata", false); }
|
||||
|
||||
set { SetValue("EmbedMetadata", value); }
|
||||
}
|
||||
|
||||
public int FirstDayOfWeek
|
||||
{
|
||||
get { return GetValueInt("FirstDayOfWeek", (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek); }
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ public interface IConfigService
|
|||
string MetadataSource { get; set; }
|
||||
WriteAudioTagsType WriteAudioTags { get; set; }
|
||||
bool ScrubAudioTags { get; set; }
|
||||
WriteBookTagsType WriteBookTags { get; set; }
|
||||
bool UpdateCovers { get; set; }
|
||||
bool EmbedMetadata { get; set; }
|
||||
|
||||
//Forms Auth
|
||||
string RijndaelPassphrase { get; }
|
||||
|
|
|
|||
9
src/NzbDrone.Core/Configuration/WriteBookTagsType.cs
Normal file
9
src/NzbDrone.Core/Configuration/WriteBookTagsType.cs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
namespace NzbDrone.Core.Configuration
|
||||
{
|
||||
public enum WriteBookTagsType
|
||||
{
|
||||
NewFiles,
|
||||
AllFiles,
|
||||
Sync
|
||||
}
|
||||
}
|
||||
|
|
@ -14,7 +14,6 @@
|
|||
using NzbDrone.Core.MediaFiles.Events;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
using NzbDrone.Core.Messaging.Events;
|
||||
using NzbDrone.Core.Parser;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.Qualities;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
|
@ -31,9 +30,9 @@ public class ImportApprovedBooks : IImportApprovedBooks
|
|||
private readonly IUpgradeMediaFiles _bookFileUpgrader;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
private readonly IEBookTagService _eBookTagService;
|
||||
private readonly IAuthorService _authorService;
|
||||
private readonly IAddAuthorService _addAuthorService;
|
||||
private readonly IRefreshAuthorService _refreshAuthorService;
|
||||
private readonly IBookService _bookService;
|
||||
private readonly IEditionService _editionService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
|
|
@ -47,9 +46,9 @@ public class ImportApprovedBooks : IImportApprovedBooks
|
|||
public ImportApprovedBooks(IUpgradeMediaFiles bookFileUpgrader,
|
||||
IMediaFileService mediaFileService,
|
||||
IAudioTagService audioTagService,
|
||||
IEBookTagService eBookTagService,
|
||||
IAuthorService authorService,
|
||||
IAddAuthorService addAuthorService,
|
||||
IRefreshAuthorService refreshAuthorService,
|
||||
IBookService bookService,
|
||||
IEditionService editionService,
|
||||
IRootFolderService rootFolderService,
|
||||
|
|
@ -63,9 +62,9 @@ public ImportApprovedBooks(IUpgradeMediaFiles bookFileUpgrader,
|
|||
_bookFileUpgrader = bookFileUpgrader;
|
||||
_mediaFileService = mediaFileService;
|
||||
_audioTagService = audioTagService;
|
||||
_eBookTagService = eBookTagService;
|
||||
_authorService = authorService;
|
||||
_addAuthorService = addAuthorService;
|
||||
_refreshAuthorService = refreshAuthorService;
|
||||
_bookService = bookService;
|
||||
_editionService = editionService;
|
||||
_rootFolderService = rootFolderService;
|
||||
|
|
@ -207,6 +206,7 @@ public List<ImportResult> Import(List<ImportDecision<LocalBook>> decisions, bool
|
|||
}
|
||||
|
||||
_audioTagService.WriteTags(bookFile, false);
|
||||
_eBookTagService.WriteTags(bookFile, false);
|
||||
}
|
||||
|
||||
filesToAdd.Add(bookFile);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
using NzbDrone.Common.Serializer;
|
||||
using NzbDrone.Core.Books;
|
||||
using NzbDrone.Core.Books.Calibre;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles.Azw;
|
||||
using NzbDrone.Core.MediaFiles.Commands;
|
||||
using NzbDrone.Core.Messaging.Commands;
|
||||
|
|
@ -25,6 +26,8 @@ namespace NzbDrone.Core.MediaFiles
|
|||
public interface IEBookTagService
|
||||
{
|
||||
ParsedTrackInfo ReadTags(IFileInfo file);
|
||||
void WriteTags(BookFile trackfile, bool newDownload, bool force = false);
|
||||
void SyncTags(List<Edition> books);
|
||||
List<RetagBookFilePreview> GetRetagPreviewsByAuthor(int authorId);
|
||||
List<RetagBookFilePreview> GetRetagPreviewsByBook(int authorId);
|
||||
}
|
||||
|
|
@ -36,18 +39,21 @@ public class EBookTagService : IEBookTagService,
|
|||
private readonly IAuthorService _authorService;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IRootFolderService _rootFolderService;
|
||||
private readonly IConfigService _configService;
|
||||
private readonly ICalibreProxy _calibre;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public EBookTagService(IAuthorService authorService,
|
||||
IMediaFileService mediaFileService,
|
||||
IRootFolderService rootFolderService,
|
||||
IConfigService configService,
|
||||
ICalibreProxy calibre,
|
||||
Logger logger)
|
||||
{
|
||||
_authorService = authorService;
|
||||
_mediaFileService = mediaFileService;
|
||||
_rootFolderService = rootFolderService;
|
||||
_configService = configService;
|
||||
_calibre = calibre;
|
||||
|
||||
_logger = logger;
|
||||
|
|
@ -72,6 +78,48 @@ public ParsedTrackInfo ReadTags(IFileInfo file)
|
|||
}
|
||||
}
|
||||
|
||||
public void WriteTags(BookFile bookFile, bool newDownload, bool force = false)
|
||||
{
|
||||
if (!force)
|
||||
{
|
||||
if (_configService.WriteBookTags == WriteBookTagsType.NewFiles && !newDownload)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Debug($"Writing tags for {bookFile}");
|
||||
|
||||
var rootFolder = _rootFolderService.GetBestRootFolder(bookFile.Path);
|
||||
_calibre.SetFields(bookFile, rootFolder.CalibreSettings, _configService.UpdateCovers, _configService.EmbedMetadata);
|
||||
}
|
||||
|
||||
public void SyncTags(List<Edition> editions)
|
||||
{
|
||||
if (_configService.WriteBookTags != WriteBookTagsType.Sync)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// get the tracks to update
|
||||
foreach (var edition in editions)
|
||||
{
|
||||
var bookFiles = edition.BookFiles.Value;
|
||||
|
||||
_logger.Debug($"Syncing ebook tags for {edition}");
|
||||
|
||||
foreach (var file in bookFiles)
|
||||
{
|
||||
// populate tracks (which should also have release/book/author set) because
|
||||
// not all of the updates will have been committed to the database yet
|
||||
file.Edition = edition;
|
||||
|
||||
var rootFolder = _rootFolderService.GetBestRootFolder(file.Path);
|
||||
_calibre.SetFields(file, rootFolder.CalibreSettings, _configService.UpdateCovers, _configService.EmbedMetadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<RetagBookFilePreview> GetRetagPreviewsByAuthor(int authorId)
|
||||
{
|
||||
var files = _mediaFileService.GetFilesByAuthor(authorId);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
using NzbDrone.Common.Disk;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Books.Calibre;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.MediaFiles.BookImport;
|
||||
using NzbDrone.Core.Parser.Model;
|
||||
using NzbDrone.Core.RootFolders;
|
||||
|
|
@ -18,6 +19,7 @@ public interface IUpgradeMediaFiles
|
|||
|
||||
public class UpgradeMediaFileService : IUpgradeMediaFiles
|
||||
{
|
||||
private readonly IConfigService _configService;
|
||||
private readonly IRecycleBinProvider _recycleBinProvider;
|
||||
private readonly IMediaFileService _mediaFileService;
|
||||
private readonly IAudioTagService _audioTagService;
|
||||
|
|
@ -28,7 +30,8 @@ public class UpgradeMediaFileService : IUpgradeMediaFiles
|
|||
private readonly ICalibreProxy _calibre;
|
||||
private readonly Logger _logger;
|
||||
|
||||
public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
|
||||
public UpgradeMediaFileService(IConfigService configService,
|
||||
IRecycleBinProvider recycleBinProvider,
|
||||
IMediaFileService mediaFileService,
|
||||
IAudioTagService audioTagService,
|
||||
IMoveBookFiles bookFileMover,
|
||||
|
|
@ -38,6 +41,7 @@ public UpgradeMediaFileService(IRecycleBinProvider recycleBinProvider,
|
|||
ICalibreProxy calibre,
|
||||
Logger logger)
|
||||
{
|
||||
_configService = configService;
|
||||
_recycleBinProvider = recycleBinProvider;
|
||||
_mediaFileService = mediaFileService;
|
||||
_audioTagService = audioTagService;
|
||||
|
|
@ -136,15 +140,15 @@ public BookFile CalibreAddAndConvert(BookFile file, CalibreSettings settings)
|
|||
_calibre.AddFormat(file, settings);
|
||||
}
|
||||
|
||||
_calibre.SetFields(file, settings);
|
||||
_rootFolderWatchingService.ReportFileSystemChangeBeginning(file.Path);
|
||||
|
||||
_calibre.SetFields(file, settings, true, _configService.EmbedMetadata);
|
||||
|
||||
var updated = _calibre.GetBook(file.CalibreId, settings);
|
||||
var path = updated.Formats.Values.OrderByDescending(x => x.LastModified).First().Path;
|
||||
|
||||
file.Path = path;
|
||||
|
||||
_rootFolderWatchingService.ReportFileSystemChangeBeginning(file.Path);
|
||||
|
||||
if (settings.OutputFormat.IsNotNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Trace($"Getting book data for {file.CalibreId}");
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using FluentValidation;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Validation;
|
||||
using NzbDrone.Http.REST.Attributes;
|
||||
using Readarr.Http;
|
||||
using Readarr.Http.REST;
|
||||
|
|
@ -19,6 +22,8 @@ public DevelopmentConfigController(IConfigFileProvider configFileProvider,
|
|||
{
|
||||
_configFileProvider = configFileProvider;
|
||||
_configService = configService;
|
||||
|
||||
SharedValidator.RuleFor(c => c.MetadataSource).IsValidUrl().When(c => !c.MetadataSource.IsNullOrWhiteSpace());
|
||||
}
|
||||
|
||||
public override DevelopmentConfigResource GetResourceById(int id)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace Prowlarr.Api.V1.Config
|
|||
{
|
||||
public class DevelopmentConfigResource : RestResource
|
||||
{
|
||||
public string MetadataSource { get; set; }
|
||||
public string ConsoleLogLevel { get; set; }
|
||||
public bool LogSql { get; set; }
|
||||
public int LogRotate { get; set; }
|
||||
|
|
@ -17,6 +18,7 @@ public static DevelopmentConfigResource ToResource(this IConfigFileProvider mode
|
|||
{
|
||||
return new DevelopmentConfigResource
|
||||
{
|
||||
MetadataSource = configService.MetadataSource,
|
||||
ConsoleLogLevel = model.ConsoleLogLevel,
|
||||
LogSql = model.LogSql,
|
||||
LogRotate = model.LogRotate,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
using FluentValidation;
|
||||
using NzbDrone.Common.Extensions;
|
||||
using NzbDrone.Core.Configuration;
|
||||
using NzbDrone.Core.Validation;
|
||||
using Readarr.Http;
|
||||
|
||||
namespace Readarr.Api.V1.Config
|
||||
|
|
@ -12,7 +9,6 @@ public class MetadataProviderConfigController : ConfigController<MetadataProvide
|
|||
public MetadataProviderConfigController(IConfigService configService)
|
||||
: base(configService)
|
||||
{
|
||||
SharedValidator.RuleFor(c => c.MetadataSource).IsValidUrl().When(c => !c.MetadataSource.IsNullOrWhiteSpace());
|
||||
}
|
||||
|
||||
protected override MetadataProviderConfigResource ToResource(IConfigService model)
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ namespace Readarr.Api.V1.Config
|
|||
{
|
||||
public class MetadataProviderConfigResource : RestResource
|
||||
{
|
||||
public string MetadataSource { get; set; }
|
||||
public WriteAudioTagsType WriteAudioTags { get; set; }
|
||||
public bool ScrubAudioTags { get; set; }
|
||||
public WriteBookTagsType WriteBookTags { get; set; }
|
||||
public bool UpdateCovers { get; set; }
|
||||
public bool EmbedMetadata { get; set; }
|
||||
}
|
||||
|
||||
public static class MetadataProviderConfigResourceMapper
|
||||
|
|
@ -16,9 +16,9 @@ public static MetadataProviderConfigResource ToResource(IConfigService model)
|
|||
{
|
||||
return new MetadataProviderConfigResource
|
||||
{
|
||||
MetadataSource = model.MetadataSource,
|
||||
WriteAudioTags = model.WriteAudioTags,
|
||||
ScrubAudioTags = model.ScrubAudioTags,
|
||||
WriteBookTags = model.WriteBookTags,
|
||||
UpdateCovers = model.UpdateCovers,
|
||||
EmbedMetadata = model.EmbedMetadata
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue