Fixed ffmpeg cover art not showing on android as discussed.
This commit is contained in:
parent
b53aabe0e3
commit
f148650e57
@ -132,8 +132,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
public bool Step2_DownloadAndCombine()
|
public bool Step2_DownloadAndCombine()
|
||||||
{
|
{
|
||||||
|
var aaxcProcesser = new FFMpegAaxcProcesser(downloadLicense);
|
||||||
var aaxcProcesser = new FFMpegAaxcProcesser(DecryptSupportLibraries.ffmpegPath);
|
|
||||||
aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;
|
aaxcProcesser.ProgressUpdate += AaxcProcesser_ProgressUpdate;
|
||||||
|
|
||||||
string metadataPath = null;
|
string metadataPath = null;
|
||||||
@ -147,10 +146,6 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
|
|
||||||
aaxcProcesser.ProcessBook(
|
aaxcProcesser.ProcessBook(
|
||||||
downloadLicense.DownloadUrl,
|
|
||||||
downloadLicense.UserAgent,
|
|
||||||
downloadLicense.AudibleKey,
|
|
||||||
downloadLicense.AudibleIV,
|
|
||||||
outputFileName,
|
outputFileName,
|
||||||
metadataPath)
|
metadataPath)
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
@ -172,9 +167,8 @@ namespace AaxDecrypter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Copy all aacx metadata to m4b file.
|
/// Copy all aacx metadata to m4b file, including cover art.
|
||||||
/// </summary>
|
/// </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);
|
||||||
@ -186,6 +180,8 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
//copy all metadata fields in the source file, even those that TagLib doesn't
|
//copy all metadata fields in the source file, even those that TagLib doesn't
|
||||||
//recognize, to the output file.
|
//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)
|
foreach (var stag in sourceTag)
|
||||||
{
|
{
|
||||||
destTags.SetData(stag.BoxType, stag.Children.Cast<TagLib.Mpeg4.AppleDataBox>().ToArray());
|
destTags.SetData(stag.BoxType, stag.Children.Cast<TagLib.Mpeg4.AppleDataBox>().ToArray());
|
||||||
|
|||||||
@ -7,96 +7,96 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
namespace AaxDecrypter
|
namespace AaxDecrypter
|
||||||
{
|
{
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Download audible aaxc, decrypt, remux,and add metadata.
|
/// Download audible aaxc, decrypt, and remux with chapters.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
class FFMpegAaxcProcesser
|
class FFMpegAaxcProcesser
|
||||||
{
|
{
|
||||||
public event EventHandler<TimeSpan> ProgressUpdate;
|
public event EventHandler<TimeSpan> ProgressUpdate;
|
||||||
public string FFMpegPath { get; }
|
public string FFMpegPath { get; }
|
||||||
|
public DownloadLicense DownloadLicense { 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();
|
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);
|
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
|
//This process gets the aaxc from the url and streams the decrypted
|
||||||
//m4b to the output file.
|
//aac stream to standard output
|
||||||
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
|
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;
|
IsRunning = true;
|
||||||
|
|
||||||
downloader.ErrorDataReceived += Remuxer_ErrorDataReceived;
|
downloader.ErrorDataReceived += Downloader_ErrorDataReceived;
|
||||||
downloader.Start();
|
downloader.Start();
|
||||||
downloader.BeginErrorReadLine();
|
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
|
//All the work done here. Copy download standard output into
|
||||||
//remuxer standard input
|
//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;
|
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)
|
private void Remuxer_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
||||||
@ -104,7 +104,7 @@ namespace AaxDecrypter
|
|||||||
if (string.IsNullOrEmpty(e.Data))
|
if (string.IsNullOrEmpty(e.Data))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
ffmpegError.AppendLine(e.Data);
|
remuxerError.AppendLine(e.Data);
|
||||||
|
|
||||||
if (processedTimeRegex.IsMatch(e.Data))
|
if (processedTimeRegex.IsMatch(e.Data))
|
||||||
{
|
{
|
||||||
@ -122,9 +122,92 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
if (e.Data.Contains("aac bitstream error"))
|
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;
|
var process = sender as Process;
|
||||||
process.Kill();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user