Libation/AaxDecrypter/FFMpegAaxcProcesser.cs
2021-06-27 18:24:44 -06:00

131 lines
4.7 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace AaxDecrypter
{
/// <summary>
/// Download audible aaxc, decrypt, remux,and add metadata.
/// </summary>
class FFMpegAaxcProcesser
{
public event EventHandler<TimeSpan> ProgressUpdate;
public string FFMpegPath { get; }
public bool IsRunning { 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);
public FFMpegAaxcProcesser(string ffmpegPath)
{
FFMpegPath = ffmpegPath;
}
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
//m4b to the output file.
var StartInfo = new ProcessStartInfo
{
FileName = FFMpegPath,
RedirectStandardError = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(FFMpegPath),
};
if (metadataPath != null)
{
StartInfo.ArgumentList.Add("-ignore_chapters"); //prevents ffmpeg from copying chapter info from aaxc to output file
StartInfo.ArgumentList.Add("true");
}
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);
if (metadataPath != null)
{
StartInfo.ArgumentList.Add("-f");
StartInfo.ArgumentList.Add("ffmetadata");
StartInfo.ArgumentList.Add("-i");
StartInfo.ArgumentList.Add(metadataPath);
StartInfo.ArgumentList.Add("-map_metadata");
StartInfo.ArgumentList.Add("1");
}
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
{
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;
ffmpegError.AppendLine(e.Data);
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();
}
}
}
}