mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-01-05 04:18:22 -05:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
38d280b7f4 | ||
|
|
468356d676 | ||
|
|
7364700899 | ||
|
|
e65f19cf24 | ||
|
|
4272dfe03d | ||
|
|
3b739328fb | ||
|
|
81c3dca740 | ||
|
|
dceb3121b1 | ||
|
|
cb60a97b91 | ||
|
|
eb658396d2 | ||
|
|
0a1cefdb76 | ||
|
|
fb618e6719 | ||
|
|
2d529539cd | ||
|
|
9d93a98a58 | ||
|
|
38dcb10a6e | ||
|
|
50651339ec | ||
|
|
d0b2889fec | ||
|
|
3ce1f94f87 | ||
|
|
888967be31 | ||
|
|
6826237657 | ||
|
|
a8987cf1d3 | ||
|
|
d48a74912a | ||
|
|
1668b7c9a1 | ||
|
|
efa2cfb50b | ||
|
|
071b1a54d5 | ||
|
|
7c3bba2ffd | ||
|
|
d58092968a | ||
|
|
1b20bb06ad | ||
|
|
5815a04712 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -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
|
||||
|
||||
12
Source/.config/dotnet-tools.json
Normal file
12
Source/.config/dotnet-tools.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"version": 1,
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"frogvall.dotnetbumpversion": {
|
||||
"version": "3.0.1",
|
||||
"commands": [
|
||||
"bump-version"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,16 @@
|
||||
<PackageReference Include="AAXClean.Codecs" Version="0.2.10" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\FileManager\FileManager.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -34,27 +34,12 @@ namespace AaxDecrypter
|
||||
|
||||
//Finishing configuring lame encoder.
|
||||
if (DownloadOptions.OutputFormat == OutputFormat.Mp3)
|
||||
{
|
||||
double bitrateMultiple = 1;
|
||||
MpegUtil.ConfigureLameOptions(
|
||||
AaxFile,
|
||||
DownloadOptions.LameConfig,
|
||||
DownloadOptions.Downsample,
|
||||
DownloadOptions.MatchSourceBitrate);
|
||||
|
||||
if (AaxFile.AudioChannels == 2)
|
||||
{
|
||||
if (DownloadOptions.Downsample)
|
||||
bitrateMultiple = 0.5;
|
||||
else
|
||||
DownloadOptions.LameConfig.Mode = NAudio.Lame.MPEGMode.Stereo;
|
||||
}
|
||||
|
||||
if (DownloadOptions.MatchSourceBitrate)
|
||||
{
|
||||
int kbps = (int)(AaxFile.AverageBitrate * bitrateMultiple / 1024);
|
||||
|
||||
if (DownloadOptions.LameConfig.VBR is null)
|
||||
DownloadOptions.LameConfig.BitRate = kbps;
|
||||
else if (DownloadOptions.LameConfig.VBR == NAudio.Lame.VBRMode.ABR)
|
||||
DownloadOptions.LameConfig.ABRRateKbps = kbps;
|
||||
}
|
||||
}
|
||||
|
||||
OnRetrievedTitle(AaxFile.AppleTags.TitleSansUnabridged);
|
||||
OnRetrievedAuthors(AaxFile.AppleTags.FirstAuthor ?? "[unknown]");
|
||||
|
||||
@@ -155,7 +155,7 @@ That naming may not be desirable for everyone, but it's an easy change to instea
|
||||
|
||||
|
||||
private void Callback(int currentChapter, ChapterInfo splitChapters, NewMP3SplitCallback newSplitCallback)
|
||||
=> Callback(currentChapter, splitChapters, newSplitCallback);
|
||||
=> Callback(currentChapter, splitChapters, newSplitCallback as NewSplitCallback);
|
||||
|
||||
private void Callback(int currentChapter, ChapterInfo splitChapters, NewSplitCallback newSplitCallback)
|
||||
{
|
||||
|
||||
@@ -78,13 +78,10 @@ namespace AaxDecrypter
|
||||
OnFileCreated(OutputFileName);
|
||||
|
||||
AaxFile.ConversionProgressUpdate += AaxFile_ConversionProgressUpdate;
|
||||
var decryptionResult
|
||||
= DownloadOptions.OutputFormat == OutputFormat.M4b
|
||||
? await AaxFile.ConvertToMp4aAsync(outputFile, DownloadOptions.ChapterInfo, DownloadOptions.TrimOutputToChapterLength)
|
||||
: await AaxFile.ConvertToMp3Async(outputFile, DownloadOptions.LameConfig, DownloadOptions.ChapterInfo, DownloadOptions.TrimOutputToChapterLength);
|
||||
AaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate;
|
||||
|
||||
DownloadOptions.ChapterInfo = AaxFile.Chapters;
|
||||
ConversionResult decryptionResult = await decryptAsync(outputFile);
|
||||
|
||||
AaxFile.ConversionProgressUpdate -= AaxFile_ConversionProgressUpdate;
|
||||
|
||||
Step_DownloadAudiobook_End(zeroProgress);
|
||||
|
||||
@@ -94,5 +91,23 @@ namespace AaxDecrypter
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private Task<ConversionResult> decryptAsync(Stream outputFile)
|
||||
=> DownloadOptions.OutputFormat == OutputFormat.Mp3 ?
|
||||
AaxFile.ConvertToMp3Async
|
||||
(
|
||||
outputFile,
|
||||
DownloadOptions.LameConfig,
|
||||
DownloadOptions.ChapterInfo,
|
||||
DownloadOptions.TrimOutputToChapterLength
|
||||
)
|
||||
: DownloadOptions.FixupFile ?
|
||||
AaxFile.ConvertToMp4aAsync
|
||||
(
|
||||
outputFile,
|
||||
DownloadOptions.ChapterInfo,
|
||||
DownloadOptions.TrimOutputToChapterLength
|
||||
)
|
||||
: AaxFile.ConvertToMp4aAsync(outputFile);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,11 @@ namespace AaxDecrypter
|
||||
bool RetainEncryptedFile { get; }
|
||||
bool StripUnabridged { get; }
|
||||
bool CreateCueSheet { get; }
|
||||
ChapterInfo ChapterInfo { get; set; }
|
||||
NAudio.Lame.LameConfig LameConfig { get; set; }
|
||||
bool Downsample { get; set; }
|
||||
bool MatchSourceBitrate { get; set; }
|
||||
ChapterInfo ChapterInfo { get; }
|
||||
bool FixupFile { get; }
|
||||
NAudio.Lame.LameConfig LameConfig { get; }
|
||||
bool Downsample { get; }
|
||||
bool MatchSourceBitrate { get; }
|
||||
string GetMultipartFileName(MultiConvertFileProperties props);
|
||||
string GetMultipartTitleName(MultiConvertFileProperties props);
|
||||
}
|
||||
|
||||
33
Source/AaxDecrypter/MpegUtil.cs
Normal file
33
Source/AaxDecrypter/MpegUtil.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using AAXClean;
|
||||
using NAudio.Lame;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
public static class MpegUtil
|
||||
{
|
||||
public static void ConfigureLameOptions(Mp4File mp4File, LameConfig lameConfig, bool downsample, bool matchSourceBitrate)
|
||||
{
|
||||
double bitrateMultiple = 1;
|
||||
|
||||
if (mp4File.AudioChannels == 2)
|
||||
{
|
||||
if (downsample)
|
||||
bitrateMultiple = 0.5;
|
||||
else
|
||||
lameConfig.Mode = MPEGMode.Stereo;
|
||||
}
|
||||
|
||||
if (matchSourceBitrate)
|
||||
{
|
||||
int kbps = (int)(mp4File.AverageBitrate * bitrateMultiple / 1024);
|
||||
|
||||
if (lameConfig.VBR is null)
|
||||
lameConfig.BitRate = kbps;
|
||||
else if (lameConfig.VBR == VBRMode.ABR)
|
||||
lameConfig.ABRRateKbps = kbps;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"//": "https://github.com/BalassaMarton/MSBump",
|
||||
BumpRevision: true
|
||||
}
|
||||
@@ -1,22 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<Version>8.1.3.1</Version>
|
||||
<Version>8.1.7.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MSBump" Version="2.3.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Octokit" Version="0.51.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ApplicationServices\ApplicationServices.csproj" />
|
||||
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
@@ -14,4 +14,12 @@
|
||||
<ProjectReference Include="..\LibationSearchEngine\LibationSearchEngine.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -162,10 +162,19 @@ namespace AudibleUtilities
|
||||
if (exceptions is not null && exceptions.Any())
|
||||
throw new AggregateException(exceptions);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private static List<IValidator> 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<List<Item>> 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<Item>();
|
||||
}
|
||||
|
||||
var realParent = seriesParents.Single(p => p.IsSeriesParent);
|
||||
@@ -329,15 +338,5 @@ namespace AudibleUtilities
|
||||
return results;
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static List<IValidator> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AudibleApi" Version="4.2.2.1" />
|
||||
<PackageReference Include="AudibleApi" Version="4.3.1.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LibationFileManager\LibationFileManager.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
|
||||
<ApplicationIcon />
|
||||
<OutputType>Library</OutputType>
|
||||
<StartupObject />
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -23,8 +21,16 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LibationFileManager\LibationFileManager.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
62
Source/DataLayer/EfClasses/AudioFormat.cs
Normal file
62
Source/DataLayer/EfClasses/AudioFormat.cs
Normal file
@@ -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<AudioFormat>, 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);
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
397
Source/DataLayer/Migrations/20220624214932_AddAudioFormat.Designer.cs
generated
Normal file
397
Source/DataLayer/Migrations/20220624214932_AddAudioFormat.Designer.cs
generated
Normal file
@@ -0,0 +1,397 @@
|
||||
// <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("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<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<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>("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<string>("Account")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("DateAdded")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
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<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
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Source/DataLayer/Migrations/20220624214932_AddAudioFormat.cs
Normal file
26
Source/DataLayer/Migrations/20220624214932_AddAudioFormat.cs
Normal file
@@ -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<long>(
|
||||
name: "_audioFormat",
|
||||
table: "Books",
|
||||
type: "INTEGER",
|
||||
nullable: false,
|
||||
defaultValue: 0L);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "_audioFormat",
|
||||
table: "Books");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<string>("Title")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<long>("_audioFormat")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.HasKey("BookId");
|
||||
|
||||
b.HasIndex("AudibleProductId");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,14 @@
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
|
||||
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace DtoImporterService
|
||||
protected ItemsImporterBase(LibationContext context) : base(context) { }
|
||||
|
||||
protected abstract IValidator Validator { get; }
|
||||
public sealed override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems)
|
||||
public sealed override IEnumerable<Exception> Validate(IEnumerable<ImportItem> importItems)
|
||||
=> Validator.Validate(importItems.Select(i => i.DtoItem));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -53,8 +53,17 @@ namespace FileLiberator
|
||||
OnNarratorsDiscovered(m4bBook.AppleTags.Narrator);
|
||||
OnCoverImageDiscovered(m4bBook.AppleTags.Cover);
|
||||
|
||||
var config = Configuration.Instance;
|
||||
var lameConfig = GetLameOptions(config);
|
||||
|
||||
//Finishing configuring lame encoder.
|
||||
AaxDecrypter.MpegUtil.ConfigureLameOptions(
|
||||
m4bBook,
|
||||
lameConfig,
|
||||
config.LameDownsampleMono,
|
||||
config.LameMatchSourceBR);
|
||||
|
||||
using var mp3File = File.OpenWrite(Path.GetTempFileName());
|
||||
var lameConfig = GetLameOptions(Configuration.Instance);
|
||||
var result = await m4bBook.ConvertToMp3Async(mp3File, lameConfig);
|
||||
m4bBook.InputStream.Close();
|
||||
mp3File.Close();
|
||||
|
||||
@@ -146,53 +146,128 @@ namespace FileLiberator
|
||||
var outputFormat = !encrypted || (config.AllowLibationFixup && config.DecryptToLossy) ?
|
||||
OutputFormat.Mp3 : OutputFormat.M4b;
|
||||
|
||||
long chapterStartMs = config.StripAudibleBrandAudio ?
|
||||
contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs : 0;
|
||||
|
||||
var dlOptions = new DownloadOptions
|
||||
(
|
||||
libraryBook,
|
||||
contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl,
|
||||
Resources.USER_AGENT
|
||||
)
|
||||
{
|
||||
AudibleKey = contentLic?.Voucher?.Key,
|
||||
AudibleIV = contentLic?.Voucher?.Iv,
|
||||
OutputFormat = outputFormat,
|
||||
TrimOutputToChapterLength = config.AllowLibationFixup && config.StripAudibleBrandAudio,
|
||||
RetainEncryptedFile = config.RetainAaxFile && encrypted,
|
||||
StripUnabridged = config.AllowLibationFixup && config.StripUnabridged,
|
||||
Downsample = config.AllowLibationFixup && config.LameDownsampleMono,
|
||||
MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate,
|
||||
CreateCueSheet = config.CreateCueSheet,
|
||||
LameConfig = GetLameOptions(config)
|
||||
};
|
||||
{
|
||||
AudibleKey = contentLic?.Voucher?.Key,
|
||||
AudibleIV = contentLic?.Voucher?.Iv,
|
||||
OutputFormat = outputFormat,
|
||||
TrimOutputToChapterLength = config.AllowLibationFixup && config.StripAudibleBrandAudio,
|
||||
RetainEncryptedFile = config.RetainAaxFile && encrypted,
|
||||
StripUnabridged = config.AllowLibationFixup && config.StripUnabridged,
|
||||
Downsample = config.AllowLibationFixup && config.LameDownsampleMono,
|
||||
MatchSourceBitrate = config.AllowLibationFixup && config.LameMatchSourceBR && config.LameTargetBitrate,
|
||||
CreateCueSheet = config.CreateCueSheet,
|
||||
LameConfig = GetLameOptions(config),
|
||||
ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(chapterStartMs)),
|
||||
FixupFile = config.AllowLibationFixup
|
||||
};
|
||||
|
||||
var chapters = flattenChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList();
|
||||
|
||||
if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3)
|
||||
if (config.MergeOpeningAndEndCredits)
|
||||
combineCredits(chapters);
|
||||
|
||||
for (int i = 0; i < chapters.Count; i++)
|
||||
{
|
||||
long startMs = dlOptions.TrimOutputToChapterLength ?
|
||||
contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs : 0;
|
||||
var chapter = chapters[i];
|
||||
long chapLenMs = chapter.LengthMs;
|
||||
|
||||
dlOptions.ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(startMs));
|
||||
if (i == 0)
|
||||
chapLenMs -= chapterStartMs;
|
||||
|
||||
for (int i = 0; i < chapters.Count; i++)
|
||||
{
|
||||
var chapter = chapters[i];
|
||||
long chapLenMs = chapter.LengthMs;
|
||||
if (config.StripAudibleBrandAudio && i == chapters.Count - 1)
|
||||
chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs;
|
||||
|
||||
if (i == 0)
|
||||
chapLenMs -= startMs;
|
||||
|
||||
if (config.StripAudibleBrandAudio && i == chapters.Count - 1)
|
||||
chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs;
|
||||
|
||||
dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs));
|
||||
}
|
||||
dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs));
|
||||
}
|
||||
|
||||
return dlOptions;
|
||||
}
|
||||
|
||||
public static List<AudibleApi.Common.Chapter> flattenChapters(IEnumerable<AudibleApi.Common.Chapter> chapters, string titleConcat = ": ")
|
||||
/*
|
||||
|
||||
Flatten Audible's new hierarchical chapters, combining children into parents.
|
||||
|
||||
Audible may deliver chapters like this:
|
||||
|
||||
00:00 - 00:10 Opening Credits
|
||||
00:10 - 00:12 Book 1
|
||||
00:12 - 00:14 | Part 1
|
||||
00:14 - 01:40 | | Chapter 1
|
||||
01:40 - 03:20 | | Chapter 2
|
||||
03:20 - 03:22 | Part 2
|
||||
03:22 - 05:00 | | Chapter 3
|
||||
05:00 - 06:40 | | Chapter 4
|
||||
06:40 - 06:42 Book 2
|
||||
06:42 - 06:44 | Part 3
|
||||
06:44 - 08:20 | | Chapter 5
|
||||
08:20 - 10:00 | | Chapter 6
|
||||
10:00 - 10:02 | Part 4
|
||||
10:02 - 11:40 | | Chapter 7
|
||||
11:40 - 13:20 | | Chapter 8
|
||||
13:20 - 13:30 End Credits
|
||||
|
||||
And flattenChapters will combine them into this:
|
||||
|
||||
00:00 - 00:10 Opening Credits
|
||||
00:10 - 01:40 Book 1: Part 1: Chapter 1
|
||||
01:40 - 03:20 Book 1: Part 1: Chapter 2
|
||||
03:20 - 05:00 Book 1: Part 2: Chapter 3
|
||||
05:00 - 06:40 Book 1: Part 2: Chapter 4
|
||||
06:40 - 08:20 Book 2: Part 3: Chapter 5
|
||||
08:20 - 10:00 Book 2: Part 3: Chapter 6
|
||||
10:00 - 11:40 Book 2: Part 4: Chapter 7
|
||||
11:40 - 13:20 Book 2: Part 4: Chapter 8
|
||||
13:20 - 13:40 End Credits
|
||||
|
||||
However, if one of the parent chapters is longer than 10000 milliseconds, it's kept as its own
|
||||
chapter. A duration longer than a few seconds implies that the chapter contains more than just
|
||||
the narrator saying the chapter title, so it should probably be preserved as a separate chapter.
|
||||
Using the example above, if "Book 1" was 15 seconds long and "Part 3" was 20 seconds long:
|
||||
|
||||
00:00 - 00:10 Opening Credits
|
||||
00:10 - 00:25 Book 1
|
||||
00:25 - 00:27 | Part 1
|
||||
00:27 - 01:40 | | Chapter 1
|
||||
01:40 - 03:20 | | Chapter 2
|
||||
03:20 - 03:22 | Part 2
|
||||
03:22 - 05:00 | | Chapter 3
|
||||
05:00 - 06:40 | | Chapter 4
|
||||
06:40 - 06:42 Book 2
|
||||
06:42 - 07:02 | Part 3
|
||||
07:02 - 08:20 | | Chapter 5
|
||||
08:20 - 10:00 | | Chapter 6
|
||||
10:00 - 10:02 | Part 4
|
||||
10:02 - 11:40 | | Chapter 7
|
||||
11:40 - 13:20 | | Chapter 8
|
||||
13:20 - 13:30 End Credits
|
||||
|
||||
then flattenChapters will combine them into this:
|
||||
|
||||
00:00 - 00:10 Opening Credits
|
||||
00:10 - 00:25 Book 1
|
||||
00:25 - 01:40 Book 1: Part 1: Chapter 1
|
||||
01:40 - 03:20 Book 1: Part 1: Chapter 2
|
||||
03:20 - 05:00 Book 1: Part 2: Chapter 3
|
||||
05:00 - 06:40 Book 1: Part 2: Chapter 4
|
||||
06:40 - 07:02 Book 2: Part 3
|
||||
07:02 - 08:20 Book 2: Part 3: Chapter 5
|
||||
08:20 - 10:00 Book 2: Part 3: Chapter 6
|
||||
10:00 - 11:40 Book 2: Part 4: Chapter 7
|
||||
11:40 - 13:20 Book 2: Part 4: Chapter 8
|
||||
13:20 - 13:40 End Credits
|
||||
|
||||
*/
|
||||
|
||||
public static List<AudibleApi.Common.Chapter> flattenChapters(IList<AudibleApi.Common.Chapter> chapters, string titleConcat = ": ")
|
||||
{
|
||||
List<AudibleApi.Common.Chapter> chaps = new();
|
||||
|
||||
@@ -200,9 +275,14 @@ namespace FileLiberator
|
||||
{
|
||||
if (c.Chapters is not null)
|
||||
{
|
||||
c.Chapters[0].StartOffsetMs = c.StartOffsetMs;
|
||||
c.Chapters[0].StartOffsetSec = c.StartOffsetSec;
|
||||
c.Chapters[0].LengthMs += c.LengthMs;
|
||||
if (c.LengthMs < 10000)
|
||||
{
|
||||
c.Chapters[0].StartOffsetMs = c.StartOffsetMs;
|
||||
c.Chapters[0].StartOffsetSec = c.StartOffsetSec;
|
||||
c.Chapters[0].LengthMs += c.LengthMs;
|
||||
}
|
||||
else
|
||||
chaps.Add(c);
|
||||
|
||||
var children = flattenChapters(c.Chapters);
|
||||
|
||||
@@ -210,6 +290,7 @@ namespace FileLiberator
|
||||
child.Title = $"{c.Title}{titleConcat}{child.Title}";
|
||||
|
||||
chaps.AddRange(children);
|
||||
c.Chapters = null;
|
||||
}
|
||||
else
|
||||
chaps.Add(c);
|
||||
@@ -217,6 +298,22 @@ namespace FileLiberator
|
||||
return chaps;
|
||||
}
|
||||
|
||||
public static void combineCredits(IList<AudibleApi.Common.Chapter> 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)
|
||||
|
||||
@@ -20,10 +20,11 @@ namespace FileLiberator
|
||||
public bool RetainEncryptedFile { get; init; }
|
||||
public bool StripUnabridged { get; init; }
|
||||
public bool CreateCueSheet { get; init; }
|
||||
public ChapterInfo ChapterInfo { get; set; }
|
||||
public NAudio.Lame.LameConfig LameConfig { get; set; }
|
||||
public bool Downsample { get; set; }
|
||||
public bool MatchSourceBitrate { get; set; }
|
||||
public ChapterInfo ChapterInfo { get; init; }
|
||||
public bool FixupFile { get; init; }
|
||||
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
||||
public bool Downsample { get; init; }
|
||||
public bool MatchSourceBitrate { get; init; }
|
||||
public ReplacementCharacters ReplacementCharacters => Configuration.Instance.ReplacementCharacters;
|
||||
|
||||
public string GetMultipartFileName(MultiConvertFileProperties props)
|
||||
|
||||
@@ -11,4 +11,13 @@
|
||||
<ProjectReference Include="..\AudibleUtilities\AudibleUtilities.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
@@ -9,4 +9,12 @@
|
||||
<PackageReference Include="Polly" Version="7.2.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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<string,string> paramReplacements)
|
||||
private string replaceFileName(string filename, Dictionary<string,string> paramReplacements, int maxFilenameLength)
|
||||
{
|
||||
List<StringBuilder> 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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,13 +6,22 @@
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>hangover.ico</ApplicationIcon>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
|
||||
- Not using SatelliteResourceLanguages will load all language packs: works
|
||||
- Specifying 'en' semicolon 1 more should load 1 language pack: works
|
||||
- Specifying only 'en' should load no language packs: broken, still loads all
|
||||
-->
|
||||
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
When LibationWinForms and Hangover output to the same dir, Hangover must build before LibationWinForms
|
||||
|
||||
@@ -24,11 +33,13 @@
|
||||
edit debug and release output paths
|
||||
-->
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\LibationWinForms\bin\Debug</OutputPath>
|
||||
<OutputPath>..\bin\Debug</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<OutputPath>..\LibationWinForms\bin\Release</OutputPath>
|
||||
<OutputPath>..\bin\Release</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -37,7 +48,7 @@
|
||||
<ProjectReference Include="..\FileLiberator\FileLiberator.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Form1.*.cs">
|
||||
<DependentUpon>Form1.cs</DependentUpon>
|
||||
</Compile>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -4,12 +4,21 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||
<IsPublishable>True</IsPublishable>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<!--
|
||||
HACK FOR COMPILER BUG 2021-09-14. Hopefully will be fixed in future versions
|
||||
- Not using SatelliteResourceLanguages will load all language packs: works
|
||||
- Specifying 'en' semicolon 1 more should load 1 language pack: works
|
||||
- Specifying only 'en' should load no language packs: broken, still loads all
|
||||
-->
|
||||
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<!--
|
||||
@@ -23,11 +32,13 @@
|
||||
edit debug and release output paths
|
||||
-->
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\LibationWinForms\bin\Debug</OutputPath>
|
||||
<OutputPath>..\bin\Debug</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<OutputPath>..\LibationWinForms\bin\Release</OutputPath>
|
||||
<OutputPath>..\bin\Release</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>..\bin\publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -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<bool>(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")]
|
||||
|
||||
@@ -14,4 +14,12 @@
|
||||
<ProjectReference Include="..\FileManager\FileManager.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -15,5 +15,14 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
@@ -41,6 +43,7 @@ namespace LibationWinForms.Dialogs
|
||||
LameMatchSourceBRCbox_CheckedChanged(this, EventArgs.Empty);
|
||||
convertFormatRb_CheckedChanged(this, EventArgs.Empty);
|
||||
allowLibationFixupCbox_CheckedChanged(this, EventArgs.Empty);
|
||||
splitFilesByChapterCbox_CheckedChanged(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void Save_AudioSettings(Configuration config)
|
||||
@@ -50,6 +53,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;
|
||||
@@ -89,6 +93,7 @@ namespace LibationWinForms.Dialogs
|
||||
}
|
||||
private void allowLibationFixupCbox_CheckedChanged(object sender, EventArgs e)
|
||||
{
|
||||
audiobookFixupsGb.Enabled = allowLibationFixupCbox.Checked;
|
||||
convertLosslessRb.Enabled = allowLibationFixupCbox.Checked;
|
||||
convertLossyRb.Enabled = allowLibationFixupCbox.Checked;
|
||||
splitFilesByChapterCbox.Enabled = allowLibationFixupCbox.Checked;
|
||||
|
||||
@@ -105,9 +105,11 @@
|
||||
this.lameTargetQualityRb = new System.Windows.Forms.RadioButton();
|
||||
this.lameTargetBitrateRb = new System.Windows.Forms.RadioButton();
|
||||
this.stripUnabridgedCbox = new System.Windows.Forms.CheckBox();
|
||||
this.mergeOpeningEndCreditsCbox = new System.Windows.Forms.CheckBox();
|
||||
this.retainAaxFileCbox = new System.Windows.Forms.CheckBox();
|
||||
this.downloadCoverArtCbox = new System.Windows.Forms.CheckBox();
|
||||
this.createCueSheetCbox = new System.Windows.Forms.CheckBox();
|
||||
this.audiobookFixupsGb = new System.Windows.Forms.GroupBox();
|
||||
this.badBookGb.SuspendLayout();
|
||||
this.tabControl.SuspendLayout();
|
||||
this.tab1ImportantSettings.SuspendLayout();
|
||||
@@ -124,6 +126,7 @@
|
||||
this.lameQualityGb.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.lameVBRQualityTb)).BeginInit();
|
||||
this.groupBox2.SuspendLayout();
|
||||
this.audiobookFixupsGb.SuspendLayout();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// booksLocationDescLbl
|
||||
@@ -251,7 +254,7 @@
|
||||
// stripAudibleBrandingCbox
|
||||
//
|
||||
this.stripAudibleBrandingCbox.AutoSize = true;
|
||||
this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(19, 168);
|
||||
this.stripAudibleBrandingCbox.Location = new System.Drawing.Point(13, 72);
|
||||
this.stripAudibleBrandingCbox.Name = "stripAudibleBrandingCbox";
|
||||
this.stripAudibleBrandingCbox.Size = new System.Drawing.Size(143, 34);
|
||||
this.stripAudibleBrandingCbox.TabIndex = 13;
|
||||
@@ -261,7 +264,7 @@
|
||||
// splitFilesByChapterCbox
|
||||
//
|
||||
this.splitFilesByChapterCbox.AutoSize = true;
|
||||
this.splitFilesByChapterCbox.Location = new System.Drawing.Point(19, 118);
|
||||
this.splitFilesByChapterCbox.Location = new System.Drawing.Point(13, 22);
|
||||
this.splitFilesByChapterCbox.Name = "splitFilesByChapterCbox";
|
||||
this.splitFilesByChapterCbox.Size = new System.Drawing.Size(162, 19);
|
||||
this.splitFilesByChapterCbox.TabIndex = 13;
|
||||
@@ -274,7 +277,7 @@
|
||||
this.allowLibationFixupCbox.AutoSize = true;
|
||||
this.allowLibationFixupCbox.Checked = true;
|
||||
this.allowLibationFixupCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.allowLibationFixupCbox.Location = new System.Drawing.Point(19, 18);
|
||||
this.allowLibationFixupCbox.Location = new System.Drawing.Point(19, 118);
|
||||
this.allowLibationFixupCbox.Name = "allowLibationFixupCbox";
|
||||
this.allowLibationFixupCbox.Size = new System.Drawing.Size(163, 19);
|
||||
this.allowLibationFixupCbox.TabIndex = 10;
|
||||
@@ -285,7 +288,7 @@
|
||||
// convertLossyRb
|
||||
//
|
||||
this.convertLossyRb.AutoSize = true;
|
||||
this.convertLossyRb.Location = new System.Drawing.Point(19, 232);
|
||||
this.convertLossyRb.Location = new System.Drawing.Point(13, 136);
|
||||
this.convertLossyRb.Name = "convertLossyRb";
|
||||
this.convertLossyRb.Size = new System.Drawing.Size(329, 19);
|
||||
this.convertLossyRb.TabIndex = 12;
|
||||
@@ -297,7 +300,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(13, 111);
|
||||
this.convertLosslessRb.Name = "convertLosslessRb";
|
||||
this.convertLosslessRb.Size = new System.Drawing.Size(335, 19);
|
||||
this.convertLosslessRb.TabIndex = 11;
|
||||
@@ -602,13 +605,10 @@
|
||||
//
|
||||
// tab4AudioFileOptions
|
||||
//
|
||||
this.tab4AudioFileOptions.Controls.Add(this.audiobookFixupsGb);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.chapterTitleTemplateGb);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.lameOptionsGb);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.convertLossyRb);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.stripAudibleBrandingCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.convertLosslessRb);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.stripUnabridgedCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.splitFilesByChapterCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.mergeOpeningEndCreditsCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.retainAaxFileCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.downloadCoverArtCbox);
|
||||
this.tab4AudioFileOptions.Controls.Add(this.createCueSheetCbox);
|
||||
@@ -980,17 +980,27 @@
|
||||
// stripUnabridgedCbox
|
||||
//
|
||||
this.stripUnabridgedCbox.AutoSize = true;
|
||||
this.stripUnabridgedCbox.Location = new System.Drawing.Point(19, 143);
|
||||
this.stripUnabridgedCbox.Location = new System.Drawing.Point(13, 47);
|
||||
this.stripUnabridgedCbox.Name = "stripUnabridgedCbox";
|
||||
this.stripUnabridgedCbox.Size = new System.Drawing.Size(147, 19);
|
||||
this.stripUnabridgedCbox.TabIndex = 13;
|
||||
this.stripUnabridgedCbox.Text = "[StripUnabridged desc]";
|
||||
this.stripUnabridgedCbox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// mergeOpeningEndCreditsCbox
|
||||
//
|
||||
this.mergeOpeningEndCreditsCbox.AutoSize = true;
|
||||
this.mergeOpeningEndCreditsCbox.Location = new System.Drawing.Point(19, 93);
|
||||
this.mergeOpeningEndCreditsCbox.Name = "mergeOpeningEndCreditsCbox";
|
||||
this.mergeOpeningEndCreditsCbox.Size = new System.Drawing.Size(198, 19);
|
||||
this.mergeOpeningEndCreditsCbox.TabIndex = 13;
|
||||
this.mergeOpeningEndCreditsCbox.Text = "[MergeOpeningEndCredits desc]";
|
||||
this.mergeOpeningEndCreditsCbox.UseVisualStyleBackColor = true;
|
||||
//
|
||||
// retainAaxFileCbox
|
||||
//
|
||||
this.retainAaxFileCbox.AutoSize = true;
|
||||
this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 93);
|
||||
this.retainAaxFileCbox.Location = new System.Drawing.Point(19, 68);
|
||||
this.retainAaxFileCbox.Name = "retainAaxFileCbox";
|
||||
this.retainAaxFileCbox.Size = new System.Drawing.Size(132, 19);
|
||||
this.retainAaxFileCbox.TabIndex = 10;
|
||||
@@ -1003,7 +1013,7 @@
|
||||
this.downloadCoverArtCbox.AutoSize = true;
|
||||
this.downloadCoverArtCbox.Checked = true;
|
||||
this.downloadCoverArtCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.downloadCoverArtCbox.Location = new System.Drawing.Point(19, 68);
|
||||
this.downloadCoverArtCbox.Location = new System.Drawing.Point(19, 43);
|
||||
this.downloadCoverArtCbox.Name = "downloadCoverArtCbox";
|
||||
this.downloadCoverArtCbox.Size = new System.Drawing.Size(162, 19);
|
||||
this.downloadCoverArtCbox.TabIndex = 10;
|
||||
@@ -1016,7 +1026,7 @@
|
||||
this.createCueSheetCbox.AutoSize = true;
|
||||
this.createCueSheetCbox.Checked = true;
|
||||
this.createCueSheetCbox.CheckState = System.Windows.Forms.CheckState.Checked;
|
||||
this.createCueSheetCbox.Location = new System.Drawing.Point(19, 43);
|
||||
this.createCueSheetCbox.Location = new System.Drawing.Point(19, 18);
|
||||
this.createCueSheetCbox.Name = "createCueSheetCbox";
|
||||
this.createCueSheetCbox.Size = new System.Drawing.Size(145, 19);
|
||||
this.createCueSheetCbox.TabIndex = 10;
|
||||
@@ -1024,6 +1034,20 @@
|
||||
this.createCueSheetCbox.UseVisualStyleBackColor = true;
|
||||
this.createCueSheetCbox.CheckedChanged += new System.EventHandler(this.allowLibationFixupCbox_CheckedChanged);
|
||||
//
|
||||
// audiobookFixupsGb
|
||||
//
|
||||
this.audiobookFixupsGb.Controls.Add(this.splitFilesByChapterCbox);
|
||||
this.audiobookFixupsGb.Controls.Add(this.stripUnabridgedCbox);
|
||||
this.audiobookFixupsGb.Controls.Add(this.convertLosslessRb);
|
||||
this.audiobookFixupsGb.Controls.Add(this.convertLossyRb);
|
||||
this.audiobookFixupsGb.Controls.Add(this.stripAudibleBrandingCbox);
|
||||
this.audiobookFixupsGb.Location = new System.Drawing.Point(6, 143);
|
||||
this.audiobookFixupsGb.Name = "audiobookFixupsGb";
|
||||
this.audiobookFixupsGb.Size = new System.Drawing.Size(403, 160);
|
||||
this.audiobookFixupsGb.TabIndex = 19;
|
||||
this.audiobookFixupsGb.TabStop = false;
|
||||
this.audiobookFixupsGb.Text = "Audiobook Fix-ups";
|
||||
//
|
||||
// SettingsDialog
|
||||
//
|
||||
this.AcceptButton = this.saveBtn;
|
||||
@@ -1070,6 +1094,8 @@
|
||||
((System.ComponentModel.ISupportInitialize)(this.lameVBRQualityTb)).EndInit();
|
||||
this.groupBox2.ResumeLayout(false);
|
||||
this.groupBox2.PerformLayout();
|
||||
this.audiobookFixupsGb.ResumeLayout(false);
|
||||
this.audiobookFixupsGb.PerformLayout();
|
||||
this.ResumeLayout(false);
|
||||
|
||||
}
|
||||
@@ -1155,5 +1181,7 @@
|
||||
private System.Windows.Forms.Button chapterTitleTemplateBtn;
|
||||
private System.Windows.Forms.TextBox chapterTitleTemplateTb;
|
||||
private System.Windows.Forms.Button editCharreplacementBtn;
|
||||
private System.Windows.Forms.CheckBox mergeOpeningEndCreditsCbox;
|
||||
private System.Windows.Forms.GroupBox audiobookFixupsGb;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<ApplicationIcon>libation.ico</ApplicationIcon>
|
||||
<AssemblyName>Libation</AssemblyName>
|
||||
|
||||
|
||||
<PublishReadyToRun>true</PublishReadyToRun>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
@@ -27,6 +27,16 @@
|
||||
<SatelliteResourceLanguages>en;es</SatelliteResourceLanguages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<OutputPath>..\bin\Debug</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<OutputPath>..\bin\Release</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.7.3" />
|
||||
<PackageReference Include="Dinah.Core.WindowsDesktop" Version="4.2.3.1" />
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
<PublishDir>..\bin\publish\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
<SelfContained>false</SelfContained>
|
||||
<PublishSingleFile>false</PublishSingleFile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -14,8 +14,277 @@ 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,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
private static Chapter[] HierarchicalChapters_LongerParents => new Chapter[]
|
||||
{
|
||||
new ()
|
||||
{
|
||||
Title = "Opening Credits",
|
||||
StartOffsetMs = 0,
|
||||
StartOffsetSec = 0,
|
||||
LengthMs = 10000,
|
||||
},
|
||||
new ()
|
||||
{
|
||||
Title = "Book 1",
|
||||
StartOffsetMs = 10000,
|
||||
StartOffsetSec = 10,
|
||||
LengthMs = 15000,
|
||||
Chapters = new Chapter[]
|
||||
{
|
||||
new ()
|
||||
{
|
||||
Title = "Part 1",
|
||||
StartOffsetMs = 25000,
|
||||
StartOffsetSec = 25,
|
||||
LengthMs = 2000,
|
||||
Chapters = new Chapter[]
|
||||
{
|
||||
new ()
|
||||
{
|
||||
Title = "Chapter 1",
|
||||
StartOffsetMs = 27000,
|
||||
StartOffsetSec = 27,
|
||||
LengthMs = 73000,
|
||||
},
|
||||
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 = 20000,
|
||||
Chapters = new Chapter[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = "Chapter 5",
|
||||
StartOffsetMs = 422000,
|
||||
StartOffsetSec = 422,
|
||||
LengthMs = 78000,
|
||||
},
|
||||
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 +342,205 @@ 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);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HierarchicalChapters_LongerParents_Flatten()
|
||||
{
|
||||
var expected = new Chapter[]
|
||||
{
|
||||
new()
|
||||
{
|
||||
Title = "Opening Credits",
|
||||
StartOffsetMs = 0,
|
||||
StartOffsetSec = 0,
|
||||
LengthMs = 10000,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Title = "Book 1",
|
||||
StartOffsetMs = 10000,
|
||||
StartOffsetSec = 10,
|
||||
LengthMs = 15000,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Title = "Book 1: Part 1: Chapter 1",
|
||||
StartOffsetMs = 25000,
|
||||
StartOffsetSec = 25,
|
||||
LengthMs = 75000,
|
||||
},
|
||||
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",
|
||||
StartOffsetMs = 400000,
|
||||
StartOffsetSec = 400,
|
||||
LengthMs = 22000,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Title = "Book 2: Part 3: Chapter 5",
|
||||
StartOffsetMs = 422000,
|
||||
StartOffsetSec = 422,
|
||||
LengthMs = 78000,
|
||||
},
|
||||
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_LongerParents);
|
||||
|
||||
checkChapters(flatChapters, expected);
|
||||
}
|
||||
|
||||
private static void checkChapters(IList<Chapter> value, IList<Chapter> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0-windows</TargetFramework>
|
||||
|
||||
<IsPackable>false</IsPackable>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
16
Source/publish.ps1
Normal file
16
Source/publish.ps1
Normal file
@@ -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 '<Version>(\d{0,3}\.\d{0,3}\.\d{0,3})\.\d{0,3}</Version>'
|
||||
$archiveName = "bin\Libation."+$verMatch.Matches.Groups[1].Value+".zip"
|
||||
Get-ChildItem -Path $pubDir -Recurse |
|
||||
Compress-Archive -DestinationPath $archiveName -Force
|
||||
Reference in New Issue
Block a user