diff --git a/.gitignore b/.gitignore
index 787caf71..7ab9c6b2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -184,7 +184,7 @@ publish/
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
+#*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
diff --git a/Source/.config/dotnet-tools.json b/Source/.config/dotnet-tools.json
new file mode 100644
index 00000000..921204ae
--- /dev/null
+++ b/Source/.config/dotnet-tools.json
@@ -0,0 +1,12 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "frogvall.dotnetbumpversion": {
+ "version": "3.0.1",
+ "commands": [
+ "bump-version"
+ ]
+ }
+ }
+}
\ No newline at end of file
diff --git a/Source/AaxDecrypter/AaxDecrypter.csproj b/Source/AaxDecrypter/AaxDecrypter.csproj
index 90fdc76d..84bfe99c 100644
--- a/Source/AaxDecrypter/AaxDecrypter.csproj
+++ b/Source/AaxDecrypter/AaxDecrypter.csproj
@@ -8,7 +8,16 @@
-
+
+ embedded
+
+
+
+ embedded
+
+
+
+
diff --git a/Source/AppScaffolding/.msbump b/Source/AppScaffolding/.msbump
deleted file mode 100644
index e9c0b591..00000000
--- a/Source/AppScaffolding/.msbump
+++ /dev/null
@@ -1,4 +0,0 @@
-{
- "//": "https://github.com/BalassaMarton/MSBump",
- BumpRevision: true
-}
diff --git a/Source/AppScaffolding/AppScaffolding.csproj b/Source/AppScaffolding/AppScaffolding.csproj
index 8e17cbea..67f36a31 100644
--- a/Source/AppScaffolding/AppScaffolding.csproj
+++ b/Source/AppScaffolding/AppScaffolding.csproj
@@ -1,22 +1,23 @@
-
+
-
net6.0-windows
- 8.1.4.1
+ 8.1.4.31
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
+
+ embedded
+
+
+ embedded
+
+
+
+
+
\ No newline at end of file
diff --git a/Source/AppScaffolding/UNSAFE_MigrationHelper.cs b/Source/AppScaffolding/UNSAFE_MigrationHelper.cs
index 61af7aac..fbfe6bb2 100644
--- a/Source/AppScaffolding/UNSAFE_MigrationHelper.cs
+++ b/Source/AppScaffolding/UNSAFE_MigrationHelper.cs
@@ -25,7 +25,7 @@ namespace AppScaffolding
: value;
#region appsettings.json
- private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "appsettings.json");
+ private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName), "appsettings.json");
public static bool APPSETTINGS_Json_Exists => File.Exists(APPSETTINGS_JSON);
diff --git a/Source/ApplicationServices/ApplicationServices.csproj b/Source/ApplicationServices/ApplicationServices.csproj
index b2c3abea..cb4528e5 100644
--- a/Source/ApplicationServices/ApplicationServices.csproj
+++ b/Source/ApplicationServices/ApplicationServices.csproj
@@ -1,4 +1,4 @@
-
+
net6.0-windows
@@ -14,4 +14,12 @@
+
+ embedded
+
+
+
+ embedded
+
+
diff --git a/Source/AudibleUtilities/ApiExtended.cs b/Source/AudibleUtilities/ApiExtended.cs
index 28fbd14f..35568c55 100644
--- a/Source/AudibleUtilities/ApiExtended.cs
+++ b/Source/AudibleUtilities/ApiExtended.cs
@@ -162,10 +162,19 @@ namespace AudibleUtilities
if (exceptions is not null && exceptions.Any())
throw new AggregateException(exceptions);
}
-
return items;
}
+ private static List getValidators()
+ {
+ var type = typeof(IValidator);
+ var types = AppDomain.CurrentDomain.GetAssemblies()
+ .SelectMany(s => s.GetTypes())
+ .Where(p => type.IsAssignableFrom(p) && !p.IsInterface);
+
+ return types.Select(t => Activator.CreateInstance(t) as IValidator).ToList();
+ }
+
#region episodes and podcasts
private async Task> getChildEpisodesAsync(SemaphoreSlim concurrencySemaphore, Item parent)
@@ -197,7 +206,8 @@ namespace AudibleUtilities
if (numSeriesParents != 1)
{
//There should only ever be 1 top-level parent per episode. If not, log
- //and throw so we can figure out what to do about those special cases.
+ //so we can figure out what to do about those special cases, and don't
+ //import the episode.
JsonSerializerSettings Settings = new()
{
MetadataPropertyHandling = MetadataPropertyHandling.Ignore,
@@ -207,9 +217,8 @@ namespace AudibleUtilities
new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal }
},
};
- var ex = new ApplicationException($"Found {numSeriesParents} parents for {parent.Asin}");
- Serilog.Log.Logger.Error(ex, $"Episode Product:\r\n{JsonConvert.SerializeObject(parent, Formatting.None, Settings)}");
- throw ex;
+ Serilog.Log.Logger.Error($"Found {numSeriesParents} parents for {parent.Asin}\r\nEpisode Product:\r\n{JsonConvert.SerializeObject(parent, Formatting.None, Settings)}");
+ return new List- ();
}
var realParent = seriesParents.Single(p => p.IsSeriesParent);
@@ -329,15 +338,5 @@ namespace AudibleUtilities
return results;
}
#endregion
-
- private static List getValidators()
- {
- var type = typeof(IValidator);
- var types = AppDomain.CurrentDomain.GetAssemblies()
- .SelectMany(s => s.GetTypes())
- .Where(p => type.IsAssignableFrom(p) && !p.IsInterface);
-
- return types.Select(t => Activator.CreateInstance(t) as IValidator).ToList();
- }
}
}
diff --git a/Source/AudibleUtilities/AudibleUtilities.csproj b/Source/AudibleUtilities/AudibleUtilities.csproj
index 1342b852..3ebae93c 100644
--- a/Source/AudibleUtilities/AudibleUtilities.csproj
+++ b/Source/AudibleUtilities/AudibleUtilities.csproj
@@ -11,5 +11,13 @@
+
+
+ embedded
+
+
+
+ embedded
+
diff --git a/Source/DataLayer/Configurations/BookConfig.cs b/Source/DataLayer/Configurations/BookConfig.cs
index 88a92a46..17054ea7 100644
--- a/Source/DataLayer/Configurations/BookConfig.cs
+++ b/Source/DataLayer/Configurations/BookConfig.cs
@@ -12,11 +12,13 @@ namespace DataLayer.Configurations
entity.OwnsOne(b => b.Rating);
+ entity.Property(nameof(Book._audioFormat));
//
// CRUCIAL: ignore unmapped collections, even get-only
//
entity.Ignore(nameof(Book.Authors));
entity.Ignore(nameof(Book.Narrators));
+ entity.Ignore(nameof(Book.AudioFormat));
//// these don't seem to matter
//entity.Ignore(nameof(Book.AuthorNames));
//entity.Ignore(nameof(Book.NarratorNames));
diff --git a/Source/DataLayer/DataLayer.csproj b/Source/DataLayer/DataLayer.csproj
index 1690e74f..87bd7c77 100644
--- a/Source/DataLayer/DataLayer.csproj
+++ b/Source/DataLayer/DataLayer.csproj
@@ -6,9 +6,7 @@
true
-
Library
-
@@ -23,8 +21,16 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+
+ embedded
+
+
+
+ embedded
+
+
+
diff --git a/Source/DataLayer/EfClasses/AudioFormat.cs b/Source/DataLayer/EfClasses/AudioFormat.cs
new file mode 100644
index 00000000..2d2f9d19
--- /dev/null
+++ b/Source/DataLayer/EfClasses/AudioFormat.cs
@@ -0,0 +1,62 @@
+using System;
+
+namespace DataLayer
+{
+ internal enum AudioFormatEnum : long
+ {
+ //Defining the enum this way ensures that when comparing:
+ //LC_128_44100_stereo > LC_64_44100_stereo > LC_64_22050_stereo > LC_64_22050_stereo
+ //This matches how audible interprets these codecs when specifying quality using AudibleApi.DownloadQuality
+ //I've never seen mono formats.
+ Unknown = 0,
+ LC_32_22050_stereo = (32L << 18) | (22050 << 2) | 2,
+ LC_64_22050_stereo = (64L << 18) | (22050 << 2) | 2,
+ LC_64_44100_stereo = (64L << 18) | (44100 << 2) | 2,
+ LC_128_44100_stereo = (128L << 18) | (44100 << 2) | 2,
+ }
+
+ public class AudioFormat : IComparable, IComparable
+ {
+
+ internal int AudioFormatID { get; private set; }
+ public int Bitrate { get; private init; }
+ public int SampleRate { get; private init; }
+ public int Channels { get; private init; }
+ public bool IsValid => Bitrate != 0 && SampleRate != 0 && Channels != 0;
+
+ public static AudioFormat FromString(string formatStr)
+ {
+ if (Enum.TryParse(formatStr, ignoreCase: true, out AudioFormatEnum enumVal))
+ return FromEnum(enumVal);
+ return FromEnum(AudioFormatEnum.Unknown);
+ }
+
+ internal static AudioFormat FromEnum(AudioFormatEnum enumVal)
+ {
+ var val = (long)enumVal;
+
+ return new()
+ {
+ Bitrate = (int)(val >> 18),
+ SampleRate = (int)(val >> 2) & ushort.MaxValue,
+ Channels = (int)(val & 3)
+ };
+ }
+ internal AudioFormatEnum ToEnum()
+ {
+ var val = (AudioFormatEnum)(((long)Bitrate << 18) | ((long)SampleRate << 2) | (long)Channels);
+
+ return Enum.IsDefined(val) ?
+ val : AudioFormatEnum.Unknown;
+ }
+
+ public override string ToString()
+ => IsValid ?
+ $"{Bitrate} Kbps, {SampleRate / 1000d:F1} kHz, {(Channels == 2 ? "Stereo" : Channels)}" :
+ "Unknown";
+
+ public int CompareTo(AudioFormat other) => ToEnum().CompareTo(other.ToEnum());
+
+ public int CompareTo(object obj) => CompareTo(obj as AudioFormat);
+ }
+}
diff --git a/Source/DataLayer/EfClasses/Book.cs b/Source/DataLayer/EfClasses/Book.cs
index ef072484..22007e13 100644
--- a/Source/DataLayer/EfClasses/Book.cs
+++ b/Source/DataLayer/EfClasses/Book.cs
@@ -25,6 +25,7 @@ namespace DataLayer
Parent = 4,
}
+
public class Book
{
// implementation detail. set by db only. only used by data layer
@@ -38,6 +39,10 @@ namespace DataLayer
public ContentType ContentType { get; private set; }
public string Locale { get; private set; }
+ internal AudioFormatEnum _audioFormat;
+
+ public AudioFormat AudioFormat { get => AudioFormat.FromEnum(_audioFormat); set => _audioFormat = value.ToEnum(); }
+
// mutable
public string PictureId { get; set; }
public string PictureLarge { get; set; }
diff --git a/Source/DataLayer/Migrations/20220624214932_AddAudioFormat.Designer.cs b/Source/DataLayer/Migrations/20220624214932_AddAudioFormat.Designer.cs
new file mode 100644
index 00000000..fce7a0ce
--- /dev/null
+++ b/Source/DataLayer/Migrations/20220624214932_AddAudioFormat.Designer.cs
@@ -0,0 +1,397 @@
+//
+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("20220624214932_AddAudioFormat")]
+ partial class AddAudioFormat
+ {
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
+
+ modelBuilder.Entity("DataLayer.Book", b =>
+ {
+ b.Property("BookId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AudibleProductId")
+ .HasColumnType("TEXT");
+
+ b.Property("CategoryId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ContentType")
+ .HasColumnType("INTEGER");
+
+ b.Property("DatePublished")
+ .HasColumnType("TEXT");
+
+ b.Property("Description")
+ .HasColumnType("TEXT");
+
+ b.Property("IsAbridged")
+ .HasColumnType("INTEGER");
+
+ b.Property("LengthInMinutes")
+ .HasColumnType("INTEGER");
+
+ b.Property("Locale")
+ .HasColumnType("TEXT");
+
+ b.Property("PictureId")
+ .HasColumnType("TEXT");
+
+ b.Property("PictureLarge")
+ .HasColumnType("TEXT");
+
+ b.Property("Title")
+ .HasColumnType("TEXT");
+
+ b.Property("_audioFormat")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("BookId");
+
+ b.HasIndex("AudibleProductId");
+
+ b.HasIndex("CategoryId");
+
+ b.ToTable("Books");
+ });
+
+ modelBuilder.Entity("DataLayer.BookContributor", b =>
+ {
+ b.Property("BookId")
+ .HasColumnType("INTEGER");
+
+ b.Property("ContributorId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Role")
+ .HasColumnType("INTEGER");
+
+ b.Property("Order")
+ .HasColumnType("INTEGER");
+
+ b.HasKey("BookId", "ContributorId", "Role");
+
+ b.HasIndex("BookId");
+
+ b.HasIndex("ContributorId");
+
+ b.ToTable("BookContributor");
+ });
+
+ modelBuilder.Entity("DataLayer.Category", b =>
+ {
+ b.Property("CategoryId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AudibleCategoryId")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.Property("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("ContributorId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AudibleContributorId")
+ .HasColumnType("TEXT");
+
+ b.Property("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("BookId")
+ .HasColumnType("INTEGER");
+
+ b.Property("Account")
+ .HasColumnType("TEXT");
+
+ b.Property("DateAdded")
+ .HasColumnType("TEXT");
+
+ b.HasKey("BookId");
+
+ b.ToTable("LibraryBooks");
+ });
+
+ modelBuilder.Entity("DataLayer.Series", b =>
+ {
+ b.Property("SeriesId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b.Property("AudibleSeriesId")
+ .HasColumnType("TEXT");
+
+ b.Property("Name")
+ .HasColumnType("TEXT");
+
+ b.HasKey("SeriesId");
+
+ b.HasIndex("AudibleSeriesId");
+
+ b.ToTable("Series");
+ });
+
+ modelBuilder.Entity("DataLayer.SeriesBook", b =>
+ {
+ b.Property("SeriesId")
+ .HasColumnType("INTEGER");
+
+ b.Property("BookId")
+ .HasColumnType("INTEGER");
+
+ b.Property("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("BookId")
+ .HasColumnType("INTEGER");
+
+ b1.Property("OverallRating")
+ .HasColumnType("REAL");
+
+ b1.Property("PerformanceRating")
+ .HasColumnType("REAL");
+
+ b1.Property("StoryRating")
+ .HasColumnType("REAL");
+
+ b1.HasKey("BookId");
+
+ b1.ToTable("Books");
+
+ b1.WithOwner()
+ .HasForeignKey("BookId");
+ });
+
+ b.OwnsMany("DataLayer.Supplement", "Supplements", b1 =>
+ {
+ b1.Property("SupplementId")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER");
+
+ b1.Property("BookId")
+ .HasColumnType("INTEGER");
+
+ b1.Property("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("BookId")
+ .HasColumnType("INTEGER");
+
+ b1.Property("BookStatus")
+ .HasColumnType("INTEGER");
+
+ b1.Property("PdfStatus")
+ .HasColumnType("INTEGER");
+
+ b1.Property("Tags")
+ .HasColumnType("TEXT");
+
+ b1.HasKey("BookId");
+
+ b1.ToTable("UserDefinedItem", (string)null);
+
+ b1.WithOwner("Book")
+ .HasForeignKey("BookId");
+
+ b1.OwnsOne("DataLayer.Rating", "Rating", b2 =>
+ {
+ b2.Property("UserDefinedItemBookId")
+ .HasColumnType("INTEGER");
+
+ b2.Property("OverallRating")
+ .HasColumnType("REAL");
+
+ b2.Property("PerformanceRating")
+ .HasColumnType("REAL");
+
+ b2.Property("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
+ }
+ }
+}
diff --git a/Source/DataLayer/Migrations/20220624214932_AddAudioFormat.cs b/Source/DataLayer/Migrations/20220624214932_AddAudioFormat.cs
new file mode 100644
index 00000000..38c7880a
--- /dev/null
+++ b/Source/DataLayer/Migrations/20220624214932_AddAudioFormat.cs
@@ -0,0 +1,26 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace DataLayer.Migrations
+{
+ public partial class AddAudioFormat : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.AddColumn(
+ name: "_audioFormat",
+ table: "Books",
+ type: "INTEGER",
+ nullable: false,
+ defaultValue: 0L);
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropColumn(
+ name: "_audioFormat",
+ table: "Books");
+ }
+ }
+}
diff --git a/Source/DataLayer/Migrations/LibationContextModelSnapshot.cs b/Source/DataLayer/Migrations/LibationContextModelSnapshot.cs
index c198e3c0..5579839c 100644
--- a/Source/DataLayer/Migrations/LibationContextModelSnapshot.cs
+++ b/Source/DataLayer/Migrations/LibationContextModelSnapshot.cs
@@ -15,7 +15,7 @@ namespace DataLayer.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
- modelBuilder.HasAnnotation("ProductVersion", "6.0.4");
+ modelBuilder.HasAnnotation("ProductVersion", "6.0.6");
modelBuilder.Entity("DataLayer.Book", b =>
{
@@ -56,6 +56,9 @@ namespace DataLayer.Migrations
b.Property("Title")
.HasColumnType("TEXT");
+ b.Property("_audioFormat")
+ .HasColumnType("INTEGER");
+
b.HasKey("BookId");
b.HasIndex("AudibleProductId");
diff --git a/Source/DtoImporterService/BookImporter.cs b/Source/DtoImporterService/BookImporter.cs
index 67d18f4e..3e05db63 100644
--- a/Source/DtoImporterService/BookImporter.cs
+++ b/Source/DtoImporterService/BookImporter.cs
@@ -162,6 +162,9 @@ namespace DtoImporterService
{
var item = importItem.DtoItem;
+ var codec = item.AvailableCodecs?.Max(f => AudioFormat.FromString(f.EnhancedCodec)) ?? new AudioFormat();
+ book.AudioFormat = codec;
+
// set/update book-specific info which may have changed
if (item.PictureId is not null)
book.PictureId = item.PictureId;
diff --git a/Source/DtoImporterService/ContributorImporter.cs b/Source/DtoImporterService/ContributorImporter.cs
index dd3e273f..2f6d255f 100644
--- a/Source/DtoImporterService/ContributorImporter.cs
+++ b/Source/DtoImporterService/ContributorImporter.cs
@@ -91,7 +91,7 @@ namespace DtoImporterService
return hash.Count;
}
- private Contributor addContributor(string name, string id = null)
+ private Contributor addContributor(string name, string id = null)
{
try
{
@@ -108,6 +108,6 @@ namespace DtoImporterService
Serilog.Log.Logger.Error(ex, "Error adding contributor. {@DebugInfo}", new { name, id });
throw;
}
- }
- }
+ }
+ }
}
diff --git a/Source/DtoImporterService/DtoImporterService.csproj b/Source/DtoImporterService/DtoImporterService.csproj
index a9b17110..d784be6f 100644
--- a/Source/DtoImporterService/DtoImporterService.csproj
+++ b/Source/DtoImporterService/DtoImporterService.csproj
@@ -4,6 +4,14 @@
net6.0-windows
+
+ embedded
+
+
+
+ embedded
+
+
diff --git a/Source/DtoImporterService/ImporterBase.cs b/Source/DtoImporterService/ImporterBase.cs
index 278659c7..5f3298b9 100644
--- a/Source/DtoImporterService/ImporterBase.cs
+++ b/Source/DtoImporterService/ImporterBase.cs
@@ -55,7 +55,7 @@ namespace DtoImporterService
protected ItemsImporterBase(LibationContext context) : base(context) { }
protected abstract IValidator Validator { get; }
- public sealed override IEnumerable Validate(IEnumerable importItems)
+ public sealed override IEnumerable Validate(IEnumerable importItems)
=> Validator.Validate(importItems.Select(i => i.DtoItem));
}
}
diff --git a/Source/DtoImporterService/LibraryBookImporter.cs b/Source/DtoImporterService/LibraryBookImporter.cs
index 634d8494..38581b7c 100644
--- a/Source/DtoImporterService/LibraryBookImporter.cs
+++ b/Source/DtoImporterService/LibraryBookImporter.cs
@@ -49,7 +49,7 @@ namespace DtoImporterService
// just use the first
var hash = newItems.ToDictionarySafe(dto => dto.DtoItem.ProductId);
foreach (var kvp in hash)
- {
+ {
var newItem = kvp.Value;
var libraryBook = new LibraryBook(
diff --git a/Source/FileLiberator/DownloadDecryptBook.cs b/Source/FileLiberator/DownloadDecryptBook.cs
index a426cff2..855a0322 100644
--- a/Source/FileLiberator/DownloadDecryptBook.cs
+++ b/Source/FileLiberator/DownloadDecryptBook.cs
@@ -166,6 +166,9 @@ namespace FileLiberator
};
var chapters = flattenChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList();
+
+ if (config.MergeOpeningAndEndCredits)
+ combineCredits(chapters);
if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3)
{
@@ -192,7 +195,7 @@ namespace FileLiberator
return dlOptions;
}
- public static List flattenChapters(IEnumerable chapters, string titleConcat = ": ")
+ public static List flattenChapters(IList chapters, string titleConcat = ": ")
{
List chaps = new();
@@ -217,6 +220,22 @@ namespace FileLiberator
return chaps;
}
+ public static void combineCredits(IList chapters)
+ {
+ if (chapters.Count > 1 && chapters[0].Title == "Opening Credits")
+ {
+ chapters[1].StartOffsetMs = chapters[0].StartOffsetMs;
+ chapters[1].StartOffsetSec = chapters[0].StartOffsetSec;
+ chapters[1].LengthMs += chapters[0].LengthMs;
+ chapters.RemoveAt(0);
+ }
+ if (chapters.Count > 1 && chapters[^1].Title == "End Credits")
+ {
+ chapters[^2].LengthMs += chapters[^1].LengthMs;
+ chapters.Remove(chapters[^1]);
+ }
+ }
+
private static void downloadValidation(LibraryBook libraryBook)
{
string errorString(string field)
diff --git a/Source/FileLiberator/FileLiberator.csproj b/Source/FileLiberator/FileLiberator.csproj
index 460308d6..65b461a1 100644
--- a/Source/FileLiberator/FileLiberator.csproj
+++ b/Source/FileLiberator/FileLiberator.csproj
@@ -11,4 +11,13 @@
+
+ embedded
+
+
+
+ embedded
+
+
+
diff --git a/Source/FileManager/FileManager.csproj b/Source/FileManager/FileManager.csproj
index 16363aa5..28f22c83 100644
--- a/Source/FileManager/FileManager.csproj
+++ b/Source/FileManager/FileManager.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
@@ -9,4 +9,12 @@
+
+ embedded
+
+
+
+ embedded
+
+
diff --git a/Source/FileManager/FileNamingTemplate.cs b/Source/FileManager/FileNamingTemplate.cs
index c00232a9..4d79c5af 100644
--- a/Source/FileManager/FileNamingTemplate.cs
+++ b/Source/FileManager/FileNamingTemplate.cs
@@ -35,18 +35,24 @@ namespace FileManager
}
else
{
- file = replaceFileName(file, paramReplacements);
- fileName = Path.GetDirectoryName(fileName);
pathParts.Add(file);
+ fileName = Path.GetDirectoryName(fileName);
}
}
pathParts.Reverse();
+ var fileNamePart = pathParts[^1];
+ pathParts.Remove(fileNamePart);
- return FileUtility.GetValidFilename(Path.Join(pathParts.ToArray()), replacements, returnFirstExisting);
+ LongPath directory = Path.Join(pathParts.Select(p => replaceFileName(p, paramReplacements, LongPath.MaxFilenameLength)).ToArray());
+
+ //If file already exists, GetValidFilename will append " (n)" to the filename.
+ //This could cause the filename length to exceed MaxFilenameLength, so reduce
+ //allowable filename length by 5 chars, allowing for up to 99 duplicates.
+ return FileUtility.GetValidFilename(Path.Join(directory, replaceFileName(fileNamePart, paramReplacements, LongPath.MaxFilenameLength - 5)), replacements, returnFirstExisting);
}
- private string replaceFileName(string filename, Dictionary paramReplacements)
+ private string replaceFileName(string filename, Dictionary paramReplacements, int maxFilenameLength)
{
List filenameParts = new();
//Build the filename in parts, replacing replacement parameters with
@@ -82,7 +88,7 @@ namespace FileManager
//Remove 1 character from the end of the longest filename part until
//the total filename is less than max filename length
- while (filenameParts.Sum(p => p.Length) > LongPath.MaxFilenameLength)
+ while (filenameParts.Sum(p => p.Length) > maxFilenameLength)
{
int maxLength = filenameParts.Max(p => p.Length);
var maxEntry = filenameParts.First(p => p.Length == maxLength);
diff --git a/Source/FileManager/ReplacementCharacters.cs b/Source/FileManager/ReplacementCharacters.cs
index b81d3865..53fd9517 100644
--- a/Source/FileManager/ReplacementCharacters.cs
+++ b/Source/FileManager/ReplacementCharacters.cs
@@ -136,19 +136,21 @@ namespace FileManager
{
if (toReplace == Replacement.QUOTE_MARK)
{
- if (preceding == default ||
- (preceding != default
- && !char.IsLetter(preceding)
- && !char.IsNumber(preceding)
- && (char.IsLetter(succeding) || char.IsNumber(succeding))
+ if (
+ preceding == default ||
+ (
+ !char.IsLetter(preceding) &&
+ !char.IsNumber(preceding) &&
+ (char.IsLetter(succeding) || char.IsNumber(succeding))
)
)
return OpenQuote;
- else if (succeding == default ||
- (succeding != default
- && !char.IsLetter(succeding)
- && !char.IsNumber(succeding)
- && (char.IsLetter(preceding) || char.IsNumber(preceding))
+ else if (
+ succeding == default ||
+ (
+ !char.IsLetter(succeding) &&
+ !char.IsNumber(succeding) &&
+ (char.IsLetter(preceding) || char.IsNumber(preceding))
)
)
return CloseQuote;
diff --git a/Source/Hangover/Hangover.csproj b/Source/Hangover/Hangover.csproj
index 7c90124e..d75c4de4 100644
--- a/Source/Hangover/Hangover.csproj
+++ b/Source/Hangover/Hangover.csproj
@@ -6,7 +6,6 @@
true
hangover.ico
enable
-
true
win-x64
false
@@ -24,11 +23,13 @@
edit debug and release output paths
-->
- ..\LibationWinForms\bin\Debug
+ ..\bin\Debug
+ embedded
- ..\LibationWinForms\bin\Release
+ ..\bin\Release
+ embedded
@@ -37,7 +38,7 @@
-
+
Form1.cs
diff --git a/Source/Hangover/Properties/PublishProfiles/FolderProfile.pubxml b/Source/Hangover/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 00000000..3ed14ad3
--- /dev/null
+++ b/Source/Hangover/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,16 @@
+
+
+
+
+ Release
+ Any CPU
+ ..\bin\publish\
+ FileSystem
+ net6.0-windows
+ win-x64
+ false
+ false
+
+
\ No newline at end of file
diff --git a/Source/LibationCli/LibationCli.csproj b/Source/LibationCli/LibationCli.csproj
index 84f16e9b..e3b1fa09 100644
--- a/Source/LibationCli/LibationCli.csproj
+++ b/Source/LibationCli/LibationCli.csproj
@@ -4,12 +4,11 @@
Exe
net6.0-windows
-
- true
true
win-x64
false
false
+ True
- ..\LibationWinForms\bin\Debug
+ ..\bin\Debug
+ embedded
- ..\LibationWinForms\bin\Release
+ ..\bin\Release
+ embedded
diff --git a/Source/LibationCli/Properties/PublishProfiles/FolderProfile.pubxml b/Source/LibationCli/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 00000000..3ed14ad3
--- /dev/null
+++ b/Source/LibationCli/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,16 @@
+
+
+
+
+ Release
+ Any CPU
+ ..\bin\publish\
+ FileSystem
+ net6.0-windows
+ win-x64
+ false
+ false
+
+
\ No newline at end of file
diff --git a/Source/LibationFileManager/Configuration.cs b/Source/LibationFileManager/Configuration.cs
index d545df22..f7000e07 100644
--- a/Source/LibationFileManager/Configuration.cs
+++ b/Source/LibationFileManager/Configuration.cs
@@ -117,6 +117,13 @@ namespace LibationFileManager
set => persistentDictionary.SetNonString(nameof(SplitFilesByChapter), value);
}
+ [Description("Merge Opening/End Credits into the following/preceding chapters")]
+ public bool MergeOpeningAndEndCredits
+ {
+ get => persistentDictionary.GetNonString(nameof(MergeOpeningAndEndCredits));
+ set => persistentDictionary.SetNonString(nameof(MergeOpeningAndEndCredits), value);
+ }
+
[Description("Strip \"(Unabridged)\" from audiobook metadata tags")]
public bool StripUnabridged
{
@@ -437,7 +444,7 @@ namespace LibationFileManager
#endregion
#region LibationFiles
- private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location), "appsettings.json");
+ private static string APPSETTINGS_JSON { get; } = Path.Combine(Path.GetDirectoryName(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName), "appsettings.json");
private const string LIBATION_FILES_KEY = "LibationFiles";
[Description("Location for storage of program-created files")]
diff --git a/Source/LibationFileManager/LibationFileManager.csproj b/Source/LibationFileManager/LibationFileManager.csproj
index 4648a9ee..04ae43f2 100644
--- a/Source/LibationFileManager/LibationFileManager.csproj
+++ b/Source/LibationFileManager/LibationFileManager.csproj
@@ -14,4 +14,12 @@
+
+ embedded
+
+
+
+ embedded
+
+
diff --git a/Source/LibationSearchEngine/LibationSearchEngine.csproj b/Source/LibationSearchEngine/LibationSearchEngine.csproj
index 9fc7e889..5e811683 100644
--- a/Source/LibationSearchEngine/LibationSearchEngine.csproj
+++ b/Source/LibationSearchEngine/LibationSearchEngine.csproj
@@ -15,5 +15,14 @@
+
+
+ embedded
+
+
+
+ embedded
+
+
diff --git a/Source/LibationWinForms/Dialogs/BookDetailsDialog.cs b/Source/LibationWinForms/Dialogs/BookDetailsDialog.cs
index fdbc7ac7..838b72b7 100644
--- a/Source/LibationWinForms/Dialogs/BookDetailsDialog.cs
+++ b/Source/LibationWinForms/Dialogs/BookDetailsDialog.cs
@@ -49,6 +49,7 @@ Title: {Book.Title}
Author(s): {Book.AuthorNames()}
Narrator(s): {Book.NarratorNames()}
Length: {(Book.LengthInMinutes == 0 ? "" : $"{Book.LengthInMinutes / 60} hr {Book.LengthInMinutes % 60} min")}
+Audio Bitrate: {Book.AudioFormat}
Category: {string.Join(" > ", Book.CategoriesNames())}
Purchase Date: {_libraryBook.DateAdded.ToString("d")}
".Trim();
diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs
index 72f9a151..d7b53a2e 100644
--- a/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs
+++ b/Source/LibationWinForms/Dialogs/SettingsDialog.AudioSettings.cs
@@ -13,6 +13,7 @@ namespace LibationWinForms.Dialogs
this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt));
this.retainAaxFileCbox.Text = desc(nameof(config.RetainAaxFile));
this.splitFilesByChapterCbox.Text = desc(nameof(config.SplitFilesByChapter));
+ this.mergeOpeningEndCreditsCbox.Text = desc(nameof(config.MergeOpeningAndEndCredits));
this.stripAudibleBrandingCbox.Text = desc(nameof(config.StripAudibleBrandAudio));
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
@@ -21,6 +22,7 @@ namespace LibationWinForms.Dialogs
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
retainAaxFileCbox.Checked = config.RetainAaxFile;
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
+ mergeOpeningEndCreditsCbox.Checked = config.MergeOpeningAndEndCredits;
stripUnabridgedCbox.Checked = config.StripUnabridged;
stripAudibleBrandingCbox.Checked = config.StripAudibleBrandAudio;
convertLosslessRb.Checked = !config.DecryptToLossy;
@@ -50,6 +52,7 @@ namespace LibationWinForms.Dialogs
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
config.RetainAaxFile = retainAaxFileCbox.Checked;
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
+ config.MergeOpeningAndEndCredits = mergeOpeningEndCreditsCbox.Checked;
config.StripUnabridged = stripUnabridgedCbox.Checked;
config.StripAudibleBrandAudio = stripAudibleBrandingCbox.Checked;
config.DecryptToLossy = convertLossyRb.Checked;
diff --git a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs
index d117e4fe..19166a4f 100644
--- a/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs
+++ b/Source/LibationWinForms/Dialogs/SettingsDialog.Designer.cs
@@ -108,6 +108,7 @@
this.retainAaxFileCbox = new System.Windows.Forms.CheckBox();
this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox();
this.createCueSheetCbox = new System.Windows.Forms.CheckBox();
+ this.mergeOpeningEndCreditsCbox = new System.Windows.Forms.CheckBox();
this.badBookGb.SuspendLayout();
this.tabControl.SuspendLayout();
this.tab1ImportantSettings.SuspendLayout();
@@ -251,7 +252,7 @@
// stripAudibleBrandingCbox
//
this.stripAudibleBrandingCbox.AutoSize = true;
- this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(19, 168);
+ this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(19, 193);
this.stripAudibleBrandingCbox.Name = "stripAudibleBrandingCbox";
this.stripAudibleBrandingCbox.Size = new System.Drawing.Size(143, 34);
this.stripAudibleBrandingCbox.TabIndex = 13;
@@ -285,7 +286,7 @@
// convertLossyRb
//
this.convertLossyRb.AutoSize = true;
- this.convertLossyRb.Location = new System.Drawing.Point(19, 232);
+ this.convertLossyRb.Location = new System.Drawing.Point(19, 257);
this.convertLossyRb.Name = "convertLossyRb";
this.convertLossyRb.Size = new System.Drawing.Size(329, 19);
this.convertLossyRb.TabIndex = 12;
@@ -297,7 +298,7 @@
//
this.convertLosslessRb.AutoSize = true;
this.convertLosslessRb.Checked = true;
- this.convertLosslessRb.Location = new System.Drawing.Point(19, 207);
+ this.convertLosslessRb.Location = new System.Drawing.Point(19, 232);
this.convertLosslessRb.Name = "convertLosslessRb";
this.convertLosslessRb.Size = new System.Drawing.Size(335, 19);
this.convertLosslessRb.TabIndex = 11;
@@ -608,6 +609,7 @@
this.tab4AudioFileOptions.Controls.Add(this.stripAudibleBrandingCbox);
this.tab4AudioFileOptions.Controls.Add(this.convertLosslessRb);
this.tab4AudioFileOptions.Controls.Add(this.stripUnabridgedCbox);
+ this.tab4AudioFileOptions.Controls.Add(this.mergeOpeningEndCreditsCbox);
this.tab4AudioFileOptions.Controls.Add(this.splitFilesByChapterCbox);
this.tab4AudioFileOptions.Controls.Add(this.retainAaxFileCbox);
this.tab4AudioFileOptions.Controls.Add(this.downloadCoverArtCbox);
@@ -980,7 +982,7 @@
// stripUnabridgedCbox
//
this.stripUnabridgedCbox.AutoSize = true;
- this.stripUnabridgedCbox.Location = new System.Drawing.Point(19, 143);
+ this.stripUnabridgedCbox.Location = new System.Drawing.Point(19, 168);
this.stripUnabridgedCbox.Name = "stripUnabridgedCbox";
this.stripUnabridgedCbox.Size = new System.Drawing.Size(147, 19);
this.stripUnabridgedCbox.TabIndex = 13;
@@ -1024,6 +1026,17 @@
this.createCueSheetCbox.UseVisualStyleBackColor = true;
this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
//
+ // mergeBeginningEndCreditsCbox
+ //
+ this.mergeOpeningEndCreditsCbox.AutoSize = true;
+ this.mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 143);
+ this.mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox";
+ this.mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(206, 19);
+ this.mergeOpeningEndCreditsCbox.TabIndex = 13;
+ this.mergeOpeningEndCreditsCbox.Text = "[MergeOpeningEndCredits desc]";
+ this.mergeOpeningEndCreditsCbox.UseVisualStyleBackColor = true;
+ this.mergeOpeningEndCreditsCbox.CheckedChanged += new System.EventHandler(this.splitFilesByChapterCbox_CheckedChanged);
+ //
// SettingsDialog
//
this.AcceptButton = this.saveBtn;
@@ -1155,5 +1168,6 @@
private System.Windows.Forms.Button chapterTitleTemplateBtn;
private System.Windows.Forms.TextBox chapterTitleTemplateTb;
private System.Windows.Forms.Button editCharreplacementBtn;
+ private System.Windows.Forms.CheckBox mergeOpeningEndCreditsCbox;
}
}
\ No newline at end of file
diff --git a/Source/LibationWinForms/LibationWinForms.csproj b/Source/LibationWinForms/LibationWinForms.csproj
index b21cadd3..b15b714c 100644
--- a/Source/LibationWinForms/LibationWinForms.csproj
+++ b/Source/LibationWinForms/LibationWinForms.csproj
@@ -7,7 +7,7 @@
true
libation.ico
Libation
-
+
true
win-x64
false
@@ -27,6 +27,16 @@
en;es
+
+ ..\bin\Debug
+ embedded
+
+
+
+ ..\bin\Release
+ embedded
+
+
diff --git a/Source/LibationWinForms/Properties/PublishProfiles/FolderProfile.pubxml b/Source/LibationWinForms/Properties/PublishProfiles/FolderProfile.pubxml
new file mode 100644
index 00000000..806982d7
--- /dev/null
+++ b/Source/LibationWinForms/Properties/PublishProfiles/FolderProfile.pubxml
@@ -0,0 +1,16 @@
+
+
+
+
+ Release
+ x64
+ ..\bin\publish\
+ FileSystem
+ net6.0-windows
+ win-x64
+ false
+ false
+
+
\ No newline at end of file
diff --git a/Source/_Tests/AudibleUtilities.Tests/AudibleUtilities.Tests.csproj b/Source/_Tests/AudibleUtilities.Tests/AudibleUtilities.Tests.csproj
index ef046649..f77a9580 100644
--- a/Source/_Tests/AudibleUtilities.Tests/AudibleUtilities.Tests.csproj
+++ b/Source/_Tests/AudibleUtilities.Tests/AudibleUtilities.Tests.csproj
@@ -2,7 +2,6 @@
net6.0-windows
-
false
diff --git a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs
index a30e585f..c2df3666 100644
--- a/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs
+++ b/Source/_Tests/FileLiberator.Tests/DownloadDecryptBookTests.cs
@@ -14,8 +14,142 @@ namespace FileLiberator.Tests
[TestClass]
public class DownloadDecryptBookTests
{
+ private static Chapter[] HierarchicalChapters => new Chapter[]
+ {
+ new ()
+ {
+ Title = "Opening Credits",
+ StartOffsetMs = 0,
+ StartOffsetSec = 0,
+ LengthMs = 10000,
+ },
+ new ()
+ {
+ Title = "Book 1",
+ StartOffsetMs = 10000,
+ StartOffsetSec = 10,
+ LengthMs = 2000,
+ Chapters = new Chapter[]
+ {
+ new ()
+ {
+ Title = "Part 1",
+ StartOffsetMs = 12000,
+ StartOffsetSec = 12,
+ LengthMs = 2000,
+ Chapters = new Chapter[]
+ {
+ new ()
+ {
+ Title = "Chapter 1",
+ StartOffsetMs = 14000,
+ StartOffsetSec = 14,
+ LengthMs = 86000,
+ },
+ new()
+ {
+ Title = "Chapter 2",
+ StartOffsetMs = 100000,
+ StartOffsetSec = 100,
+ LengthMs = 100000,
+ },
+ }
+ },
+ new()
+ {
+ Title = "Part 2",
+ StartOffsetMs = 200000,
+ StartOffsetSec = 200,
+ LengthMs = 2000,
+ Chapters = new Chapter[]
+ {
+ new()
+ {
+ Title = "Chapter 3",
+ StartOffsetMs = 202000,
+ StartOffsetSec = 202,
+ LengthMs = 98000,
+ },
+ new()
+ {
+ Title = "Chapter 4",
+ StartOffsetMs = 300000,
+ StartOffsetSec = 300,
+ LengthMs = 100000,
+ },
+ }
+ }
+ }
+ },
+ new()
+ {
+ Title = "Book 2",
+ StartOffsetMs = 400000,
+ StartOffsetSec = 400,
+ LengthMs = 2000,
+ Chapters = new Chapter[]
+ {
+ new()
+ {
+ Title = "Part 3",
+ StartOffsetMs = 402000,
+ StartOffsetSec = 402,
+ LengthMs = 2000,
+ Chapters = new Chapter[]
+ {
+ new()
+ {
+ Title = "Chapter 5",
+ StartOffsetMs = 404000,
+ StartOffsetSec = 404,
+ LengthMs = 96000,
+ },
+ new()
+ {
+ Title = "Chapter 6",
+ StartOffsetMs = 500000,
+ StartOffsetSec = 500,
+ LengthMs = 100000,
+ },
+ }
+ },
+ new()
+ {
+ Title = "Part 4",
+ StartOffsetMs = 600000,
+ StartOffsetSec = 600,
+ LengthMs = 2000,
+ Chapters = new Chapter[]
+ {
+ new()
+ {
+ Title = "Chapter 7",
+ StartOffsetMs = 602000,
+ StartOffsetSec = 602,
+ LengthMs = 98000,
+ },
+ new()
+ {
+ Title = "Chapter 8",
+ StartOffsetMs = 700000,
+ StartOffsetSec = 700,
+ LengthMs = 100000,
+ },
+ }
+ }
+ }
+ },
+ new()
+ {
+ Title = "End Credits",
+ StartOffsetMs = 800000,
+ StartOffsetSec = 800,
+ LengthMs = 10000,
+ },
+ };
+
[TestMethod]
- public void HierarchicalChapters_Flatten()
+ public void Chapters_CombineCredits()
{
var expected = new Chapter[]
{
@@ -73,129 +207,109 @@ namespace FileLiberator.Tests
Title = "Book 2: Part 4: Chapter 8",
StartOffsetMs = 700000,
StartOffsetSec = 700,
- LengthMs = 100000,
+ LengthMs = 110000,
}
};
- var hierarchicalChapters = new Chapter[]
- {
- new()
- {
- Title = "Book 1",
- StartOffsetMs = 0,
- StartOffsetSec = 0,
- LengthMs = 2000,
- Chapters = new Chapter[]
- {
- new()
- { Title = "Part 1",
- StartOffsetMs = 2000,
- StartOffsetSec = 2,
- LengthMs = 2000,
- Chapters = new Chapter[]
- {
- new()
- { Title = "Chapter 1",
- StartOffsetMs = 4000,
- StartOffsetSec = 4,
- LengthMs = 96000,
- },
- new()
- { Title = "Chapter 2",
- StartOffsetMs = 100000,
- StartOffsetSec = 100,
- LengthMs = 100000,
- },
- }
- },
- new()
- { Title = "Part 2",
- StartOffsetMs = 200000,
- StartOffsetSec = 200,
- LengthMs = 2000,
- Chapters = new Chapter[]
- {
- new()
- { Title = "Chapter 3",
- StartOffsetMs = 202000,
- StartOffsetSec = 202,
- LengthMs = 98000,
- },
- new()
- { Title = "Chapter 4",
- StartOffsetMs = 300000,
- StartOffsetSec = 300,
- LengthMs = 100000,
- },
- }
- }
- }
- },
- new()
- {
- Title = "Book 2",
- StartOffsetMs = 400000,
- StartOffsetSec = 400,
- LengthMs = 2000,
- Chapters = new Chapter[]
- {
- new()
- { Title = "Part 3",
- StartOffsetMs = 402000,
- StartOffsetSec = 402,
- LengthMs = 2000,
- Chapters = new Chapter[]
- {
- new()
- { Title = "Chapter 5",
- StartOffsetMs = 404000,
- StartOffsetSec = 404,
- LengthMs = 96000,
- },
- new()
- { Title = "Chapter 6",
- StartOffsetMs = 500000,
- StartOffsetSec = 500,
- LengthMs = 100000,
- },
- }
- },
- new()
- { Title = "Part 4",
- StartOffsetMs = 600000,
- StartOffsetSec = 600,
- LengthMs = 2000,
- Chapters = new Chapter[]
- {
- new()
- { Title = "Chapter 7",
- StartOffsetMs = 602000,
- StartOffsetSec = 602,
- LengthMs = 98000,
- },
- new()
- { Title = "Chapter 8",
- StartOffsetMs = 700000,
- StartOffsetSec = 700,
- LengthMs = 100000,
- },
- }
- }
- }
- }
- };
+ var flatChapters = DownloadDecryptBook.flattenChapters(HierarchicalChapters);
+ DownloadDecryptBook.combineCredits(flatChapters);
+ checkChapters(flatChapters, expected);
+ }
- var flatChapters = DownloadDecryptBook.flattenChapters(hierarchicalChapters);
- flatChapters.Count.Should().Be(expected.Length);
-
- for (int i = 0; i < flatChapters.Count; i++)
+ [TestMethod]
+ public void HierarchicalChapters_Flatten()
+ {
+ var expected = new Chapter[]
{
- flatChapters[i].Title.Should().Be(expected[i].Title);
- flatChapters[i].StartOffsetMs.Should().Be(expected[i].StartOffsetMs);
- flatChapters[i].StartOffsetSec.Should().Be(expected[i].StartOffsetSec);
- flatChapters[i].LengthMs.Should().Be(expected[i].LengthMs);
- flatChapters[i].Chapters.Should().BeNull();
+ new()
+ {
+ Title = "Opening Credits",
+ StartOffsetMs = 0,
+ StartOffsetSec = 0,
+ LengthMs = 10000,
+ },
+ new()
+ {
+ Title = "Book 1: Part 1: Chapter 1",
+ StartOffsetMs = 10000,
+ StartOffsetSec = 10,
+ LengthMs = 90000,
+ },
+ new()
+ {
+ Title = "Book 1: Part 1: Chapter 2",
+ StartOffsetMs = 100000,
+ StartOffsetSec = 100,
+ LengthMs = 100000,
+ },
+ new()
+ {
+ Title = "Book 1: Part 2: Chapter 3",
+ StartOffsetMs = 200000,
+ StartOffsetSec = 200,
+ LengthMs = 100000,
+ },
+ new()
+ {
+ Title = "Book 1: Part 2: Chapter 4",
+ StartOffsetMs = 300000,
+ StartOffsetSec = 300,
+ LengthMs = 100000,
+ },
+ new()
+ {
+ Title = "Book 2: Part 3: Chapter 5",
+ StartOffsetMs = 400000,
+ StartOffsetSec = 400,
+ LengthMs = 100000,
+ },
+ new()
+ {
+ Title = "Book 2: Part 3: Chapter 6",
+ StartOffsetMs = 500000,
+ StartOffsetSec = 500,
+ LengthMs = 100000,
+ },
+ new()
+ {
+ Title = "Book 2: Part 4: Chapter 7",
+ StartOffsetMs = 600000,
+ StartOffsetSec = 600,
+ LengthMs = 100000,
+ },
+ new()
+ {
+ Title = "Book 2: Part 4: Chapter 8",
+ StartOffsetMs = 700000,
+ StartOffsetSec = 700,
+ LengthMs = 100000,
+ },
+ new()
+ {
+ Title = "End Credits",
+ StartOffsetMs = 800000,
+ StartOffsetSec = 800,
+ LengthMs = 10000,
+ }
+ };
+
+ var flatChapters = DownloadDecryptBook.flattenChapters(HierarchicalChapters);
+
+ checkChapters(flatChapters, expected);
+ }
+
+ private static void checkChapters(IList value, IList expected)
+ {
+ value.Count.Should().Be(expected.Count);
+
+ for (int i = 0; i < value.Count; i++)
+ {
+ value[i].Title.Should().Be(expected[i].Title);
+ value[i].StartOffsetMs.Should().Be(expected[i].StartOffsetMs);
+ value[i].StartOffsetSec.Should().Be(expected[i].StartOffsetSec);
+ value[i].LengthMs.Should().Be(expected[i].LengthMs);
+ value[i].Chapters.Should().BeNull();
}
}
}
diff --git a/Source/_Tests/FileLiberator.Tests/FileLiberator.Tests.csproj b/Source/_Tests/FileLiberator.Tests/FileLiberator.Tests.csproj
index 61822f5e..4e1d1373 100644
--- a/Source/_Tests/FileLiberator.Tests/FileLiberator.Tests.csproj
+++ b/Source/_Tests/FileLiberator.Tests/FileLiberator.Tests.csproj
@@ -2,7 +2,6 @@
net6.0-windows
-
false
diff --git a/Source/publish.ps1 b/Source/publish.ps1
new file mode 100644
index 00000000..23fba050
--- /dev/null
+++ b/Source/publish.ps1
@@ -0,0 +1,16 @@
+<# You must enable running powershell scripts.
+
+ Set-ExecutionPolicy -Scope CurrentUser Unrestricted
+#>
+
+$pubDir = "bin\Publish"
+Remove-Item $pubDir -Recurse -Force
+
+dotnet publish -c Release LibationWinForms\LibationWinForms.csproj -p:PublishProfile=LibationWinForms\Properties\PublishProfiles\FolderProfile.pubxml
+dotnet publish -c Release LibationCli\LibationCli.csproj -p:PublishProfile=LibationCli\Properties\PublishProfiles\FolderProfile.pubxml
+dotnet publish -c Release Hangover\Hangover.csproj -p:PublishProfile=Hangover\Properties\PublishProfiles\FolderProfile.pubxml
+
+$verMatch = Select-String -Path 'AppScaffolding\AppScaffolding.csproj' -Pattern '(\d{0,3}\.\d{0,3}\.\d{0,3})\.\d{0,3}'
+$archiveName = "bin\Libation."+$verMatch.Matches.Groups[1].Value+".zip"
+Get-ChildItem -Path $pubDir -Recurse |
+ Compress-Archive -DestinationPath $archiveName -Force
\ No newline at end of file