New: Allow keeping calibre in sync with goodreads

pull/965/head
ta264 3 years ago
parent 7072b913a6
commit f584d2d8d2

@ -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.writeBookTags}
/>
</FormGroup>
<FormGroup>
<FormLabel>Update Covers</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="updateCovers"
helpText="Set book covers in Calibre to match those in Readarr"
onChange={onInputChange}
{...settings.writeAudioTags}
{...settings.updateCovers}
/>
</FormGroup>
<FormGroup>
<FormLabel>Scrub Existing Tags</FormLabel>
<FormLabel>Embed Metadata in Book Files</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="scrubAudioTags"
helpText="Remove existing tags from files, leaving only those added by Readarr."
name="embedMetadata"
helpText="Tell Calibre to write metadata into the actual book file"
onChange={onInputChange}
{...settings.scrubAudioTags}
{...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 @@ namespace NzbDrone.Core.Books
{
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 @@ namespace NzbDrone.Core.Books
}
_audioTagService.SyncTags(tagsToUpdate);
_eBookTagService.SyncTags(tagsToUpdate);
return add.Any() || delete.Any() || updateList.Any() || merge.Any();
}

@ -290,6 +290,27 @@ namespace NzbDrone.Core.Configuration
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 @@ namespace NzbDrone.Core.Configuration
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; }

@ -0,0 +1,9 @@
namespace NzbDrone.Core.Configuration
{
public enum WriteBookTagsType
{
NewFiles,
AllFiles,
Sync
}
}

@ -14,7 +14,6 @@ using NzbDrone.Core.Extras;
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 @@ namespace NzbDrone.Core.MediaFiles.BookImport
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 @@ namespace NzbDrone.Core.MediaFiles.BookImport
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 @@ namespace NzbDrone.Core.MediaFiles.BookImport
_bookFileUpgrader = bookFileUpgrader;
_mediaFileService = mediaFileService;
_audioTagService = audioTagService;
_eBookTagService = eBookTagService;
_authorService = authorService;
_addAuthorService = addAuthorService;
_refreshAuthorService = refreshAuthorService;
_bookService = bookService;
_editionService = editionService;
_rootFolderService = rootFolderService;
@ -207,6 +206,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport
}
_audioTagService.WriteTags(bookFile, false);
_eBookTagService.WriteTags(bookFile, false);
}
filesToAdd.Add(bookFile);

@ -10,6 +10,7 @@ using NzbDrone.Common.Instrumentation.Extensions;
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 @@ namespace NzbDrone.Core.MediaFiles
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 @@ namespace NzbDrone.Core.MediaFiles
}
}
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 NLog;
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 @@ namespace NzbDrone.Core.MediaFiles
public class UpgradeMediaFileService : IUpgradeMediaFiles
{
private readonly IConfigService _configService;
private readonly IRecycleBinProvider _recycleBinProvider;
private readonly IMediaFileService _mediaFileService;
private readonly IAudioTagService _audioTagService;
@ -28,7 +30,8 @@ namespace NzbDrone.Core.MediaFiles
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 @@ namespace NzbDrone.Core.MediaFiles
ICalibreProxy calibre,
Logger logger)
{
_configService = configService;
_recycleBinProvider = recycleBinProvider;
_mediaFileService = mediaFileService;
_audioTagService = audioTagService;
@ -136,15 +140,15 @@ namespace NzbDrone.Core.MediaFiles
_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 @@ namespace Prowlarr.Api.V1.Config
{
_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 @@ namespace Prowlarr.Api.V1.Config
{
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 @@ namespace Readarr.Api.V1.Config
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 @@ namespace Readarr.Api.V1.Config
{
return new MetadataProviderConfigResource
{
MetadataSource = model.MetadataSource,
WriteAudioTags = model.WriteAudioTags,
ScrubAudioTags = model.ScrubAudioTags,
WriteBookTags = model.WriteBookTags,
UpdateCovers = model.UpdateCovers,
EmbedMetadata = model.EmbedMetadata
};
}
}

Loading…
Cancel
Save