Merge pull request #646 from Alanoll/feat-add-book-subtitles
feat: add Book subtitle capturing so TitleShort reflects titles better
This commit is contained in:
commit
30aa691aae
@ -25,8 +25,10 @@ These tags will be replaced in the template with the audiobook's values.
|
|||||||
|Tag|Description|Type|
|
|Tag|Description|Type|
|
||||||
|-|-|-|
|
|-|-|-|
|
||||||
|\<id\> **†**|Audible book ID (ASIN)|Text|
|
|\<id\> **†**|Audible book ID (ASIN)|Text|
|
||||||
|\<title\>|Full title|Text|
|
|\<title\>|Full title with subtitle|Text|
|
||||||
|\<title short\>|Title. Stop at first colon|Text|
|
|\<title short\>|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|
|
|\<author\>|Author(s)|Name List|
|
||||||
|\<first author\>|First author|Text|
|
|\<first author\>|First author|Text|
|
||||||
|\<narrator\>|Narrator(s)|Name List|
|
|\<narrator\>|Narrator(s)|Name List|
|
||||||
|
|||||||
@ -35,6 +35,9 @@ namespace ApplicationServices
|
|||||||
[Name("Title")]
|
[Name("Title")]
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[Name("Subtitle")]
|
||||||
|
public string Subtitle { get; set; }
|
||||||
|
|
||||||
[Name("Authors")]
|
[Name("Authors")]
|
||||||
public string AuthorNames { get; set; }
|
public string AuthorNames { get; set; }
|
||||||
|
|
||||||
@ -123,6 +126,7 @@ namespace ApplicationServices
|
|||||||
AudibleProductId = a.Book.AudibleProductId,
|
AudibleProductId = a.Book.AudibleProductId,
|
||||||
Locale = a.Book.Locale,
|
Locale = a.Book.Locale,
|
||||||
Title = a.Book.Title,
|
Title = a.Book.Title,
|
||||||
|
Subtitle = a.Book.Subtitle,
|
||||||
AuthorNames = a.Book.AuthorNames(),
|
AuthorNames = a.Book.AuthorNames(),
|
||||||
NarratorNames = a.Book.NarratorNames(),
|
NarratorNames = a.Book.NarratorNames(),
|
||||||
LengthInMinutes = a.Book.LengthInMinutes,
|
LengthInMinutes = a.Book.LengthInMinutes,
|
||||||
@ -198,6 +202,7 @@ namespace ApplicationServices
|
|||||||
nameof(ExportDto.AudibleProductId),
|
nameof(ExportDto.AudibleProductId),
|
||||||
nameof(ExportDto.Locale),
|
nameof(ExportDto.Locale),
|
||||||
nameof(ExportDto.Title),
|
nameof(ExportDto.Title),
|
||||||
|
nameof(ExportDto.Subtitle),
|
||||||
nameof(ExportDto.AuthorNames),
|
nameof(ExportDto.AuthorNames),
|
||||||
nameof(ExportDto.NarratorNames),
|
nameof(ExportDto.NarratorNames),
|
||||||
nameof(ExportDto.LengthInMinutes),
|
nameof(ExportDto.LengthInMinutes),
|
||||||
@ -256,6 +261,7 @@ namespace ApplicationServices
|
|||||||
row.CreateCell(col++).SetCellValue(dto.AudibleProductId);
|
row.CreateCell(col++).SetCellValue(dto.AudibleProductId);
|
||||||
row.CreateCell(col++).SetCellValue(dto.Locale);
|
row.CreateCell(col++).SetCellValue(dto.Locale);
|
||||||
row.CreateCell(col++).SetCellValue(dto.Title);
|
row.CreateCell(col++).SetCellValue(dto.Title);
|
||||||
|
row.CreateCell(col++).SetCellValue(dto.Subtitle);
|
||||||
row.CreateCell(col++).SetCellValue(dto.AuthorNames);
|
row.CreateCell(col++).SetCellValue(dto.AuthorNames);
|
||||||
row.CreateCell(col++).SetCellValue(dto.NarratorNames);
|
row.CreateCell(col++).SetCellValue(dto.NarratorNames);
|
||||||
row.CreateCell(col++).SetCellValue(dto.LengthInMinutes);
|
row.CreateCell(col++).SetCellValue(dto.LengthInMinutes);
|
||||||
|
|||||||
@ -108,7 +108,7 @@ namespace ApplicationServices
|
|||||||
|
|
||||||
var recordsObj = new JObject
|
var recordsObj = new JObject
|
||||||
{
|
{
|
||||||
{ "title", libraryBook.Book.Title},
|
{ "title", libraryBook.Book.TitleWithSubtitle},
|
||||||
{ "asin", libraryBook.Book.AudibleProductId},
|
{ "asin", libraryBook.Book.AudibleProductId},
|
||||||
{ "exportTime", DateTime.Now},
|
{ "exportTime", DateTime.Now},
|
||||||
{ "records", JArray.FromObject(recordsEx) }
|
{ "records", JArray.FromObject(recordsEx) }
|
||||||
|
|||||||
@ -20,6 +20,7 @@ namespace DataLayer.Configurations
|
|||||||
entity.Ignore(nameof(Book.Authors));
|
entity.Ignore(nameof(Book.Authors));
|
||||||
entity.Ignore(nameof(Book.Narrators));
|
entity.Ignore(nameof(Book.Narrators));
|
||||||
entity.Ignore(nameof(Book.AudioFormat));
|
entity.Ignore(nameof(Book.AudioFormat));
|
||||||
|
entity.Ignore(nameof(Book.TitleWithSubtitle));
|
||||||
//// these don't seem to matter
|
//// these don't seem to matter
|
||||||
//entity.Ignore(nameof(Book.AuthorNames));
|
//entity.Ignore(nameof(Book.AuthorNames));
|
||||||
//entity.Ignore(nameof(Book.NarratorNames));
|
//entity.Ignore(nameof(Book.NarratorNames));
|
||||||
|
|||||||
@ -34,6 +34,9 @@ namespace DataLayer
|
|||||||
// immutable
|
// immutable
|
||||||
public string AudibleProductId { get; private set; }
|
public string AudibleProductId { get; private set; }
|
||||||
public string Title { 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 string Description { get; private set; }
|
||||||
public int LengthInMinutes { get; private set; }
|
public int LengthInMinutes { get; private set; }
|
||||||
public ContentType ContentType { get; private set; }
|
public ContentType ContentType { get; private set; }
|
||||||
@ -70,6 +73,7 @@ namespace DataLayer
|
|||||||
public Book(
|
public Book(
|
||||||
AudibleProductId audibleProductId,
|
AudibleProductId audibleProductId,
|
||||||
string title,
|
string title,
|
||||||
|
string subtitle,
|
||||||
string description,
|
string description,
|
||||||
int lengthInMinutes,
|
int lengthInMinutes,
|
||||||
ContentType contentType,
|
ContentType contentType,
|
||||||
@ -98,7 +102,7 @@ namespace DataLayer
|
|||||||
Category = category;
|
Category = category;
|
||||||
|
|
||||||
// simple assigns
|
// simple assigns
|
||||||
Title = title.Trim() ?? "";
|
UpdateTitle(title, subtitle);
|
||||||
Description = description?.Trim() ?? "";
|
Description = description?.Trim() ?? "";
|
||||||
LengthInMinutes = lengthInMinutes;
|
LengthInMinutes = lengthInMinutes;
|
||||||
ContentType = contentType;
|
ContentType = contentType;
|
||||||
@ -107,6 +111,12 @@ namespace DataLayer
|
|||||||
ReplaceAuthors(authors);
|
ReplaceAuthors(authors);
|
||||||
ReplaceNarrators(narrators);
|
ReplaceNarrators(narrators);
|
||||||
}
|
}
|
||||||
|
public void UpdateTitle(string title, string subtitle)
|
||||||
|
{
|
||||||
|
Title = title?.Trim() ?? "";
|
||||||
|
Subtitle = subtitle?.Trim() ?? "";
|
||||||
|
_titleWithSubtitle = null;
|
||||||
|
}
|
||||||
|
|
||||||
#region contributors, authors, narrators
|
#region contributors, authors, narrators
|
||||||
// use uninitialised backing fields - this means we can detect if the collection was loaded
|
// use uninitialised backing fields - this means we can detect if the collection was loaded
|
||||||
@ -233,6 +243,6 @@ namespace DataLayer
|
|||||||
Category = category;
|
Category = category;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"[{AudibleProductId}] {Title}";
|
public override string ToString() => $"[{AudibleProductId}] {TitleWithSubtitle}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ namespace DataLayer
|
|||||||
{
|
{
|
||||||
public static class EntityExtensions
|
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 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));
|
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);
|
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}");
|
var titlesAgg = titles.Take(max).Aggregate((a, b) => $"{a}\r\n{b}");
|
||||||
if (titles.Count == max + 1)
|
if (titles.Count == max + 1)
|
||||||
titlesAgg += $"\r\n\r\nand 1 other";
|
titlesAgg += $"\r\n\r\nand 1 other";
|
||||||
|
|||||||
416
Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.Designer.cs
generated
Normal file
416
Source/DataLayer/Migrations/20230626171442_AddBookSubtitle.Designer.cs
generated
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using DataLayer;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DataLayer.Migrations
|
||||||
|
{
|
||||||
|
[DbContext(typeof(LibationContext))]
|
||||||
|
[Migration("20230626171442_AddBookSubtitle")]
|
||||||
|
partial class AddBookSubtitle
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
#pragma warning disable 612, 618
|
||||||
|
modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Book", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("BookId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AudibleProductId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ContentType")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<DateTime?>("DatePublished")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Description")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsAbridged")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Language")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int>("LengthInMinutes")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Locale")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PictureId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("PictureLarge")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Subtitle")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Title")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<long>("_audioFormat")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("BookId");
|
||||||
|
|
||||||
|
b.HasIndex("AudibleProductId");
|
||||||
|
|
||||||
|
b.HasIndex("CategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Books");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("BookId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("ContributorId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("Role")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<byte>("Order")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("BookId", "ContributorId", "Role");
|
||||||
|
|
||||||
|
b.HasIndex("BookId");
|
||||||
|
|
||||||
|
b.HasIndex("ContributorId");
|
||||||
|
|
||||||
|
b.ToTable("BookContributor");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Category", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("CategoryId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AudibleCategoryId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<int?>("ParentCategoryCategoryId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("CategoryId");
|
||||||
|
|
||||||
|
b.HasIndex("AudibleCategoryId");
|
||||||
|
|
||||||
|
b.HasIndex("ParentCategoryCategoryId");
|
||||||
|
|
||||||
|
b.ToTable("Categories");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
CategoryId = -1,
|
||||||
|
AudibleCategoryId = "",
|
||||||
|
Name = ""
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("ContributorId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AudibleContributorId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("ContributorId");
|
||||||
|
|
||||||
|
b.HasIndex("Name");
|
||||||
|
|
||||||
|
b.ToTable("Contributors");
|
||||||
|
|
||||||
|
b.HasData(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
ContributorId = -1,
|
||||||
|
Name = ""
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("BookId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<bool>("AbsentFromLastScan")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Account")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<DateTime>("DateAdded")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<bool>("IsDeleted")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.HasKey("BookId");
|
||||||
|
|
||||||
|
b.ToTable("LibraryBooks");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Series", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("SeriesId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("AudibleSeriesId")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("SeriesId");
|
||||||
|
|
||||||
|
b.HasIndex("AudibleSeriesId");
|
||||||
|
|
||||||
|
b.ToTable("Series");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("SeriesId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<int>("BookId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("Order")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("SeriesId", "BookId");
|
||||||
|
|
||||||
|
b.HasIndex("BookId");
|
||||||
|
|
||||||
|
b.HasIndex("SeriesId");
|
||||||
|
|
||||||
|
b.ToTable("SeriesBook");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Book", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DataLayer.Category", "Category")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("CategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.OwnsOne("DataLayer.Rating", "Rating", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("BookId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.Property<float>("OverallRating")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b1.Property<float>("PerformanceRating")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b1.Property<float>("StoryRating")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b1.HasKey("BookId");
|
||||||
|
|
||||||
|
b1.ToTable("Books");
|
||||||
|
|
||||||
|
b1.WithOwner()
|
||||||
|
.HasForeignKey("BookId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("SupplementId")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.Property<int>("BookId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.Property<string>("Url")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.HasKey("SupplementId");
|
||||||
|
|
||||||
|
b1.HasIndex("BookId");
|
||||||
|
|
||||||
|
b1.ToTable("Supplement");
|
||||||
|
|
||||||
|
b1.WithOwner("Book")
|
||||||
|
.HasForeignKey("BookId");
|
||||||
|
|
||||||
|
b1.Navigation("Book");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.OwnsOne("DataLayer.UserDefinedItem", "UserDefinedItem", b1 =>
|
||||||
|
{
|
||||||
|
b1.Property<int>("BookId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.Property<int>("BookStatus")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.Property<DateTime?>("LastDownloaded")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<string>("LastDownloadedVersion")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.Property<int?>("PdfStatus")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b1.Property<string>("Tags")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b1.HasKey("BookId");
|
||||||
|
|
||||||
|
b1.ToTable("UserDefinedItem", (string)null);
|
||||||
|
|
||||||
|
b1.WithOwner("Book")
|
||||||
|
.HasForeignKey("BookId");
|
||||||
|
|
||||||
|
b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
|
||||||
|
{
|
||||||
|
b2.Property<int>("UserDefinedItemBookId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b2.Property<float>("OverallRating")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b2.Property<float>("PerformanceRating")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b2.Property<float>("StoryRating")
|
||||||
|
.HasColumnType("REAL");
|
||||||
|
|
||||||
|
b2.HasKey("UserDefinedItemBookId");
|
||||||
|
|
||||||
|
b2.ToTable("UserDefinedItem");
|
||||||
|
|
||||||
|
b2.WithOwner()
|
||||||
|
.HasForeignKey("UserDefinedItemBookId");
|
||||||
|
});
|
||||||
|
|
||||||
|
b1.Navigation("Book");
|
||||||
|
|
||||||
|
b1.Navigation("Rating");
|
||||||
|
});
|
||||||
|
|
||||||
|
b.Navigation("Category");
|
||||||
|
|
||||||
|
b.Navigation("Rating");
|
||||||
|
|
||||||
|
b.Navigation("Supplements");
|
||||||
|
|
||||||
|
b.Navigation("UserDefinedItem");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.BookContributor", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DataLayer.Book", "Book")
|
||||||
|
.WithMany("ContributorsLink")
|
||||||
|
.HasForeignKey("BookId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DataLayer.Contributor", "Contributor")
|
||||||
|
.WithMany("BooksLink")
|
||||||
|
.HasForeignKey("ContributorId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Book");
|
||||||
|
|
||||||
|
b.Navigation("Contributor");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Category", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DataLayer.Category", "ParentCategory")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("ParentCategoryCategoryId");
|
||||||
|
|
||||||
|
b.Navigation("ParentCategory");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.LibraryBook", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DataLayer.Book", "Book")
|
||||||
|
.WithOne()
|
||||||
|
.HasForeignKey("DataLayer.LibraryBook", "BookId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Book");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.SeriesBook", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("DataLayer.Book", "Book")
|
||||||
|
.WithMany("SeriesLink")
|
||||||
|
.HasForeignKey("BookId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("DataLayer.Series", "Series")
|
||||||
|
.WithMany("BooksLink")
|
||||||
|
.HasForeignKey("SeriesId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
b.Navigation("Book");
|
||||||
|
|
||||||
|
b.Navigation("Series");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Book", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("ContributorsLink");
|
||||||
|
|
||||||
|
b.Navigation("SeriesLink");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Contributor", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("BooksLink");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("DataLayer.Series", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("BooksLink");
|
||||||
|
});
|
||||||
|
#pragma warning restore 612, 618
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace DataLayer.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddBookSubtitle : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "Subtitle",
|
||||||
|
table: "Books",
|
||||||
|
type: "TEXT",
|
||||||
|
nullable: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Subtitle",
|
||||||
|
table: "Books");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -15,7 +15,7 @@ namespace DataLayer.Migrations
|
|||||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder.HasAnnotation("ProductVersion", "7.0.3");
|
modelBuilder.HasAnnotation("ProductVersion", "7.0.5");
|
||||||
|
|
||||||
modelBuilder.Entity("DataLayer.Book", b =>
|
modelBuilder.Entity("DataLayer.Book", b =>
|
||||||
{
|
{
|
||||||
@ -56,6 +56,9 @@ namespace DataLayer.Migrations
|
|||||||
b.Property<string>("PictureLarge")
|
b.Property<string>("PictureLarge")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("Subtitle")
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
b.Property<string>("Title")
|
b.Property<string>("Title")
|
||||||
.HasColumnType("TEXT");
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
|||||||
@ -118,7 +118,8 @@ namespace DtoImporterService
|
|||||||
{
|
{
|
||||||
book = DbContext.Books.Add(new Book(
|
book = DbContext.Books.Add(new Book(
|
||||||
new AudibleProductId(item.ProductId),
|
new AudibleProductId(item.ProductId),
|
||||||
item.TitleWithSubtitle,
|
item.Title,
|
||||||
|
item.Subtitle,
|
||||||
item.Description,
|
item.Description,
|
||||||
item.LengthInMinutes,
|
item.LengthInMinutes,
|
||||||
contentType,
|
contentType,
|
||||||
@ -164,6 +165,9 @@ namespace DtoImporterService
|
|||||||
{
|
{
|
||||||
var item = importItem.DtoItem;
|
var item = importItem.DtoItem;
|
||||||
|
|
||||||
|
// Update the book titles, since formatting can change
|
||||||
|
book.UpdateTitle(item.Title, item.Subtitle);
|
||||||
|
|
||||||
var codec = item.AvailableCodecs?.Max(f => AudioFormat.FromString(f.EnhancedCodec)) ?? new AudioFormat();
|
var codec = item.AvailableCodecs?.Max(f => AudioFormat.FromString(f.EnhancedCodec)) ?? new AudioFormat();
|
||||||
book.AudioFormat = codec;
|
book.AudioFormat = codec;
|
||||||
|
|
||||||
|
|||||||
@ -331,9 +331,9 @@ namespace FileLiberator
|
|||||||
string errorTitle()
|
string errorTitle()
|
||||||
{
|
{
|
||||||
var title
|
var title
|
||||||
= (libraryBook.Book.Title.Length > 53)
|
= (libraryBook.Book.TitleWithSubtitle.Length > 53)
|
||||||
? $"{libraryBook.Book.Title.Truncate(50)}..."
|
? $"{libraryBook.Book.TitleWithSubtitle.Truncate(50)}..."
|
||||||
: libraryBook.Book.Title;
|
: libraryBook.Book.TitleWithSubtitle;
|
||||||
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
|
var errorBookTitle = $"{title} [{libraryBook.Book.AudibleProductId}]";
|
||||||
return errorBookTitle;
|
return errorBookTitle;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -45,7 +45,7 @@ namespace FileLiberator
|
|||||||
|
|
||||||
Serilog.Log.Logger.Information("Begin " + nameof(ProcessSingleAsync) + " {@DebugInfo}", new
|
Serilog.Log.Logger.Information("Begin " + nameof(ProcessSingleAsync) + " {@DebugInfo}", new
|
||||||
{
|
{
|
||||||
libraryBook.Book.Title,
|
libraryBook.Book.TitleWithSubtitle,
|
||||||
libraryBook.Book.AudibleProductId,
|
libraryBook.Book.AudibleProductId,
|
||||||
libraryBook.Book.Locale,
|
libraryBook.Book.Locale,
|
||||||
Account = libraryBook.Account?.ToMask() ?? "[empty]"
|
Account = libraryBook.Account?.ToMask() ?? "[empty]"
|
||||||
|
|||||||
@ -14,7 +14,7 @@ namespace FileLiberator
|
|||||||
public static (string id, string title, string locale, string account) LogFriendly(this LibraryBook libraryBook)
|
public static (string id, string title, string locale, string account) LogFriendly(this LibraryBook libraryBook)
|
||||||
=> (
|
=> (
|
||||||
id: libraryBook.Book.AudibleProductId,
|
id: libraryBook.Book.AudibleProductId,
|
||||||
title: libraryBook.Book.Title,
|
title: libraryBook.Book.TitleWithSubtitle,
|
||||||
locale: libraryBook.Book.Locale,
|
locale: libraryBook.Book.Locale,
|
||||||
account: libraryBook.Account.ToMask()
|
account: libraryBook.Account.ToMask()
|
||||||
);
|
);
|
||||||
@ -40,7 +40,9 @@ namespace FileLiberator
|
|||||||
DateAdded = libraryBook.DateAdded,
|
DateAdded = libraryBook.DateAdded,
|
||||||
|
|
||||||
AudibleProductId = libraryBook.Book.AudibleProductId,
|
AudibleProductId = libraryBook.Book.AudibleProductId,
|
||||||
Title = libraryBook.Book.Title ?? "",
|
Title = libraryBook.Book.Title,
|
||||||
|
Subtitle = libraryBook.Book.Subtitle,
|
||||||
|
TitleWithSubtitle = libraryBook.Book.TitleWithSubtitle,
|
||||||
Locale = libraryBook.Book.Locale,
|
Locale = libraryBook.Book.Locale,
|
||||||
YearPublished = libraryBook.Book.DatePublished?.Year,
|
YearPublished = libraryBook.Book.DatePublished?.Year,
|
||||||
DatePublished = libraryBook.Book.DatePublished,
|
DatePublished = libraryBook.Book.DatePublished,
|
||||||
|
|||||||
@ -21,7 +21,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
_libraryBook = value;
|
_libraryBook = value;
|
||||||
Title = _libraryBook.Book.Title;
|
Title = _libraryBook.Book.TitleWithSubtitle;
|
||||||
DataContext = _viewModel = new BookDetailsDialogViewModel(_libraryBook);
|
DataContext = _viewModel = new BookDetailsDialogViewModel(_libraryBook);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,9 +106,11 @@ namespace LibationAvalonia.Dialogs
|
|||||||
var picture = PictureStorage.GetPictureSynchronously(new PictureDefinition(libraryBook.Book.PictureId, PictureSize._80x80));
|
var picture = PictureStorage.GetPictureSynchronously(new PictureDefinition(libraryBook.Book.PictureId, PictureSize._80x80));
|
||||||
Cover = AvaloniaUtils.TryLoadImageOrDefault(picture, 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
|
//init book details
|
||||||
DetailsText = @$"
|
DetailsText = @$"
|
||||||
Title: {Book.Title}
|
Title: {title}
|
||||||
Author(s): {Book.AuthorNames()}
|
Author(s): {Book.AuthorNames()}
|
||||||
Narrator(s): {Book.NarratorNames()}
|
Narrator(s): {Book.NarratorNames()}
|
||||||
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
|
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
public BookRecordsDialog(LibraryBook libraryBook) : this()
|
public BookRecordsDialog(LibraryBook libraryBook) : this()
|
||||||
{
|
{
|
||||||
this.libraryBook = libraryBook;
|
this.libraryBook = libraryBook;
|
||||||
Title = $"{libraryBook.Book.Title} - Clips and Bookmarks";
|
Title = $"{libraryBook.Book.TitleWithSubtitle} - Clips and Bookmarks";
|
||||||
|
|
||||||
Loaded += BookRecordsDialog_Loaded;
|
Loaded += BookRecordsDialog_Loaded;
|
||||||
}
|
}
|
||||||
@ -148,7 +148,7 @@ namespace LibationAvalonia.Dialogs
|
|||||||
await Dispatcher.UIThread.InvokeAsync(() => new FilePickerSaveOptions
|
await Dispatcher.UIThread.InvokeAsync(() => new FilePickerSaveOptions
|
||||||
{
|
{
|
||||||
Title = "Where to export book records",
|
Title = "Where to export book records",
|
||||||
SuggestedFileName = $"{libraryBook.Book.Title} - Records",
|
SuggestedFileName = $"{libraryBook.Book.TitleWithSubtitle} - Records",
|
||||||
DefaultExtension = "xlsx",
|
DefaultExtension = "xlsx",
|
||||||
ShowOverwritePrompt = true,
|
ShowOverwritePrompt = true,
|
||||||
FileTypeChoices = new FilePickerFileType[]
|
FileTypeChoices = new FilePickerFileType[]
|
||||||
|
|||||||
@ -105,7 +105,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
LibraryBook = libraryBook;
|
LibraryBook = libraryBook;
|
||||||
Logger = logme;
|
Logger = logme;
|
||||||
|
|
||||||
_title = LibraryBook.Book.Title;
|
_title = LibraryBook.Book.TitleWithSubtitle;
|
||||||
_author = LibraryBook.Book.AuthorNames();
|
_author = LibraryBook.Book.AuthorNames();
|
||||||
_narrator = LibraryBook.Book.NarratorNames();
|
_narrator = LibraryBook.Book.NarratorNames();
|
||||||
|
|
||||||
@ -305,7 +305,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
|
|
||||||
Logger.Info($"{Environment.NewLine}{((Processable)sender).Name} Step, Begin: {libraryBook.Book}");
|
Logger.Info($"{Environment.NewLine}{((Processable)sender).Name} Step, Begin: {libraryBook.Book}");
|
||||||
|
|
||||||
Title = libraryBook.Book.Title;
|
Title = libraryBook.Book.TitleWithSubtitle;
|
||||||
Author = libraryBook.Book.AuthorNames();
|
Author = libraryBook.Book.AuthorNames();
|
||||||
Narrator = libraryBook.Book.NarratorNames();
|
Narrator = libraryBook.Book.NarratorNames();
|
||||||
}
|
}
|
||||||
@ -372,7 +372,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
: str;
|
: str;
|
||||||
|
|
||||||
details =
|
details =
|
||||||
$@" Title: {libraryBook.Book.Title}
|
$@" Title: {libraryBook.Book.TitleWithSubtitle}
|
||||||
ID: {libraryBook.Book.AudibleProductId}
|
ID: {libraryBook.Book.AudibleProductId}
|
||||||
Author: {trunc(libraryBook.Book.AuthorNames())}
|
Author: {trunc(libraryBook.Book.AuthorNames())}
|
||||||
Narr: {trunc(libraryBook.Book.NarratorNames())}";
|
Narr: {trunc(libraryBook.Book.NarratorNames())}";
|
||||||
@ -392,7 +392,7 @@ $@" Title: {libraryBook.Book.Title}
|
|||||||
{
|
{
|
||||||
libraryBook.UpdateBookStatus(LiberatedStatus.Error);
|
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;
|
return ProcessBookResult.FailedSkip;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -227,7 +227,7 @@ namespace LibationAvalonia.ViewModels
|
|||||||
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
||||||
{
|
{
|
||||||
await MessageBox.Show(@$"
|
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.
|
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.
|
||||||
",
|
",
|
||||||
|
|||||||
@ -159,7 +159,7 @@ namespace LibationAvalonia.Views
|
|||||||
|
|
||||||
var openFileDialogOptions = new FilePickerOpenOptions
|
var openFileDialogOptions = new FilePickerOpenOptions
|
||||||
{
|
{
|
||||||
Title = $"Locate the audio file for '{entry.Book.Title}'",
|
Title = $"Locate the audio file for '{entry.Book.TitleWithSubtitle}'",
|
||||||
AllowMultiple = false,
|
AllowMultiple = false,
|
||||||
SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
|
SuggestedStartLocation = await window.StorageProvider.TryGetFolderFromPathAsync(Configuration.Instance.Books.PathWithoutPrefix),
|
||||||
FileTypeFilter = new FilePickerFileType[]
|
FileTypeFilter = new FilePickerFileType[]
|
||||||
|
|||||||
@ -8,6 +8,8 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
public string AudibleProductId { get; set; }
|
public string AudibleProductId { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
|
public string Subtitle { get; set; }
|
||||||
|
public string TitleWithSubtitle { get; set; }
|
||||||
public string Locale { get; set; }
|
public string Locale { get; set; }
|
||||||
public int? YearPublished { get; set; }
|
public int? YearPublished { get; set; }
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,8 @@ namespace LibationFileManager
|
|||||||
DateAdded = new DateTime(2022, 6, 9, 0, 0, 0),
|
DateAdded = new DateTime(2022, 6, 9, 0, 0, 0),
|
||||||
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
|
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
|
||||||
AudibleProductId = "123456789",
|
AudibleProductId = "123456789",
|
||||||
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
|
Title = "A Study in Scarlet",
|
||||||
|
Subtitle = "A Sherlock Holmes Novel",
|
||||||
Locale = "us",
|
Locale = "us",
|
||||||
YearPublished = 2017,
|
YearPublished = 2017,
|
||||||
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
|
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
|
||||||
|
|||||||
@ -24,8 +24,10 @@ namespace LibationFileManager
|
|||||||
public static TemplateTags ChNumber0 { get; } = new TemplateTags("ch# 0", "Chapter # with leading zeros");
|
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 Id { get; } = new TemplateTags("id", "Audible ID");
|
||||||
public static TemplateTags Title { get; } = new TemplateTags("title", "Full title");
|
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 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 Author { get; } = new TemplateTags("author", "Author(s)");
|
||||||
public static TemplateTags FirstAuthor { get; } = new TemplateTags("first author", "First author");
|
public static TemplateTags FirstAuthor { get; } = new TemplateTags("first author", "First author");
|
||||||
public static TemplateTags Narrator { get; } = new TemplateTags("narrator", "Narrator(s)");
|
public static TemplateTags Narrator { get; } = new TemplateTags("narrator", "Narrator(s)");
|
||||||
|
|||||||
@ -247,8 +247,10 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
//Don't allow formatting of Id
|
//Don't allow formatting of Id
|
||||||
{ TemplateTags.Id, lb => lb.AudibleProductId, v => v },
|
{ TemplateTags.Id, lb => lb.AudibleProductId, v => v },
|
||||||
{ TemplateTags.Title, lb => lb.Title },
|
{ TemplateTags.Title, lb => lb.TitleWithSubtitle },
|
||||||
{ TemplateTags.TitleShort, lb => getTitleShort(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.Author, lb => lb.Authors, NameListFormat.Formatter },
|
||||||
{ TemplateTags.FirstAuthor, lb => lb.FirstAuthor },
|
{ TemplateTags.FirstAuthor, lb => lb.FirstAuthor },
|
||||||
{ TemplateTags.Narrator, lb => lb.Narrators, NameListFormat.Formatter },
|
{ TemplateTags.Narrator, lb => lb.Narrators, NameListFormat.Formatter },
|
||||||
@ -274,8 +276,10 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
new PropertyTagCollection<LibraryBookDto>(caseSensative: true, StringFormatter)
|
new PropertyTagCollection<LibraryBookDto>(caseSensative: true, StringFormatter)
|
||||||
{
|
{
|
||||||
{ TemplateTags.Title, lb => lb.Title },
|
{ TemplateTags.Title, lb => lb.TitleWithSubtitle },
|
||||||
{ TemplateTags.TitleShort, lb => getTitleShort(lb.Title) },
|
{ TemplateTags.TitleShort, lb => getTitleShort(lb.Title) },
|
||||||
|
{ TemplateTags.AudibleTitle, lb => lb.Title },
|
||||||
|
{ TemplateTags.AudibleSubtitle, lb => lb.Subtitle },
|
||||||
{ TemplateTags.Series, lb => lb.SeriesName },
|
{ TemplateTags.Series, lb => lb.SeriesName },
|
||||||
},
|
},
|
||||||
new PropertyTagCollection<MultiConvertFileProperties>(caseSensative: true, StringFormatter, IntegerFormatter, DateTimeFormatter)
|
new PropertyTagCollection<MultiConvertFileProperties>(caseSensative: true, StringFormatter, IntegerFormatter, DateTimeFormatter)
|
||||||
|
|||||||
@ -35,7 +35,7 @@ namespace LibationSearchEngine
|
|||||||
{
|
{
|
||||||
{ FieldType.ID, lb => lb.Book.AudibleProductId.ToLowerInvariant(), nameof(Book.AudibleProductId), "ProductId", "Id", "ASIN" },
|
{ FieldType.ID, lb => lb.Book.AudibleProductId.ToLowerInvariant(), nameof(Book.AudibleProductId), "ProductId", "Id", "ASIN" },
|
||||||
{ FieldType.Raw, lb => lb.Book.AudibleProductId, _ID_ },
|
{ 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.AuthorNames(), "AuthorNames", "Author", "Authors" },
|
||||||
{ FieldType.String, lb => lb.Book.NarratorNames(), "NarratorNames", "Narrator", "Narrators" },
|
{ FieldType.String, lb => lb.Book.NarratorNames(), "NarratorNames", "Narrator", "Narrators" },
|
||||||
{ FieldType.String, lb => lb.Book.Publisher, nameof(Book.Publisher) },
|
{ FieldType.String, lb => lb.Book.Publisher, nameof(Book.Publisher) },
|
||||||
|
|||||||
@ -105,7 +105,7 @@ namespace LibationUiBase.GridView
|
|||||||
Liberate = TStatus.Create(libraryBook);
|
Liberate = TStatus.Create(libraryBook);
|
||||||
Liberate.Expanded = expanded;
|
Liberate.Expanded = expanded;
|
||||||
|
|
||||||
Title = Book.Title;
|
Title = Book.TitleWithSubtitle;
|
||||||
Series = Book.SeriesNames(includeIndex: true);
|
Series = Book.SeriesNames(includeIndex: true);
|
||||||
SeriesOrder = new SeriesOrder(Book.SeriesLink);
|
SeriesOrder = new SeriesOrder(Book.SeriesLink);
|
||||||
Length = GetBookLengthString();
|
Length = GetBookLengthString();
|
||||||
|
|||||||
@ -108,7 +108,7 @@ namespace LibationUiBase.SeriesView
|
|||||||
{
|
{
|
||||||
Asin = seriesParent.AudibleProductId,
|
Asin = seriesParent.AudibleProductId,
|
||||||
Sequence = item.Relationships.FirstOrDefault(r => r.Asin == seriesParent.AudibleProductId)?.Sort?.ToString() ?? "0",
|
Sequence = item.Relationships.FirstOrDefault(r => r.Asin == seriesParent.AudibleProductId)?.Sort?.ToString() ?? "0",
|
||||||
Title = seriesParent.Title
|
Title = seriesParent.TitleWithSubtitle
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,13 +38,14 @@ namespace LibationWinForms.Dialogs
|
|||||||
// 1st draft: lazily cribbed from GridEntry.ctor()
|
// 1st draft: lazily cribbed from GridEntry.ctor()
|
||||||
private void initDetails()
|
private void initDetails()
|
||||||
{
|
{
|
||||||
this.Text = Book.Title;
|
this.Text = Book.TitleWithSubtitle;
|
||||||
|
|
||||||
(_, var picture) = PictureStorage.GetPicture(new PictureDefinition(Book.PictureId, PictureSize._80x80));
|
(_, var picture) = PictureStorage.GetPicture(new PictureDefinition(Book.PictureId, PictureSize._80x80));
|
||||||
this.coverPb.Image = WinFormsUtil.TryLoadImageOrDefault(picture, 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 = @$"
|
var t = @$"
|
||||||
Title: {Book.Title}
|
Title: {title}
|
||||||
Author(s): {Book.AuthorNames()}
|
Author(s): {Book.AuthorNames()}
|
||||||
Narrator(s): {Book.NarratorNames()}
|
Narrator(s): {Book.NarratorNames()}
|
||||||
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
|
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
|
||||||
|
|||||||
@ -45,7 +45,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
{
|
{
|
||||||
this.libraryBook = libraryBook;
|
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)
|
private async void BookRecordsDialog_Shown(object sender, EventArgs e)
|
||||||
@ -182,7 +182,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
{
|
{
|
||||||
Title = "Where to export records",
|
Title = "Where to export records",
|
||||||
AddExtension = true,
|
AddExtension = true,
|
||||||
FileName = $"{libraryBook.Book.Title} - Records",
|
FileName = $"{libraryBook.Book.TitleWithSubtitle} - Records",
|
||||||
DefaultExt = "xlsx",
|
DefaultExt = "xlsx",
|
||||||
Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*"
|
Filter = "Excel Workbook (*.xlsx)|*.xlsx|CSV files (*.csv)|*.csv|JSON files (*.json)|*.json" // + "|All files (*.*)|*.*"
|
||||||
});
|
});
|
||||||
|
|||||||
@ -176,7 +176,7 @@ namespace LibationWinForms.GridView
|
|||||||
{
|
{
|
||||||
var openFileDialog = new OpenFileDialog
|
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 (*.*)|*.*",
|
Filter = "All files (*.*)|*.*",
|
||||||
FilterIndex = 1
|
FilterIndex = 1
|
||||||
};
|
};
|
||||||
|
|||||||
@ -77,7 +77,7 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
LibraryBook = libraryBook;
|
LibraryBook = libraryBook;
|
||||||
Logger = logme;
|
Logger = logme;
|
||||||
|
|
||||||
title = LibraryBook.Book.Title;
|
title = LibraryBook.Book.TitleWithSubtitle;
|
||||||
authorNames = LibraryBook.Book.AuthorNames();
|
authorNames = LibraryBook.Book.AuthorNames();
|
||||||
narratorNames = LibraryBook.Book.NarratorNames();
|
narratorNames = LibraryBook.Book.NarratorNames();
|
||||||
_bookText = $"{title}\r\nBy {authorNames}\r\nNarrated by {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}");
|
Logger.Info($"{Environment.NewLine}{((Processable)sender).Name} Step, Begin: {libraryBook.Book}");
|
||||||
|
|
||||||
title = libraryBook.Book.Title;
|
title = libraryBook.Book.TitleWithSubtitle;
|
||||||
authorNames = libraryBook.Book.AuthorNames();
|
authorNames = libraryBook.Book.AuthorNames();
|
||||||
narratorNames = libraryBook.Book.NarratorNames();
|
narratorNames = libraryBook.Book.NarratorNames();
|
||||||
updateBookInfo();
|
updateBookInfo();
|
||||||
@ -359,7 +359,7 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
: str;
|
: str;
|
||||||
|
|
||||||
details =
|
details =
|
||||||
$@" Title: {libraryBook.Book.Title}
|
$@" Title: {libraryBook.Book.TitleWithSubtitle}
|
||||||
ID: {libraryBook.Book.AudibleProductId}
|
ID: {libraryBook.Book.AudibleProductId}
|
||||||
Author: {trunc(libraryBook.Book.AuthorNames())}
|
Author: {trunc(libraryBook.Book.AuthorNames())}
|
||||||
Narr: {trunc(libraryBook.Book.NarratorNames())}";
|
Narr: {trunc(libraryBook.Book.NarratorNames())}";
|
||||||
@ -379,7 +379,7 @@ $@" Title: {libraryBook.Book.Title}
|
|||||||
{
|
{
|
||||||
libraryBook.UpdateBookStatus(LiberatedStatus.Error);
|
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;
|
return ProcessBookResult.FailedSkip;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -188,7 +188,7 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
else if (result == ProcessBookResult.LicenseDeniedPossibleOutage && !shownServiceOutageMessage)
|
||||||
{
|
{
|
||||||
MessageBox.Show(@$"
|
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.
|
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.
|
||||||
",
|
",
|
||||||
|
|||||||
@ -463,7 +463,7 @@ namespace Templates_Other
|
|||||||
extension = FileUtility.GetStandardizedExtension(extension);
|
extension = FileUtility.GetStandardizedExtension(extension);
|
||||||
|
|
||||||
var lbDto = GetLibraryBook();
|
var lbDto = GetLibraryBook();
|
||||||
lbDto.Title = title;
|
lbDto.TitleWithSubtitle = title;
|
||||||
lbDto.AudibleProductId = "ID123456";
|
lbDto.AudibleProductId = "ID123456";
|
||||||
|
|
||||||
Templates.TryGetTemplate<Templates.FolderTemplate>(template, out var fileNamingTemplate).Should().BeTrue();
|
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 template = Path.GetFileNameWithoutExtension(originalPath) + " - <ch# 0> - <title>" + estension;
|
||||||
|
|
||||||
var lbDto = GetLibraryBook();
|
var lbDto = GetLibraryBook();
|
||||||
lbDto.Title = suffix;
|
lbDto.TitleWithSubtitle = suffix;
|
||||||
|
|
||||||
Templates.TryGetTemplate<Templates.ChapterFileTemplate>(template, out var chapterFileTemplate).Should().BeTrue();
|
Templates.TryGetTemplate<Templates.ChapterFileTemplate>(template, out var chapterFileTemplate).Should().BeTrue();
|
||||||
|
|
||||||
@ -508,7 +508,7 @@ namespace Templates_Other
|
|||||||
if (Environment.OSVersion.Platform == platformID)
|
if (Environment.OSVersion.Platform == platformID)
|
||||||
{
|
{
|
||||||
var lbDto = GetLibraryBook();
|
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 directory = Path.GetDirectoryName(inStr);
|
||||||
var fileName = Path.GetFileName(inStr);
|
var fileName = Path.GetFileName(inStr);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user