diff --git a/frontend/src/Settings/Metadata/MetadataProvider/MetadataProvider.js b/frontend/src/Settings/Metadata/MetadataProvider/MetadataProvider.js
index 99e631463..1e80619c9 100644
--- a/frontend/src/Settings/Metadata/MetadataProvider/MetadataProvider.js
+++ b/frontend/src/Settings/Metadata/MetadataProvider/MetadataProvider.js
@@ -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) {
+
+
}
diff --git a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs
index 09d384670..1a5be8274 100644
--- a/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs
+++ b/src/NzbDrone.Core.Test/MediaFiles/TrackImport/ImportDecisionMakerFixture.cs
@@ -130,6 +130,10 @@ namespace NzbDrone.Core.Test.MediaFiles.BookImport
.Setup(c => c.FilterUnchangedFiles(It.IsAny>(), It.IsAny()))
.Returns((List files, FilterFilesType filter) => files);
+ Mocker.GetMock()
+ .Setup(s => s.ReadTags(It.IsAny()))
+ .Returns(new ParsedTrackInfo());
+
GivenSpecifications(_bookpass1);
}
diff --git a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
index 181ad5cb1..bb02f7bda 100644
--- a/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
+++ b/src/NzbDrone.Core.Test/OrganizerTests/FileNameBuilderTests/FileNameBuilderFixture.cs
@@ -54,6 +54,8 @@ namespace NzbDrone.Core.Test.OrganizerTests.FileNameBuilderTests
.Setup(c => c.GetConfig()).Returns(_namingConfig);
_trackFile = Builder.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
diff --git a/src/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetAudioFilesFixture.cs b/src/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetAudioFilesFixture.cs
index 719eb3544..f578ee770 100644
--- a/src/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetAudioFilesFixture.cs
+++ b/src/NzbDrone.Core.Test/ProviderTests/DiskScanProviderTests/GetAudioFilesFixture.cs
@@ -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")]
diff --git a/src/NzbDrone.Core/Datastore/Migration/010_add_bookfile_part.cs b/src/NzbDrone.Core/Datastore/Migration/010_add_bookfile_part.cs
new file mode 100644
index 000000000..e10b51a23
--- /dev/null
+++ b/src/NzbDrone.Core/Datastore/Migration/010_add_bookfile_part.cs
@@ -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);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/Datastore/TableMapping.cs b/src/NzbDrone.Core/Datastore/TableMapping.cs
index dc1ba9d33..c1d7e7320 100644
--- a/src/NzbDrone.Core/Datastore/TableMapping.cs
+++ b/src/NzbDrone.Core/Datastore/TableMapping.cs
@@ -150,6 +150,7 @@ namespace NzbDrone.Core.Datastore
b => b.Id > 0);
Mapper.Entity("BookFiles").RegisterModel()
+ .Ignore(x => x.PartCount)
.HasOne(f => f.Edition, f => f.EditionId)
.LazyLoad(x => x.Author,
(db, f) => AuthorRepository.Query(db,
diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs
index b717e06e0..3082f02de 100644
--- a/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs
+++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabCategoryFieldOptionsConverter.cs
@@ -10,7 +10,7 @@ namespace NzbDrone.Core.Indexers.Newznab
public static List GetFieldSelectOptions(List 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();
categories.Add(new NewznabCategory
+ {
+ Id = 3000,
+ Name = "Audio",
+ Subcategories = new List
+ {
+ new NewznabCategory { Id = 3030, Name = "Audiobook" }
+ }
+ });
+ categories.Add(new NewznabCategory
{
Id = 7000,
Name = "Books",
diff --git a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs
index e81b6d1dd..984c8d4cc 100644
--- a/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs
+++ b/src/NzbDrone.Core/Indexers/Newznab/NewznabSettings.cs
@@ -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")]
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json
index 1d68fd762..5c89a1e7e 100644
--- a/src/NzbDrone.Core/Localization/Core/en.json
+++ b/src/NzbDrone.Core/Localization/Core/en.json
@@ -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"
}
diff --git a/src/NzbDrone.Core/MediaFiles/AudioTagService.cs b/src/NzbDrone.Core/MediaFiles/AudioTagService.cs
index b4ab5cbd1..2b9740c68 100644
--- a/src/NzbDrone.Core/MediaFiles/AudioTagService.cs
+++ b/src/NzbDrone.Core/MediaFiles/AudioTagService.cs
@@ -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 tracks);
List GetRetagPreviewsByAuthor(int authorId);
- List GetRetagPreviewsByBook(int authorId);
+ List GetRetagPreviewsByBook(int bookId);
+ void RetagFiles(RetagFilesCommand message);
+ void RetagAuthor(RetagAuthorCommand message);
}
- public class AudioTagService : IAudioTagService,
- IExecute,
- IExecute
+ 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 GetPreviews(List 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);
}
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/BookFile.cs b/src/NzbDrone.Core/MediaFiles/BookFile.cs
index 7fcea767c..56f2ee6c3 100644
--- a/src/NzbDrone.Core/MediaFiles/BookFile.cs
+++ b/src/NzbDrone.Core/MediaFiles/BookFile.cs
@@ -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 { get; set; }
public LazyLoaded Edition { get; set; }
+ // Calculated manually
+ public int PartCount { get; set; }
+
public override string ToString()
{
return string.Format("[{0}] {1}", Id, Path);
diff --git a/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs b/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs
index 6a2a01262..18acb5e6f 100644
--- a/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs
+++ b/src/NzbDrone.Core/MediaFiles/BookImport/ImportApprovedBooks.cs
@@ -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,
diff --git a/src/NzbDrone.Core/MediaFiles/BookImport/ImportDecisionMaker.cs b/src/NzbDrone.Core/MediaFiles/BookImport/ImportDecisionMaker.cs
index a9d742fe2..3a83dc367 100644
--- a/src/NzbDrone.Core/MediaFiles/BookImport/ImportDecisionMaker.cs
+++ b/src/NzbDrone.Core/MediaFiles/BookImport/ImportDecisionMaker.cs
@@ -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> _trackSpecifications;
private readonly IEnumerable> _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> trackSpecifications,
IEnumerable> 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
};
diff --git a/src/NzbDrone.Core/MediaFiles/BookImport/Manual/ManualImportService.cs b/src/NzbDrone.Core/MediaFiles/BookImport/Manual/ManualImportService.cs
index c059c435f..9de8f0906 100644
--- a/src/NzbDrone.Core/MediaFiles/BookImport/Manual/ManualImportService.cs
+++ b/src/NzbDrone.Core/MediaFiles/BookImport/Manual/ManualImportService.cs
@@ -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,
diff --git a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs
index 670631f85..6ec75457c 100644
--- a/src/NzbDrone.Core/MediaFiles/DiskScanService.cs
+++ b/src/NzbDrone.Core/MediaFiles/DiskScanService.cs
@@ -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,
diff --git a/src/NzbDrone.Core/MediaFiles/EbookTagService.cs b/src/NzbDrone.Core/MediaFiles/EbookTagService.cs
index 69222027a..92286ba41 100644
--- a/src/NzbDrone.Core/MediaFiles/EbookTagService.cs
+++ b/src/NzbDrone.Core/MediaFiles/EbookTagService.cs
@@ -28,12 +28,12 @@ namespace NzbDrone.Core.MediaFiles
void WriteTags(BookFile trackfile, bool newDownload, bool force = false);
void SyncTags(List books);
List GetRetagPreviewsByAuthor(int authorId);
- List GetRetagPreviewsByBook(int authorId);
+ List GetRetagPreviewsByBook(int bookId);
+ void RetagFiles(RetagFilesCommand message);
+ void RetagAuthor(RetagAuthorCommand message);
}
- public class EBookTagService : IEBookTagService,
- IExecute,
- IExecute
+ 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);
}
}
diff --git a/src/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs b/src/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs
index 0cfa7bd28..29346de64 100644
--- a/src/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs
+++ b/src/NzbDrone.Core/MediaFiles/MediaFileExtensions.cs
@@ -23,6 +23,22 @@ namespace NzbDrone.Core.MediaFiles
_audioExtensions = new Dictionary(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 },
};
}
diff --git a/src/NzbDrone.Core/MediaFiles/MetadataTagService.cs b/src/NzbDrone.Core/MediaFiles/MetadataTagService.cs
new file mode 100644
index 000000000..b122b3286
--- /dev/null
+++ b/src/NzbDrone.Core/MediaFiles/MetadataTagService.cs
@@ -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 GetRetagPreviewsByAuthor(int authorId);
+ List GetRetagPreviewsByBook(int authorId);
+ }
+
+ public class MetadataTagService : IMetadataTagService,
+ IExecute,
+ IExecute
+ {
+ 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 GetRetagPreviewsByAuthor(int authorId)
+ {
+ var previews = _audioTagService.GetRetagPreviewsByAuthor(authorId);
+ previews.AddRange(_eBookTagService.GetRetagPreviewsByAuthor(authorId));
+
+ return previews;
+ }
+
+ public List 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);
+ }
+ }
+}
diff --git a/src/NzbDrone.Core/MediaFiles/RenameBookFileService.cs b/src/NzbDrone.Core/MediaFiles/RenameBookFileService.cs
index b17c28493..7d182bc6d 100644
--- a/src/NzbDrone.Core/MediaFiles/RenameBookFileService.cs
+++ b/src/NzbDrone.Core/MediaFiles/RenameBookFileService.cs
@@ -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 GetPreviews(Author author, List 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;
diff --git a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
index eb12735ce..a5c53e9db 100644
--- a/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
+++ b/src/NzbDrone.Core/Organizer/FileNameBuilder.cs
@@ -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;
}
diff --git a/src/NzbDrone.Core/Parser/Model/LocalBook.cs b/src/NzbDrone.Core/Parser/Model/LocalBook.cs
index 0f9133b85..65f74e9e6 100644
--- a/src/NzbDrone.Core/Parser/Model/LocalBook.cs
+++ b/src/NzbDrone.Core/Parser/Model/LocalBook.cs
@@ -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; }
diff --git a/src/NzbDrone.Core/Parser/Model/LocalEdition.cs b/src/NzbDrone.Core/Parser/Model/LocalEdition.cs
index 4122f2b1d..ca40b82ac 100644
--- a/src/NzbDrone.Core/Parser/Model/LocalEdition.cs
+++ b/src/NzbDrone.Core/Parser/Model/LocalEdition.cs
@@ -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;
}
}
}
diff --git a/src/Readarr.Api.V1/BookFiles/BookFileController.cs b/src/Readarr.Api.V1/BookFiles/BookFileController.cs
index 71db5a9fb..d633fc0ae 100644
--- a/src/Readarr.Api.V1/BookFiles/BookFileController.cs
+++ b/src/Readarr.Api.V1/BookFiles/BookFileController.cs
@@ -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;
}
diff --git a/src/Readarr.Api.V1/Books/RetagBookController.cs b/src/Readarr.Api.V1/Books/RetagBookController.cs
index c3737a0af..0b6d77b1b 100644
--- a/src/Readarr.Api.V1/Books/RetagBookController.cs
+++ b/src/Readarr.Api.V1/Books/RetagBookController.cs
@@ -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
{
diff --git a/src/Readarr.Api.V1/Config/MetadataProviderConfigResource.cs b/src/Readarr.Api.V1/Config/MetadataProviderConfigResource.cs
index 03f112d9d..20ae164b3 100644
--- a/src/Readarr.Api.V1/Config/MetadataProviderConfigResource.cs
+++ b/src/Readarr.Api.V1/Config/MetadataProviderConfigResource.cs
@@ -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