Modified ffmpeg arguments and added argument checks.

This commit is contained in:
Michael Bucari-Tovo 2021-06-27 18:24:44 -06:00
parent e32a39085f
commit b53aabe0e3
4 changed files with 84 additions and 76 deletions

View File

@ -46,9 +46,8 @@ namespace AaxDecrypter
private TagLib.Mpeg4.File aaxcTagLib { get; set; } 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 string metadataPath => Path.Combine(outDir, Path.GetFileName(outputFileName) + ".ffmeta");
public static async Task<AaxcDownloadConverter> CreateAsync(string outDirectory, DownloadLicense dlLic, ChapterInfo chapters) public static async Task<AaxcDownloadConverter> CreateAsync(string outDirectory, DownloadLicense dlLic, ChapterInfo chapters = null)
{ {
var converter = new AaxcDownloadConverter(outDirectory, dlLic, chapters); var converter = new AaxcDownloadConverter(outDirectory, dlLic, chapters);
await converter.prelimProcessing(); await converter.prelimProcessing();
@ -59,7 +58,6 @@ namespace AaxDecrypter
{ {
ArgumentValidator.EnsureNotNullOrWhiteSpace(outDirectory, nameof(outDirectory)); ArgumentValidator.EnsureNotNullOrWhiteSpace(outDirectory, nameof(outDirectory));
ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic)); ArgumentValidator.EnsureNotNull(dlLic, nameof(dlLic));
ArgumentValidator.EnsureNotNull(chapters, nameof(chapters));
if (!Directory.Exists(outDirectory)) if (!Directory.Exists(outDirectory))
throw new ArgumentNullException(nameof(outDirectory), "Directory does not exist"); throw new ArgumentNullException(nameof(outDirectory), "Directory does not exist");
@ -134,27 +132,35 @@ namespace AaxDecrypter
public bool Step2_DownloadAndCombine() public bool Step2_DownloadAndCombine()
{ {
var ffmetaHeader = $";FFMETADATA1\n";
File.WriteAllText(metadataPath, ffmetaHeader + chapters.ToFFMeta());
var aaxcProcesser = new FFMpegAaxcProcesser(DecryptSupportLibraries.ffmpegPath); var aaxcProcesser = new FFMpegAaxcProcesser(DecryptSupportLibraries.ffmpegPath);
aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate; aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;
string metadataPath = null;
if (chapters != null)
{
//Only write chaopters to the metadata file. All other aaxc metadata will be
//wiped out but is restored in Step 3.
metadataPath = Path.Combine(outDir, Path.GetFileName(outputFileName) + ".ffmeta");
File.WriteAllText(metadataPath, chapters.ToFFMeta(true));
}
aaxcProcesser.ProcessBook( aaxcProcesser.ProcessBook(
downloadLicense.DownloadUrl, downloadLicense.DownloadUrl,
downloadLicense.UserAgent, downloadLicense.UserAgent,
downloadLicense.AudibleKey, downloadLicense.AudibleKey,
downloadLicense.AudibleIV, downloadLicense.AudibleIV,
metadataPath, outputFileName,
outputFileName) metadataPath)
.GetAwaiter() .GetAwaiter()
.GetResult(); .GetResult();
if (chapters != null)
FileExt.SafeDelete(metadataPath);
DecryptProgressUpdate?.Invoke(this, 0); DecryptProgressUpdate?.Invoke(this, 0);
FileExt.SafeDelete(metadataPath);
return aaxcProcesser.Succeeded; return aaxcProcesser.Succeeded;
} }
@ -165,6 +171,10 @@ namespace AaxDecrypter
DecryptProgressUpdate?.Invoke(this, (int)progressPercent); DecryptProgressUpdate?.Invoke(this, (int)progressPercent);
} }
/// <summary>
/// Copy all aacx metadata to m4b file.
/// </summary>
/// <returns></returns>
public bool Step3_RestoreMetadata() public bool Step3_RestoreMetadata()
{ {
var outFile = new TagLib.Mpeg4.File(outputFileName, TagLib.ReadStyle.Average); var outFile = new TagLib.Mpeg4.File(outputFileName, TagLib.ReadStyle.Average);

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using Dinah.Core;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -11,11 +12,16 @@ namespace AaxDecrypter
public int Count => _chapterList.Count; public int Count => _chapterList.Count;
public void AddChapter(Chapter chapter) public void AddChapter(Chapter chapter)
{ {
ArgumentValidator.EnsureNotNull(chapter, nameof(chapter));
_chapterList.Add(chapter); _chapterList.Add(chapter);
} }
public string ToFFMeta() public string ToFFMeta(bool includeFFMetaHeader)
{ {
var ffmetaChapters = new StringBuilder(); var ffmetaChapters = new StringBuilder();
if (includeFFMetaHeader)
ffmetaChapters.AppendLine(";FFMETADATA1\n");
foreach (var c in Chapters) foreach (var c in Chapters)
{ {
ffmetaChapters.AppendLine(c.ToFFMeta()); ffmetaChapters.AppendLine(c.ToFFMeta());
@ -30,6 +36,10 @@ namespace AaxDecrypter
public long EndOffsetMs { get; } public long EndOffsetMs { get; }
public Chapter(string title, long startOffsetMs, long lengthMs) public Chapter(string title, long startOffsetMs, long lengthMs)
{ {
ArgumentValidator.EnsureNotNullOrEmpty(title, nameof(title));
ArgumentValidator.EnsureGreaterThan(startOffsetMs, nameof(startOffsetMs), -1);
ArgumentValidator.EnsureGreaterThan(lengthMs, nameof(lengthMs), 0);
Title = title; Title = title;
StartOffsetMs = startOffsetMs; StartOffsetMs = startOffsetMs;
EndOffsetMs = StartOffsetMs + lengthMs; EndOffsetMs = StartOffsetMs + lengthMs;

View File

@ -1,4 +1,5 @@
using System; using Dinah.Core;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -15,6 +16,11 @@ namespace AaxDecrypter
public DownloadLicense(string downloadUrl, string audibleKey, string audibleIV, string userAgent) public DownloadLicense(string downloadUrl, string audibleKey, string audibleIV, string userAgent)
{ {
ArgumentValidator.EnsureNotNullOrEmpty(downloadUrl, nameof(downloadUrl));
ArgumentValidator.EnsureNotNullOrEmpty(audibleKey, nameof(audibleKey));
ArgumentValidator.EnsureNotNullOrEmpty(audibleIV, nameof(audibleIV));
ArgumentValidator.EnsureNotNullOrEmpty(userAgent, nameof(userAgent));
DownloadUrl = downloadUrl; DownloadUrl = downloadUrl;
AudibleKey = audibleKey; AudibleKey = audibleKey;
AudibleIV = audibleIV; AudibleIV = audibleIV;

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -15,17 +16,20 @@ namespace AaxDecrypter
public string FFMpegPath { get; } public string FFMpegPath { get; }
public bool IsRunning { get; private set; } public bool IsRunning { get; private set; }
public bool Succeeded { get; private set; } public bool Succeeded { get; private set; }
public string FFMpegStandardError => ffmpegError.ToString();
private StringBuilder ffmpegError = 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}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public FFMpegAaxcProcesser(string ffmpegPath) public FFMpegAaxcProcesser(string ffmpegPath)
{ {
FFMpegPath = ffmpegPath; FFMpegPath = ffmpegPath;
} }
public async Task ProcessBook(string aaxcUrl, string userAgent, string audibleKey, string audibleIV, string outputFile) public async Task ProcessBook(string aaxcUrl, string userAgent, string audibleKey, string audibleIV, string outputFile, string metadataPath = null)
{ {
//This process gets the aaxc from the url and streams the decrypted //This process gets the aaxc from the url and streams the decrypted
//m4b to the output file. Preserves album art, and ignores metadata. //m4b to the output file.
var StartInfo = new ProcessStartInfo var StartInfo = new ProcessStartInfo
{ {
FileName = FFMpegPath, FileName = FFMpegPath,
@ -34,71 +38,47 @@ namespace AaxDecrypter
WindowStyle = ProcessWindowStyle.Hidden, WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false, UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(FFMpegPath), WorkingDirectory = Path.GetDirectoryName(FFMpegPath),
ArgumentList =
{
"-audible_key",
audibleKey,
"-audible_iv",
audibleIV,
"-user_agent",
userAgent,
"-i",
aaxcUrl,
"-c", //audio codec
"copy", //copy stream
"-f", //force output format: adts
"mp4",
outputFile, //pipe output to standard output
"-y"
}
}; };
await ProcessBool(StartInfo); if (metadataPath != null)
{
StartInfo.ArgumentList.Add("-ignore_chapters"); //prevents ffmpeg from copying chapter info from aaxc to output file
StartInfo.ArgumentList.Add("true");
} }
public async Task ProcessBook(string aaxcUrl, string userAgent, string audibleKey, string audibleIV, string metadataPath, string outputFile) StartInfo.ArgumentList.Add("-audible_key");
{ StartInfo.ArgumentList.Add(audibleKey);
StartInfo.ArgumentList.Add("-audible_iv");
StartInfo.ArgumentList.Add(audibleIV);
StartInfo.ArgumentList.Add("-user_agent");
StartInfo.ArgumentList.Add(userAgent);
StartInfo.ArgumentList.Add("-i");
StartInfo.ArgumentList.Add(aaxcUrl);
//This process gets the aaxc from the url and streams the decrypted if (metadataPath != null)
//m4b to the output file. Preserves album art, but replaces metadata.
var StartInfo = new ProcessStartInfo
{ {
FileName = FFMpegPath, StartInfo.ArgumentList.Add("-f");
RedirectStandardError = true, StartInfo.ArgumentList.Add("ffmetadata");
CreateNoWindow = true, StartInfo.ArgumentList.Add("-i");
WindowStyle = ProcessWindowStyle.Hidden, StartInfo.ArgumentList.Add(metadataPath);
UseShellExecute = false, StartInfo.ArgumentList.Add("-map_metadata");
WorkingDirectory = Path.GetDirectoryName(FFMpegPath), StartInfo.ArgumentList.Add("1");
ArgumentList =
{
"-ignore_chapters", //prevents ffmpeg from copying chapter info from aaxc to output file
"true",
"-audible_key",
audibleKey,
"-audible_iv",
audibleIV,
"-user_agent",
userAgent,
"-i",
aaxcUrl,
"-f",
"ffmetadata",
"-i",
metadataPath,
"-map_metadata",
"1",
"-c", //audio codec
"copy", //copy stream
"-f", //force output format: adts
"mp4",
outputFile, //pipe output to standard output
"-y"
}
};
await ProcessBool(StartInfo);
} }
private async Task ProcessBool(ProcessStartInfo startInfo) StartInfo.ArgumentList.Add("-c"); //copy all codecs to output
StartInfo.ArgumentList.Add("copy");
StartInfo.ArgumentList.Add("-f"); //force output format: mp4
StartInfo.ArgumentList.Add("mp4");
StartInfo.ArgumentList.Add("-movflags"); //don't add nero format chapter flags
StartInfo.ArgumentList.Add("disable_chpl+faststart");
StartInfo.ArgumentList.Add(outputFile);
StartInfo.ArgumentList.Add("-y"); //overwrite existing file
await ProcessBook(StartInfo);
}
private async Task ProcessBook(ProcessStartInfo startInfo)
{ {
var downloader = new Process var downloader = new Process
{ {
@ -124,6 +104,8 @@ namespace AaxDecrypter
if (string.IsNullOrEmpty(e.Data)) if (string.IsNullOrEmpty(e.Data))
return; return;
ffmpegError.AppendLine(e.Data);
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