diff --git a/Documentation/NamingTemplates.md b/Documentation/NamingTemplates.md index 0f26c62e..8d311613 100644 --- a/Documentation/NamingTemplates.md +++ b/Documentation/NamingTemplates.md @@ -25,8 +25,10 @@ These tags will be replaced in the template with the audiobook's values. |Tag|Description|Type| |-|-|-| |\ **†**|Audible book ID (ASIN)|Text| -|\|Full title|Text| +|\|Full title with subtitle|Text| |\|Title. Stop at first colon|Text| +|\<audible title\>|Audible's title (does not include subtitle)|Text| +|\<audible subtitle\>|Audible's subtitle|Text| |\<author\>|Author(s)|Name List| |\<first author\>|First author|Text| |\<narrator\>|Narrator(s)|Name List| diff --git a/Source/ApplicationServices/LibraryExporter.cs b/Source/ApplicationServices/LibraryExporter.cs index 713e2317..29a0d74f 100644 --- a/Source/ApplicationServices/LibraryExporter.cs +++ b/Source/ApplicationServices/LibraryExporter.cs @@ -35,6 +35,9 @@ namespace ApplicationServices [Name("Title")] public string Title { get; set; } + [Name("Subtitle")] + public string Subtitle { get; set; } + [Name("Authors")] public string AuthorNames { get; set; } @@ -123,6 +126,7 @@ namespace ApplicationServices AudibleProductId = a.Book.AudibleProductId, Locale = a.Book.Locale, Title = a.Book.Title, + Subtitle = a.Book.Subtitle, AuthorNames = a.Book.AuthorNames(), NarratorNames = a.Book.NarratorNames(), LengthInMinutes = a.Book.LengthInMinutes, @@ -198,6 +202,7 @@ namespace ApplicationServices nameof(ExportDto.AudibleProductId), nameof(ExportDto.Locale), nameof(ExportDto.Title), + nameof(ExportDto.Subtitle), nameof(ExportDto.AuthorNames), nameof(ExportDto.NarratorNames), nameof(ExportDto.LengthInMinutes), @@ -256,6 +261,7 @@ namespace ApplicationServices row.CreateCell(col++).SetCellValue(dto.AudibleProductId); row.CreateCell(col++).SetCellValue(dto.Locale); row.CreateCell(col++).SetCellValue(dto.Title); + row.CreateCell(col++).SetCellValue(dto.Subtitle); row.CreateCell(col++).SetCellValue(dto.AuthorNames); row.CreateCell(col++).SetCellValue(dto.NarratorNames); row.CreateCell(col++).SetCellValue(dto.LengthInMinutes); diff --git a/Source/ApplicationServices/RecordExporter.cs b/Source/ApplicationServices/RecordExporter.cs index 44101738..52506383 100644 --- a/Source/ApplicationServices/RecordExporter.cs +++ b/Source/ApplicationServices/RecordExporter.cs @@ -108,7 +108,7 @@ namespace ApplicationServices var recordsObj = new JObject { - { "title", libraryBook.Book.Title}, + { "title", libraryBook.Book.TitleWithSubtitle}, { "asin", libraryBook.Book.AudibleProductId}, { "exportTime", DateTime.Now}, { "records", JArray.FromObject(recordsEx) } diff --git a/Source/DataLayer/Configurations/BookConfig.cs b/Source/DataLayer/Configurations/BookConfig.cs index 55c13038..af5bad8a 100644 --- a/Source/DataLayer/Configurations/BookConfig.cs +++ b/Source/DataLayer/Configurations/BookConfig.cs @@ -20,6 +20,7 @@ namespace DataLayer.Configurations entity.Ignore(nameof(Book.Authors)); entity.Ignore(nameof(Book.Narrators)); entity.Ignore(nameof(Book.AudioFormat)); + entity.Ignore(nameof(Book.TitleWithSubtitle)); //// these don't seem to matter //entity.Ignore(nameof(Book.AuthorNames)); //entity.Ignore(nameof(Book.NarratorNames)); diff --git a/Source/DataLayer/EfClasses/Book.cs b/Source/DataLayer/EfClasses/Book.cs index 47c2442b..2492a5dd 100644 --- a/Source/DataLayer/EfClasses/Book.cs +++ b/Source/DataLayer/EfClasses/Book.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Dinah.Core; using Microsoft.EntityFrameworkCore; @@ -34,9 +33,11 @@ namespace DataLayer // immutable public string AudibleProductId { get; private set; } - public string Title { get; set; } - public string Subtitle { get; set; } - public string Description { get; private set; } + public string Title { get; private set; } + public string Subtitle { get; private set; } + private string _titleWithSubtitle; + public string TitleWithSubtitle => _titleWithSubtitle ??= string.IsNullOrEmpty(Subtitle) ? Title : $"{Title}: {Subtitle}"; + public string Description { get; private set; } public int LengthInMinutes { get; private set; } public ContentType ContentType { get; private set; } public string Locale { get; private set; } @@ -101,9 +102,8 @@ namespace DataLayer Category = category; // simple assigns - Title = title.Trim() ?? ""; - Subtitle = subtitle?.Trim() ?? ""; - Description = description?.Trim() ?? ""; + UpdateTitle(title, subtitle); + Description = description?.Trim() ?? ""; LengthInMinutes = lengthInMinutes; ContentType = contentType; @@ -111,10 +111,16 @@ namespace DataLayer ReplaceAuthors(authors); ReplaceNarrators(narrators); } + public void UpdateTitle(string title, string subtitle) + { + Title = title?.Trim() ?? ""; + Subtitle = subtitle?.Trim() ?? ""; + _titleWithSubtitle = null; + } - #region contributors, authors, narrators - // use uninitialised backing fields - this means we can detect if the collection was loaded - private HashSet<BookContributor> _contributorsLink; + #region contributors, authors, narrators + // use uninitialised backing fields - this means we can detect if the collection was loaded + private HashSet<BookContributor> _contributorsLink; // i'd like this to be internal but migration throws this exception when i try: // Value cannot be null. // Parameter name: property @@ -237,6 +243,6 @@ namespace DataLayer Category = category; } - public override string ToString() => $"[{AudibleProductId}] {Title}"; + public override string ToString() => $"[{AudibleProductId}] {TitleWithSubtitle}"; } } diff --git a/Source/DataLayer/EntityExtensions.cs b/Source/DataLayer/EntityExtensions.cs index d5b264cf..97b6ff8d 100644 --- a/Source/DataLayer/EntityExtensions.cs +++ b/Source/DataLayer/EntityExtensions.cs @@ -8,7 +8,7 @@ namespace DataLayer { public static class EntityExtensions { - public static string TitleSortable(this Book book) => Formatters.GetSortName(book.Title); + public static string TitleSortable(this Book book) => Formatters.GetSortName(book.Title + book.Subtitle); public static string AuthorNames(this Book book) => string.Join(", ", book.Authors.Select(a => a.Name)); public static string NarratorNames(this Book book) => string.Join(", ", book.Narrators.Select(n => n.Name)); @@ -62,7 +62,7 @@ namespace DataLayer max = Math.Max(max, 1); - var titles = libraryBooks.Select(lb => "- " + lb.Book.Title).ToList(); + var titles = libraryBooks.Select(lb => "- " + lb.Book.TitleWithSubtitle).ToList(); var titlesAgg = titles.Take(max).Aggregate((a, b) => $"{a}\r\n{b}"); if (titles.Count == max + 1) titlesAgg += $"\r\n\r\nand 1 other"; diff --git a/Source/DtoImporterService/BookImporter.cs b/Source/DtoImporterService/BookImporter.cs index 2cb3b875..14d5d196 100644 --- a/Source/DtoImporterService/BookImporter.cs +++ b/Source/DtoImporterService/BookImporter.cs @@ -166,8 +166,7 @@ namespace DtoImporterService var item = importItem.DtoItem; // Update the book titles, since formatting can change - book.Title = item.Title; - book.Subtitle = item.Subtitle; + book.UpdateTitle(item.Title, item.Subtitle); var codec = item.AvailableCodecs?.Max(f => AudioFormat.FromString(f.EnhancedCodec)) ?? new AudioFormat(); book.AudioFormat = codec; diff --git a/Source/FileLiberator/DownloadDecryptBook.cs b/Source/FileLiberator/DownloadDecryptBook.cs index 5f0902aa..6ffc7f9b 100644 --- a/Source/FileLiberator/DownloadDecryptBook.cs +++ b/Source/FileLiberator/DownloadDecryptBook.cs @@ -331,9 +331,9 @@ namespace FileLiberator string errorTitle() { var title - = (libraryBook.Book.Title.Length > 53) - ? $"{libraryBook.Book.Title.Truncate(50)}..." - : libraryBook.Book.Title; + = (libraryBook.Book.TitleWithSubtitle.Length > 53) + ? $"{libraryBook.Book.TitleWithSubtitle.Truncate(50)}..." + : libraryBook.Book.TitleWithSubtitle; var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]"; return errorBookTitle; }; diff --git a/Source/FileLiberator/Processable.cs b/Source/FileLiberator/Processable.cs index 724c0cdc..54d360bf 100644 --- a/Source/FileLiberator/Processable.cs +++ b/Source/FileLiberator/Processable.cs @@ -45,7 +45,7 @@ namespace FileLiberator Serilog.Log.Logger.Information("Begin " + nameof(ProcessSingleAsync) + " {@DebugInfo}", new { - libraryBook.Book.Title, + libraryBook.Book.TitleWithSubtitle, libraryBook.Book.AudibleProductId, libraryBook.Book.Locale, Account = libraryBook.Account?.ToMask() ?? "[empty]" diff --git a/Source/FileLiberator/UtilityExtensions.cs b/Source/FileLiberator/UtilityExtensions.cs index 14e818f9..3cef22ca 100644 --- a/Source/FileLiberator/UtilityExtensions.cs +++ b/Source/FileLiberator/UtilityExtensions.cs @@ -14,7 +14,7 @@ namespace FileLiberator public static (string id, string title, string locale, string account) LogFriendly(this LibraryBook libraryBook) => ( id: libraryBook.Book.AudibleProductId, - title: libraryBook.Book.Title, + title: libraryBook.Book.TitleWithSubtitle, locale: libraryBook.Book.Locale, account: libraryBook.Account.ToMask() ); @@ -40,8 +40,9 @@ namespace FileLiberator DateAdded = libraryBook.DateAdded, AudibleProductId = libraryBook.Book.AudibleProductId, - Title = libraryBook.Book.Title ?? "", - Subtitle = libraryBook.Book.Subtitle ?? "", + Title = libraryBook.Book.Title, + Subtitle = libraryBook.Book.Subtitle, + TitleWithSubtitle = libraryBook.Book.TitleWithSubtitle, Locale = libraryBook.Book.Locale, YearPublished = libraryBook.Book.DatePublished?.Year, DatePublished = libraryBook.Book.DatePublished, diff --git a/Source/LibationAvalonia/Dialogs/BookDetailsDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/BookDetailsDialog.axaml.cs index 0e26b28f..49833015 100644 --- a/Source/LibationAvalonia/Dialogs/BookDetailsDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/BookDetailsDialog.axaml.cs @@ -21,7 +21,7 @@ namespace LibationAvalonia.Dialogs set { _libraryBook = value; - Title = _libraryBook.Book.Title; + Title = _libraryBook.Book.TitleWithSubtitle; DataContext = _viewModel = new BookDetailsDialogViewModel(_libraryBook); } } @@ -106,9 +106,11 @@ namespace LibationAvalonia.Dialogs var picture = PictureStorage.GetPictureSynchronously(new PictureDefinition(libraryBook.Book.PictureId, PictureSize._80x80)); Cover = AvaloniaUtils.TryLoadImageOrDefault(picture, PictureSize._80x80); + var title = string.IsNullOrEmpty(Book.Subtitle) ? Book.Title : $"{Book.Title}\r\n {Book.Subtitle}"; + //init book details DetailsText = @$" -Title: {Book.Title} +Title: {title} Author(s): {Book.AuthorNames()} Narrator(s): {Book.NarratorNames()} Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")} diff --git a/Source/LibationAvalonia/Dialogs/BookRecordsDialog.axaml.cs b/Source/LibationAvalonia/Dialogs/BookRecordsDialog.axaml.cs index 0dc1e65f..bb04161e 100644 --- a/Source/LibationAvalonia/Dialogs/BookRecordsDialog.axaml.cs +++ b/Source/LibationAvalonia/Dialogs/BookRecordsDialog.axaml.cs @@ -37,7 +37,7 @@ namespace LibationAvalonia.Dialogs public BookRecordsDialog(LibraryBook libraryBook) : this() { this.libraryBook = libraryBook; - Title = $"{libraryBook.Book.Title} - Clips and Bookmarks"; + Title = $"{libraryBook.Book.TitleWithSubtitle} - Clips and Bookmarks"; Loaded += BookRecordsDialog_Loaded; } @@ -148,7 +148,7 @@ namespace LibationAvalonia.Dialogs await Dispatcher.UIThread.InvokeAsync(() => new FilePickerSaveOptions { Title = "Where to export book records", - SuggestedFileName = $"{libraryBook.Book.Title} - Records", + SuggestedFileName = $"{libraryBook.Book.TitleWithSubtitle} - Records", DefaultExtension = "xlsx", ShowOverwritePrompt = true, FileTypeChoices = new FilePickerFileType[] diff --git a/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs b/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs index f8fc4159..80d16cf8 100644 --- a/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProcessBookViewModel.cs @@ -105,7 +105,7 @@ namespace LibationAvalonia.ViewModels LibraryBook = libraryBook; Logger = logme; - _title = LibraryBook.Book.Title; + _title = LibraryBook.Book.TitleWithSubtitle; _author = LibraryBook.Book.AuthorNames(); _narrator = LibraryBook.Book.NarratorNames(); @@ -305,7 +305,7 @@ namespace LibationAvalonia.ViewModels Logger.Info($"{Environment.NewLine}{((Processable)sender).Name} Step, Begin: {libraryBook.Book}"); - Title = libraryBook.Book.Title; + Title = libraryBook.Book.TitleWithSubtitle; Author = libraryBook.Book.AuthorNames(); Narrator = libraryBook.Book.NarratorNames(); } @@ -372,7 +372,7 @@ namespace LibationAvalonia.ViewModels : str; details = -$@" Title: {libraryBook.Book.Title} +$@" Title: {libraryBook.Book.TitleWithSubtitle} ID: {libraryBook.Book.AudibleProductId} Author: {trunc(libraryBook.Book.AuthorNames())} Narr: {trunc(libraryBook.Book.NarratorNames())}"; @@ -392,7 +392,7 @@ $@" Title: {libraryBook.Book.Title} { libraryBook.UpdateBookStatus(LiberatedStatus.Error); - Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}"); + Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.TitleWithSubtitle}"); return ProcessBookResult.FailedSkip; } diff --git a/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs b/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs index 0f86d4b5..d1005185 100644 --- a/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs +++ b/Source/LibationAvalonia/ViewModels/ProcessQueueViewModel.cs @@ -227,7 +227,7 @@ namespace LibationAvalonia.ViewModels else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage) { await MessageBox.Show(@$" -You were denied a content license for {nextBook.LibraryBook.Book.Title} +You were denied a content license for {nextBook.LibraryBook.Book.TitleWithSubtitle} This error appears to be caused by a temporary interruption of service that sometimes affects Libation's users. This type of error usually resolves itself in 1 to 2 days, and in the meantime you should still be able to access your books through Audible's website or app. ", diff --git a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs index 2e421f09..c5313917 100644 --- a/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs +++ b/Source/LibationAvalonia/Views/ProductsDisplay.axaml.cs @@ -159,7 +159,7 @@ namespace LibationAvalonia.Views var openFileDialogOptions = new FilePickerOpenOptions { - Title = $"Locate the audio file for '{entry.Book.Title}'", + Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'", AllowMultiple = false, SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix), FileTypeFilter = new FilePickerFileType[] diff --git a/Source/LibationFileManager/LibraryBookDto.cs b/Source/LibationFileManager/LibraryBookDto.cs index cb3a022f..0703873c 100644 --- a/Source/LibationFileManager/LibraryBookDto.cs +++ b/Source/LibationFileManager/LibraryBookDto.cs @@ -8,22 +8,9 @@ namespace LibationFileManager { public string AudibleProductId { get; set; } public string Title { get; set; } - public string Subtitle { get; set; } - public string TitleWithSubtitle - { - get - { - string text = Title?.Trim(); - string text2 = Subtitle?.Trim(); - if (string.IsNullOrWhiteSpace(text2)) - { - return text; - } - - return text + ": " + text2; - } - } - public string Locale { get; set; } + public string Subtitle { get; set; } + public string TitleWithSubtitle { get; set; } + public string Locale { get; set; } public int? YearPublished { get; set; } public IEnumerable<string> Authors { get; set; } diff --git a/Source/LibationFileManager/TemplateTags.cs b/Source/LibationFileManager/TemplateTags.cs index f9320c56..725eb98d 100644 --- a/Source/LibationFileManager/TemplateTags.cs +++ b/Source/LibationFileManager/TemplateTags.cs @@ -2,13 +2,13 @@ namespace LibationFileManager { - public sealed class TemplateTags : ITemplateTag + public sealed class TemplateTags : ITemplateTag { public const string DEFAULT_DATE_FORMAT = "yyyy-MM-dd"; public string TagName { get; } public string DefaultValue { get; } - public string Description { get; } - public string Display { get; } + public string Description { get; } + public string Display { get; } private TemplateTags(string tagName, string description, string defaultValue = null, string display = null) { @@ -19,36 +19,38 @@ namespace LibationFileManager } public static TemplateTags ChCount { get; } = new TemplateTags("ch count", "Number of chapters"); - public static TemplateTags ChTitle { get; } = new TemplateTags("ch title", "Chapter title"); - public static TemplateTags ChNumber { get; } = new TemplateTags("ch#", "Chapter #"); - public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter # with leading zeros"); + public static TemplateTags ChTitle { get; } = new TemplateTags("ch title", "Chapter title"); + public static TemplateTags ChNumber { get; } = new TemplateTags("ch#", "Chapter #"); + public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter # with leading zeros"); - public static TemplateTags Id { get; } = new TemplateTags("id", "Audible ID"); - public static TemplateTags Title { get; } = new TemplateTags("title", "Full title"); - public static TemplateTags TitleShort { get; } = new TemplateTags("title short", "Title. Stop at first colon"); - public static TemplateTags Author { get; } = new TemplateTags("author", "Author(s)"); - public static TemplateTags FirstAuthor { get; } = new TemplateTags("first author", "First author"); - public static TemplateTags Narrator { get; } = new TemplateTags("narrator", "Narrator(s)"); - public static TemplateTags FirstNarrator { get; } = new TemplateTags("first narrator", "First narrator"); - public static TemplateTags Series { get; } = new TemplateTags("series", "Name of series"); - // can't also have a leading zeros version. Too many weird edge cases. Eg: "1-4" - public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series"); - public static TemplateTags Bitrate { get; } = new TemplateTags("bitrate", "File's orig. bitrate"); - public static TemplateTags SampleRate { get; } = new TemplateTags("samplerate", "File's orig. sample rate"); - public static TemplateTags Channels { get; } = new TemplateTags("channels", "Number of audio channels"); - public static TemplateTags Account { get; } = new TemplateTags("account", "Audible account of this book"); - public static TemplateTags AccountNickname { get; } = new TemplateTags("account nickname", "Audible account nickname of this book"); - public static TemplateTags Locale { get; } = new ("locale", "Region/country"); - public static TemplateTags YearPublished { get; } = new("year", "Year published"); + public static TemplateTags Id { get; } = new TemplateTags("id", "Audible ID"); + public static TemplateTags Title { get; } = new TemplateTags("title", "Full title with subtitle"); + public static TemplateTags TitleShort { get; } = new TemplateTags("title short", "Title. Stop at first colon"); + public static TemplateTags AudibleTitle { get; } = new TemplateTags("audible title", "Audible's title (does not include subtitle)"); + public static TemplateTags AudibleSubtitle { get; } = new TemplateTags("audible subtitle", "Audible's subtitle"); + public static TemplateTags Author { get; } = new TemplateTags("author", "Author(s)"); + public static TemplateTags FirstAuthor { get; } = new TemplateTags("first author", "First author"); + public static TemplateTags Narrator { get; } = new TemplateTags("narrator", "Narrator(s)"); + public static TemplateTags FirstNarrator { get; } = new TemplateTags("first narrator", "First narrator"); + public static TemplateTags Series { get; } = new TemplateTags("series", "Name of series"); + // can't also have a leading zeros version. Too many weird edge cases. Eg: "1-4" + public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series"); + public static TemplateTags Bitrate { get; } = new TemplateTags("bitrate", "File's orig. bitrate"); + public static TemplateTags SampleRate { get; } = new TemplateTags("samplerate", "File's orig. sample rate"); + public static TemplateTags Channels { get; } = new TemplateTags("channels", "Number of audio channels"); + public static TemplateTags Account { get; } = new TemplateTags("account", "Audible account of this book"); + public static TemplateTags AccountNickname { get; } = new TemplateTags("account nickname", "Audible account nickname of this book"); + public static TemplateTags Locale { get; } = new ("locale", "Region/country"); + public static TemplateTags YearPublished { get; } = new("year", "Year published"); public static TemplateTags Language { get; } = new("language", "Book's language"); public static TemplateTags LanguageShort { get; } = new("language short", "Book's language abbreviated. Eg: ENG"); public static TemplateTags FileDate { get; } = new TemplateTags("file date", "File date/time. e.g. yyyy-MM-dd HH-mm", $"<file date [{DEFAULT_DATE_FORMAT}]>", "<file date [...]>"); - public static TemplateTags DatePublished { get; } = new TemplateTags("pub date", "Publication date. e.g. yyyy-MM-dd", $"<pub date [{DEFAULT_DATE_FORMAT}]>", "<pub date [...]>"); - public static TemplateTags DateAdded { get; } = new TemplateTags("date added", "Date added to your Audible account. e.g. yyyy-MM-dd", $"<date added [{DEFAULT_DATE_FORMAT}]>", "<date added [...]>"); - public static TemplateTags IfSeries { get; } = new TemplateTags("if series", "Only include if part of a book series or podcast", "<if series-><-if series>", "<if series->...<-if series>"); - public static TemplateTags IfPodcast { get; } = new TemplateTags("if podcast", "Only include if part of a podcast", "<if podcast-><-if podcast>", "<if podcast->...<-if podcast>"); - public static TemplateTags IfPodcastParent { get; } = new TemplateTags("if podcastparent", "Only include if item is a podcast series parent", "<if podcastparent-><-if podcastparent>", "<if podcastparent->...<-if podcastparent>"); - public static TemplateTags IfBookseries { get; } = new TemplateTags("if bookseries", "Only include if part of a book series", "<if bookseries-><-if bookseries>", "<if bookseries->...<-if bookseries>"); - } + public static TemplateTags DatePublished { get; } = new TemplateTags("pub date", "Publication date. e.g. yyyy-MM-dd", $"<pub date [{DEFAULT_DATE_FORMAT}]>", "<pub date [...]>"); + public static TemplateTags DateAdded { get; } = new TemplateTags("date added", "Date added to your Audible account. e.g. yyyy-MM-dd", $"<date added [{DEFAULT_DATE_FORMAT}]>", "<date added [...]>"); + public static TemplateTags IfSeries { get; } = new TemplateTags("if series", "Only include if part of a book series or podcast", "<if series-><-if series>", "<if series->...<-if series>"); + public static TemplateTags IfPodcast { get; } = new TemplateTags("if podcast", "Only include if part of a podcast", "<if podcast-><-if podcast>", "<if podcast->...<-if podcast>"); + public static TemplateTags IfPodcastParent { get; } = new TemplateTags("if podcastparent", "Only include if item is a podcast series parent", "<if podcastparent-><-if podcastparent>", "<if podcastparent->...<-if podcastparent>"); + public static TemplateTags IfBookseries { get; } = new TemplateTags("if bookseries", "Only include if part of a book series", "<if bookseries-><-if bookseries>", "<if bookseries->...<-if bookseries>"); + } } diff --git a/Source/LibationFileManager/Templates.cs b/Source/LibationFileManager/Templates.cs index 6b8be956..2a24cf22 100644 --- a/Source/LibationFileManager/Templates.cs +++ b/Source/LibationFileManager/Templates.cs @@ -248,7 +248,9 @@ namespace LibationFileManager //Don't allow formatting of Id { TemplateTags.Id, lb => lb.AudibleProductId, v => v }, { TemplateTags.Title, lb => lb.TitleWithSubtitle }, - { TemplateTags.TitleShort, lb => lb.Title }, + { TemplateTags.TitleShort, lb => getTitleShort(lb.Title) }, + { TemplateTags.AudibleTitle, lb => lb.Title }, + { TemplateTags.AudibleSubtitle, lb => lb.Subtitle }, { TemplateTags.Author, lb => lb.Authors, NameListFormat.Formatter }, { TemplateTags.FirstAuthor, lb => lb.FirstAuthor }, { TemplateTags.Narrator, lb => lb.Narrators, NameListFormat.Formatter }, @@ -275,7 +277,9 @@ namespace LibationFileManager new PropertyTagCollection<LibraryBookDto>(caseSensative: true, StringFormatter) { { TemplateTags.Title, lb => lb.TitleWithSubtitle }, - { TemplateTags.TitleShort, lb => lb.Title }, + { TemplateTags.TitleShort, lb => getTitleShort(lb.Title) }, + { TemplateTags.AudibleTitle, lb => lb.Title }, + { TemplateTags.AudibleSubtitle, lb => lb.Subtitle }, { TemplateTags.Series, lb => lb.SeriesName }, }, new PropertyTagCollection<MultiConvertFileProperties>(caseSensative: true, StringFormatter, IntegerFormatter, DateTimeFormatter) diff --git a/Source/LibationSearchEngine/SearchEngine.cs b/Source/LibationSearchEngine/SearchEngine.cs index 00b80db7..061957f2 100644 --- a/Source/LibationSearchEngine/SearchEngine.cs +++ b/Source/LibationSearchEngine/SearchEngine.cs @@ -35,7 +35,7 @@ namespace LibationSearchEngine { { FieldType.ID, lb => lb.Book.AudibleProductId.ToLowerInvariant(), nameof(Book.AudibleProductId), "ProductId", "Id", "ASIN" }, { FieldType.Raw, lb => lb.Book.AudibleProductId, _ID_ }, - { FieldType.String, lb => lb.Book.Title, nameof(Book.Title), "ProductId", "Id", "ASIN" }, + { FieldType.String, lb => lb.Book.TitleWithSubtitle, "Title", "ProductId", "Id", "ASIN" }, { FieldType.String, lb => lb.Book.AuthorNames(), "AuthorNames", "Author", "Authors" }, { FieldType.String, lb => lb.Book.NarratorNames(), "NarratorNames", "Narrator", "Narrators" }, { FieldType.String, lb => lb.Book.Publisher, nameof(Book.Publisher) }, diff --git a/Source/LibationUiBase/GridView/GridEntry[TStatus].cs b/Source/LibationUiBase/GridView/GridEntry[TStatus].cs index 01d89f01..7517dd25 100644 --- a/Source/LibationUiBase/GridView/GridEntry[TStatus].cs +++ b/Source/LibationUiBase/GridView/GridEntry[TStatus].cs @@ -105,7 +105,7 @@ namespace LibationUiBase.GridView Liberate = TStatus.Create(libraryBook); Liberate.Expanded = expanded; - Title = Book.Title; + Title = Book.TitleWithSubtitle; Series = Book.SeriesNames(includeIndex: true); SeriesOrder = new SeriesOrder(Book.SeriesLink); Length = GetBookLengthString(); diff --git a/Source/LibationUiBase/SeriesView/AyceButton.cs b/Source/LibationUiBase/SeriesView/AyceButton.cs index 07d4318c..263e6773 100644 --- a/Source/LibationUiBase/SeriesView/AyceButton.cs +++ b/Source/LibationUiBase/SeriesView/AyceButton.cs @@ -108,7 +108,7 @@ namespace LibationUiBase.SeriesView { Asin = seriesParent.AudibleProductId, Sequence = item.Relationships.FirstOrDefault(r => r.Asin == seriesParent.AudibleProductId)?.Sort?.ToString() ?? "0", - Title = seriesParent.Title + Title = seriesParent.TitleWithSubtitle } }; } diff --git a/Source/LibationWinForms/Dialogs/BookDetailsDialog.cs b/Source/LibationWinForms/Dialogs/BookDetailsDialog.cs index be111a04..bad7ee86 100644 --- a/Source/LibationWinForms/Dialogs/BookDetailsDialog.cs +++ b/Source/LibationWinForms/Dialogs/BookDetailsDialog.cs @@ -38,13 +38,14 @@ namespace LibationWinForms.Dialogs // 1st draft: lazily cribbed from GridEntry.ctor() private void initDetails() { - this.Text = Book.Title; + this.Text = Book.TitleWithSubtitle; (_, var picture) = PictureStorage.GetPicture(new PictureDefinition(Book.PictureId, PictureSize._80x80)); this.coverPb.Image = WinFormsUtil.TryLoadImageOrDefault(picture, PictureSize._80x80); + var title = string.IsNullOrEmpty(Book.Subtitle) ? Book.Title : $"{Book.Title}\r\n {Book.Subtitle}"; var t = @$" -Title: {Book.Title} +Title: {title} Author(s): {Book.AuthorNames()} Narrator(s): {Book.NarratorNames()} Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")} diff --git a/Source/LibationWinForms/Dialogs/BookRecordsDialog.cs b/Source/LibationWinForms/Dialogs/BookRecordsDialog.cs index bc54876e..a93e652e 100644 --- a/Source/LibationWinForms/Dialogs/BookRecordsDialog.cs +++ b/Source/LibationWinForms/Dialogs/BookRecordsDialog.cs @@ -45,7 +45,7 @@ namespace LibationWinForms.Dialogs { this.libraryBook = libraryBook; - Text = $"{libraryBook.Book.Title} - Clips and Bookmarks"; + Text = $"{libraryBook.Book.TitleWithSubtitle} - Clips and Bookmarks"; } private async void BookRecordsDialog_Shown(object sender, EventArgs e) @@ -182,7 +182,7 @@ namespace LibationWinForms.Dialogs { Title = "Where to export records", AddExtension = true, - FileName = $"{libraryBook.Book.Title} - Records", + FileName = $"{libraryBook.Book.TitleWithSubtitle} - Records", DefaultExt = "xlsx", Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*" }); diff --git a/Source/LibationWinForms/GridView/ProductsDisplay.cs b/Source/LibationWinForms/GridView/ProductsDisplay.cs index 342c5fcf..75996447 100644 --- a/Source/LibationWinForms/GridView/ProductsDisplay.cs +++ b/Source/LibationWinForms/GridView/ProductsDisplay.cs @@ -176,7 +176,7 @@ namespace LibationWinForms.GridView { var openFileDialog = new OpenFileDialog { - Title = $"Locate the audio file for '{entry.Book.Title}'", + Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'", Filter = "All files (*.*)|*.*", FilterIndex = 1 }; diff --git a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs index 50b3e1b5..ecb372f5 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessBook.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessBook.cs @@ -77,7 +77,7 @@ namespace LibationWinForms.ProcessQueue LibraryBook = libraryBook; Logger = logme; - title = LibraryBook.Book.Title; + title = LibraryBook.Book.TitleWithSubtitle; authorNames = LibraryBook.Book.AuthorNames(); narratorNames = LibraryBook.Book.NarratorNames(); _bookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}"; @@ -291,7 +291,7 @@ namespace LibationWinForms.ProcessQueue Logger.Info($"{Environment.NewLine}{((Processable)sender).Name} Step, Begin: {libraryBook.Book}"); - title = libraryBook.Book.Title; + title = libraryBook.Book.TitleWithSubtitle; authorNames = libraryBook.Book.AuthorNames(); narratorNames = libraryBook.Book.NarratorNames(); updateBookInfo(); @@ -359,7 +359,7 @@ namespace LibationWinForms.ProcessQueue : str; details = -$@" Title: {libraryBook.Book.Title} +$@" Title: {libraryBook.Book.TitleWithSubtitle} ID: {libraryBook.Book.AudibleProductId} Author: {trunc(libraryBook.Book.AuthorNames())} Narr: {trunc(libraryBook.Book.NarratorNames())}"; @@ -379,7 +379,7 @@ $@" Title: {libraryBook.Book.Title} { libraryBook.UpdateBookStatus(LiberatedStatus.Error); - Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.Title}"); + Logger.Info($"Error. Skip: [{libraryBook.Book.AudibleProductId}] {libraryBook.Book.TitleWithSubtitle}"); return ProcessBookResult.FailedSkip; } diff --git a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs index e05b23b7..da7bebbc 100644 --- a/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs +++ b/Source/LibationWinForms/ProcessQueue/ProcessQueueControl.cs @@ -188,7 +188,7 @@ namespace LibationWinForms.ProcessQueue else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage) { MessageBox.Show(@$" -You were denied a content license for {nextBook.LibraryBook.Book.Title} +You were denied a content license for {nextBook.LibraryBook.Book.TitleWithSubtitle} This error appears to be caused by a temporary interruption of service that sometimes affects Libation's users. This type of error usually resolves itself in 1 to 2 days, and in the meantime you should still be able to access your books through Audible's website or app. ", diff --git a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs index aeb66acb..0864d632 100644 --- a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs +++ b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs @@ -463,7 +463,7 @@ namespace Templates_Other extension = FileUtility.GetStandardizedExtension(extension); var lbDto = GetLibraryBook(); - lbDto.Title = title; + lbDto.TitleWithSubtitle = title; lbDto.AudibleProductId = "ID123456"; Templates.TryGetTemplate<Templates.FolderTemplate>(template, out var fileNamingTemplate).Should().BeTrue(); @@ -491,7 +491,7 @@ namespace Templates_Other var template = Path.GetFileNameWithoutExtension(originalPath) + " - <ch# 0> - <title>" + estension; var lbDto = GetLibraryBook(); - lbDto.Title = suffix; + lbDto.TitleWithSubtitle = suffix; Templates.TryGetTemplate<Templates.ChapterFileTemplate>(template, out var chapterFileTemplate).Should().BeTrue(); @@ -508,7 +508,7 @@ namespace Templates_Other if (Environment.OSVersion.Platform == platformID) { var lbDto = GetLibraryBook(); - lbDto.Title = @"s\l/a\s/h\e/s"; + lbDto.TitleWithSubtitle = @"s\l/a\s/h\e/s"; var directory = Path.GetDirectoryName(inStr); var fileName = Path.GetFileName(inStr);