Fixed ffmpeg cover art not showing on android as discussed.

This commit is contained in:
Michael Bucari-Tovo 2021-06-28 11:19:03 -06:00
parent b53aabe0e3
commit f148650e57
2 changed files with 150 additions and 71 deletions

View File

@ -132,8 +132,7 @@ namespace AaxDecrypter
public bool Step2_DownloadAndCombine()
{
var aaxcProcesser = new FFMpegAaxcProcesser(DecryptSupportLibraries.ffmpegPath);
var aaxcProcesser = new FFMpegAaxcProcesser(downloadLicense);
aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;
string metadataPath = null;
@ -147,10 +146,6 @@ namespace AaxDecrypter
}
aaxcProcesser.ProcessBook(
downloadLicense.DownloadUrl,
downloadLicense.UserAgent,
downloadLicense.AudibleKey,
downloadLicense.AudibleIV,
outputFileName,
metadataPath)
.GetAwaiter()
@ -172,9 +167,8 @@ namespace AaxDecrypter
}
/// <summary>
/// Copy all aacx metadata to m4b file.
/// Copy all aacx metadata to m4b file, including cover art.
/// </summary>
/// <returns></returns>
public bool Step3_RestoreMetadata()
{
var outFile = new TagLib.Mpeg4.File(outputFileName, TagLib.ReadStyle.Average);
@ -186,6 +180,8 @@ namespace AaxDecrypter
//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());

View File

@ -7,96 +7,96 @@ using System.Threading.Tasks;
namespace AaxDecrypter
{
/// <summary>
/// Download audible aaxc, decrypt, remux,and add metadata.
/// Download audible aaxc, decrypt, and remux with chapters.
/// </summary>
class FFMpegAaxcProcesser
{
public event EventHandler<TimeSpan> ProgressUpdate;
public string FFMpegPath { get; }
public DownloadLicense DownloadLicense { get; }
public bool IsRunning { get; private set; }
public bool Succeeded { get; private set; }
public string FFMpegStandardError => ffmpegError.ToString();
public string FFMpegRemuxerStandardError => remuxerError.ToString();
public string FFMpegDownloaderStandardError => downloaderError.ToString();
private StringBuilder ffmpegError = new StringBuilder();
private StringBuilder remuxerError = 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);
public FFMpegAaxcProcesser(string ffmpegPath)
public FFMpegAaxcProcesser( DownloadLicense downloadLicense)
{
FFMpegPath = ffmpegPath;
FFMpegPath = DecryptSupportLibraries.ffmpegPath;
DownloadLicense = downloadLicense;
}
public async Task ProcessBook(string aaxcUrl, string userAgent, string audibleKey, string audibleIV, string outputFile, string metadataPath = null)
public async Task ProcessBook(string outputFile, string ffmetaChaptersPath = 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)
{
//aac stream to standard output
var downloader = new Process
{
StartInfo = startInfo
StartInfo = getDownloaderStartInfo()
};
//This process retreves an aac stream from standard input and muxes
// it into an m4b along with the cover art and metadata.
var remuxer = new Process
{
StartInfo = getRemuxerStartInfo(outputFile, ffmetaChaptersPath)
};
IsRunning = true;
downloader.ErrorDataReceived += Remuxer_ErrorDataReceived;
downloader.ErrorDataReceived += Downloader_ErrorDataReceived;
downloader.Start();
downloader.BeginErrorReadLine();
remuxer.ErrorDataReceived += Remuxer_ErrorDataReceived;
remuxer.Start();
remuxer.BeginErrorReadLine();
var pipedOutput = downloader.StandardOutput.BaseStream;
var pipedInput = remuxer.StandardInput.BaseStream;
//All the work done here. Copy download standard output into
//remuxer standard input
await downloader.WaitForExitAsync();
await Task.Run(() =>
{
int lastRead = 0;
byte[] buffer = new byte[32 * 1024];
do
{
lastRead = pipedOutput.Read(buffer, 0, buffer.Length);
pipedInput.Write(buffer, 0, lastRead);
} while (lastRead > 0 && !remuxer.HasExited);
});
//Closing input stream terminates remuxer
pipedInput.Close();
//If the remuxer exited due to failure, downloader will still have
//data in the pipe. Force kill downloader to continue.
if (remuxer.HasExited && !downloader.HasExited)
downloader.Kill();
remuxer.WaitForExit();
downloader.WaitForExit();
IsRunning = false;
Succeeded = downloader.ExitCode == 0;
Succeeded = downloader.ExitCode == 0 && remuxer.ExitCode == 0;
}
private void Downloader_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
if (string.IsNullOrEmpty(e.Data))
return;
downloaderError.AppendLine(e.Data);
}
private void Remuxer_ErrorDataReceived(object sender, DataReceivedEventArgs e)
@ -104,7 +104,7 @@ namespace AaxDecrypter
if (string.IsNullOrEmpty(e.Data))
return;
ffmpegError.AppendLine(e.Data);
remuxerError.AppendLine(e.Data);
if (processedTimeRegex.IsMatch(e.Data))
{
@ -122,9 +122,92 @@ namespace AaxDecrypter
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,
RedirectStandardOutput = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(FFMpegPath),
ArgumentList ={
"-nostdin",
"-audible_key",
DownloadLicense.AudibleKey,
"-audible_iv",
DownloadLicense.AudibleIV,
"-user_agent",
DownloadLicense.UserAgent, //user-agent is requied for CDN to serve the file
"-i",
DownloadLicense.DownloadUrl,
"-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:");
if (ffmetaChaptersPath is null)
{
//copy metadata from aaxc file.
startInfo.ArgumentList.Add("-user_agent");
startInfo.ArgumentList.Add(DownloadLicense.UserAgent);
startInfo.ArgumentList.Add("-i");
startInfo.ArgumentList.Add(DownloadLicense.DownloadUrl);
}
else
{
//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 1 (either metadata file or aaxc 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;
}
}
}