Improve comvert to mp3 task
- Improve progress reporting and cancellation performance - Clear current book from queue before queueing single convert to mp3 task
This commit is contained in:
parent
accedeb1b1
commit
b27325cdcb
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AAXClean;
|
using AAXClean;
|
||||||
using AAXClean.Codecs;
|
using AAXClean.Codecs;
|
||||||
@ -19,7 +20,13 @@ namespace FileLiberator
|
|||||||
private readonly AaxDecrypter.AverageSpeed averageSpeed = new();
|
private readonly AaxDecrypter.AverageSpeed averageSpeed = new();
|
||||||
private static string Mp3FileName(string m4bPath) => Path.ChangeExtension(m4bPath ?? "", ".mp3");
|
private static string Mp3FileName(string m4bPath) => Path.ChangeExtension(m4bPath ?? "", ".mp3");
|
||||||
|
|
||||||
public override Task CancelAsync() => Mp4Operation?.CancelAsync() ?? Task.CompletedTask;
|
private CancellationTokenSource CancellationTokenSource { get; set; }
|
||||||
|
public override async Task CancelAsync()
|
||||||
|
{
|
||||||
|
await CancellationTokenSource.CancelAsync();
|
||||||
|
if (Mp4Operation is not null)
|
||||||
|
await Mp4Operation.CancelAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public static bool ValidateMp3(LibraryBook libraryBook)
|
public static bool ValidateMp3(LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
@ -32,17 +39,29 @@ namespace FileLiberator
|
|||||||
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
public override async Task<StatusHandler> ProcessAsync(LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
OnBegin(libraryBook);
|
OnBegin(libraryBook);
|
||||||
|
var cancellationToken = (CancellationTokenSource = new()).Token;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var m4bPaths = AudibleFileStorage.Audio.GetPaths(libraryBook.Book.AudibleProductId);
|
var m4bPaths = AudibleFileStorage.Audio.GetPaths(libraryBook.Book.AudibleProductId)
|
||||||
|
.Where(m4bPath => File.Exists(m4bPath))
|
||||||
|
.Select(m4bPath => new { m4bPath, proposedMp3Path = Mp3FileName(m4bPath), m4bSize = new FileInfo(m4bPath).Length })
|
||||||
|
.Where(p => !File.Exists(p.proposedMp3Path))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
foreach (var m4bPath in m4bPaths)
|
long totalInputSize = m4bPaths.Sum(p => p.m4bSize);
|
||||||
|
long sizeOfCompletedFiles = 0L;
|
||||||
|
foreach (var entry in m4bPaths)
|
||||||
{
|
{
|
||||||
var proposedMp3Path = Mp3FileName(m4bPath);
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
if (File.Exists(proposedMp3Path) || !File.Exists(m4bPath)) continue;
|
if (File.Exists(entry.proposedMp3Path) || !File.Exists(entry.m4bPath))
|
||||||
|
{
|
||||||
|
sizeOfCompletedFiles += entry.m4bSize;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var m4bBook = await Task.Run(() => new Mp4File(m4bPath, FileAccess.Read));
|
using var m4bFileStream = File.Open(entry.m4bPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
var m4bBook = new Mp4File(m4bFileStream);
|
||||||
|
|
||||||
//AAXClean.Codecs only supports decoding AAC and E-AC-3 audio.
|
//AAXClean.Codecs only supports decoding AAC and E-AC-3 audio.
|
||||||
if (m4bBook.AudioSampleEntry.Esds is null && m4bBook.AudioSampleEntry.Dec3 is null)
|
if (m4bBook.AudioSampleEntry.Esds is null && m4bBook.AudioSampleEntry.Dec3 is null)
|
||||||
@ -69,74 +88,85 @@ namespace FileLiberator
|
|||||||
lameConfig.ID3.Track = trackCount > 0 ? $"{trackNum}/{trackCount}" : trackNum.ToString();
|
lameConfig.ID3.Track = trackCount > 0 ? $"{trackNum}/{trackCount}" : trackNum.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
using var mp3File = File.Open(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite);
|
long currentFileNumBytesProcessed = 0;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Mp4Operation = m4bBook.ConvertToMp3Async(mp3File, lameConfig, chapters);
|
var tempPath = Path.GetTempFileName();
|
||||||
Mp4Operation.ConversionProgressUpdate += M4bBook_ConversionProgressUpdate;
|
using (var mp3File = File.Open(tempPath, FileMode.OpenOrCreate, FileAccess.ReadWrite))
|
||||||
await Mp4Operation;
|
|
||||||
|
|
||||||
if (Mp4Operation.IsCanceled)
|
|
||||||
{
|
{
|
||||||
FileUtility.SaferDelete(mp3File.Name);
|
Mp4Operation = m4bBook.ConvertToMp3Async(mp3File, lameConfig, chapters);
|
||||||
return new StatusHandler { "Cancelled" };
|
Mp4Operation.ConversionProgressUpdate += m4bBook_ConversionProgressUpdate;
|
||||||
|
await Mp4Operation;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
if (cancellationToken.IsCancellationRequested)
|
||||||
var realMp3Path
|
FileUtility.SaferDelete(tempPath);
|
||||||
|
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var realMp3Path
|
||||||
= FileUtility.SaferMoveToValidPath(
|
= FileUtility.SaferMoveToValidPath(
|
||||||
mp3File.Name,
|
tempPath,
|
||||||
proposedMp3Path,
|
entry.proposedMp3Path,
|
||||||
Configuration.Instance.ReplacementCharacters,
|
Configuration.Instance.ReplacementCharacters,
|
||||||
extension: "mp3",
|
extension: "mp3",
|
||||||
Configuration.Instance.OverwriteExisting);
|
Configuration.Instance.OverwriteExisting);
|
||||||
|
|
||||||
SetFileTime(libraryBook, realMp3Path);
|
SetFileTime(libraryBook, realMp3Path);
|
||||||
SetDirectoryTime(libraryBook, Path.GetDirectoryName(realMp3Path));
|
SetDirectoryTime(libraryBook, Path.GetDirectoryName(realMp3Path));
|
||||||
|
OnFileCreated(libraryBook, realMp3Path);
|
||||||
OnFileCreated(libraryBook, realMp3Path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Serilog.Log.Error(ex, "AAXClean error");
|
|
||||||
return new StatusHandler { "Conversion failed" };
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (Mp4Operation is not null)
|
if (Mp4Operation is not null)
|
||||||
Mp4Operation.ConversionProgressUpdate -= M4bBook_ConversionProgressUpdate;
|
Mp4Operation.ConversionProgressUpdate -= m4bBook_ConversionProgressUpdate;
|
||||||
|
|
||||||
m4bBook.InputStream.Close();
|
sizeOfCompletedFiles += entry.m4bSize;
|
||||||
mp3File.Close();
|
}
|
||||||
|
void m4bBook_ConversionProgressUpdate(object sender, ConversionProgressEventArgs e)
|
||||||
|
{
|
||||||
|
currentFileNumBytesProcessed = (long)(e.FractionCompleted * entry.m4bSize);
|
||||||
|
var bytesCompleted = sizeOfCompletedFiles + currentFileNumBytesProcessed;
|
||||||
|
ConversionProgressUpdate(totalInputSize, bytesCompleted);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return new StatusHandler();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
Serilog.Log.Error(ex, "AAXClean error");
|
||||||
|
return new StatusHandler { "Conversion failed" };
|
||||||
|
}
|
||||||
|
return new StatusHandler { "Cancelled" };
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
OnCompleted(libraryBook);
|
OnCompleted(libraryBook);
|
||||||
|
CancellationTokenSource.Dispose();
|
||||||
|
CancellationTokenSource = null;
|
||||||
}
|
}
|
||||||
return new StatusHandler();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void M4bBook_ConversionProgressUpdate(object sender, ConversionProgressEventArgs e)
|
private void ConversionProgressUpdate(long totalInputSize, long bytesCompleted)
|
||||||
{
|
{
|
||||||
averageSpeed.AddPosition(e.ProcessPosition.TotalSeconds);
|
averageSpeed.AddPosition(bytesCompleted);
|
||||||
|
|
||||||
var remainingTimeToProcess = (e.EndTime - e.ProcessPosition).TotalSeconds;
|
var remainingBytes = (totalInputSize - bytesCompleted);
|
||||||
var estTimeRemaining = remainingTimeToProcess / averageSpeed.Average;
|
var estTimeRemaining = remainingBytes / averageSpeed.Average;
|
||||||
|
|
||||||
if (double.IsNormal(estTimeRemaining))
|
if (double.IsNormal(estTimeRemaining))
|
||||||
OnStreamingTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining));
|
OnStreamingTimeRemaining(TimeSpan.FromSeconds(estTimeRemaining));
|
||||||
|
|
||||||
double progressPercent = 100 * e.FractionCompleted;
|
double progressPercent = 100 * bytesCompleted / totalInputSize;
|
||||||
|
|
||||||
OnStreamingProgressChanged(
|
OnStreamingProgressChanged(
|
||||||
new DownloadProgress
|
new DownloadProgress
|
||||||
{
|
{
|
||||||
ProgressPercentage = progressPercent,
|
ProgressPercentage = progressPercent,
|
||||||
BytesReceived = (long)(e.ProcessPosition - e.StartTime).TotalSeconds,
|
BytesReceived = bytesCompleted,
|
||||||
TotalBytesToReceive = (long)(e.EndTime - e.StartTime).TotalSeconds
|
TotalBytesToReceive = totalInputSize
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -111,6 +111,8 @@ public class ProcessQueueViewModel : ReactiveObject
|
|||||||
var preLiberated = libraryBooks.Where(lb => !lb.AbsentFromLastScan && lb.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated && lb.Book.ContentType is DataLayer.ContentType.Product).ToArray();
|
var preLiberated = libraryBooks.Where(lb => !lb.AbsentFromLastScan && lb.Book.UserDefinedItem.BookStatus is LiberatedStatus.Liberated && lb.Book.ContentType is DataLayer.ContentType.Product).ToArray();
|
||||||
if (preLiberated.Length > 0)
|
if (preLiberated.Length > 0)
|
||||||
{
|
{
|
||||||
|
if (preLiberated.Length == 1)
|
||||||
|
RemoveCompleted(preLiberated[0]);
|
||||||
Serilog.Log.Logger.Information("Begin convert {count} books to mp3", preLiberated.Length);
|
Serilog.Log.Logger.Information("Begin convert {count} books to mp3", preLiberated.Length);
|
||||||
AddConvertMp3(preLiberated);
|
AddConvertMp3(preLiberated);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user