diff --git a/AaxDecrypter/AaxDecrypter.csproj b/AaxDecrypter/AaxDecrypter.csproj
index 04be65c8..9262a8fc 100644
--- a/AaxDecrypter/AaxDecrypter.csproj
+++ b/AaxDecrypter/AaxDecrypter.csproj
@@ -9,6 +9,7 @@
+
diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs
index c24a8c14..ec1f89ef 100644
--- a/AaxDecrypter/AaxcDownloadConverter.cs
+++ b/AaxDecrypter/AaxcDownloadConverter.cs
@@ -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 RetrievedTags;
+ event EventHandler RetrievedTags;
event EventHandler RetrievedCoverArt;
event EventHandler DecryptTimeRemaining;
event EventHandler DecryptProgressUpdate;
@@ -18,7 +19,7 @@ namespace AaxDecrypter
string outDir { get; }
string outputFileName { get; }
DownloadLicense downloadLicense { get; }
- AaxcTagLibFile aaxcTagLib { get; }
+ Mp4File 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();
}
public class AaxcDownloadConverter : IAdvancedAaxcToM4bConverter
{
- public event EventHandler RetrievedTags;
+ public event EventHandler RetrievedTags;
public event EventHandler RetrievedCoverArt;
public event EventHandler DecryptProgressUpdate;
public event EventHandler 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 Mp4File 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,
};
- 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,8 +133,7 @@ namespace AaxDecrypter
public bool Step2_GetMetadata()
{
//Get metadata from the file over http
-
- NetworkFileStreamPersister nfsPersister;
+
if (File.Exists(jsonDownloadState))
{
nfsPersister = new NetworkFileStreamPersister(jsonDownloadState);
@@ -154,18 +149,12 @@ namespace AaxDecrypter
NetworkFileStream networkFileStream = new NetworkFileStream(tempFile, new Uri(downloadLicense.DownloadUrl), 0, headers);
nfsPersister = new NetworkFileStreamPersister(networkFileStream, jsonDownloadState);
}
+ nfsPersister.NetworkFileStream.BeginDownloading();
- var networkFile = new NetworkFileAbstraction(nfsPersister.NetworkFileStream);
- aaxcTagLib = new AaxcTagLibFile(networkFile);
- nfsPersister.Dispose();
+ aaxFile = new Mp4File(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;
@@ -175,87 +164,40 @@ namespace AaxDecrypter
{
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);
- string metadataPath = Path.Combine(outDir, Path.GetFileName(outputFileName) + ".ffmeta");
+ aaxFile.DecryptionProgressUpdate += AaxFile_DecryptionProgressUpdate;
+ var decryptedBook = aaxFile.DecryptAaxc(outFile, downloadLicense.AudibleKey, downloadLicense.AudibleIV, downloadLicense.ChapterInfo);
+ aaxFile.DecryptionProgressUpdate -= AaxFile_DecryptionProgressUpdate;
- 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);
- }
+ decryptedBook?.AppleTags?.SetCoverArt(coverArt);
+ decryptedBook?.Save();
+ decryptedBook?.Close();
- //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();
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);
}
- ///
- /// Copy all aacx metadata to m4b file, including cover art.
- ///
- 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()
{
try
@@ -273,7 +215,7 @@ namespace AaxDecrypter
{
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)
{
@@ -292,7 +234,7 @@ namespace AaxDecrypter
public void Cancel()
{
isCanceled = true;
- aaxcProcesser.Cancel();
+ aaxFile?.Cancel();
}
}
}
diff --git a/AaxDecrypter/AaxcTagLibFile.cs b/AaxDecrypter/AaxcTagLibFile.cs
deleted file mode 100644
index 44461dfd..00000000
--- a/AaxDecrypter/AaxcTagLibFile.cs
+++ /dev/null
@@ -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))
- {
- }
- ///
- /// 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.
- ///
- /// File from which tags will be coppied.
- public void CopyTagsFrom(AaxcTagLibFile sourceFile)
- {
- AppleTags.Clear();
-
- foreach (var stag in sourceFile.AppleTags)
- {
- AppleTags.SetData(stag.BoxType, stag.Children.Cast().ToArray());
- }
- }
- public void AddPicture(byte[] coverArt)
- {
- AppleTags.SetData("covr", coverArt, 0);
- }
- }
-}
diff --git a/AaxDecrypter/Chapters.cs b/AaxDecrypter/Chapters.cs
deleted file mode 100644
index 490de153..00000000
--- a/AaxDecrypter/Chapters.cs
+++ /dev/null
@@ -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 _chapterList = new List();
- public IEnumerable 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(),
- c["start_time"].Value(),
- c["end_time"].Value()
- ));
-
- _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;
- }
- }
-}
diff --git a/AaxDecrypter/Cue.cs b/AaxDecrypter/Cue.cs
index 8f492f66..44c6fc7c 100644
--- a/AaxDecrypter/Cue.cs
+++ b/AaxDecrypter/Cue.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
using System.Text;
+using AAXClean;
using Dinah.Core;
namespace AaxDecrypter
diff --git a/AaxDecrypter/DownloadLicense.cs b/AaxDecrypter/DownloadLicense.cs
index 633e8bf2..30e6cb3d 100644
--- a/AaxDecrypter/DownloadLicense.cs
+++ b/AaxDecrypter/DownloadLicense.cs
@@ -1,4 +1,5 @@
-using Dinah.Core;
+using AAXClean;
+using Dinah.Core;
namespace AaxDecrypter
{
diff --git a/AaxDecrypter/FFMpegAaxcProcesser.cs b/AaxDecrypter/FFMpegAaxcProcesser.cs
deleted file mode 100644
index 403eb87f..00000000
--- a/AaxDecrypter/FFMpegAaxcProcesser.cs
+++ /dev/null
@@ -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; }
- }
-
- ///
- /// Download audible aaxc, decrypt, and remux with chapters.
- ///
- class FFMpegAaxcProcesser
- {
- public event EventHandler 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;
- }
- }
-}
\ No newline at end of file
diff --git a/AaxDecrypter/NFO.cs b/AaxDecrypter/NFO.cs
index f2bcd8c2..64f601fd 100644
--- a/AaxDecrypter/NFO.cs
+++ b/AaxDecrypter/NFO.cs
@@ -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;
}
diff --git a/AaxDecrypter/NetworkFileStream.cs b/AaxDecrypter/NetworkFileStream.cs
index 362d5805..f3d6091b 100644
--- a/AaxDecrypter/NetworkFileStream.cs
+++ b/AaxDecrypter/NetworkFileStream.cs
@@ -176,7 +176,7 @@ namespace AaxDecrypter
///
/// Begins downloading to in a background thread.
///
- 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);
}
diff --git a/FileLiberator/DownloadDecryptBook.cs b/FileLiberator/DownloadDecryptBook.cs
index 9a9270d7..fe88d1a2 100644
--- a/FileLiberator/DownloadDecryptBook.cs
+++ b/FileLiberator/DownloadDecryptBook.cs
@@ -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]");
diff --git a/Libation.sln b/Libation.sln
index 180dc899..9de9110b 100644
--- a/Libation.sln
+++ b/Libation.sln
@@ -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}
diff --git a/LibationLauncher/LibationLauncher.csproj b/LibationLauncher/LibationLauncher.csproj
index 48d9ee3d..7284be1f 100644
--- a/LibationLauncher/LibationLauncher.csproj
+++ b/LibationLauncher/LibationLauncher.csproj
@@ -1,4 +1,4 @@
-
+