mirror of
https://github.com/rmcrackan/Libation.git
synced 2026-01-03 11:28:30 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ce4faaf29 | ||
|
|
bfd494cf93 | ||
|
|
dc7ec3b328 | ||
|
|
8f2827108b | ||
|
|
fdcaf5e534 | ||
|
|
732695c019 | ||
|
|
2a2faf6f7b | ||
|
|
c653e17c3d | ||
|
|
833bc3a8f2 | ||
|
|
11e63ae5a2 | ||
|
|
827eaefd29 | ||
|
|
8240a97f6d | ||
|
|
b766e43656 | ||
|
|
7d805728cb | ||
|
|
c3c8a6fa6b | ||
|
|
f40df002a2 | ||
|
|
3180ea993c | ||
|
|
9f2fd54018 | ||
|
|
07532f7e65 |
72
.gitignore
vendored
72
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
@@ -12,6 +13,9 @@
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
@@ -19,10 +23,15 @@
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
bld/
|
||||
[Bb]in/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
@@ -36,9 +45,10 @@ Generated\ Files/
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
|
||||
# NUNIT
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
@@ -52,7 +62,9 @@ BenchmarkDotNet.Artifacts/
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
**/Properties/launchSettings.json
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
@@ -60,7 +72,7 @@ StyleCopReport.xml
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_i.h
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
@@ -77,6 +89,7 @@ StyleCopReport.xml
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
@@ -119,9 +132,6 @@ _ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# JustCode is a .NET coding add-in
|
||||
.JustCode
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
@@ -132,6 +142,11 @@ _TeamCity*
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
@@ -179,6 +194,8 @@ PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
@@ -203,12 +220,14 @@ BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!*.[Cc]ache/
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
@@ -221,7 +240,7 @@ ClientBin/
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
@@ -252,6 +271,9 @@ ServiceFabricBackup/
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
@@ -287,12 +309,8 @@ paket-files/
|
||||
# FAKE - F# Make
|
||||
.fake/
|
||||
|
||||
# JetBrains Rider
|
||||
.idea/
|
||||
*.sln.iml
|
||||
|
||||
# CodeRush
|
||||
.cr/
|
||||
# CodeRush personal settings
|
||||
.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
__pycache__/
|
||||
@@ -317,7 +335,7 @@ __pycache__/
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
@@ -326,11 +344,29 @@ ASALocalRun/
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
.localhistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
|
||||
### manually ignored files
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
# manually ignored files
|
||||
/__TODO.txt
|
||||
/DataLayer/LibationContext.db
|
||||
*.lnk
|
||||
|
||||
@@ -5,41 +5,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\AAXClean\AAXClean.csproj" />
|
||||
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core\Dinah.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="DecryptLib\avcodec-58.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DecryptLib\avdevice-58.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DecryptLib\avfilter-7.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DecryptLib\avformat-58.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DecryptLib\avutil-56.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DecryptLib\ffmpeg.exe">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DecryptLib\ffprobe.exe">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DecryptLib\swresample-3.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="DecryptLib\swscale-5.dll">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Dinah.Core;
|
||||
using AAXClean;
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Diagnostics;
|
||||
using Dinah.Core.IO;
|
||||
using Dinah.Core.StepRunner;
|
||||
@@ -9,7 +10,7 @@ namespace AaxDecrypter
|
||||
{
|
||||
public interface ISimpleAaxcToM4bConverter
|
||||
{
|
||||
event EventHandler<AaxcTagLibFile> RetrievedTags;
|
||||
event EventHandler<AppleTags> RetrievedTags;
|
||||
event EventHandler<byte[]> RetrievedCoverArt;
|
||||
event EventHandler<TimeSpan> DecryptTimeRemaining;
|
||||
event EventHandler<int> DecryptProgressUpdate;
|
||||
@@ -18,7 +19,7 @@ namespace AaxDecrypter
|
||||
string outDir { get; }
|
||||
string outputFileName { get; }
|
||||
DownloadLicense downloadLicense { get; }
|
||||
AaxcTagLibFile aaxcTagLib { get; }
|
||||
AaxFile aaxFile { get; }
|
||||
byte[] coverArt { get; }
|
||||
void SetCoverArt(byte[] coverArt);
|
||||
void SetOutputFilename(string outFileName);
|
||||
@@ -29,14 +30,13 @@ namespace AaxDecrypter
|
||||
bool Step1_CreateDir();
|
||||
bool Step2_GetMetadata();
|
||||
bool Step3_DownloadAndCombine();
|
||||
bool Step4_RestoreMetadata();
|
||||
bool Step5_CreateCue();
|
||||
bool Step6_CreateNfo();
|
||||
bool Step7_Cleanup();
|
||||
bool Step4_CreateCue();
|
||||
bool Step5_CreateNfo();
|
||||
bool Step6_Cleanup();
|
||||
}
|
||||
public class AaxcDownloadConverter : IAdvancedAaxcToM4bConverter
|
||||
{
|
||||
public event EventHandler<AaxcTagLibFile> RetrievedTags;
|
||||
public event EventHandler<AppleTags> RetrievedTags;
|
||||
public event EventHandler<byte[]> RetrievedCoverArt;
|
||||
public event EventHandler<int> DecryptProgressUpdate;
|
||||
public event EventHandler<TimeSpan> DecryptTimeRemaining;
|
||||
@@ -45,11 +45,11 @@ namespace AaxDecrypter
|
||||
public string cacheDir { get; private set; }
|
||||
public string outputFileName { get; private set; }
|
||||
public DownloadLicense downloadLicense { get; private set; }
|
||||
public AaxcTagLibFile aaxcTagLib { get; private set; }
|
||||
public AaxFile aaxFile { get; private set; }
|
||||
public byte[] coverArt { get; private set; }
|
||||
|
||||
private StepSequence steps { get; }
|
||||
private FFMpegAaxcProcesser aaxcProcesser;
|
||||
private NetworkFileStreamPersister nfsPersister;
|
||||
private bool isCanceled { get; set; }
|
||||
private string jsonDownloadState => Path.Combine(cacheDir, Path.GetFileNameWithoutExtension(outputFileName) + ".json");
|
||||
private string tempFile => PathLib.ReplaceExtension(jsonDownloadState, ".aaxc");
|
||||
@@ -81,15 +81,11 @@ namespace AaxDecrypter
|
||||
["Step 1: Create Dir"] = Step1_CreateDir,
|
||||
["Step 2: Get Aaxc Metadata"] = Step2_GetMetadata,
|
||||
["Step 3: Download Decrypted Audiobook"] = Step3_DownloadAndCombine,
|
||||
["Step 4: Restore Aaxc Metadata"] = Step4_RestoreMetadata,
|
||||
["Step 5: Create Cue"] = Step5_CreateCue,
|
||||
["Step 6: Create Nfo"] = Step6_CreateNfo,
|
||||
["Step 7: Cleanup"] = Step7_Cleanup,
|
||||
["Step 4: Create Cue"] = Step4_CreateCue,
|
||||
["Step 5: Create Nfo"] = Step5_CreateNfo,
|
||||
["Step 6: Cleanup"] = Step6_Cleanup,
|
||||
};
|
||||
|
||||
aaxcProcesser = new FFMpegAaxcProcesser(dlLic);
|
||||
aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;
|
||||
|
||||
downloadLicense = dlLic;
|
||||
}
|
||||
|
||||
@@ -120,7 +116,7 @@ namespace AaxDecrypter
|
||||
return false;
|
||||
}
|
||||
|
||||
var speedup = (int)(aaxcTagLib.Properties.Duration.TotalSeconds / (long)Elapsed.TotalSeconds);
|
||||
var speedup = (int)(aaxFile.Duration.TotalSeconds / (long)Elapsed.TotalSeconds);
|
||||
Console.WriteLine("Speedup is " + speedup + "x realtime.");
|
||||
Console.WriteLine("Done");
|
||||
return true;
|
||||
@@ -137,126 +133,90 @@ namespace AaxDecrypter
|
||||
public bool Step2_GetMetadata()
|
||||
{
|
||||
//Get metadata from the file over http
|
||||
|
||||
NetworkFileStreamPersister nfsPersister;
|
||||
|
||||
if (File.Exists(jsonDownloadState))
|
||||
{
|
||||
nfsPersister = new NetworkFileStreamPersister(jsonDownloadState);
|
||||
//If More thaan ~1 hour has elapsed since getting the download url, it will expire.
|
||||
//The new url will be to the same file.
|
||||
nfsPersister.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl));
|
||||
try
|
||||
{
|
||||
nfsPersister = new NetworkFileStreamPersister(jsonDownloadState);
|
||||
//If More thaan ~1 hour has elapsed since getting the download url, it will expire.
|
||||
//The new url will be to the same file.
|
||||
nfsPersister.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl));
|
||||
}
|
||||
catch
|
||||
{
|
||||
FileExt.SafeDelete(jsonDownloadState);
|
||||
FileExt.SafeDelete(tempFile);
|
||||
nfsPersister = NewNetworkFilePersister();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var headers = new System.Net.WebHeaderCollection();
|
||||
headers.Add("User-Agent", downloadLicense.UserAgent);
|
||||
|
||||
NetworkFileStream networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers);
|
||||
nfsPersister = new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
|
||||
nfsPersister = NewNetworkFilePersister();
|
||||
}
|
||||
nfsPersister.NetworkFileStream.BeginDownloading();
|
||||
|
||||
var networkFile = new NetworkFileAbstraction(nfsPersister.NetworkFileStream);
|
||||
aaxcTagLib = new AaxcTagLibFile(networkFile);
|
||||
nfsPersister.Dispose();
|
||||
aaxFile = new AaxFile(nfsPersister.NetworkFileStream);
|
||||
coverArt = aaxFile.AppleTags.Cover;
|
||||
|
||||
|
||||
if (coverArt is null && aaxcTagLib.AppleTags.Pictures.Length > 0)
|
||||
{
|
||||
coverArt = aaxcTagLib.AppleTags.Pictures[0].Data.Data;
|
||||
}
|
||||
|
||||
RetrievedTags?.Invoke(this, aaxcTagLib);
|
||||
RetrievedTags?.Invoke(this, aaxFile.AppleTags);
|
||||
RetrievedCoverArt?.Invoke(this, coverArt);
|
||||
|
||||
return !isCanceled;
|
||||
}
|
||||
private NetworkFileStreamPersister NewNetworkFilePersister()
|
||||
{
|
||||
var headers = new System.Net.WebHeaderCollection();
|
||||
headers.Add("User-Agent", downloadLicense.UserAgent);
|
||||
|
||||
NetworkFileStream networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers);
|
||||
return new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
|
||||
}
|
||||
|
||||
public bool Step3_DownloadAndCombine()
|
||||
{
|
||||
DecryptProgressUpdate?.Invoke(this, int.MaxValue);
|
||||
|
||||
NetworkFileStreamPersister nfsPersister;
|
||||
if (File.Exists(jsonDownloadState))
|
||||
{
|
||||
nfsPersister = new NetworkFileStreamPersister(jsonDownloadState);
|
||||
//If More thaan ~1 hour has elapsed since getting the download url, it will expire.
|
||||
//The new url will be to the same file.
|
||||
nfsPersister.NetworkFileStream.SetUriForSameFile(new Uri(downloadLicense.DownloadUrl));
|
||||
}
|
||||
else
|
||||
{
|
||||
var headers = new System.Net.WebHeaderCollection();
|
||||
headers.Add("User-Agent", downloadLicense.UserAgent);
|
||||
if (File.Exists(outputFileName))
|
||||
FileExt.SafeDelete(outputFileName);
|
||||
|
||||
NetworkFileStream networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers);
|
||||
nfsPersister = new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
|
||||
FileStream outFile = File.OpenWrite(outputFileName);
|
||||
|
||||
aaxFile.DecryptionProgressUpdate += AaxFile_DecryptionProgressUpdate;
|
||||
using var decryptedBook = aaxFile.DecryptAaxc(outFile, downloadLicense.AudibleKey, downloadLicense.AudibleIV, downloadLicense.ChapterInfo);
|
||||
aaxFile.DecryptionProgressUpdate -= AaxFile_DecryptionProgressUpdate;
|
||||
|
||||
downloadLicense.ChapterInfo = aaxFile.Chapters;
|
||||
|
||||
if (coverArt is not null)
|
||||
{
|
||||
decryptedBook?.AppleTags?.SetCoverArt(coverArt);
|
||||
decryptedBook?.Save();
|
||||
}
|
||||
|
||||
string metadataPath = Path.Combine(outDir, Path.GetFileName(outputFileName) + ".ffmeta");
|
||||
|
||||
if (downloadLicense.ChapterInfo is null)
|
||||
{
|
||||
//If we want to keep the original chapters, we need to get them from the url.
|
||||
//Ffprobe needs to seek to find metadata and it can't seek a pipe. Also, there's
|
||||
//no guarantee that enough of the file will have been downloaded at this point
|
||||
//to be able to use the cache file.
|
||||
downloadLicense.ChapterInfo = new ChapterInfo(downloadLicense.DownloadUrl);
|
||||
}
|
||||
|
||||
//Only write chapters to the metadata file. All other aaxc metadata will be
|
||||
//wiped out but is restored in Step 3.
|
||||
File.WriteAllText(metadataPath, downloadLicense.ChapterInfo.ToFFMeta(true));
|
||||
|
||||
|
||||
aaxcProcesser.ProcessBook(
|
||||
nfsPersister.NetworkFileStream,
|
||||
outputFileName,
|
||||
metadataPath)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
|
||||
nfsPersister.NetworkFileStream.Close();
|
||||
decryptedBook?.Close();
|
||||
nfsPersister.Dispose();
|
||||
|
||||
FileExt.SafeDelete(metadataPath);
|
||||
|
||||
DecryptProgressUpdate?.Invoke(this, 0);
|
||||
|
||||
return aaxcProcesser.Succeeded && !isCanceled;
|
||||
return aaxFile is not null && !isCanceled;
|
||||
}
|
||||
|
||||
private void AaxcProcesser_ProgressUpdate(object sender, AaxcProcessUpdate e)
|
||||
private void AaxFile_DecryptionProgressUpdate(object sender, DecryptionProgressEventArgs e)
|
||||
{
|
||||
double remainingSecsToProcess = (aaxcTagLib.Properties.Duration - e.ProcessPosition).TotalSeconds;
|
||||
var duration = aaxFile.Duration;
|
||||
double remainingSecsToProcess = (duration - e.ProcessPosition).TotalSeconds;
|
||||
double estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed;
|
||||
|
||||
if (double.IsNormal(estTimeRemaining))
|
||||
DecryptTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining));
|
||||
|
||||
double progressPercent = 100 * e.ProcessPosition.TotalSeconds / aaxcTagLib.Properties.Duration.TotalSeconds;
|
||||
double progressPercent = 100 * e.ProcessPosition.TotalSeconds / duration.TotalSeconds;
|
||||
|
||||
DecryptProgressUpdate?.Invoke(this, (int)progressPercent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copy all aacx metadata to m4b file, including cover art.
|
||||
/// </summary>
|
||||
public bool Step4_RestoreMetadata()
|
||||
{
|
||||
var outFile = new AaxcTagLibFile(outputFileName);
|
||||
outFile.CopyTagsFrom(aaxcTagLib);
|
||||
|
||||
if (outFile.AppleTags.Pictures.Length == 0 && coverArt is not null)
|
||||
{
|
||||
outFile.AddPicture(coverArt);
|
||||
}
|
||||
|
||||
outFile.Save();
|
||||
|
||||
return !isCanceled;
|
||||
}
|
||||
|
||||
public bool Step5_CreateCue()
|
||||
public bool Step4_CreateCue()
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -264,25 +224,25 @@ namespace AaxDecrypter
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"{nameof(Step5_CreateCue)}. FAILED");
|
||||
Serilog.Log.Logger.Error(ex, $"{nameof(Step4_CreateCue)}. FAILED");
|
||||
}
|
||||
return !isCanceled;
|
||||
}
|
||||
|
||||
public bool Step6_CreateNfo()
|
||||
public bool Step5_CreateNfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxcTagLib, downloadLicense.ChapterInfo));
|
||||
File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxFile, downloadLicense.ChapterInfo));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Logger.Error(ex, $"{nameof(Step6_CreateNfo)}. FAILED");
|
||||
Serilog.Log.Logger.Error(ex, $"{nameof(Step5_CreateNfo)}. FAILED");
|
||||
}
|
||||
return !isCanceled;
|
||||
}
|
||||
|
||||
public bool Step7_Cleanup()
|
||||
public bool Step6_Cleanup()
|
||||
{
|
||||
FileExt.SafeDelete(jsonDownloadState);
|
||||
FileExt.SafeDelete(tempFile);
|
||||
@@ -292,7 +252,7 @@ namespace AaxDecrypter
|
||||
public void Cancel()
|
||||
{
|
||||
isCanceled = true;
|
||||
aaxcProcesser.Cancel();
|
||||
aaxFile?.Cancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Dinah.Core;
|
||||
using TagLib;
|
||||
using TagLib.Mpeg4;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
public class AaxcTagLibFile : TagLib.Mpeg4.File
|
||||
{
|
||||
// ©
|
||||
private const byte COPYRIGHT = 0xa9;
|
||||
|
||||
private static ReadOnlyByteVector narratorType { get; } = new ReadOnlyByteVector(COPYRIGHT, (byte)'n', (byte)'r', (byte)'t');
|
||||
private static ReadOnlyByteVector descriptionType { get; } = new ReadOnlyByteVector(COPYRIGHT, (byte)'d', (byte)'e', (byte)'s');
|
||||
private static ReadOnlyByteVector publisherType { get; } = new ReadOnlyByteVector(COPYRIGHT, (byte)'p', (byte)'u', (byte)'b');
|
||||
|
||||
public string AsciiTitleSansUnabridged => TitleSansUnabridged?.UnicodeToAscii();
|
||||
public string AsciiFirstAuthor => FirstAuthor?.UnicodeToAscii();
|
||||
public string AsciiNarrator => Narrator?.UnicodeToAscii();
|
||||
public string AsciiComment => Comment?.UnicodeToAscii();
|
||||
public string AsciiLongDescription => LongDescription?.UnicodeToAscii();
|
||||
|
||||
public AppleTag AppleTags => GetTag(TagTypes.Apple) as AppleTag;
|
||||
|
||||
public string Comment => AppleTags.Comment;
|
||||
public string[] Authors => AppleTags.Performers;
|
||||
public string FirstAuthor => Authors?.Length > 0 ? Authors[0] : default;
|
||||
public string TitleSansUnabridged => AppleTags.Title?.Replace(" (Unabridged)", "");
|
||||
|
||||
public string BookCopyright => _copyright is not null && _copyright.Length > 0 ? _copyright[0] : default;
|
||||
public string RecordingCopyright => _copyright is not null && _copyright.Length > 1 ? _copyright[1] : default;
|
||||
private string[] _copyright => AppleTags.Copyright?.Replace("©", string.Empty)?.Replace("(P)", string.Empty)?.Split(';');
|
||||
|
||||
public string Narrator => getAppleTagsText(narratorType);
|
||||
public string LongDescription => getAppleTagsText(descriptionType);
|
||||
public string ReleaseDate => getAppleTagsText("rldt");
|
||||
public string Publisher => getAppleTagsText(publisherType);
|
||||
private string getAppleTagsText(ByteVector byteVector)
|
||||
{
|
||||
string[] text = AppleTags.GetText(byteVector);
|
||||
return text.Length == 0 ? default : text[0];
|
||||
}
|
||||
|
||||
public AaxcTagLibFile(IFileAbstraction abstraction)
|
||||
: base(abstraction, ReadStyle.Average)
|
||||
{
|
||||
}
|
||||
|
||||
public AaxcTagLibFile(string path)
|
||||
: this(new LocalFileAbstraction(path))
|
||||
{
|
||||
}
|
||||
/// <summary>
|
||||
/// Copy all metadata fields in the source file, even those that TagLib doesn't
|
||||
/// recognize, to the output file.
|
||||
/// NOTE: Chapters aren't stored in MPEG-4 metadata. They are encoded as a Timed
|
||||
/// Text Stream (MPEG-4 Part 17), so taglib doesn't read or write them.
|
||||
/// </summary>
|
||||
/// <param name="sourceFile">File from which tags will be coppied.</param>
|
||||
public void CopyTagsFrom(AaxcTagLibFile sourceFile)
|
||||
{
|
||||
AppleTags.Clear();
|
||||
|
||||
foreach (var stag in sourceFile.AppleTags)
|
||||
{
|
||||
AppleTags.SetData(stag.BoxType, stag.Children.Cast<AppleDataBox>().ToArray());
|
||||
}
|
||||
}
|
||||
public void AddPicture(byte[] coverArt)
|
||||
{
|
||||
AppleTags.SetData("covr", coverArt, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
using Dinah.Core;
|
||||
using Dinah.Core.Diagnostics;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
public class ChapterInfo
|
||||
{
|
||||
private List<Chapter> _chapterList = new List<Chapter>();
|
||||
public IEnumerable<Chapter> Chapters => _chapterList.AsEnumerable();
|
||||
public int Count => _chapterList.Count;
|
||||
public ChapterInfo() { }
|
||||
public ChapterInfo(string audiobookFile)
|
||||
{
|
||||
var info = new ProcessStartInfo
|
||||
{
|
||||
FileName = DecryptSupportLibraries.ffprobePath,
|
||||
Arguments = "-loglevel panic -show_chapters -print_format json \"" + audiobookFile + "\""
|
||||
};
|
||||
|
||||
var jString = info.RunHidden().Output;
|
||||
var chapterJObject = JObject.Parse(jString);
|
||||
var chapters = chapterJObject["chapters"]
|
||||
.Select(c => new Chapter(
|
||||
c["tags"]?["title"]?.Value<string>(),
|
||||
c["start_time"].Value<double>(),
|
||||
c["end_time"].Value<double>()
|
||||
));
|
||||
|
||||
_chapterList.AddRange(chapters);
|
||||
}
|
||||
public void AddChapter(Chapter chapter)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNull(chapter, nameof(chapter));
|
||||
_chapterList.Add(chapter);
|
||||
}
|
||||
public string ToFFMeta(bool includeFFMetaHeader)
|
||||
{
|
||||
var ffmetaChapters = new StringBuilder();
|
||||
|
||||
if (includeFFMetaHeader)
|
||||
ffmetaChapters.AppendLine(";FFMETADATA1");
|
||||
|
||||
foreach (var c in Chapters)
|
||||
{
|
||||
ffmetaChapters.AppendLine(c.ToFFMeta());
|
||||
}
|
||||
return ffmetaChapters.ToString();
|
||||
}
|
||||
}
|
||||
public class Chapter
|
||||
{
|
||||
public string Title { get; }
|
||||
public TimeSpan StartOffset { get; }
|
||||
public TimeSpan EndOffset { get; }
|
||||
public Chapter(string title, long startOffsetMs, long lengthMs)
|
||||
{
|
||||
ArgumentValidator.EnsureNotNullOrEmpty(title, nameof(title));
|
||||
ArgumentValidator.EnsureGreaterThan(startOffsetMs, nameof(startOffsetMs), -1);
|
||||
|
||||
// do not validate lengthMs for '> 0'. It is valid to set sections this way. eg: 11-22-63 [B005UR3VFO] by Stephen King
|
||||
|
||||
Title = title;
|
||||
StartOffset = TimeSpan.FromMilliseconds(startOffsetMs);
|
||||
EndOffset = StartOffset + TimeSpan.FromMilliseconds(lengthMs);
|
||||
}
|
||||
public Chapter(string title, double startTimeSec, double endTimeSec)
|
||||
: this(title, (long)(startTimeSec * 1000), (long)((endTimeSec - startTimeSec) * 1000))
|
||||
{
|
||||
}
|
||||
|
||||
public string ToFFMeta()
|
||||
{
|
||||
return "[CHAPTER]\n" +
|
||||
"TIMEBASE=1/1000\n" +
|
||||
"START=" + StartOffset.TotalMilliseconds + "\n" +
|
||||
"END=" + EndOffset.TotalMilliseconds + "\n" +
|
||||
"title=" + Title;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using AAXClean;
|
||||
using Dinah.Core;
|
||||
|
||||
namespace AaxDecrypter
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,16 +0,0 @@
|
||||
using System.IO;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
public static class DecryptSupportLibraries
|
||||
{
|
||||
// OTHER EXTERNAL DEPENDENCIES
|
||||
// ffprobe has these pre-req.s as I'm using it:
|
||||
// avcodec-58.dll, avdevice-58.dll, avfilter-7.dll, avformat-58.dll, avutil-56.dll, swresample-3.dll, swscale-5.dll, taglib-sharp.dll
|
||||
|
||||
private static string appPath_ { get; } = Path.GetDirectoryName(Dinah.Core.Exe.FileLocationOnDisk);
|
||||
private static string decryptLib_ { get; } = Path.Combine(appPath_, "DecryptLib");
|
||||
public static string ffmpegPath { get; } = Path.Combine(decryptLib_, "ffmpeg.exe");
|
||||
public static string ffprobePath { get; } = Path.Combine(decryptLib_, "ffprobe.exe");
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Dinah.Core;
|
||||
using AAXClean;
|
||||
using Dinah.Core;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
class AaxcProcessUpdate
|
||||
{
|
||||
public AaxcProcessUpdate(TimeSpan position, double speed)
|
||||
{
|
||||
ProcessPosition = position;
|
||||
ProcessSpeed = speed;
|
||||
EventTime = DateTime.Now;
|
||||
}
|
||||
public TimeSpan ProcessPosition { get; }
|
||||
public double ProcessSpeed { get; }
|
||||
public DateTime EventTime { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Download audible aaxc, decrypt, and remux with chapters.
|
||||
/// </summary>
|
||||
class FFMpegAaxcProcesser
|
||||
{
|
||||
public event EventHandler<AaxcProcessUpdate> ProgressUpdate;
|
||||
public string FFMpegPath { get; }
|
||||
public DownloadLicense DownloadLicense { get; }
|
||||
public bool IsRunning { get; private set; }
|
||||
public bool Succeeded { get; private set; }
|
||||
public string FFMpegRemuxerStandardError => remuxerError.ToString();
|
||||
public string FFMpegDecrypterStandardError => decrypterError.ToString();
|
||||
|
||||
|
||||
private StringBuilder remuxerError { get; } = new StringBuilder();
|
||||
private StringBuilder decrypterError { get; } = new StringBuilder();
|
||||
private static Regex processedTimeRegex { get; } = new Regex("time=(\\d{2}):(\\d{2}):(\\d{2}).\\d{2}.*speed=\\s{0,1}([0-9]*[.]?[0-9]+)(?:e\\+([0-9]+)){0,1}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
||||
private Process decrypter;
|
||||
private Process remuxer;
|
||||
private Stream inputFile;
|
||||
private bool isCanceled = false;
|
||||
|
||||
public FFMpegAaxcProcesser(DownloadLicense downloadLicense)
|
||||
{
|
||||
FFMpegPath = DecryptSupportLibraries.ffmpegPath;
|
||||
DownloadLicense = downloadLicense;
|
||||
}
|
||||
|
||||
public async Task ProcessBook(Stream inputFile, string outputFile, string ffmetaChaptersPath)
|
||||
{
|
||||
this.inputFile = inputFile;
|
||||
|
||||
//This process gets the aaxc from the url and streams the decrypted
|
||||
//aac stream to standard output
|
||||
decrypter = new Process
|
||||
{
|
||||
StartInfo = getDownloaderStartInfo()
|
||||
};
|
||||
|
||||
//This process retreves an aac stream from standard input and muxes
|
||||
// it into an m4b along with the cover art and metadata.
|
||||
remuxer = new Process
|
||||
{
|
||||
StartInfo = getRemuxerStartInfo(outputFile, ffmetaChaptersPath)
|
||||
};
|
||||
|
||||
IsRunning = true;
|
||||
|
||||
decrypter.ErrorDataReceived += Downloader_ErrorDataReceived;
|
||||
decrypter.Start();
|
||||
decrypter.BeginErrorReadLine();
|
||||
|
||||
remuxer.ErrorDataReceived += Remuxer_ErrorDataReceived;
|
||||
remuxer.Start();
|
||||
remuxer.BeginErrorReadLine();
|
||||
|
||||
//Thic check needs to be placed after remuxer has started.
|
||||
if (isCanceled) return;
|
||||
|
||||
var decrypterInput = decrypter.StandardInput.BaseStream;
|
||||
var decrypterOutput = decrypter.StandardOutput.BaseStream;
|
||||
var remuxerInput = remuxer.StandardInput.BaseStream;
|
||||
|
||||
//Read inputFile into decrypter stdin in the background
|
||||
var t = new Thread(() => CopyStream(inputFile, decrypterInput, decrypter));
|
||||
t.Start();
|
||||
|
||||
//All the work done here. Copy download standard output into
|
||||
//remuxer standard input
|
||||
await Task.Run(() => CopyStream(decrypterOutput, remuxerInput, remuxer));
|
||||
|
||||
//If the remuxer exited due to failure, downloader will still have
|
||||
//data in the pipe. Force kill downloader to continue.
|
||||
if (remuxer.HasExited && !decrypter.HasExited)
|
||||
decrypter.Kill();
|
||||
|
||||
remuxer.WaitForExit();
|
||||
decrypter.WaitForExit();
|
||||
|
||||
IsRunning = false;
|
||||
Succeeded = decrypter.ExitCode == 0 && remuxer.ExitCode == 0;
|
||||
}
|
||||
|
||||
private void CopyStream(Stream inputStream, Stream outputStream, Process returnOnProcExit)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] buffer = new byte[32 * 1024];
|
||||
int lastRead;
|
||||
do
|
||||
{
|
||||
lastRead = inputStream.Read(buffer, 0, buffer.Length);
|
||||
outputStream.Write(buffer, 0, lastRead);
|
||||
} while (lastRead > 0 && !returnOnProcExit.HasExited);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
//There is no way to tell if the process closed the input stream
|
||||
//before trying to write to it. If it did close, throws IOException.
|
||||
isCanceled = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
outputStream.Close();
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
isCanceled = true;
|
||||
|
||||
if (IsRunning && !remuxer.HasExited)
|
||||
remuxer.Kill();
|
||||
if (IsRunning && !decrypter.HasExited)
|
||||
decrypter.Kill();
|
||||
inputFile?.Close();
|
||||
}
|
||||
private void Downloader_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Data))
|
||||
return;
|
||||
|
||||
decrypterError.AppendLine(e.Data);
|
||||
}
|
||||
|
||||
private void Remuxer_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||
{
|
||||
if (string.IsNullOrEmpty(e.Data))
|
||||
return;
|
||||
|
||||
remuxerError.AppendLine(e.Data);
|
||||
|
||||
if (processedTimeRegex.IsMatch(e.Data))
|
||||
{
|
||||
//get timestamp of of last processed audio stream position
|
||||
//and processing speed
|
||||
var match = processedTimeRegex.Match(e.Data);
|
||||
|
||||
int hours = int.Parse(match.Groups[1].Value);
|
||||
int minutes = int.Parse(match.Groups[2].Value);
|
||||
int seconds = int.Parse(match.Groups[3].Value);
|
||||
|
||||
var position = new TimeSpan(hours, minutes, seconds);
|
||||
|
||||
double speed = double.Parse(match.Groups[4].Value);
|
||||
int exp = match.Groups[5].Success ? int.Parse(match.Groups[5].Value) : 0;
|
||||
speed *= Math.Pow(10, exp);
|
||||
|
||||
ProgressUpdate?.Invoke(this, new AaxcProcessUpdate(position, speed));
|
||||
}
|
||||
|
||||
if (e.Data.Contains("aac bitstream error"))
|
||||
{
|
||||
//This happens if input is corrupt (should never happen) or if caller
|
||||
//supplied wrong key/iv
|
||||
var process = sender as Process;
|
||||
process.Kill();
|
||||
}
|
||||
}
|
||||
|
||||
private ProcessStartInfo getDownloaderStartInfo() =>
|
||||
new ProcessStartInfo
|
||||
{
|
||||
FileName = FFMpegPath,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = Path.GetDirectoryName(FFMpegPath),
|
||||
ArgumentList ={
|
||||
"-audible_key",
|
||||
DownloadLicense.AudibleKey,
|
||||
"-audible_iv",
|
||||
DownloadLicense.AudibleIV,
|
||||
"-f",
|
||||
"mp4",
|
||||
"-i",
|
||||
"pipe:",
|
||||
"-c:a", //audio codec
|
||||
"copy", //copy stream
|
||||
"-f", //force output format: adts
|
||||
"adts",
|
||||
"pipe:" //pipe output to stdout
|
||||
}
|
||||
};
|
||||
|
||||
private ProcessStartInfo getRemuxerStartInfo(string outputFile, string ffmetaChaptersPath = null)
|
||||
{
|
||||
var startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = FFMpegPath,
|
||||
RedirectStandardError = true,
|
||||
RedirectStandardInput = true,
|
||||
CreateNoWindow = true,
|
||||
WindowStyle = ProcessWindowStyle.Hidden,
|
||||
UseShellExecute = false,
|
||||
WorkingDirectory = Path.GetDirectoryName(FFMpegPath),
|
||||
};
|
||||
|
||||
startInfo.ArgumentList.Add("-thread_queue_size");
|
||||
startInfo.ArgumentList.Add("1024");
|
||||
startInfo.ArgumentList.Add("-f"); //force input format: aac
|
||||
startInfo.ArgumentList.Add("aac");
|
||||
startInfo.ArgumentList.Add("-i"); //read input from stdin
|
||||
startInfo.ArgumentList.Add("pipe:");
|
||||
|
||||
//copy metadata from supplied metadata file
|
||||
startInfo.ArgumentList.Add("-f");
|
||||
startInfo.ArgumentList.Add("ffmetadata");
|
||||
startInfo.ArgumentList.Add("-i");
|
||||
startInfo.ArgumentList.Add(ffmetaChaptersPath);
|
||||
|
||||
startInfo.ArgumentList.Add("-map"); //map file 0 (aac audio stream)
|
||||
startInfo.ArgumentList.Add("0");
|
||||
startInfo.ArgumentList.Add("-map_chapters"); //copy chapter data from file metadata file
|
||||
startInfo.ArgumentList.Add("1");
|
||||
startInfo.ArgumentList.Add("-c"); //copy all mapped streams
|
||||
startInfo.ArgumentList.Add("copy");
|
||||
startInfo.ArgumentList.Add("-f"); //force output format: mp4
|
||||
startInfo.ArgumentList.Add("mp4");
|
||||
startInfo.ArgumentList.Add("-movflags");
|
||||
startInfo.ArgumentList.Add("disable_chpl"); //Disable Nero chapters format
|
||||
startInfo.ArgumentList.Add(outputFile);
|
||||
startInfo.ArgumentList.Add("-y"); //overwrite existing
|
||||
|
||||
return startInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,29 @@
|
||||
|
||||
using AAXClean;
|
||||
using Dinah.Core;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
public static class NFO
|
||||
{
|
||||
public static string CreateContents(string ripper, AaxcTagLibFile aaxcTagLib, ChapterInfo chapters)
|
||||
public static string CreateContents(string ripper, AAXClean.Mp4File aaxcTagLib, ChapterInfo chapters)
|
||||
{
|
||||
var _hours = (int)aaxcTagLib.Properties.Duration.TotalHours;
|
||||
var _hours = (int)aaxcTagLib.Duration.TotalHours;
|
||||
var myDuration
|
||||
= (_hours > 0 ? _hours + " hours, " : string.Empty)
|
||||
+ aaxcTagLib.Properties.Duration.Minutes + " minutes, "
|
||||
+ aaxcTagLib.Properties.Duration.Seconds + " seconds";
|
||||
+ aaxcTagLib.Duration.Minutes + " minutes, "
|
||||
+ aaxcTagLib.Duration.Seconds + " seconds";
|
||||
|
||||
var nfoString
|
||||
= "General Information\r\n"
|
||||
+ "======================\r\n"
|
||||
+ $" Title: {aaxcTagLib.AsciiTitleSansUnabridged ?? "[unknown]"}\r\n"
|
||||
+ $" Author: {aaxcTagLib.AsciiFirstAuthor ?? "[unknown]"}\r\n"
|
||||
+ $" Read By: {aaxcTagLib.AsciiNarrator ?? "[unknown]"}\r\n"
|
||||
+ $" Release Date: {aaxcTagLib.ReleaseDate ?? "[unknown]"}\r\n"
|
||||
+ $" Book Copyright: {aaxcTagLib.BookCopyright ?? "[unknown]"}\r\n"
|
||||
+ $" Recording Copyright: {aaxcTagLib.RecordingCopyright ?? "[unknown]"}\r\n"
|
||||
+ $" Genre: {aaxcTagLib.AppleTags.FirstGenre ?? "[unknown]"}\r\n"
|
||||
+ $" Publisher: {aaxcTagLib.Publisher ?? "[unknown]"}\r\n"
|
||||
+ $" Title: {aaxcTagLib.AppleTags.TitleSansUnabridged?.UnicodeToAscii() ?? "[unknown]"}\r\n"
|
||||
+ $" Author: {aaxcTagLib.AppleTags.FirstAuthor?.UnicodeToAscii() ?? "[unknown]"}\r\n"
|
||||
+ $" Read By: {aaxcTagLib.AppleTags.Narrator?.UnicodeToAscii() ?? "[unknown]"}\r\n"
|
||||
+ $" Release Date: {aaxcTagLib.AppleTags.ReleaseDate ?? "[unknown]"}\r\n"
|
||||
+ $" Book Copyright: {aaxcTagLib.AppleTags.BookCopyright ?? "[unknown]"}\r\n"
|
||||
+ $" Recording Copyright: {aaxcTagLib.AppleTags.RecordingCopyright ?? "[unknown]"}\r\n"
|
||||
+ $" Genre: {aaxcTagLib.AppleTags.Generes ?? "[unknown]"}\r\n"
|
||||
+ $" Publisher: {aaxcTagLib.AppleTags.Publisher ?? "[unknown]"}\r\n"
|
||||
+ $" Duration: {myDuration}\r\n"
|
||||
+ $" Chapters: {chapters.Count}\r\n"
|
||||
+ "\r\n"
|
||||
@@ -29,22 +31,22 @@ namespace AaxDecrypter
|
||||
+ "Media Information\r\n"
|
||||
+ "======================\r\n"
|
||||
+ " Source Format: Audible AAX\r\n"
|
||||
+ $" Source Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n"
|
||||
+ $" Source Channels: {aaxcTagLib.Properties.AudioChannels}\r\n"
|
||||
+ $" Source Bitrate: {aaxcTagLib.Properties.AudioBitrate} Kbps\r\n"
|
||||
+ $" Source Sample Rate: {aaxcTagLib.TimeScale} Hz\r\n"
|
||||
+ $" Source Channels: {aaxcTagLib.AudioChannels}\r\n"
|
||||
+ $" Source Bitrate: {aaxcTagLib.AverageBitrate} Kbps\r\n"
|
||||
+ "\r\n"
|
||||
+ " Lossless Encode: Yes\r\n"
|
||||
+ " Encoded Codec: AAC / M4B\r\n"
|
||||
+ $" Encoded Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n"
|
||||
+ $" Encoded Channels: {aaxcTagLib.Properties.AudioChannels}\r\n"
|
||||
+ $" Encoded Bitrate: {aaxcTagLib.Properties.AudioBitrate} Kbps\r\n"
|
||||
+ $" Encoded Sample Rate: {aaxcTagLib.TimeScale} Hz\r\n"
|
||||
+ $" Encoded Channels: {aaxcTagLib.AudioChannels}\r\n"
|
||||
+ $" Encoded Bitrate: {aaxcTagLib.AverageBitrate} Kbps\r\n"
|
||||
+ "\r\n"
|
||||
+ $" Ripper: {ripper}\r\n"
|
||||
+ "\r\n"
|
||||
+ "\r\n"
|
||||
+ "Book Description\r\n"
|
||||
+ "================\r\n"
|
||||
+ (!string.IsNullOrWhiteSpace(aaxcTagLib.LongDescription) ? aaxcTagLib.AsciiLongDescription : aaxcTagLib.AsciiComment);
|
||||
+ (!string.IsNullOrWhiteSpace(aaxcTagLib.AppleTags.LongDescription) ? aaxcTagLib.AppleTags.LongDescription.UnicodeToAscii() : aaxcTagLib.AppleTags.Comment?.UnicodeToAscii());
|
||||
|
||||
return nfoString;
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AaxDecrypter
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a <see cref="TagLib.File.IFileAbstraction"/> for a file over Http.
|
||||
/// </summary>
|
||||
class NetworkFileAbstraction : TagLib.File.IFileAbstraction
|
||||
{
|
||||
private NetworkFileStream aaxNetworkStream;
|
||||
|
||||
public NetworkFileAbstraction( NetworkFileStream networkFileStream)
|
||||
{
|
||||
Name = networkFileStream.SaveFilePath;
|
||||
aaxNetworkStream = networkFileStream;
|
||||
}
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Stream ReadStream => aaxNetworkStream;
|
||||
|
||||
public Stream WriteStream => throw new NotImplementedException();
|
||||
|
||||
public void CloseStream(Stream stream)
|
||||
{
|
||||
aaxNetworkStream.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -176,7 +176,7 @@ namespace AaxDecrypter
|
||||
/// <summary>
|
||||
/// Begins downloading <see cref="Uri"/> to <see cref="SaveFilePath"/> in a background thread.
|
||||
/// </summary>
|
||||
private void BeginDownloading()
|
||||
public void BeginDownloading()
|
||||
{
|
||||
if (ContentLength != 0 && WritePosition == ContentLength)
|
||||
{
|
||||
@@ -393,7 +393,7 @@ namespace AaxDecrypter
|
||||
//read operation will block until file contains enough data
|
||||
//to fulfil the request, or until cancelled.
|
||||
while (requiredPosition > WritePosition && !isCancelled)
|
||||
Thread.Sleep(0);
|
||||
Thread.Sleep(2);
|
||||
|
||||
return _readFile.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ namespace ApplicationServices
|
||||
}
|
||||
catch (AudibleApi.Authentication.LoginFailedException lfEx)
|
||||
{
|
||||
lfEx.MoveResponseBodyFile(FileManager.Configuration.Instance.LibationFiles);
|
||||
lfEx.SaveFiles(FileManager.Configuration.Instance.LibationFiles);
|
||||
|
||||
// nuget Serilog.Exceptions would automatically log custom properties
|
||||
// However, it comes with a scary warning when used with EntityFrameworkCore which I'm not yet ready to implement:
|
||||
@@ -46,7 +46,7 @@ namespace ApplicationServices
|
||||
ResponseStatusCodeNumber = (int)lfEx.ResponseStatusCode,
|
||||
ResponseStatusCodeDesc = lfEx.ResponseStatusCode,
|
||||
lfEx.ResponseInputFields,
|
||||
lfEx.ResponseBodyFilePath
|
||||
lfEx.ResponseBodyFilePaths
|
||||
});
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -81,15 +81,10 @@ namespace FileLiberator
|
||||
|
||||
if (Configuration.Instance.AllowLibationFixup)
|
||||
{
|
||||
aaxcDecryptDlLic.ChapterInfo = new ChapterInfo();
|
||||
aaxcDecryptDlLic.ChapterInfo = new AAXClean.ChapterInfo();
|
||||
|
||||
foreach (var chap in contentLic.ContentMetadata?.ChapterInfo?.Chapters)
|
||||
aaxcDecryptDlLic.ChapterInfo.AddChapter(
|
||||
new Chapter(
|
||||
chap.Title,
|
||||
chap.StartOffsetMs,
|
||||
chap.LengthMs
|
||||
));
|
||||
aaxcDecryptDlLic.ChapterInfo.AddChapter(chap.Title, TimeSpan.FromMilliseconds(chap.LengthMs));
|
||||
}
|
||||
|
||||
aaxcDownloader = AaxcDownloadConverter.Create(cacheDir, destinationDir, aaxcDecryptDlLic);
|
||||
@@ -132,7 +127,7 @@ namespace FileLiberator
|
||||
}
|
||||
}
|
||||
|
||||
private void aaxcDownloader_RetrievedTags(object sender, AaxcTagLibFile e)
|
||||
private void aaxcDownloader_RetrievedTags(object sender, AAXClean.AppleTags e)
|
||||
{
|
||||
TitleDiscovered?.Invoke(this, e.TitleSansUnabridged);
|
||||
AuthorsDiscovered?.Invoke(this, e.FirstAuthor ?? "[unknown]");
|
||||
|
||||
@@ -100,8 +100,13 @@ namespace FileManager
|
||||
lock (locker)
|
||||
{
|
||||
var jObject = readFile();
|
||||
var startContents = JsonConvert.SerializeObject(jObject, Formatting.Indented);
|
||||
|
||||
jObject[propertyName] = newValue;
|
||||
File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
|
||||
var endContents = JsonConvert.SerializeObject(jObject, Formatting.Indented);
|
||||
|
||||
if (startContents != endContents)
|
||||
File.WriteAllText(Filepath, endContents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,10 +117,13 @@ namespace FileManager
|
||||
{
|
||||
var jObject = readFile();
|
||||
var token = jObject.SelectToken(jsonPath);
|
||||
var debug_oldValue = (string)token[propertyName];
|
||||
var oldValue = (string)token[propertyName];
|
||||
|
||||
token[propertyName] = newValue;
|
||||
File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
token[propertyName] = newValue;
|
||||
File.WriteAllText(Filepath, JsonConvert.SerializeObject(jObject, Formatting.Indented));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LibationSearchEngine.Tests"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dinah.EntityFrameworkCore.Tests", "..\Dinah.Core\_Tests\Dinah.EntityFrameworkCore.Tests\Dinah.EntityFrameworkCore.Tests.csproj", "{6F5131A0-09AE-4707-B82B-5E53CB74688E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AAXClean", "..\AAXClean\AAXClean.csproj", "{94BEB7CC-511D-45AB-9F09-09BE858EE486}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -208,6 +210,10 @@ Global
|
||||
{6F5131A0-09AE-4707-B82B-5E53CB74688E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6F5131A0-09AE-4707-B82B-5E53CB74688E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6F5131A0-09AE-4707-B82B-5E53CB74688E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -242,6 +248,7 @@ Global
|
||||
{8447C956-B03E-4F59-9DD4-877793B849D9} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
{C5B21768-C7C9-4FCB-AC1E-187B223D5A98} = {67E66E82-5532-4440-AFB3-9FB1DF9DEF53}
|
||||
{6F5131A0-09AE-4707-B82B-5E53CB74688E} = {38E6C6D9-963A-4C5B-89F4-F2F14885ADFD}
|
||||
{94BEB7CC-511D-45AB-9F09-09BE858EE486} = {7FBBB086-0807-4998-85BF-6D1A49C8AD05}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {615E00ED-BAEF-4E8E-A92A-9B82D87942A9}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<!-- <PublishSingleFile>true</PublishSingleFile> -->
|
||||
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||
|
||||
<Version>5.1.5.1</Version>
|
||||
<Version>5.1.9.1</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using AudibleApi;
|
||||
using AudibleApi.Authorization;
|
||||
using Dinah.Core.Logging;
|
||||
using FileManager;
|
||||
using InternalUtilities;
|
||||
using LibationWinForms;
|
||||
@@ -12,6 +13,7 @@ using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
using Serilog.Events;
|
||||
|
||||
namespace LibationLauncher
|
||||
{
|
||||
@@ -31,7 +33,6 @@ namespace LibationLauncher
|
||||
migrate_to_v4_0_0();
|
||||
migrate_to_v5_0_0();
|
||||
|
||||
ensureLoggingConfig();
|
||||
ensureSerilogConfig();
|
||||
configureLogging();
|
||||
checkForUpdate();
|
||||
@@ -155,7 +156,7 @@ namespace LibationLauncher
|
||||
// identity has likely been updated above. re-get contents
|
||||
var legacyContents = File.ReadAllText(AccountsSettingsFileLegacy30);
|
||||
|
||||
var identity = AudibleApi.Authorization.Identity.FromJson(legacyContents);
|
||||
var identity = Identity.FromJson(legacyContents);
|
||||
|
||||
if (!identity.IsValid)
|
||||
return null;
|
||||
@@ -257,42 +258,13 @@ namespace LibationLauncher
|
||||
}
|
||||
#endregion
|
||||
|
||||
private static string defaultLoggingLevel { get; } = "Information";
|
||||
private static void ensureLoggingConfig()
|
||||
{
|
||||
var config = Configuration.Instance;
|
||||
|
||||
if (config.GetObject("Logging") != null)
|
||||
return;
|
||||
|
||||
// "Logging": {
|
||||
// "LogLevel": {
|
||||
// "Default": "Debug"
|
||||
// }
|
||||
// }
|
||||
var loggingObj = new JObject
|
||||
{
|
||||
{
|
||||
"LogLevel", new JObject { { "Default", defaultLoggingLevel } }
|
||||
}
|
||||
};
|
||||
config.SetObject("Logging", loggingObj);
|
||||
}
|
||||
|
||||
private static void ensureSerilogConfig()
|
||||
private static void ensureSerilogConfig()
|
||||
{
|
||||
var config = Configuration.Instance;
|
||||
|
||||
if (config.GetObject("Serilog") != null)
|
||||
return;
|
||||
|
||||
// default. for reference. output example:
|
||||
// 2019-11-26 08:48:40.224 -05:00 [DBG] Begin Libation
|
||||
var default_outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}";
|
||||
// with class and method info. output example:
|
||||
// 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForms.Program.init()) Begin Libation
|
||||
var code_outputTemplate = "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}";
|
||||
|
||||
// "Serilog": {
|
||||
// "MinimumLevel": "Information"
|
||||
// "WriteTo": [
|
||||
@@ -312,7 +284,7 @@ namespace LibationLauncher
|
||||
// }
|
||||
var serilogObj = new JObject
|
||||
{
|
||||
{ "MinimumLevel", defaultLoggingLevel },
|
||||
{ "MinimumLevel", "Information" },
|
||||
{ "WriteTo", new JArray
|
||||
{
|
||||
new JObject { {"Name", "Console" } },
|
||||
@@ -325,7 +297,12 @@ namespace LibationLauncher
|
||||
// for this sink to work, a path must be provided. we override this below
|
||||
{ "path", Path.Combine(Configuration.Instance.LibationFiles, "_Log.log") },
|
||||
{ "rollingInterval", "Month" },
|
||||
{ "outputTemplate", code_outputTemplate }
|
||||
// Serilog template formatting examples
|
||||
// - default: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
|
||||
// output example: 2019-11-26 08:48:40.224 -05:00 [DBG] Begin Libation
|
||||
// - with class and method info: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}";
|
||||
// output example: 2019-11-26 08:48:40.224 -05:00 [DBG] (at LibationWinForms.Program.init()) Begin Libation
|
||||
{ "outputTemplate", "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] (at {Caller}) {Message:lj}{NewLine}{Exception}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -379,7 +356,7 @@ namespace LibationLauncher
|
||||
|
||||
// https://octokitnet.readthedocs.io/en/latest/releases/
|
||||
var releases = gitHubClient.Repository.Release.GetAll("rmcrackan", "Libation").GetAwaiter().GetResult();
|
||||
var latest = releases.First(r => !r.Draft);
|
||||
var latest = releases.First(r => !r.Draft && !r.Prerelease);
|
||||
|
||||
var latestVersionString = latest.TagName.Trim('v');
|
||||
if (!Version.TryParse(latestVersionString, out var latestRelease))
|
||||
@@ -433,10 +410,19 @@ namespace LibationLauncher
|
||||
{
|
||||
var config = Configuration.Instance;
|
||||
|
||||
// begin logging session with a form feed
|
||||
Log.Logger.Information("\r\n\f");
|
||||
Log.Logger.Information("Begin Libation. {@DebugInfo}", new
|
||||
{
|
||||
Version = BuildVersion.ToString(),
|
||||
|
||||
LogLevel_Verbose_Enabled = Log.Logger.IsVerboseEnabled(),
|
||||
LogLevel_Debug_Enabled = Log.Logger.IsDebugEnabled(),
|
||||
LogLevel_Information_Enabled = Log.Logger.IsInformationEnabled(),
|
||||
LogLevel_Warning_Enabled = Log.Logger.IsWarningEnabled(),
|
||||
LogLevel_Error_Enabled = Log.Logger.IsErrorEnabled(),
|
||||
LogLevel_Fatal_Enabled = Log.Logger.IsFatalEnabled(),
|
||||
|
||||
config.LibationFiles,
|
||||
AudibleFileStorage.BooksDirectory,
|
||||
|
||||
@@ -451,6 +437,20 @@ namespace LibationLauncher
|
||||
DecryptInProgressDir = AudibleFileStorage.DecryptInProgress,
|
||||
DecryptInProgressFiles = Directory.EnumerateFiles(AudibleFileStorage.DecryptInProgress).Count(),
|
||||
});
|
||||
|
||||
// when turning on debug (and especially Verbose) to share logs, some privacy settings may not be obscured
|
||||
if (Log.Logger.IsVerboseEnabled())
|
||||
MessageBox.Show(@"
|
||||
Warning: verbose logging is enabled.
|
||||
|
||||
This should be used for debugging only. It creates many
|
||||
more logs and debug files, neither of which are as
|
||||
strictly anonomous.
|
||||
|
||||
When you are finished debugging, it's highly recommended
|
||||
to set your debug MinimumLevel to Information and restart
|
||||
Libation.
|
||||
".Trim(), "Verbose logging enabled", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||
}
|
||||
|
||||
private static Version BuildVersion => System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
|
||||
|
||||
@@ -6,18 +6,9 @@ namespace LibationWinForms.BookLiberation
|
||||
{
|
||||
public partial class AutomatedBackupsForm : Form
|
||||
{
|
||||
public bool KeepGoingVisible
|
||||
{
|
||||
get => keepGoingCb.Visible;
|
||||
set => keepGoingCb.Visible = value;
|
||||
}
|
||||
|
||||
public bool KeepGoingChecked => keepGoingCb.Checked;
|
||||
|
||||
public bool KeepGoing
|
||||
=> keepGoingCb.Visible
|
||||
&& keepGoingCb.Enabled
|
||||
&& keepGoingCb.Checked;
|
||||
public bool KeepGoing => keepGoingCb.Enabled && keepGoingCb.Checked;
|
||||
|
||||
public AutomatedBackupsForm()
|
||||
{
|
||||
|
||||
@@ -508,7 +508,7 @@ An error occurred while trying to process this book
|
||||
|
||||
if (!AutomatedBackupsForm.KeepGoing)
|
||||
{
|
||||
if (AutomatedBackupsForm.KeepGoingVisible && !AutomatedBackupsForm.KeepGoingChecked)
|
||||
if (!AutomatedBackupsForm.KeepGoingChecked)
|
||||
LogMe.Info("'Keep going' is unchecked");
|
||||
return;
|
||||
}
|
||||
|
||||
120
LibationWinForms/Dialogs/IndexLibraryDialog.resx
Normal file
120
LibationWinForms/Dialogs/IndexLibraryDialog.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
120
LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx
Normal file
120
LibationWinForms/Dialogs/Login/AudibleLoginDialog.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -44,6 +44,7 @@ namespace LibationWinForms.Dialogs.Login
|
||||
this.submitBtn.TabIndex = 3;
|
||||
this.submitBtn.Text = "Submit";
|
||||
this.submitBtn.UseVisualStyleBackColor = true;
|
||||
this.submitBtn.Click += new System.EventHandler(this.submitBtn_Click);
|
||||
//
|
||||
// radioButton1
|
||||
//
|
||||
|
||||
@@ -23,12 +23,9 @@ namespace LibationWinForms.Dialogs.Login
|
||||
// optional string settings
|
||||
if (!string.IsNullOrWhiteSpace(mfaConfig.Title))
|
||||
this.Text = mfaConfig.Title;
|
||||
if (!string.IsNullOrWhiteSpace(mfaConfig.Button1Text))
|
||||
this.radioButton1.Text = mfaConfig.Button1Text;
|
||||
if (!string.IsNullOrWhiteSpace(mfaConfig.Button2Text))
|
||||
this.radioButton2.Text = mfaConfig.Button2Text;
|
||||
if (!string.IsNullOrWhiteSpace(mfaConfig.Button3Text))
|
||||
this.radioButton3.Text = mfaConfig.Button3Text;
|
||||
setOptional(this.radioButton1, mfaConfig.Button1Text);
|
||||
setOptional(this.radioButton2, mfaConfig.Button2Text);
|
||||
setOptional(this.radioButton3, mfaConfig.Button3Text);
|
||||
|
||||
// mandatory values
|
||||
radioButton1.Name = mfaConfig.Button1Name;
|
||||
@@ -41,6 +38,11 @@ namespace LibationWinForms.Dialogs.Login
|
||||
radioButton3.Tag = mfaConfig.Button3Value;
|
||||
}
|
||||
|
||||
private static void setOptional(RadioButton radioButton, string text)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
radioButton.Text = text;
|
||||
}
|
||||
|
||||
public string SelectedName { get; private set; }
|
||||
public string SelectedValue { get; private set; }
|
||||
|
||||
120
LibationWinForms/Dialogs/Login/_2faCodeDialog.resx
Normal file
120
LibationWinForms/Dialogs/Login/_2faCodeDialog.resx
Normal file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
||||
@@ -1,64 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<root>
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
|
||||
Reference in New Issue
Block a user