using System;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace AaxDecrypter
{
///
/// Download audible aaxc, decrypt, remux,and add metadata.
///
class FFMpegAaxcProcesser
{
public event EventHandler ProgressUpdate;
public string FFMpegPath { get; }
public bool IsRunning { get; private set; }
public bool Succeeded { get; private set; }
private static Regex processedTimeRegex = new Regex("time=(\\d{2}):(\\d{2}):(\\d{2}).\\d{2}", RegexOptions.IgnoreCase | RegexOptions.Compiled);
public FFMpegAaxcProcesser(string ffmpegPath)
{
FFMpegPath = ffmpegPath;
}
public async Task ProcessBook(string aaxcUrl, string userAgent, string audibleKey, string audibleIV, string outputFile)
{
//This process gets the aaxc from the url and streams the decrypted
//m4b to the output file. Preserves album art, and ignores metadata.
var StartInfo = new ProcessStartInfo
{
FileName = FFMpegPath,
RedirectStandardError = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(FFMpegPath),
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,
"-c", //audio codec
"copy", //copy stream
"-f", //force output format: adts
"mp4",
outputFile, //pipe output to standard output
"-y"
}
};
await ProcessBool(StartInfo);
}
public async Task ProcessBook(string aaxcUrl, string userAgent, string audibleKey, string audibleIV, string metadataPath, string outputFile)
{
//This process gets the aaxc from the url and streams the decrypted
//m4b to the output file. Preserves album art, but replaces metadata.
var StartInfo = new ProcessStartInfo
{
FileName = FFMpegPath,
RedirectStandardError = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(FFMpegPath),
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)
{
var downloader = new Process
{
StartInfo = startInfo
};
IsRunning = true;
downloader.ErrorDataReceived += Remuxer_ErrorDataReceived;
downloader.Start();
downloader.BeginErrorReadLine();
//All the work done here. Copy download standard output into
//remuxer standard input
await downloader.WaitForExitAsync();
IsRunning = false;
Succeeded = downloader.ExitCode == 0;
}
private void Remuxer_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data))
return;
if (processedTimeRegex.IsMatch(e.Data))
{
//get timestamp of of last processed audio stream position
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);
ProgressUpdate?.Invoke(sender, position);
}
if (e.Data.Contains("aac bitstream error"))
{
var process = sender as Process;
process.Kill();
}
}
}
}