Merge pull request #32 from Mbucari/master
Changes we discussed, plus a few more.
This commit is contained in:
commit
2146ebff29
@ -5,9 +5,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="taglib-sharp">
|
<PackageReference Include="TagLibSharp" Version="2.2.0" />
|
||||||
<HintPath>lib\taglib-sharp.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -42,9 +40,6 @@
|
|||||||
<None Update="DecryptLib\swscale-5.dll">
|
<None Update="DecryptLib\swscale-5.dll">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Update="DecryptLib\taglib-sharp.dll">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -10,51 +10,54 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace AaxDecrypter
|
namespace AaxDecrypter
|
||||||
{
|
{
|
||||||
public interface ISimpleAaxToM4bConverter
|
public interface ISimpleAaxcToM4bConverter
|
||||||
{
|
{
|
||||||
|
event EventHandler<AaxcTagLibFile> RetrievedTags;
|
||||||
|
event EventHandler<byte[]> RetrievedCoverArt;
|
||||||
|
event EventHandler<TimeSpan> DecryptTimeRemaining;
|
||||||
event EventHandler<int> DecryptProgressUpdate;
|
event EventHandler<int> DecryptProgressUpdate;
|
||||||
bool Run();
|
bool Run();
|
||||||
string AppName { get; set; }
|
string AppName { get; set; }
|
||||||
string outDir { get; }
|
string outDir { get; }
|
||||||
string outputFileName { get; }
|
string outputFileName { get; }
|
||||||
ChapterInfo chapters { get; }
|
ChapterInfo chapters { get; }
|
||||||
|
AaxcTagLibFile aaxcTagLib { get; }
|
||||||
|
byte[] coverArt { get; }
|
||||||
|
void SetCoverArt(byte[] coverArt);
|
||||||
void SetOutputFilename(string outFileName);
|
void SetOutputFilename(string outFileName);
|
||||||
string Title { get; }
|
|
||||||
string Author { get; }
|
|
||||||
string Narrator { get; }
|
|
||||||
byte[] CoverArt { get; }
|
|
||||||
}
|
}
|
||||||
public interface IAdvancedAaxcToM4bConverter : ISimpleAaxToM4bConverter
|
public interface IAdvancedAaxcToM4bConverter : ISimpleAaxcToM4bConverter
|
||||||
{
|
{
|
||||||
void Cancel();
|
void Cancel();
|
||||||
bool Step1_CreateDir();
|
bool Step1_CreateDir();
|
||||||
bool Step2_DownloadAndCombine();
|
bool Step2_GetMetadata();
|
||||||
bool Step3_RestoreMetadata();
|
bool Step3_DownloadAndCombine();
|
||||||
bool Step4_CreateCue();
|
bool Step4_RestoreMetadata();
|
||||||
bool Step5_CreateNfo();
|
bool Step5_CreateCue();
|
||||||
|
bool Step6_CreateNfo();
|
||||||
}
|
}
|
||||||
public class AaxcDownloadConverter : IAdvancedAaxcToM4bConverter
|
public class AaxcDownloadConverter : IAdvancedAaxcToM4bConverter
|
||||||
{
|
{
|
||||||
|
public event EventHandler<AaxcTagLibFile> RetrievedTags;
|
||||||
|
public event EventHandler<byte[]> RetrievedCoverArt;
|
||||||
public event EventHandler<int> DecryptProgressUpdate;
|
public event EventHandler<int> DecryptProgressUpdate;
|
||||||
public event EventHandler<TimeSpan> DecryptTimeRemaining;
|
public event EventHandler<TimeSpan> DecryptTimeRemaining;
|
||||||
public string AppName { get; set; } = nameof(AaxcDownloadConverter);
|
public string AppName { get; set; } = nameof(AaxcDownloadConverter);
|
||||||
public string outDir { get; private set; }
|
public string outDir { get; private set; }
|
||||||
public string outputFileName { get; private set; }
|
public string outputFileName { get; private set; }
|
||||||
public ChapterInfo chapters { get; private set; }
|
public ChapterInfo chapters { get; private set; }
|
||||||
public string Title => aaxcTagLib.Tag.Title.Replace(" (Unabridged)", "");
|
public AaxcTagLibFile aaxcTagLib { get; private set; }
|
||||||
public string Author => aaxcTagLib.Tag.FirstPerformer ?? "[unknown]";
|
public byte[] coverArt { get; private set; }
|
||||||
public string Narrator => aaxcTagLib.GetTag(TagLib.TagTypes.Apple).Narrator;
|
|
||||||
public byte[] CoverArt => aaxcTagLib.Tag.Pictures.Length > 0 ? aaxcTagLib.Tag.Pictures[0].Data.Data : default;
|
|
||||||
|
|
||||||
private TagLib.Mpeg4.File aaxcTagLib { get; set; }
|
|
||||||
private StepSequence steps { get; }
|
private StepSequence steps { get; }
|
||||||
private DownloadLicense downloadLicense { get; set; }
|
private DownloadLicense downloadLicense { get; set; }
|
||||||
private FFMpegAaxcProcesser aaxcProcesser;
|
private FFMpegAaxcProcesser aaxcProcesser;
|
||||||
|
private bool isCanceled { get; set; }
|
||||||
|
|
||||||
public static async Task<AaxcDownloadConverter> CreateAsync(string outDirectory, DownloadLicense dlLic, ChapterInfo chapters = null)
|
public static AaxcDownloadConverter Create(string outDirectory, DownloadLicense dlLic, ChapterInfo chapters = null)
|
||||||
{
|
{
|
||||||
var converter = new AaxcDownloadConverter(outDirectory, dlLic, chapters);
|
var converter = new AaxcDownloadConverter(outDirectory, dlLic, chapters);
|
||||||
await converter.prelimProcessing();
|
converter.SetOutputFilename(Path.GetTempFileName());
|
||||||
return converter;
|
return converter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,35 +75,20 @@ namespace AaxDecrypter
|
|||||||
Name = "Convert Aax To M4b",
|
Name = "Convert Aax To M4b",
|
||||||
|
|
||||||
["Step 1: Create Dir"] = Step1_CreateDir,
|
["Step 1: Create Dir"] = Step1_CreateDir,
|
||||||
["Step 2: Download and Combine Audiobook"] = Step2_DownloadAndCombine,
|
["Step 2: Get Aaxc Metadata"] = Step2_GetMetadata,
|
||||||
["Step 3: Restore Aaxc Metadata"] = Step3_RestoreMetadata,
|
["Step 3: Download Decrypted Audiobook"] = Step3_DownloadAndCombine,
|
||||||
["Step 4: Create Cue"] = Step4_CreateCue,
|
["Step 4: Restore Aaxc Metadata"] = Step4_RestoreMetadata,
|
||||||
["Step 5: Create Nfo"] = Step5_CreateNfo,
|
["Step 5: Create Cue"] = Step5_CreateCue,
|
||||||
|
["Step 6: Create Nfo"] = Step6_CreateNfo,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
aaxcProcesser = new FFMpegAaxcProcesser(dlLic);
|
||||||
|
aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;
|
||||||
|
|
||||||
downloadLicense = dlLic;
|
downloadLicense = dlLic;
|
||||||
this.chapters = chapters;
|
this.chapters = chapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task prelimProcessing()
|
|
||||||
{
|
|
||||||
//Get metadata from the file over http
|
|
||||||
var client = new System.Net.Http.HttpClient();
|
|
||||||
client.DefaultRequestHeaders.Add("User-Agent", downloadLicense.UserAgent);
|
|
||||||
|
|
||||||
var networkFile = await NetworkFileAbstraction.CreateAsync(client, new Uri(downloadLicense.DownloadUrl));
|
|
||||||
|
|
||||||
aaxcTagLib = await Task.Run(() => TagLib.File.Create(networkFile, "audio/mp4", TagLib.ReadStyle.Average) as TagLib.Mpeg4.File);
|
|
||||||
|
|
||||||
var defaultFilename = Path.Combine(
|
|
||||||
outDir,
|
|
||||||
PathLib.ToPathSafeString(aaxcTagLib.Tag.FirstPerformer??"[unknown]"),
|
|
||||||
PathLib.ToPathSafeString(aaxcTagLib.Tag.Title.Replace(" (Unabridged)", "")) + ".m4b"
|
|
||||||
);
|
|
||||||
|
|
||||||
SetOutputFilename(defaultFilename);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetOutputFilename(string outFileName)
|
public void SetOutputFilename(string outFileName)
|
||||||
{
|
{
|
||||||
outputFileName = PathLib.ReplaceExtension(outFileName, ".m4b");
|
outputFileName = PathLib.ReplaceExtension(outFileName, ".m4b");
|
||||||
@ -110,6 +98,14 @@ namespace AaxDecrypter
|
|||||||
File.Delete(outputFileName);
|
File.Delete(outputFileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetCoverArt(byte[] coverArt)
|
||||||
|
{
|
||||||
|
if (coverArt is null) return;
|
||||||
|
|
||||||
|
this.coverArt = coverArt;
|
||||||
|
RetrievedCoverArt?.Invoke(this, coverArt);
|
||||||
|
}
|
||||||
|
|
||||||
public bool Run()
|
public bool Run()
|
||||||
{
|
{
|
||||||
var (IsSuccess, Elapsed) = steps.Run();
|
var (IsSuccess, Elapsed) = steps.Run();
|
||||||
@ -131,14 +127,32 @@ namespace AaxDecrypter
|
|||||||
ProcessRunner.WorkingDir = outDir;
|
ProcessRunner.WorkingDir = outDir;
|
||||||
Directory.CreateDirectory(outDir);
|
Directory.CreateDirectory(outDir);
|
||||||
|
|
||||||
return true;
|
return !isCanceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Step2_DownloadAndCombine()
|
public bool Step2_GetMetadata()
|
||||||
{
|
{
|
||||||
aaxcProcesser = new FFMpegAaxcProcesser(downloadLicense);
|
//Get metadata from the file over http
|
||||||
aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;
|
var client = new System.Net.Http.HttpClient();
|
||||||
|
client.DefaultRequestHeaders.Add("User-Agent", downloadLicense.UserAgent);
|
||||||
|
var networkFile = NetworkFileAbstraction.CreateAsync(client, new Uri(downloadLicense.DownloadUrl)).GetAwaiter().GetResult();
|
||||||
|
|
||||||
|
aaxcTagLib = new AaxcTagLibFile(networkFile);
|
||||||
|
|
||||||
|
if (coverArt is null && aaxcTagLib.AppleTags.Pictures.Length > 0)
|
||||||
|
{
|
||||||
|
coverArt = aaxcTagLib.AppleTags.Pictures[0].Data.Data;
|
||||||
|
}
|
||||||
|
|
||||||
|
RetrievedTags?.Invoke(this, aaxcTagLib);
|
||||||
|
RetrievedCoverArt?.Invoke(this, coverArt);
|
||||||
|
|
||||||
|
return !isCanceled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Step3_DownloadAndCombine()
|
||||||
|
{
|
||||||
|
DecryptProgressUpdate?.Invoke(this, int.MaxValue);
|
||||||
bool userSuppliedChapters = chapters != null;
|
bool userSuppliedChapters = chapters != null;
|
||||||
|
|
||||||
string metadataPath = null;
|
string metadataPath = null;
|
||||||
@ -165,115 +179,56 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
DecryptProgressUpdate?.Invoke(this, 0);
|
DecryptProgressUpdate?.Invoke(this, 0);
|
||||||
|
|
||||||
return aaxcProcesser.Succeeded;
|
return aaxcProcesser.Succeeded && !isCanceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AaxcProcesser_ProgressUpdate(object sender, TimeSpan e)
|
private void AaxcProcesser_ProgressUpdate(object sender, AaxcProcessUpdate e)
|
||||||
{
|
{
|
||||||
double averageRate = getAverageProcessRate(e);
|
double remainingSecsToProcess = (aaxcTagLib.Properties.Duration - e.ProcessPosition).TotalSeconds;
|
||||||
double remainingSecsToProcess = (aaxcTagLib.Properties.Duration - e).TotalSeconds;
|
double estTimeRemaining = remainingSecsToProcess / e.ProcessSpeed;
|
||||||
double estTimeRemaining = remainingSecsToProcess / averageRate;
|
|
||||||
|
|
||||||
if (double.IsNormal(estTimeRemaining))
|
if (double.IsNormal(estTimeRemaining))
|
||||||
DecryptTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining));
|
DecryptTimeRemaining?.Invoke(this, TimeSpan.FromSeconds(estTimeRemaining));
|
||||||
|
|
||||||
double progressPercent = 100 * e.TotalSeconds / aaxcTagLib.Properties.Duration.TotalSeconds;
|
double progressPercent = 100 * e.ProcessPosition.TotalSeconds / aaxcTagLib.Properties.Duration.TotalSeconds;
|
||||||
|
|
||||||
DecryptProgressUpdate?.Invoke(this, (int)progressPercent);
|
DecryptProgressUpdate?.Invoke(this, (int)progressPercent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Calculates the average processing rate based on the last 2 to <see cref="MAX_NUM_AVERAGE"/> samples.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="lastProcessedPosition">Position in the audio file last processed</param>
|
|
||||||
/// <returns>The average processing rate, in book_duration_seconds / second.</returns>
|
|
||||||
private double getAverageProcessRate(TimeSpan lastProcessedPosition)
|
|
||||||
{
|
|
||||||
streamPositions.Enqueue(new StreamPosition
|
|
||||||
{
|
|
||||||
ProcessPosition = lastProcessedPosition,
|
|
||||||
EventTime = DateTime.Now,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (streamPositions.Count < 2)
|
|
||||||
return double.PositiveInfinity;
|
|
||||||
|
|
||||||
//Calculate the harmonic mean of the last 2 to MAX_NUM_AVERAGE progress updates
|
|
||||||
//Units are Book_Duration_Seconds / second
|
|
||||||
|
|
||||||
var lastPos = streamPositions.Count > MAX_NUM_AVERAGE ? streamPositions.Dequeue() : null;
|
|
||||||
|
|
||||||
double harmonicDenominator = 0;
|
|
||||||
int harmonicNumerator = 0;
|
|
||||||
|
|
||||||
foreach (var pos in streamPositions)
|
|
||||||
{
|
|
||||||
if (lastPos is null)
|
|
||||||
{
|
|
||||||
lastPos = pos;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
double dP = (pos.ProcessPosition - lastPos.ProcessPosition).TotalSeconds;
|
|
||||||
double dT = (pos.EventTime - lastPos.EventTime).TotalSeconds;
|
|
||||||
|
|
||||||
harmonicDenominator += dT / dP;
|
|
||||||
harmonicNumerator++;
|
|
||||||
lastPos = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
double harmonicMean = harmonicNumerator / harmonicDenominator;
|
|
||||||
return harmonicMean;
|
|
||||||
}
|
|
||||||
|
|
||||||
private const int MAX_NUM_AVERAGE = 15;
|
|
||||||
private class StreamPosition
|
|
||||||
{
|
|
||||||
public TimeSpan ProcessPosition { get; set; }
|
|
||||||
public DateTime EventTime { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private Queue<StreamPosition> streamPositions = new Queue<StreamPosition>();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy all aacx metadata to m4b file, including cover art.
|
/// Copy all aacx metadata to m4b file, including cover art.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Step3_RestoreMetadata()
|
public bool Step4_RestoreMetadata()
|
||||||
{
|
{
|
||||||
var outFile = new TagLib.Mpeg4.File(outputFileName, TagLib.ReadStyle.Average);
|
var outFile = new AaxcTagLibFile(outputFileName);
|
||||||
|
outFile.CopyTagsFrom(aaxcTagLib);
|
||||||
|
|
||||||
var destTags = outFile.GetTag(TagLib.TagTypes.Apple) as TagLib.Mpeg4.AppleTag;
|
if (outFile.AppleTags.Pictures.Length == 0 && coverArt is not null)
|
||||||
destTags.Clear();
|
|
||||||
|
|
||||||
var sourceTag = aaxcTagLib.GetTag(TagLib.TagTypes.Apple) as TagLib.Mpeg4.AppleTag;
|
|
||||||
|
|
||||||
//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.
|
|
||||||
foreach (var stag in sourceTag)
|
|
||||||
{
|
{
|
||||||
destTags.SetData(stag.BoxType, stag.Children.Cast<TagLib.Mpeg4.AppleDataBox>().ToArray());
|
outFile.AddPicture(coverArt);
|
||||||
}
|
}
|
||||||
|
|
||||||
outFile.Save();
|
outFile.Save();
|
||||||
|
|
||||||
return true;
|
return !isCanceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Step4_CreateCue()
|
public bool Step5_CreateCue()
|
||||||
{
|
{
|
||||||
File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(outputFileName), chapters));
|
File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".cue"), Cue.CreateContents(Path.GetFileName(outputFileName), chapters));
|
||||||
return true;
|
return !isCanceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Step5_CreateNfo()
|
public bool Step6_CreateNfo()
|
||||||
{
|
{
|
||||||
File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxcTagLib, chapters));
|
File.WriteAllText(PathLib.ReplaceExtension(outputFileName, ".nfo"), NFO.CreateContents(AppName, aaxcTagLib, chapters));
|
||||||
return true;
|
return !isCanceled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
{
|
{
|
||||||
aaxcProcesser?.Cancel();
|
isCanceled = true;
|
||||||
|
aaxcProcesser.Cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
165
AaxDecrypter/AaxcTagLibFile.cs
Normal file
165
AaxDecrypter/AaxcTagLibFile.cs
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using TagLib;
|
||||||
|
using TagLib.Mpeg4;
|
||||||
|
|
||||||
|
namespace AaxDecrypter
|
||||||
|
{
|
||||||
|
public class AaxcTagLibFile : TagLib.Mpeg4.File
|
||||||
|
{
|
||||||
|
public AppleTag AppleTags => GetTag(TagTypes.Apple) as AppleTag;
|
||||||
|
|
||||||
|
private static ReadOnlyByteVector narratorType = new ReadOnlyByteVector(0xa9, (byte)'n', (byte)'r', (byte)'t');
|
||||||
|
private static ReadOnlyByteVector descriptionType = new ReadOnlyByteVector(0xa9, (byte)'d', (byte)'e', (byte)'s');
|
||||||
|
private static ReadOnlyByteVector publisherType = new ReadOnlyByteVector(0xa9, (byte)'p', (byte)'u', (byte)'b');
|
||||||
|
|
||||||
|
public string AsciiTitleSansUnabridged => TitleSansUnabridged is not null? unicodeToAscii(TitleSansUnabridged) : default;
|
||||||
|
public string AsciiFirstAuthor => FirstAuthor is not null? unicodeToAscii(FirstAuthor) : default;
|
||||||
|
public string AsciiNarrator => Narrator is not null ? unicodeToAscii(Narrator) : default;
|
||||||
|
public string AsciiComment => Comment is not null ? unicodeToAscii(Comment) : default;
|
||||||
|
public string AsciiLongDescription => LongDescription is not null ? unicodeToAscii(LongDescription) : default;
|
||||||
|
|
||||||
|
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;
|
||||||
|
public string Narrator
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string[] text = AppleTags.GetText(narratorType);
|
||||||
|
return text.Length == 0 ? default : text[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string LongDescription
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string[] text = AppleTags.GetText(descriptionType);
|
||||||
|
return text.Length == 0 ? default : text[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string ReleaseDate
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string[] text = AppleTags.GetText("rldt");
|
||||||
|
return text.Length == 0 ? default : text[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public string Publisher
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
string[] text = AppleTags.GetText(publisherType);
|
||||||
|
return text.Length == 0 ? default : text[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] _copyright => AppleTags.Copyright?.Replace("©", string.Empty)?.Replace("(P)", string.Empty)?.Split(';');
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to convert unicode characters to an approximately equal ASCII character.
|
||||||
|
/// </summary>
|
||||||
|
private string unicodeToAscii(string unicodeStr)
|
||||||
|
{
|
||||||
|
//Accents
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[éèëêð]", "e");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ÉÈËÊ]", "E");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[àâä]", "a");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ÀÁÂÃÄÅ]", "A");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[àáâãäå]", "a");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ÙÚÛÜ]", "U");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ùúûüµ]", "u");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[òóôõöø]", "o");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ÒÓÔÕÖØ]", "O");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ìíîï]", "i");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ÌÍÎÏ]", "I");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[š]", "s");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Š]", "S");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ñ]", "n");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ñ]", "N");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ç]", "c");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ç]", "C");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ÿ]", "y");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ÿ]", "Y");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ž]", "z");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ž]", "Z");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ð]", "D");
|
||||||
|
|
||||||
|
//Ligatures
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[œ]", "oe");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Œ]", "Oe");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ꜳ]", "aa");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ꜳ]", "AA");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[æ]", "ae");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Æ]", "AE");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ꜵ]", "ao");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ꜵ]", "AO");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ꜷ]", "au");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ꜷ]", "AU");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[«»ꜹꜻ]", "av");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[«»ꜸꜺ]", "AV");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[🙰]", "et");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ff]", "ff");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ffi]", "ffi");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ffl]", "ffl");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[fi]", "fi");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[fl]", "fl");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ƕ]", "hv");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ƕ]", "Hv");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[℔]", "lb");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ꝏ]", "oo");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ꝏ]", "OO");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[st]", "st");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ꜩ]", "tz");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[Ꜩ]", "TZ");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ᵫ]", "ue");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[ꭣ]", "uo");
|
||||||
|
|
||||||
|
//Punctuation
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[«»\u2018\u2019\u201A\u201B\u2032\u2035]", "\'");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[«»\u201C\u201D\u201E\u201F\u2033\u2036]", "\"");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[\u2026]", "...");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[\u1680]", "-");
|
||||||
|
|
||||||
|
//Spaces
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[«»\u00A0\u2000\u2002\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200F\u205F\u3000]", " ");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[«»\u2001\u2003]", " ");
|
||||||
|
unicodeStr = Regex.Replace(unicodeStr, "[«»\u180E\u200B\uFEFF]", "");
|
||||||
|
|
||||||
|
return unicodeStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
@ -7,13 +7,25 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace AaxDecrypter
|
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>
|
/// <summary>
|
||||||
/// Download audible aaxc, decrypt, and remux with chapters.
|
/// Download audible aaxc, decrypt, and remux with chapters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class FFMpegAaxcProcesser
|
class FFMpegAaxcProcesser
|
||||||
{
|
{
|
||||||
public event EventHandler<TimeSpan> ProgressUpdate;
|
public event EventHandler<AaxcProcessUpdate> ProgressUpdate;
|
||||||
public string FFMpegPath { get; }
|
public string FFMpegPath { get; }
|
||||||
public DownloadLicense DownloadLicense { get; }
|
public DownloadLicense DownloadLicense { get; }
|
||||||
public bool IsRunning { get; private set; }
|
public bool IsRunning { get; private set; }
|
||||||
@ -24,9 +36,10 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
private StringBuilder remuxerError = new StringBuilder();
|
private StringBuilder remuxerError = new StringBuilder();
|
||||||
private StringBuilder downloaderError = new StringBuilder();
|
private StringBuilder downloaderError = new StringBuilder();
|
||||||
private static Regex processedTimeRegex = new Regex("time=(\\d{2}):(\\d{2}):(\\d{2}).\\d{2}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
|
private static Regex processedTimeRegex = 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 downloader;
|
private Process downloader;
|
||||||
private Process remuxer;
|
private Process remuxer;
|
||||||
|
private bool isCanceled = false;
|
||||||
|
|
||||||
public FFMpegAaxcProcesser( DownloadLicense downloadLicense)
|
public FFMpegAaxcProcesser( DownloadLicense downloadLicense)
|
||||||
{
|
{
|
||||||
@ -60,6 +73,9 @@ namespace AaxDecrypter
|
|||||||
remuxer.Start();
|
remuxer.Start();
|
||||||
remuxer.BeginErrorReadLine();
|
remuxer.BeginErrorReadLine();
|
||||||
|
|
||||||
|
//Thic check needs to be placed after remuxer has started
|
||||||
|
if (isCanceled) return;
|
||||||
|
|
||||||
var pipedOutput = downloader.StandardOutput.BaseStream;
|
var pipedOutput = downloader.StandardOutput.BaseStream;
|
||||||
var pipedInput = remuxer.StandardInput.BaseStream;
|
var pipedInput = remuxer.StandardInput.BaseStream;
|
||||||
|
|
||||||
@ -94,8 +110,12 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
public void Cancel()
|
public void Cancel()
|
||||||
{
|
{
|
||||||
|
isCanceled = true;
|
||||||
|
|
||||||
if (IsRunning && !remuxer.HasExited)
|
if (IsRunning && !remuxer.HasExited)
|
||||||
remuxer.Kill();
|
remuxer.Kill();
|
||||||
|
if (IsRunning && !downloader.HasExited)
|
||||||
|
downloader.Kill();
|
||||||
}
|
}
|
||||||
private void Downloader_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
private void Downloader_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||||
{
|
{
|
||||||
@ -115,6 +135,7 @@ namespace AaxDecrypter
|
|||||||
if (processedTimeRegex.IsMatch(e.Data))
|
if (processedTimeRegex.IsMatch(e.Data))
|
||||||
{
|
{
|
||||||
//get timestamp of of last processed audio stream position
|
//get timestamp of of last processed audio stream position
|
||||||
|
//and processing speed
|
||||||
var match = processedTimeRegex.Match(e.Data);
|
var match = processedTimeRegex.Match(e.Data);
|
||||||
|
|
||||||
int hours = int.Parse(match.Groups[1].Value);
|
int hours = int.Parse(match.Groups[1].Value);
|
||||||
@ -123,7 +144,11 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
var position = new TimeSpan(hours, minutes, seconds);
|
var position = new TimeSpan(hours, minutes, seconds);
|
||||||
|
|
||||||
ProgressUpdate?.Invoke(sender, position);
|
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"))
|
if (e.Data.Contains("aac bitstream error"))
|
||||||
|
|||||||
@ -3,57 +3,50 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
public static class NFO
|
public static class NFO
|
||||||
{
|
{
|
||||||
public static string CreateContents(string ripper, TagLib.File aaxcTagLib, ChapterInfo chapters)
|
public static string CreateContents(string ripper, AaxcTagLibFile aaxcTagLib, ChapterInfo chapters)
|
||||||
{
|
{
|
||||||
var tag = aaxcTagLib.GetTag(TagLib.TagTypes.Apple);
|
|
||||||
|
|
||||||
string narator = string.IsNullOrWhiteSpace(aaxcTagLib.Tag.FirstComposer) ? tag.Narrator : aaxcTagLib.Tag.FirstComposer;
|
|
||||||
|
|
||||||
var _hours = (int)aaxcTagLib.Properties.Duration.TotalHours;
|
var _hours = (int)aaxcTagLib.Properties.Duration.TotalHours;
|
||||||
var myDuration
|
var myDuration
|
||||||
= (_hours > 0 ? _hours + " hours, " : "")
|
= (_hours > 0 ? _hours + " hours, " : string.Empty)
|
||||||
+ aaxcTagLib.Properties.Duration.Minutes + " minutes, "
|
+ aaxcTagLib.Properties.Duration.Minutes + " minutes, "
|
||||||
+ aaxcTagLib.Properties.Duration.Seconds + " seconds";
|
+ aaxcTagLib.Properties.Duration.Seconds + " seconds";
|
||||||
|
|
||||||
var header
|
var nfoString
|
||||||
= "General Information\r\n"
|
= "General Information\r\n"
|
||||||
+ "===================\r\n"
|
+ "======================\r\n"
|
||||||
+ $" Title: {aaxcTagLib.Tag.Title.Replace(" (Unabridged)", "")}\r\n"
|
+ $" Title: {aaxcTagLib.AsciiTitleSansUnabridged ?? "[unknown]"}\r\n"
|
||||||
+ $" Author: {aaxcTagLib.Tag.FirstPerformer ?? "[unknown]"}\r\n"
|
+ $" Author: {aaxcTagLib.AsciiFirstAuthor ?? "[unknown]"}\r\n"
|
||||||
+ $" Read By: {aaxcTagLib.GetTag(TagLib.TagTypes.Apple).Narrator??"[unknown]"}\r\n"
|
+ $" Read By: {aaxcTagLib.AsciiNarrator ?? "[unknown]"}\r\n"
|
||||||
+ $" Copyright: {aaxcTagLib.Tag.Year}\r\n"
|
+ $" Release Date: {aaxcTagLib.ReleaseDate ?? "[unknown]"}\r\n"
|
||||||
+ $" Audiobook Copyright: {aaxcTagLib.Tag.Year}\r\n";
|
+ $" Book Copyright: {aaxcTagLib.BookCopyright ?? "[unknown]"}\r\n"
|
||||||
if (!string.IsNullOrEmpty(aaxcTagLib.Tag.FirstGenre))
|
+ $" Recording Copyright: {aaxcTagLib.RecordingCopyright ?? "[unknown]"}\r\n"
|
||||||
header += $" Genre: {aaxcTagLib.Tag.FirstGenre}\r\n";
|
+ $" Genre: {aaxcTagLib.AppleTags.FirstGenre ?? "[unknown]"}\r\n"
|
||||||
|
+ $" Publisher: {aaxcTagLib.Publisher ?? "[unknown]"}\r\n"
|
||||||
var s
|
|
||||||
= header
|
|
||||||
+ $" Publisher: {tag.Publisher ?? ""}\r\n"
|
|
||||||
+ $" Duration: {myDuration}\r\n"
|
+ $" Duration: {myDuration}\r\n"
|
||||||
+ $" Chapters: {chapters.Count}\r\n"
|
+ $" Chapters: {chapters.Count}\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "Media Information\r\n"
|
+ "Media Information\r\n"
|
||||||
+ "=================\r\n"
|
+ "======================\r\n"
|
||||||
+ " Source Format: Audible AAX\r\n"
|
+ " Source Format: Audible AAX\r\n"
|
||||||
+ $" Source Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n"
|
+ $" Source Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n"
|
||||||
+ $" Source Channels: {aaxcTagLib.Properties.AudioChannels}\r\n"
|
+ $" Source Channels: {aaxcTagLib.Properties.AudioChannels}\r\n"
|
||||||
+ $" Source Bitrate: {aaxcTagLib.Properties.AudioBitrate} kbits\r\n"
|
+ $" Source Bitrate: {aaxcTagLib.Properties.AudioBitrate} Kbps\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ " Lossless Encode: Yes\r\n"
|
+ " Lossless Encode: Yes\r\n"
|
||||||
+ " Encoded Codec: AAC / M4B\r\n"
|
+ " Encoded Codec: AAC / M4B\r\n"
|
||||||
+ $" Encoded Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n"
|
+ $" Encoded Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n"
|
||||||
+ $" Encoded Channels: {aaxcTagLib.Properties.AudioChannels}\r\n"
|
+ $" Encoded Channels: {aaxcTagLib.Properties.AudioChannels}\r\n"
|
||||||
+ $" Encoded Bitrate: {aaxcTagLib.Properties.AudioBitrate} kbits\r\n"
|
+ $" Encoded Bitrate: {aaxcTagLib.Properties.AudioBitrate} Kbps\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ $" Ripper: {ripper}\r\n"
|
+ $" Ripper: {ripper}\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "\r\n"
|
+ "\r\n"
|
||||||
+ "Book Description\r\n"
|
+ "Book Description\r\n"
|
||||||
+ "================\r\n"
|
+ "================\r\n"
|
||||||
+ (!string.IsNullOrWhiteSpace(tag.LongDescription) ? tag.LongDescription : tag.Description);
|
+ (!string.IsNullOrWhiteSpace(aaxcTagLib.LongDescription) ? aaxcTagLib.AsciiLongDescription : aaxcTagLib.AsciiComment);
|
||||||
|
|
||||||
return s;
|
return nfoString;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +0,0 @@
|
|||||||
using DataLayer;
|
|
||||||
using Dinah.Core.ErrorHandling;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace FileLiberator.AaxcDownloadDecrypt
|
|
||||||
{
|
|
||||||
public class DownloadBookDummy : DownloadableBase
|
|
||||||
{
|
|
||||||
public override Task<StatusHandler> ProcessItemAsync(LibraryBook libraryBook) => Task.FromResult(new StatusHandler());
|
|
||||||
|
|
||||||
public override bool Validate(LibraryBook libraryBook)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -3,7 +3,7 @@ using System.Linq;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using Dinah.Core.ErrorHandling;
|
using Dinah.Core.ErrorHandling;
|
||||||
using FileLiberator.AaxcDownloadDecrypt;
|
using FileLiberator;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
|
|
||||||
namespace FileLiberator
|
namespace FileLiberator
|
||||||
@ -22,8 +22,6 @@ namespace FileLiberator
|
|||||||
public event EventHandler<string> StatusUpdate;
|
public event EventHandler<string> StatusUpdate;
|
||||||
public event EventHandler<LibraryBook> Completed;
|
public event EventHandler<LibraryBook> Completed;
|
||||||
|
|
||||||
|
|
||||||
public DownloadBookDummy DownloadBook { get; } = new DownloadBookDummy();
|
|
||||||
public DownloadDecryptBook DecryptBook { get; } = new DownloadDecryptBook();
|
public DownloadDecryptBook DecryptBook { get; } = new DownloadDecryptBook();
|
||||||
public DownloadPdf DownloadPdf { get; } = new DownloadPdf();
|
public DownloadPdf DownloadPdf { get; } = new DownloadPdf();
|
||||||
|
|
||||||
@ -38,12 +36,6 @@ namespace FileLiberator
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
{
|
|
||||||
var statusHandler = await DownloadBook.TryProcessAsync(libraryBook);
|
|
||||||
if (statusHandler.HasErrors)
|
|
||||||
return statusHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
var statusHandler = await DecryptBook.TryProcessAsync(libraryBook);
|
var statusHandler = await DecryptBook.TryProcessAsync(libraryBook);
|
||||||
if (statusHandler.HasErrors)
|
if (statusHandler.HasErrors)
|
||||||
|
|||||||
@ -11,10 +11,11 @@ using System.Threading.Tasks;
|
|||||||
using AaxDecrypter;
|
using AaxDecrypter;
|
||||||
using AudibleApi;
|
using AudibleApi;
|
||||||
|
|
||||||
namespace FileLiberator.AaxcDownloadDecrypt
|
namespace FileLiberator
|
||||||
{
|
{
|
||||||
public class DownloadDecryptBook : IDecryptable
|
public class DownloadDecryptBook : IDecryptable
|
||||||
{
|
{
|
||||||
|
public event EventHandler<Action<byte[]>> RequestCoverArt;
|
||||||
public event EventHandler<LibraryBook> Begin;
|
public event EventHandler<LibraryBook> Begin;
|
||||||
public event EventHandler<string> DecryptBegin;
|
public event EventHandler<string> DecryptBegin;
|
||||||
public event EventHandler<string> TitleDiscovered;
|
public event EventHandler<string> TitleDiscovered;
|
||||||
@ -34,7 +35,6 @@ namespace FileLiberator.AaxcDownloadDecrypt
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
||||||
if (AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId))
|
if (AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId))
|
||||||
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
|
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
|
||||||
|
|
||||||
@ -57,7 +57,6 @@ namespace FileLiberator.AaxcDownloadDecrypt
|
|||||||
{
|
{
|
||||||
Completed?.Invoke(this, libraryBook);
|
Completed?.Invoke(this, libraryBook);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> aaxToM4bConverterDecryptAsync(string destinationDir, LibraryBook libraryBook)
|
private async Task<string> aaxToM4bConverterDecryptAsync(string destinationDir, LibraryBook libraryBook)
|
||||||
@ -74,38 +73,30 @@ namespace FileLiberator.AaxcDownloadDecrypt
|
|||||||
|
|
||||||
var aaxcDecryptDlLic = new DownloadLicense(dlLic.DownloadUrl, dlLic.AudibleKey, dlLic.AudibleIV, Resources.UserAgent);
|
var aaxcDecryptDlLic = new DownloadLicense(dlLic.DownloadUrl, dlLic.AudibleKey, dlLic.AudibleIV, Resources.UserAgent);
|
||||||
|
|
||||||
var destinationDirectory = Path.GetDirectoryName(destinationDir);
|
if (Configuration.Instance.AllowLibationFixup)
|
||||||
|
|
||||||
if (Configuration.Instance.DownloadChapters)
|
|
||||||
{
|
{
|
||||||
var contentMetadata = await api.GetLibraryBookMetadataAsync(libraryBook.Book.AudibleProductId);
|
var contentMetadata = await api.GetLibraryBookMetadataAsync(libraryBook.Book.AudibleProductId);
|
||||||
|
|
||||||
var aaxcDecryptChapters = new ChapterInfo();
|
var aaxcDecryptChapters = new ChapterInfo();
|
||||||
|
|
||||||
foreach (var chap in contentMetadata?.ChapterInfo?.Chapters)
|
foreach (var chap in contentMetadata?.ChapterInfo?.Chapters)
|
||||||
aaxcDecryptChapters.AddChapter(new Chapter(chap.Title, chap.StartOffsetMs, chap.LengthMs));
|
aaxcDecryptChapters.AddChapter(new Chapter(chap.Title, chap.StartOffsetMs, chap.LengthMs));
|
||||||
|
|
||||||
aaxcDownloader = await AaxcDownloadConverter.CreateAsync(destinationDirectory, aaxcDecryptDlLic, aaxcDecryptChapters);
|
aaxcDownloader = AaxcDownloadConverter.Create(destinationDir, aaxcDecryptDlLic, aaxcDecryptChapters);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
aaxcDownloader = await AaxcDownloadConverter.CreateAsync(destinationDirectory, aaxcDecryptDlLic);
|
aaxcDownloader = AaxcDownloadConverter.Create(destinationDir, aaxcDecryptDlLic);
|
||||||
}
|
}
|
||||||
|
|
||||||
aaxcDownloader.AppName = "Libation";
|
aaxcDownloader.AppName = "Libation";
|
||||||
|
|
||||||
TitleDiscovered?.Invoke(this, aaxcDownloader.Title);
|
|
||||||
AuthorsDiscovered?.Invoke(this, aaxcDownloader.Author);
|
|
||||||
NarratorsDiscovered?.Invoke(this, aaxcDownloader.Narrator);
|
|
||||||
|
|
||||||
if (aaxcDownloader.CoverArt is not null)
|
|
||||||
CoverImageFilepathDiscovered?.Invoke(this, aaxcDownloader.CoverArt);
|
|
||||||
|
|
||||||
// override default which was set in CreateAsync
|
// override default which was set in CreateAsync
|
||||||
var proposedOutputFile = Path.Combine(destinationDir, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].m4b");
|
var proposedOutputFile = Path.Combine(destinationDir, $"{PathLib.ToPathSafeString(libraryBook.Book.Title)} [{libraryBook.Book.AudibleProductId}].m4b");
|
||||||
aaxcDownloader.SetOutputFilename(proposedOutputFile);
|
aaxcDownloader.SetOutputFilename(proposedOutputFile);
|
||||||
aaxcDownloader.DecryptProgressUpdate += (s, progress) => UpdateProgress?.Invoke(this, progress);
|
aaxcDownloader.DecryptProgressUpdate += (s, progress) => UpdateProgress?.Invoke(this, progress);
|
||||||
aaxcDownloader.DecryptTimeRemaining += (s, remaining) => UpdateRemainingTime?.Invoke(this, remaining);
|
aaxcDownloader.DecryptTimeRemaining += (s, remaining) => UpdateRemainingTime?.Invoke(this, remaining);
|
||||||
|
aaxcDownloader.RetrievedCoverArt += AaxcDownloader_RetrievedCoverArt;
|
||||||
|
aaxcDownloader.RetrievedTags += aaxcDownloader_RetrievedTags;
|
||||||
|
|
||||||
// REAL WORK DONE HERE
|
// REAL WORK DONE HERE
|
||||||
var success = await Task.Run(() => aaxcDownloader.Run());
|
var success = await Task.Run(() => aaxcDownloader.Run());
|
||||||
@ -122,6 +113,26 @@ namespace FileLiberator.AaxcDownloadDecrypt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AaxcDownloader_RetrievedCoverArt(object sender, byte[] e)
|
||||||
|
{
|
||||||
|
if (e is null && Configuration.Instance.AllowLibationFixup)
|
||||||
|
{
|
||||||
|
RequestCoverArt?.Invoke(this, aaxcDownloader.SetCoverArt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e is not null)
|
||||||
|
{
|
||||||
|
CoverImageFilepathDiscovered?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void aaxcDownloader_RetrievedTags(object sender, AaxcTagLibFile e)
|
||||||
|
{
|
||||||
|
TitleDiscovered?.Invoke(this, e.TitleSansUnabridged);
|
||||||
|
AuthorsDiscovered?.Invoke(this, e.FirstAuthor ?? "[unknown]");
|
||||||
|
NarratorsDiscovered?.Invoke(this, e.Narrator ?? "[unknown]");
|
||||||
|
}
|
||||||
|
|
||||||
private static string moveFilesToBooksDir(Book product, string outputAudioFilename)
|
private static string moveFilesToBooksDir(Book product, string outputAudioFilename)
|
||||||
{
|
{
|
||||||
// create final directory. move each file into it. MOVE AUDIO FILE LAST
|
// create final directory. move each file into it. MOVE AUDIO FILE LAST
|
||||||
@ -6,6 +6,7 @@ namespace FileLiberator
|
|||||||
{
|
{
|
||||||
event EventHandler<string> DecryptBegin;
|
event EventHandler<string> DecryptBegin;
|
||||||
|
|
||||||
|
event EventHandler<Action<byte[]>> RequestCoverArt;
|
||||||
event EventHandler<string> TitleDiscovered;
|
event EventHandler<string> TitleDiscovered;
|
||||||
event EventHandler<string> AuthorsDiscovered;
|
event EventHandler<string> AuthorsDiscovered;
|
||||||
event EventHandler<string> NarratorsDiscovered;
|
event EventHandler<string> NarratorsDiscovered;
|
||||||
|
|||||||
@ -83,11 +83,11 @@ namespace FileManager
|
|||||||
set => persistentDictionary.Set(nameof(DecryptInProgressEnum), value);
|
set => persistentDictionary.Set(nameof(DecryptInProgressEnum), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Description("Download chapter titles from Audible?")]
|
[Description("Allow Libation for fix up audiobook metadata?")]
|
||||||
public bool DownloadChapters
|
public bool AllowLibationFixup
|
||||||
{
|
{
|
||||||
get => persistentDictionary.Get<bool>(nameof(DownloadChapters));
|
get => persistentDictionary.Get<bool>(nameof(AllowLibationFixup));
|
||||||
set => persistentDictionary.Set(nameof(DownloadChapters), value);
|
set => persistentDictionary.Set(nameof(AllowLibationFixup), value);
|
||||||
}
|
}
|
||||||
// note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app
|
// note: any potential file manager static ctors can't compensate if storage dir is changed at run time via settings. this is partly bad architecture. but the side effect is desirable. if changing LibationFiles location: restart app
|
||||||
|
|
||||||
|
|||||||
@ -60,7 +60,7 @@ namespace LibationLauncher
|
|||||||
config.DownloadsInProgressEnum ??= "WinTemp";
|
config.DownloadsInProgressEnum ??= "WinTemp";
|
||||||
config.DecryptInProgressEnum ??= "WinTemp";
|
config.DecryptInProgressEnum ??= "WinTemp";
|
||||||
config.Books ??= Configuration.AppDir;
|
config.Books ??= Configuration.AppDir;
|
||||||
config.DownloadChapters = true;
|
config.AllowLibationFixup = true;
|
||||||
};
|
};
|
||||||
// setupDialog.BasicBtn_Click += (_, __) => // no action needed
|
// setupDialog.BasicBtn_Click += (_, __) => // no action needed
|
||||||
setupDialog.AdvancedBtn_Click += (_, __) => isAdvanced = true;
|
setupDialog.AdvancedBtn_Click += (_, __) => isAdvanced = true;
|
||||||
@ -235,9 +235,9 @@ namespace LibationLauncher
|
|||||||
{
|
{
|
||||||
var persistentDictionary = new PersistentDictionary(Configuration.Instance.SettingsFilePath);
|
var persistentDictionary = new PersistentDictionary(Configuration.Instance.SettingsFilePath);
|
||||||
|
|
||||||
if (persistentDictionary.GetString("DownloadChapters") is null)
|
if (persistentDictionary.GetString("AllowLibationFixup") is null)
|
||||||
{
|
{
|
||||||
persistentDictionary.Set("DownloadChapters", true);
|
persistentDictionary.Set("AllowLibationFixup", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!File.Exists(AudibleApiStorage.AccountsSettingsFile))
|
if (!File.Exists(AudibleApiStorage.AccountsSettingsFile))
|
||||||
|
|||||||
@ -56,15 +56,22 @@ namespace LibationWinForms.BookLiberation
|
|||||||
private void updateBookInfo()
|
private void updateBookInfo()
|
||||||
=> bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}");
|
=> bookInfoLbl.UIThread(() => bookInfoLbl.Text = $"{title}\r\nBy {authorNames}\r\nNarrated by {narratorNames}");
|
||||||
|
|
||||||
public void SetCoverImage(byte[] coverBytes)
|
public void SetCoverImage(System.Drawing.Image coverImage)
|
||||||
=> pictureBox1.UIThread(() => pictureBox1.Image = ImageReader.ToImage(coverBytes));
|
=> pictureBox1.UIThread(() => pictureBox1.Image = coverImage);
|
||||||
|
|
||||||
public void UpdateProgress(int percentage)
|
public void UpdateProgress(int percentage)
|
||||||
{
|
{
|
||||||
if (percentage == 0)
|
if (percentage == 0)
|
||||||
remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = "ETA:\r\n0 sec");
|
remainingTimeLbl.UIThread(() => remainingTimeLbl.Text = "ETA:\r\n0 sec");
|
||||||
|
|
||||||
progressBar1.UIThread(() => progressBar1.Value = percentage);
|
if (percentage == int.MaxValue)
|
||||||
|
progressBar1.UIThread(() => progressBar1.Style = ProgressBarStyle.Marquee);
|
||||||
|
else
|
||||||
|
progressBar1.UIThread(() =>
|
||||||
|
{
|
||||||
|
progressBar1.Value = percentage;
|
||||||
|
progressBar1.Style = ProgressBarStyle.Blocks;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateRemainingTime(TimeSpan remaining)
|
public void UpdateRemainingTime(TimeSpan remaining)
|
||||||
|
|||||||
@ -70,8 +70,7 @@ namespace LibationWinForms.BookLiberation
|
|||||||
{
|
{
|
||||||
var backupBook = new BackupBook();
|
var backupBook = new BackupBook();
|
||||||
|
|
||||||
backupBook.DownloadBook.Begin += (_, __) => wireUpEvents(backupBook.DownloadBook);
|
backupBook.DecryptBook.Begin += (_, l) => wireUpEvents(backupBook.DecryptBook, l);
|
||||||
backupBook.DecryptBook.Begin += (_, __) => wireUpEvents(backupBook.DecryptBook);
|
|
||||||
backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf);
|
backupBook.DownloadPdf.Begin += (_, __) => wireUpEvents(backupBook.DownloadPdf);
|
||||||
|
|
||||||
// must occur before completedAction. A common use case is:
|
// must occur before completedAction. A common use case is:
|
||||||
@ -80,13 +79,11 @@ namespace LibationWinForms.BookLiberation
|
|||||||
// completedAction is to refresh grid
|
// completedAction is to refresh grid
|
||||||
// - want to see that book disappear from grid
|
// - want to see that book disappear from grid
|
||||||
// also for this to work, updateIsLiberated can NOT be async
|
// also for this to work, updateIsLiberated can NOT be async
|
||||||
backupBook.DownloadBook.Completed += updateIsLiberated;
|
|
||||||
backupBook.DecryptBook.Completed += updateIsLiberated;
|
backupBook.DecryptBook.Completed += updateIsLiberated;
|
||||||
backupBook.DownloadPdf.Completed += updateIsLiberated;
|
backupBook.DownloadPdf.Completed += updateIsLiberated;
|
||||||
|
|
||||||
if (completedAction != null)
|
if (completedAction != null)
|
||||||
{
|
{
|
||||||
backupBook.DownloadBook.Completed += completedAction;
|
|
||||||
backupBook.DecryptBook.Completed += completedAction;
|
backupBook.DecryptBook.Completed += completedAction;
|
||||||
backupBook.DownloadPdf.Completed += completedAction;
|
backupBook.DownloadPdf.Completed += completedAction;
|
||||||
}
|
}
|
||||||
@ -104,9 +101,7 @@ namespace LibationWinForms.BookLiberation
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region define how model actions will affect form behavior
|
#region define how model actions will affect form behavior
|
||||||
void downloadBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Download Step, Begin: {libraryBook.Book}");
|
|
||||||
void statusUpdate(object _, string str) => logMe.Info("- " + str);
|
void statusUpdate(object _, string str) => logMe.Info("- " + str);
|
||||||
void downloadBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Download Step, Completed: {libraryBook.Book}");
|
|
||||||
void decryptBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Begin: {libraryBook.Book}");
|
void decryptBookBegin(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Begin: {libraryBook.Book}");
|
||||||
// extra line after book is completely finished
|
// extra line after book is completely finished
|
||||||
void decryptBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}");
|
void decryptBookCompleted(object _, LibraryBook libraryBook) => logMe.Info($"Decrypt Step, Completed: {libraryBook.Book}{Environment.NewLine}");
|
||||||
@ -116,9 +111,6 @@ namespace LibationWinForms.BookLiberation
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region subscribe new form to model's events
|
#region subscribe new form to model's events
|
||||||
backupBook.DownloadBook.Begin += downloadBookBegin;
|
|
||||||
backupBook.DownloadBook.StatusUpdate += statusUpdate;
|
|
||||||
backupBook.DownloadBook.Completed += downloadBookCompleted;
|
|
||||||
backupBook.DecryptBook.Begin += decryptBookBegin;
|
backupBook.DecryptBook.Begin += decryptBookBegin;
|
||||||
backupBook.DecryptBook.StatusUpdate += statusUpdate;
|
backupBook.DecryptBook.StatusUpdate += statusUpdate;
|
||||||
backupBook.DecryptBook.Completed += decryptBookCompleted;
|
backupBook.DecryptBook.Completed += decryptBookCompleted;
|
||||||
@ -131,9 +123,6 @@ namespace LibationWinForms.BookLiberation
|
|||||||
// unsubscribe so disposed forms aren't still trying to receive notifications
|
// unsubscribe so disposed forms aren't still trying to receive notifications
|
||||||
automatedBackupsForm.FormClosing += (_, __) =>
|
automatedBackupsForm.FormClosing += (_, __) =>
|
||||||
{
|
{
|
||||||
backupBook.DownloadBook.Begin -= downloadBookBegin;
|
|
||||||
backupBook.DownloadBook.StatusUpdate -= statusUpdate;
|
|
||||||
backupBook.DownloadBook.Completed -= downloadBookCompleted;
|
|
||||||
backupBook.DecryptBook.Begin -= decryptBookBegin;
|
backupBook.DecryptBook.Begin -= decryptBookBegin;
|
||||||
backupBook.DecryptBook.StatusUpdate -= statusUpdate;
|
backupBook.DecryptBook.StatusUpdate -= statusUpdate;
|
||||||
backupBook.DecryptBook.Completed -= decryptBookCompleted;
|
backupBook.DecryptBook.Completed -= decryptBookCompleted;
|
||||||
@ -250,23 +239,51 @@ namespace LibationWinForms.BookLiberation
|
|||||||
}
|
}
|
||||||
|
|
||||||
// subscribed to Begin event because a new form should be created+processed+closed on each iteration
|
// subscribed to Begin event because a new form should be created+processed+closed on each iteration
|
||||||
private static void wireUpEvents(IDecryptable decryptBook)
|
private static void wireUpEvents(IDecryptable decryptBook, LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
#region create form
|
#region create form
|
||||||
var decryptDialog = new DecryptForm();
|
var decryptDialog = new DecryptForm();
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Set initially displayed book properties from library info.
|
||||||
|
decryptDialog.SetTitle(libraryBook.Book.Title);
|
||||||
|
decryptDialog.SetAuthorNames(string.Join(", ", libraryBook.Book.Authors));
|
||||||
|
decryptDialog.SetNarratorNames(string.Join(", ", libraryBook.Book.NarratorNames));
|
||||||
|
decryptDialog.SetCoverImage(
|
||||||
|
WindowsDesktopUtilities.WinAudibleImageServer.GetImage(
|
||||||
|
libraryBook.Book.PictureId,
|
||||||
|
FileManager.PictureSize._80x80
|
||||||
|
));
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region define how model actions will affect form behavior
|
#region define how model actions will affect form behavior
|
||||||
void decryptBegin(object _, string __) => decryptDialog.Show();
|
void decryptBegin(object _, string __) => decryptDialog.Show();
|
||||||
|
|
||||||
void titleDiscovered(object _, string title) => decryptDialog.SetTitle(title);
|
void titleDiscovered(object _, string title) => decryptDialog.SetTitle(title);
|
||||||
void authorsDiscovered(object _, string authors) => decryptDialog.SetAuthorNames(authors);
|
void authorsDiscovered(object _, string authors) => decryptDialog.SetAuthorNames(authors);
|
||||||
void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators);
|
void narratorsDiscovered(object _, string narrators) => decryptDialog.SetNarratorNames(narrators);
|
||||||
void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(coverBytes);
|
void coverImageFilepathDiscovered(object _, byte[] coverBytes) => decryptDialog.SetCoverImage(Dinah.Core.Drawing.ImageReader.ToImage(coverBytes));
|
||||||
void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage);
|
void updateProgress(object _, int percentage) => decryptDialog.UpdateProgress(percentage);
|
||||||
void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining);
|
void updateRemainingTime(object _, TimeSpan remaining) => decryptDialog.UpdateRemainingTime(remaining);
|
||||||
|
|
||||||
void decryptCompleted(object _, string __) => decryptDialog.Close();
|
void decryptCompleted(object _, string __) => decryptDialog.Close();
|
||||||
|
|
||||||
|
void requestCoverArt(object _, Action<byte[]> setArt)
|
||||||
|
{
|
||||||
|
var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500);
|
||||||
|
(bool isDefault, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef);
|
||||||
|
|
||||||
|
if (isDefault)
|
||||||
|
{
|
||||||
|
void pictureCached(object _, string pictureId) => onPictureCached(libraryBook, pictureId, setArt, pictureCached);
|
||||||
|
FileManager.PictureStorage.PictureCached += pictureCached;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
setArt(picture);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region subscribe new form to model's events
|
#region subscribe new form to model's events
|
||||||
@ -278,6 +295,7 @@ namespace LibationWinForms.BookLiberation
|
|||||||
decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered;
|
decryptBook.CoverImageFilepathDiscovered += coverImageFilepathDiscovered;
|
||||||
decryptBook.UpdateProgress += updateProgress;
|
decryptBook.UpdateProgress += updateProgress;
|
||||||
decryptBook.UpdateRemainingTime += updateRemainingTime;
|
decryptBook.UpdateRemainingTime += updateRemainingTime;
|
||||||
|
decryptBook.RequestCoverArt += requestCoverArt;
|
||||||
|
|
||||||
decryptBook.DecryptCompleted += decryptCompleted;
|
decryptBook.DecryptCompleted += decryptCompleted;
|
||||||
#endregion
|
#endregion
|
||||||
@ -294,13 +312,25 @@ namespace LibationWinForms.BookLiberation
|
|||||||
decryptBook.CoverImageFilepathDiscovered -= coverImageFilepathDiscovered;
|
decryptBook.CoverImageFilepathDiscovered -= coverImageFilepathDiscovered;
|
||||||
decryptBook.UpdateProgress -= updateProgress;
|
decryptBook.UpdateProgress -= updateProgress;
|
||||||
decryptBook.UpdateRemainingTime -= updateRemainingTime;
|
decryptBook.UpdateRemainingTime -= updateRemainingTime;
|
||||||
|
decryptBook.RequestCoverArt -= requestCoverArt;
|
||||||
|
|
||||||
decryptBook.DecryptCompleted -= decryptCompleted;
|
decryptBook.DecryptCompleted -= decryptCompleted;
|
||||||
decryptBook.Cancel();
|
decryptBook.Cancel();
|
||||||
};
|
};
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
private static void onPictureCached(LibraryBook libraryBook, string picId, Action<byte[]> setArt, EventHandler<string> pictureCacheDelegate)
|
||||||
|
{
|
||||||
|
if (picId == libraryBook.Book.PictureId)
|
||||||
|
{
|
||||||
|
FileManager.PictureStorage.PictureCached -= pictureCacheDelegate;
|
||||||
|
|
||||||
|
var picDef = new FileManager.PictureDefinition(libraryBook.Book.PictureId, FileManager.PictureSize._500x500);
|
||||||
|
(_, byte[] picture) = FileManager.PictureStorage.GetPicture(picDef);
|
||||||
|
|
||||||
|
setArt(picture);
|
||||||
|
}
|
||||||
|
}
|
||||||
private static (AutomatedBackupsForm, LogMe) attachToBackupsForm(IDownloadableProcessable downloadable)
|
private static (AutomatedBackupsForm, LogMe) attachToBackupsForm(IDownloadableProcessable downloadable)
|
||||||
{
|
{
|
||||||
#region create form and logger
|
#region create form and logger
|
||||||
|
|||||||
20
LibationWinForms/Dialogs/SettingsDialog.Designer.cs
generated
20
LibationWinForms/Dialogs/SettingsDialog.Designer.cs
generated
@ -43,7 +43,7 @@
|
|||||||
this.saveBtn = new System.Windows.Forms.Button();
|
this.saveBtn = new System.Windows.Forms.Button();
|
||||||
this.cancelBtn = new System.Windows.Forms.Button();
|
this.cancelBtn = new System.Windows.Forms.Button();
|
||||||
this.groupBox1 = new System.Windows.Forms.GroupBox();
|
this.groupBox1 = new System.Windows.Forms.GroupBox();
|
||||||
this.downloadChaptersCbox = new System.Windows.Forms.CheckBox();
|
this.allowLibationFixupCbox = new System.Windows.Forms.CheckBox();
|
||||||
this.downloadsInProgressGb.SuspendLayout();
|
this.downloadsInProgressGb.SuspendLayout();
|
||||||
this.decryptInProgressGb.SuspendLayout();
|
this.decryptInProgressGb.SuspendLayout();
|
||||||
this.groupBox1.SuspendLayout();
|
this.groupBox1.SuspendLayout();
|
||||||
@ -215,7 +215,7 @@
|
|||||||
//
|
//
|
||||||
// groupBox1
|
// groupBox1
|
||||||
//
|
//
|
||||||
this.groupBox1.Controls.Add(this.downloadChaptersCbox);
|
this.groupBox1.Controls.Add(this.allowLibationFixupCbox);
|
||||||
this.groupBox1.Controls.Add(this.downloadsInProgressGb);
|
this.groupBox1.Controls.Add(this.downloadsInProgressGb);
|
||||||
this.groupBox1.Controls.Add(this.decryptInProgressGb);
|
this.groupBox1.Controls.Add(this.decryptInProgressGb);
|
||||||
this.groupBox1.Location = new System.Drawing.Point(18, 61);
|
this.groupBox1.Location = new System.Drawing.Point(18, 61);
|
||||||
@ -229,13 +229,13 @@
|
|||||||
//
|
//
|
||||||
// downloadChaptersCbox
|
// downloadChaptersCbox
|
||||||
//
|
//
|
||||||
this.downloadChaptersCbox.AutoSize = true;
|
this.allowLibationFixupCbox.AutoSize = true;
|
||||||
this.downloadChaptersCbox.Location = new System.Drawing.Point(10, 24);
|
this.allowLibationFixupCbox.Location = new System.Drawing.Point(10, 24);
|
||||||
this.downloadChaptersCbox.Name = "downloadChaptersCbox";
|
this.allowLibationFixupCbox.Name = "downloadChaptersCbox";
|
||||||
this.downloadChaptersCbox.Size = new System.Drawing.Size(224, 19);
|
this.allowLibationFixupCbox.Size = new System.Drawing.Size(262, 19);
|
||||||
this.downloadChaptersCbox.TabIndex = 6;
|
this.allowLibationFixupCbox.TabIndex = 6;
|
||||||
this.downloadChaptersCbox.Text = "Download chapter titles from Audible";
|
this.allowLibationFixupCbox.Text = "Allow Libation to fix up audiobook metadata";
|
||||||
this.downloadChaptersCbox.UseVisualStyleBackColor = true;
|
this.allowLibationFixupCbox.UseVisualStyleBackColor = true;
|
||||||
//
|
//
|
||||||
// SettingsDialog
|
// SettingsDialog
|
||||||
//
|
//
|
||||||
@ -284,6 +284,6 @@
|
|||||||
private System.Windows.Forms.Button saveBtn;
|
private System.Windows.Forms.Button saveBtn;
|
||||||
private System.Windows.Forms.Button cancelBtn;
|
private System.Windows.Forms.Button cancelBtn;
|
||||||
private System.Windows.Forms.GroupBox groupBox1;
|
private System.Windows.Forms.GroupBox groupBox1;
|
||||||
private System.Windows.Forms.CheckBox downloadChaptersCbox;
|
private System.Windows.Forms.CheckBox allowLibationFixupCbox;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
? config.Books
|
? config.Books
|
||||||
: Path.GetDirectoryName(Exe.FileLocationOnDisk);
|
: Path.GetDirectoryName(Exe.FileLocationOnDisk);
|
||||||
|
|
||||||
downloadChaptersCbox.Checked = config.DownloadChapters;
|
allowLibationFixupCbox.Checked = config.AllowLibationFixup;
|
||||||
|
|
||||||
switch (config.DownloadsInProgressEnum)
|
switch (config.DownloadsInProgressEnum)
|
||||||
{
|
{
|
||||||
@ -73,7 +73,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
|
|
||||||
private void saveBtn_Click(object sender, EventArgs e)
|
private void saveBtn_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
config.DownloadChapters = downloadChaptersCbox.Checked;
|
config.AllowLibationFixup = allowLibationFixupCbox.Checked;
|
||||||
config.DownloadsInProgressEnum = downloadsInProgressLibationFilesRb.Checked ? "LibationFiles" : "WinTemp";
|
config.DownloadsInProgressEnum = downloadsInProgressLibationFilesRb.Checked ? "LibationFiles" : "WinTemp";
|
||||||
config.DecryptInProgressEnum = decryptInProgressLibationFilesRb.Checked ? "LibationFiles" : "WinTemp";
|
config.DecryptInProgressEnum = decryptInProgressLibationFilesRb.Checked ? "LibationFiles" : "WinTemp";
|
||||||
|
|
||||||
|
|||||||
@ -81,15 +81,6 @@
|
|||||||
<metadata name="downloadsInProgressDescLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="downloadsInProgressDescLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
<metadata name="downloadsInProgressLibationFilesRb.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
|
||||||
<value>True</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="downloadsInProgressWinTempRb.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
|
||||||
<value>True</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="downloadsInProgressDescLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
|
||||||
<value>True</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="decryptInProgressGb.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="decryptInProgressGb.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
@ -102,15 +93,6 @@
|
|||||||
<metadata name="decryptInProgressDescLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="decryptInProgressDescLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
<metadata name="decryptInProgressLibationFilesRb.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
|
||||||
<value>True</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="decryptInProgressWinTempRb.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
|
||||||
<value>True</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="decryptInProgressDescLbl.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
|
||||||
<value>True</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="saveBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="saveBtn.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
@ -123,9 +105,6 @@
|
|||||||
<metadata name="downloadChaptersCbox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="downloadChaptersCbox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
<metadata name="downloadChaptersCbox.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
|
||||||
<value>True</value>
|
|
||||||
</metadata>
|
|
||||||
<metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
<metadata name="$this.Locked" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||||
<value>True</value>
|
<value>True</value>
|
||||||
</metadata>
|
</metadata>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user