New: Basic audiobook support

pull/1063/head
ta264 3 years ago
parent 62928b227b
commit f6a04f7890

@ -9,10 +9,17 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import { inputTypes } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
const writeAudioTagOptions = [
{ key: 'no', value: translate('WriteTagsNo') },
{ key: 'sync', value: translate('WriteTagsSync') },
{ key: 'allFiles', value: translate('WriteTagsAll') },
{ key: 'newFiles', value: translate('WriteTagsNew') }
];
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: 'sync', value: translate('WriteTagsSync') },
{ key: 'allFiles', value: translate('WriteTagsAll') },
{ key: 'newFiles', value: translate('WriteTagsNew') }
];
function MetadataProvider(props) {
@ -88,6 +95,35 @@ function MetadataProvider(props) {
</FormGroup>
</FieldSet>
<FieldSet legend={translate('AudioFileMetadata')}>
<FormGroup>
<FormLabel>{translate('WriteAudioTags')}</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="writeAudioTags"
helpTextWarning={translate('WriteBookTagsHelpTextWarning')}
helpLink="https://wiki.servarr.com/Lidarr_Settings#Write_Metadata_to_Audio_Files"
values={writeAudioTagOptions}
onChange={onInputChange}
{...settings.writeAudioTags}
/>
</FormGroup>
<FormGroup>
<FormLabel>{translate('WriteAudioTagsScrub')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="scrubAudioTags"
helpTextWarning={translate('WriteAudioTagsScrubHelp')}
onChange={onInputChange}
{...settings.scrubAudioTags}
/>
</FormGroup>
</FieldSet>
</Form>
}
</div>

@ -130,6 +130,10 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
.Setup(c => c.FilterUnchangedFiles(It.IsAny<List<IFileInfo>>(), It.IsAny<FilterFilesType>()))
.Returns((List<IFileInfo> files, FilterFilesType filter) => files);
Mocker.GetMock<IMetadataTagService>()
.Setup(s => s.ReadTags(It.IsAny<IFileInfo>()))
.Returns(new ParsedTrackInfo());
GivenSpecifications(_bookpass1);
}

@ -54,6 +54,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Setup(c => c.GetConfig()).Returns(_namingConfig);
_trackFile = Builder<BookFile>.CreateNew()
.With(e => e.Part = 1)
.With(e => e.PartCount = 1)
.With(e => e.Quality = new QualityModel(Quality.MP3_320))
.With(e => e.ReleaseGroup = "ReadarrTest")
.With(e => e.MediaInfo = new Parser.Model.MediaInfoModel

@ -83,11 +83,11 @@ namespace NzbDrone.Core.Test.ProviderTests.DiskScanProviderTests
}
[Test]
public void should_return_audio_files_only()
public void should_return_book_files_only()
{
GivenFiles(GetFiles(_path));
Subject.GetBookFiles(_path).Should().HaveCount(3);
Subject.GetBookFiles(_path).Should().HaveCount(5);
}
[TestCase("Extras")]

@ -0,0 +1,14 @@
using FluentMigrator;
using NzbDrone.Core.Datastore.Migration.Framework;
namespace NzbDrone.Core.Datastore.Migration
{
[Migration(010)]
public class add_bookfile_part : NzbDroneMigrationBase
{
protected override void MainDbUpgrade()
{
Alter.Table("BookFiles").AddColumn("Part").AsInt32().NotNullable().WithDefaultValue(1);
}
}
}

@ -150,6 +150,7 @@ namespace NzbDrone.Core.Datastore
b => b.Id > 0);
Mapper.Entity<BookFile>("BookFiles").RegisterModel()
.Ignore(x => x.PartCount)
.HasOne(f => f.Edition, f => f.EditionId)
.LazyLoad(x => x.Author,
(db, f) => AuthorRepository.Query(db,

@ -10,7 +10,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public static List<FieldSelectOption> GetFieldSelectOptions(List<NewznabCategory> categories)
{
// Ignore categories not relevant for Readarr
var ignoreCategories = new[] { 1000, 2000, 3000, 4000, 5000, 6000 };
var ignoreCategories = new[] { 1000, 2000, 4000, 5000, 6000 };
// And maybe relevant for specific users
var unimportantCategories = new[] { 0, 8000 };
@ -22,6 +22,15 @@ namespace NzbDrone.Core.Indexers.Newznab
// Fetching categories failed, use default Newznab categories
categories = new List<NewznabCategory>();
categories.Add(new NewznabCategory
{
Id = 3000,
Name = "Audio",
Subcategories = new List<NewznabCategory>
{
new NewznabCategory { Id = 3030, Name = "Audiobook" }
}
});
categories.Add(new NewznabCategory
{
Id = 7000,
Name = "Books",

@ -58,7 +58,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public NewznabSettings()
{
ApiPath = "/api";
Categories = new[] { 7020, 8010 };
Categories = new[] { 3030, 7020, 8010 };
}
[FieldDefinition(0, Label = "URL")]

@ -38,6 +38,7 @@
"ApplyTagsHelpTexts2": "Add: Add the tags the existing list of tags",
"ApplyTagsHelpTexts3": "Remove: Remove the entered tags",
"ApplyTagsHelpTexts4": "Replace: Replace the tags with the entered tags (enter no tags to clear all tags)",
"AudioFileMetadata": "Write Metadata to Audio Files",
"Authentication": "Authentication",
"AuthenticationMethodHelpText": "Require Username and Password to access Readarr",
"Author": "Author",
@ -351,7 +352,7 @@
"MinimumFreeSpaceWhenImportingHelpText": "Prevent import if it would leave less than this amount of disk space available",
"MinimumLimits": "Minimum Limits",
"MinimumPages": "Minimum Pages",
"MinPagesHelpText": "Ignore books with fewer pages than this",
"MinPagesHelpText": "Ignore books with fewer pages than this",
"MinimumPopularity": "Minimum Popularity",
"Missing": "Missing",
"MissingBooks": "Missing Books",
@ -691,7 +692,14 @@
"WatchLibraryForChangesHelpText": "Rescan automatically when files change in a root folder",
"WatchRootFoldersForFileChanges": "Watch Root Folders for file changes",
"WeekColumnHeader": "Week Column Header",
"WriteAudioTags": "Tag Audio Files with Metadata",
"WriteAudioTagsScrub": "Scrub Existing Tags",
"WriteAudioTagsScrubHelp": "Remove existing tags from files, leaving only those added by Readarr.",
"WriteBookTagsHelpTextWarning": "Selecting 'All files' will alter existing files when they are imported.",
"WriteTagsAll": "All files; initial import only",
"WriteTagsNew": "For new downloads only",
"WriteTagsNo": "Never",
"WriteTagsSync": "All files; keep in sync with Goodreads",
"Year": "Year",
"YesCancel": "Yes, Cancel"
}

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NLog;
using NLog.Fluent;
@ -24,12 +25,12 @@ namespace NzbDrone.Core.MediaFiles
void WriteTags(BookFile trackfile, bool newDownload, bool force = false);
void SyncTags(List<Edition> tracks);
List<RetagBookFilePreview> GetRetagPreviewsByAuthor(int authorId);
List<RetagBookFilePreview> GetRetagPreviewsByBook(int authorId);
List<RetagBookFilePreview> GetRetagPreviewsByBook(int bookId);
void RetagFiles(RetagFilesCommand message);
void RetagAuthor(RetagAuthorCommand message);
}
public class AudioTagService : IAudioTagService,
IExecute<RetagAuthorCommand>,
IExecute<RetagFilesCommand>
public class AudioTagService : IAudioTagService
{
private readonly IConfigService _configService;
private readonly IMediaFileService _mediaFileService;
@ -71,7 +72,52 @@ namespace NzbDrone.Core.MediaFiles
public AudioTag GetTrackMetadata(BookFile trackfile)
{
return new AudioTag();
var edition = trackfile.Edition.Value;
var book = edition.Book.Value;
var author = book.Author.Value;
var fileTags = ReadAudioTag(trackfile.Path);
var cover = edition.Images.FirstOrDefault(x => x.CoverType == MediaCoverTypes.Cover);
string imageFile = null;
long imageSize = 0;
if (cover != null)
{
imageFile = _mediaCoverService.GetCoverPath(book.Id, MediaCoverEntity.Book, cover.CoverType, cover.Extension, null);
_logger.Trace($"Embedding: {imageFile}");
var fileInfo = _diskProvider.GetFileInfo(imageFile);
if (fileInfo.Exists)
{
imageSize = fileInfo.Length;
}
else
{
imageFile = null;
}
}
return new AudioTag
{
Title = edition.Title,
Performers = new[] { author.Name },
BookAuthors = new[] { author.Name },
Track = fileTags.Track,
TrackCount = fileTags.TrackCount,
Book = book.Title,
Disc = fileTags.Disc,
DiscCount = fileTags.DiscCount,
// We may have omitted media so index in the list isn't the same as medium number
Media = fileTags.Media,
Date = edition.ReleaseDate,
Year = (uint)edition.ReleaseDate?.Year,
OriginalReleaseDate = book.ReleaseDate,
OriginalYear = (uint)book.ReleaseDate?.Year,
Publisher = edition.Publisher,
Genres = new string[0],
ImageFile = imageFile,
ImageSize = imageSize,
};
}
private void UpdateTrackfileSizeAndModified(BookFile trackfile, string path)
@ -187,7 +233,7 @@ namespace NzbDrone.Core.MediaFiles
private IEnumerable<RetagBookFilePreview> GetPreviews(List<BookFile> files)
{
foreach (var f in files.OrderBy(x => x.Edition.Value.Title))
foreach (var f in files.Where(x => MediaFileExtensions.AudioExtensions.Contains(Path.GetExtension(x.Path))).OrderBy(x => x.Edition.Value.Title))
{
var file = f;
@ -215,35 +261,38 @@ namespace NzbDrone.Core.MediaFiles
}
}
public void Execute(RetagFilesCommand message)
public void RetagFiles(RetagFilesCommand message)
{
var author = _authorService.GetAuthor(message.AuthorId);
var bookFiles = _mediaFileService.Get(message.Files);
var audioFiles = bookFiles.Where(x => MediaFileExtensions.AudioExtensions.Contains(Path.GetExtension(x.Path))).ToList();
_logger.ProgressInfo("Re-tagging {0} files for {1}", bookFiles.Count, author.Name);
foreach (var file in bookFiles)
_logger.ProgressInfo("Re-tagging {0} audio files for {1}", audioFiles.Count, author.Name);
foreach (var file in audioFiles)
{
WriteTags(file, false, force: true);
}
_logger.ProgressInfo("Selected track files re-tagged for {0}", author.Name);
_logger.ProgressInfo("Selected audio files re-tagged for {0}", author.Name);
}
public void Execute(RetagAuthorCommand message)
public void RetagAuthor(RetagAuthorCommand message)
{
_logger.Debug("Re-tagging all files for selected authors");
_logger.Debug("Re-tagging all audio files for selected authors");
var authorToRename = _authorService.GetAuthors(message.AuthorIds);
foreach (var author in authorToRename)
{
var bookFiles = _mediaFileService.GetFilesByAuthor(author.Id);
_logger.ProgressInfo("Re-tagging all files for author: {0}", author.Name);
foreach (var file in bookFiles)
var audioFiles = bookFiles.Where(x => MediaFileExtensions.AudioExtensions.Contains(Path.GetExtension(x.Path))).ToList();
_logger.ProgressInfo("Re-tagging all audio files for author: {0}", author.Name);
foreach (var file in audioFiles)
{
WriteTags(file, false, force: true);
}
_logger.ProgressInfo("All track files re-tagged for {0}", author.Name);
_logger.ProgressInfo("All audio files re-tagged for {0}", author.Name);
}
}
}

@ -21,11 +21,15 @@ namespace NzbDrone.Core.MediaFiles
public MediaInfoModel MediaInfo { get; set; }
public int EditionId { get; set; }
public int CalibreId { get; set; }
public int Part { get; set; }
// These are queried from the database
public LazyLoaded<Author> Author { get; set; }
public LazyLoaded<Edition> Edition { get; set; }
// Calculated manually
public int PartCount { get; set; }
public override string ToString()
{
return string.Format("[{0}] {1}", Id, Path);

@ -153,7 +153,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport
try
{
//check if already imported
if (importResults.Select(r => r.ImportDecision.Item.Book.Id).Contains(localTrack.Book.Id))
if (importResults.Where(r => r.ImportDecision.Item.Book.Id == localTrack.Book.Id).Any(r => r.ImportDecision.Item.Part == localTrack.Part))
{
importResults.Add(new ImportResult(importDecision, "Book has already been imported"));
continue;
@ -165,6 +165,8 @@ namespace NzbDrone.Core.MediaFiles.BookImport
{
Path = localTrack.Path.CleanFilePath(),
CalibreId = localTrack.CalibreId,
Part = localTrack.Part,
PartCount = localTrack.PartCount,
Size = localTrack.Size,
Modified = localTrack.Modified,
DateAdded = DateTime.UtcNow,

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using NLog;
@ -48,8 +49,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport
private readonly IEnumerable<IImportDecisionEngineSpecification<LocalBook>> _trackSpecifications;
private readonly IEnumerable<IImportDecisionEngineSpecification<LocalEdition>> _bookSpecifications;
private readonly IMediaFileService _mediaFileService;
private readonly IEBookTagService _eBookTagService;
private readonly IAudioTagService _audioTagService;
private readonly IMetadataTagService _metadataTagService;
private readonly IAugmentingService _augmentingService;
private readonly IIdentificationService _identificationService;
private readonly IRootFolderService _rootFolderService;
@ -59,8 +59,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport
public ImportDecisionMaker(IEnumerable<IImportDecisionEngineSpecification<LocalBook>> trackSpecifications,
IEnumerable<IImportDecisionEngineSpecification<LocalEdition>> bookSpecifications,
IMediaFileService mediaFileService,
IEBookTagService eBookTagService,
IAudioTagService audioTagService,
IMetadataTagService metadataTagService,
IAugmentingService augmentingService,
IIdentificationService identificationService,
IRootFolderService rootFolderService,
@ -70,8 +69,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport
_trackSpecifications = trackSpecifications;
_bookSpecifications = bookSpecifications;
_mediaFileService = mediaFileService;
_eBookTagService = eBookTagService;
_audioTagService = audioTagService;
_metadataTagService = metadataTagService;
_augmentingService = augmentingService;
_identificationService = identificationService;
_rootFolderService = rootFolderService;
@ -108,14 +106,17 @@ namespace NzbDrone.Core.MediaFiles.BookImport
{
_logger.ProgressInfo($"Reading file {i++}/{files.Count}");
var fileTrackInfo = _metadataTagService.ReadTags(file);
var localTrack = new LocalBook
{
DownloadClientBookInfo = downloadClientItemInfo,
FolderTrackInfo = folderInfo,
Path = file.FullName,
Part = fileTrackInfo.TrackNumbers.Any() ? fileTrackInfo.TrackNumbers.First() : 1,
Size = file.Length,
Modified = file.LastWriteTimeUtc,
FileTrackInfo = _eBookTagService.ReadTags(file),
FileTrackInfo = fileTrackInfo,
AdditionalFile = false
};

@ -40,7 +40,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Manual
private readonly IBookService _bookService;
private readonly IEditionService _editionService;
private readonly IProvideBookInfo _bookInfo;
private readonly IAudioTagService _audioTagService;
private readonly IMetadataTagService _metadataTagService;
private readonly IImportApprovedBooks _importApprovedBooks;
private readonly ITrackedDownloadService _trackedDownloadService;
private readonly IDownloadedBooksImportService _downloadedTracksImportService;
@ -57,7 +57,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Manual
IBookService bookService,
IEditionService editionService,
IProvideBookInfo bookInfo,
IAudioTagService audioTagService,
IMetadataTagService metadataTagService,
IImportApprovedBooks importApprovedBooks,
ITrackedDownloadService trackedDownloadService,
IDownloadedBooksImportService downloadedTracksImportService,
@ -74,7 +74,7 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Manual
_bookService = bookService;
_editionService = editionService;
_bookInfo = bookInfo;
_audioTagService = audioTagService;
_metadataTagService = metadataTagService;
_importApprovedBooks = importApprovedBooks;
_trackedDownloadService = trackedDownloadService;
_downloadedTracksImportService = downloadedTracksImportService;
@ -312,16 +312,17 @@ namespace NzbDrone.Core.MediaFiles.BookImport.Manual
edition = tuple.Item2.Editions.Value.SingleOrDefault(x => x.ForeignEditionId == file.ForeignEditionId);
}
var fileTrackInfo = _audioTagService.ReadTags(file.Path) ?? new ParsedTrackInfo();
var fileInfo = _diskProvider.GetFileInfo(file.Path);
var fileRootFolder = _rootFolderService.GetBestRootFolder(file.Path);
var fileInfo = _diskProvider.GetFileInfo(file.Path);
var fileTrackInfo = _metadataTagService.ReadTags(fileInfo) ?? new ParsedTrackInfo();
var localTrack = new LocalBook
{
ExistingFile = fileRootFolder != null,
FileTrackInfo = fileTrackInfo,
Path = file.Path,
Part = fileTrackInfo.TrackNumbers.Any() ? fileTrackInfo.TrackNumbers.First() : 1,
PartCount = importBookId.Count(),
Size = fileInfo.Length,
Modified = fileInfo.LastWriteTimeUtc,
Quality = file.Quality,

@ -181,6 +181,8 @@ namespace NzbDrone.Core.MediaFiles
{
Path = decision.Item.Path,
CalibreId = decision.Item.CalibreId,
Part = decision.Item.Part,
PartCount = decision.Item.PartCount,
Size = decision.Item.Size,
Modified = decision.Item.Modified,
DateAdded = DateTime.UtcNow,

@ -28,12 +28,12 @@ namespace NzbDrone.Core.MediaFiles
void WriteTags(BookFile trackfile, bool newDownload, bool force = false);
void SyncTags(List<Edition> books);
List<RetagBookFilePreview> GetRetagPreviewsByAuthor(int authorId);
List<RetagBookFilePreview> GetRetagPreviewsByBook(int authorId);
List<RetagBookFilePreview> GetRetagPreviewsByBook(int bookId);
void RetagFiles(RetagFilesCommand message);
void RetagAuthor(RetagAuthorCommand message);
}
public class EBookTagService : IEBookTagService,
IExecute<RetagFilesCommand>,
IExecute<RetagAuthorCommand>
public class EBookTagService : IEBookTagService
{
private readonly IAuthorService _authorService;
private readonly IMediaFileService _mediaFileService;
@ -132,38 +132,38 @@ namespace NzbDrone.Core.MediaFiles
return GetPreviews(files).ToList();
}
public void Execute(RetagFilesCommand message)
public void RetagFiles(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);
_logger.ProgressInfo("Re-tagging {0} ebook files for {1}", files.Count, author.Name);
foreach (var file in files.Where(x => x.CalibreId != 0))
{
WriteTagsInternal(file, message.UpdateCovers, message.EmbedMetadata);
}
_logger.ProgressInfo("Selected files re-tagged for {0}", author.Name);
_logger.ProgressInfo("Selected ebook files re-tagged for {0}", author.Name);
}
public void Execute(RetagAuthorCommand message)
public void RetagAuthor(RetagAuthorCommand message)
{
_logger.Debug("Re-tagging all files for selected authors");
_logger.Debug("Re-tagging all ebook 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);
_logger.ProgressInfo("Re-tagging all ebook files for author: {0}", author.Name);
foreach (var file in files.Where(x => x.CalibreId != 0))
{
WriteTagsInternal(file, message.UpdateCovers, message.EmbedMetadata);
}
_logger.ProgressInfo("All files re-tagged for {0}", author.Name);
_logger.ProgressInfo("All ebook files re-tagged for {0}", author.Name);
}
}

@ -23,6 +23,22 @@ namespace NzbDrone.Core.MediaFiles
_audioExtensions = new Dictionary<string, Quality>(StringComparer.OrdinalIgnoreCase)
{
{ ".flac", Quality.FLAC },
{ ".ape", Quality.FLAC },
{ ".wavpack", Quality.FLAC },
{ ".wav", Quality.FLAC },
{ ".alac", Quality.FLAC },
{ ".mp2", Quality.MP3_320 },
{ ".mp3", Quality.MP3_320 },
{ ".wma", Quality.MP3_320 },
{ ".m4a", Quality.MP3_320 },
{ ".m4p", Quality.MP3_320 },
{ ".m4b", Quality.MP3_320 },
{ ".aac", Quality.MP3_320 },
{ ".mp4a", Quality.MP3_320 },
{ ".ogg", Quality.MP3_320 },
{ ".oga", Quality.MP3_320 },
{ ".vorbis", Quality.MP3_320 },
};
}

@ -0,0 +1,92 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
using NLog;
using NzbDrone.Common.Instrumentation.Extensions;
using NzbDrone.Core.MediaFiles.Commands;
using NzbDrone.Core.Messaging.Commands;
using NzbDrone.Core.Parser.Model;
namespace NzbDrone.Core.MediaFiles
{
public interface IMetadataTagService
{
ParsedTrackInfo ReadTags(IFileInfo file);
void WriteTags(BookFile trackfile, bool newDownload, bool force = false);
List<RetagBookFilePreview> GetRetagPreviewsByAuthor(int authorId);
List<RetagBookFilePreview> GetRetagPreviewsByBook(int authorId);
}
public class MetadataTagService : IMetadataTagService,
IExecute<RetagFilesCommand>,
IExecute<RetagAuthorCommand>
{
private readonly IAudioTagService _audioTagService;
private readonly IEBookTagService _eBookTagService;
private readonly Logger _logger;
public MetadataTagService(IAudioTagService audioTagService,
IEBookTagService eBookTagService,
Logger logger)
{
_audioTagService = audioTagService;
_eBookTagService = eBookTagService;
_logger = logger;
}
public ParsedTrackInfo ReadTags(IFileInfo file)
{
if (MediaFileExtensions.AudioExtensions.Contains(file.Extension))
{
return _audioTagService.ReadTags(file.FullName);
}
else
{
return _eBookTagService.ReadTags(file);
}
}
public void WriteTags(BookFile bookFile, bool newDownload, bool force = false)
{
var extension = Path.GetExtension(bookFile.Path);
if (MediaFileExtensions.AudioExtensions.Contains(extension))
{
_audioTagService.WriteTags(bookFile, newDownload, force);
}
else
{
_eBookTagService.WriteTags(bookFile, newDownload, force);
}
}
public List<RetagBookFilePreview> GetRetagPreviewsByAuthor(int authorId)
{
var previews = _audioTagService.GetRetagPreviewsByAuthor(authorId);
previews.AddRange(_eBookTagService.GetRetagPreviewsByAuthor(authorId));
return previews;
}
public List<RetagBookFilePreview> GetRetagPreviewsByBook(int bookId)
{
var previews = _audioTagService.GetRetagPreviewsByBook(bookId);
previews.AddRange(_eBookTagService.GetRetagPreviewsByBook(bookId));
return previews;
}
public void Execute(RetagFilesCommand message)
{
_eBookTagService.RetagFiles(message);
_audioTagService.RetagFiles(message);
}
public void Execute(RetagAuthorCommand message)
{
_eBookTagService.RetagAuthor(message);
_audioTagService.RetagAuthor(message);
}
}
}

@ -57,6 +57,7 @@ namespace NzbDrone.Core.MediaFiles
return GetPreviews(author, files)
.OrderByDescending(e => e.BookId)
.ThenBy(e => e.ExistingPath)
.ToList();
}
@ -66,15 +67,19 @@ namespace NzbDrone.Core.MediaFiles
var files = _mediaFileService.GetFilesByBook(bookId);
return GetPreviews(author, files)
.OrderByDescending(e => e.TrackNumbers.First()).ToList();
.OrderBy(e => e.ExistingPath).ToList();
}
private IEnumerable<RenameBookFilePreview> GetPreviews(Author author, List<BookFile> files)
{
var counts = files.GroupBy(x => x.EditionId).ToDictionary(g => g.Key, g => g.Count());
// Don't rename Calibre files
foreach (var f in files.Where(x => x.CalibreId == 0))
{
var file = f;
file.PartCount = counts[file.EditionId];
var book = file.Edition.Value;
var bookFilePath = file.Path;

@ -113,6 +113,11 @@ namespace NzbDrone.Core.Organizer
fileName = FileNameCleanupRegex.Replace(fileName, match => match.Captures[0].Value[0].ToString());
fileName = TrimSeparatorsRegex.Replace(fileName, string.Empty);
if (bookFile.PartCount > 1)
{
fileName = fileName + " (" + bookFile.Part + ")";
}
return fileName;
}

@ -10,6 +10,8 @@ namespace NzbDrone.Core.Parser.Model
{
public string Path { get; set; }
public int CalibreId { get; set; }
public int Part { get; set; }
public int PartCount { get; set; }
public long Size { get; set; }
public DateTime Modified { get; set; }
public ParsedTrackInfo FileTrackInfo { get; set; }

@ -45,6 +45,7 @@ namespace NzbDrone.Core.Parser.Model
localTrack.Edition = Edition;
localTrack.Book = Edition.Book.Value;
localTrack.Author = Edition.Book.Value.Author.Value;
localTrack.PartCount = LocalBooks.Count;
}
}
}

@ -26,7 +26,7 @@ namespace Readarr.Api.V1.BookFiles
{
private readonly IMediaFileService _mediaFileService;
private readonly IDeleteMediaFiles _mediaFileDeletionService;
private readonly IEBookTagService _eBookTagService;
private readonly IMetadataTagService _metadataTagService;
private readonly IAuthorService _authorService;
private readonly IBookService _bookService;
private readonly IUpgradableSpecification _upgradableSpecification;
@ -34,7 +34,7 @@ namespace Readarr.Api.V1.BookFiles
public BookFileController(IBroadcastSignalRMessage signalRBroadcaster,
IMediaFileService mediaFileService,
IDeleteMediaFiles mediaFileDeletionService,
IEBookTagService eBookTagService,
IMetadataTagService metadataTagService,
IAuthorService authorService,
IBookService bookService,
IUpgradableSpecification upgradableSpecification)
@ -42,7 +42,7 @@ namespace Readarr.Api.V1.BookFiles
{
_mediaFileService = mediaFileService;
_mediaFileDeletionService = mediaFileDeletionService;
_eBookTagService = eBookTagService;
_metadataTagService = metadataTagService;
_authorService = authorService;
_bookService = bookService;
_upgradableSpecification = upgradableSpecification;
@ -63,7 +63,7 @@ namespace Readarr.Api.V1.BookFiles
public override BookFileResource GetResourceById(int id)
{
var resource = MapToResource(_mediaFileService.Get(id));
resource.AudioTags = _eBookTagService.ReadTags((FileInfoBase)new FileInfo(resource.Path));
resource.AudioTags = _metadataTagService.ReadTags((FileInfoBase)new FileInfo(resource.Path));
return resource;
}

@ -10,11 +10,11 @@ namespace Readarr.Api.V1.Books
[V1ApiController("retag")]
public class RetagBookController : Controller
{
private readonly IEBookTagService _eBookTagService;
private readonly IMetadataTagService _metadataTagService;
public RetagBookController(IEBookTagService eBookTagService)
public RetagBookController(IMetadataTagService metadataTagService)
{
_eBookTagService = eBookTagService;
_metadataTagService = metadataTagService;
}
[HttpGet]
@ -22,11 +22,11 @@ namespace Readarr.Api.V1.Books
{
if (bookId.HasValue)
{
return _eBookTagService.GetRetagPreviewsByBook(bookId.Value).Where(x => x.Changes.Any()).ToResource();
return _metadataTagService.GetRetagPreviewsByBook(bookId.Value).Where(x => x.Changes.Any()).ToResource();
}
else if (authorId.HasValue)
{
return _eBookTagService.GetRetagPreviewsByAuthor(authorId.Value).Where(x => x.Changes.Any()).ToResource();
return _metadataTagService.GetRetagPreviewsByAuthor(authorId.Value).Where(x => x.Changes.Any()).ToResource();
}
else
{

@ -5,6 +5,8 @@ namespace Readarr.Api.V1.Config
{
public class MetadataProviderConfigResource : RestResource
{
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; }
@ -16,6 +18,8 @@ namespace Readarr.Api.V1.Config
{
return new MetadataProviderConfigResource
{
WriteAudioTags = model.WriteAudioTags,
ScrubAudioTags = model.ScrubAudioTags,
WriteBookTags = model.WriteBookTags,
UpdateCovers = model.UpdateCovers,
EmbedMetadata = model.EmbedMetadata

Loading…
Cancel
Save