Merge pull request #651 from Mbucari/master
Overhaul LibationCli and add Download Quality Option
This commit is contained in:
commit
ecfe0dc033
@ -13,7 +13,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AAXClean.Codecs" Version="1.0.4" />
|
<PackageReference Include="AAXClean.Codecs" Version="1.1.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -51,6 +51,34 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Copyright))
|
if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Copyright))
|
||||||
AaxFile.AppleTags.Copyright = AaxFile.AppleTags.Copyright.Replace("(P)", "℗").Replace("©", "©");
|
AaxFile.AppleTags.Copyright = AaxFile.AppleTags.Copyright.Replace("(P)", "℗").Replace("©", "©");
|
||||||
|
|
||||||
|
//Add audiobook shelf tags
|
||||||
|
//https://github.com/advplyr/audiobookshelf/issues/1794#issuecomment-1565050213
|
||||||
|
const string tagDomain = "com.pilabor.tone";
|
||||||
|
|
||||||
|
AaxFile.AppleTags.Title = DownloadOptions.Title;
|
||||||
|
|
||||||
|
if (DownloadOptions.Subtitle is string subtitle)
|
||||||
|
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "SUBTITLE", subtitle);
|
||||||
|
|
||||||
|
if (DownloadOptions.Publisher is string publisher)
|
||||||
|
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "PUBLISHER", publisher);
|
||||||
|
|
||||||
|
if (DownloadOptions.Language is string language)
|
||||||
|
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "LANGUAGE", language);
|
||||||
|
|
||||||
|
if (DownloadOptions.AudibleProductId is string asin)
|
||||||
|
{
|
||||||
|
AaxFile.AppleTags.Asin = asin;
|
||||||
|
AaxFile.AppleTags.AppleListBox.EditOrAddTag("asin", asin);
|
||||||
|
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "AUDIBLE_ASIN", asin);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (DownloadOptions.SeriesName is string series)
|
||||||
|
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "SERIES", series);
|
||||||
|
|
||||||
|
if (DownloadOptions.SeriesNumber is float part)
|
||||||
|
AaxFile.AppleTags.AppleListBox.EditOrAddFreeformTag(tagDomain, "PART", part.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
//Finishing configuring lame encoder.
|
//Finishing configuring lame encoder.
|
||||||
|
|||||||
@ -21,6 +21,13 @@ namespace AaxDecrypter
|
|||||||
long DownloadSpeedBps { get; }
|
long DownloadSpeedBps { get; }
|
||||||
ChapterInfo ChapterInfo { get; }
|
ChapterInfo ChapterInfo { get; }
|
||||||
bool FixupFile { get; }
|
bool FixupFile { get; }
|
||||||
|
string AudibleProductId { get; }
|
||||||
|
string Title { get; }
|
||||||
|
string Subtitle { get; }
|
||||||
|
string Publisher { get; }
|
||||||
|
string Language { get; }
|
||||||
|
string SeriesName { get; }
|
||||||
|
float? SeriesNumber { get; }
|
||||||
NAudio.Lame.LameConfig LameConfig { get; }
|
NAudio.Lame.LameConfig LameConfig { get; }
|
||||||
bool Downsample { get; }
|
bool Downsample { get; }
|
||||||
bool MatchSourceBitrate { get; }
|
bool MatchSourceBitrate { get; }
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using AAXClean;
|
using AAXClean;
|
||||||
|
using AAXClean.Codecs;
|
||||||
using NAudio.Lame;
|
using NAudio.Lame;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
@ -6,6 +7,7 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
public static class MpegUtil
|
public static class MpegUtil
|
||||||
{
|
{
|
||||||
|
private const string TagDomain = "com.pilabor.tone";
|
||||||
public static void ConfigureLameOptions(Mp4File mp4File, LameConfig lameConfig, bool downsample, bool matchSourceBitrate)
|
public static void ConfigureLameOptions(Mp4File mp4File, LameConfig lameConfig, bool downsample, bool matchSourceBitrate)
|
||||||
{
|
{
|
||||||
double bitrateMultiple = 1;
|
double bitrateMultiple = 1;
|
||||||
@ -36,6 +38,21 @@ namespace AaxDecrypter
|
|||||||
else if (lameConfig.VBR == VBRMode.ABR)
|
else if (lameConfig.VBR == VBRMode.ABR)
|
||||||
lameConfig.ABRRateKbps = kbps;
|
lameConfig.ABRRateKbps = kbps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Setup metadata tags
|
||||||
|
lameConfig.ID3 = mp4File.AppleTags.ToIDTags();
|
||||||
|
|
||||||
|
if (mp4File.AppleTags.AppleListBox.GetFreeformTagString(TagDomain, "SUBTITLE") is string subtitle)
|
||||||
|
lameConfig.ID3.Subtitle = subtitle;
|
||||||
|
|
||||||
|
if (mp4File.AppleTags.AppleListBox.GetFreeformTagString(TagDomain, "LANGUAGE") is string lang)
|
||||||
|
lameConfig.ID3.UserDefinedText.Add("LANGUAGE", lang);
|
||||||
|
|
||||||
|
if (mp4File.AppleTags.AppleListBox.GetFreeformTagString(TagDomain, "SERIES") is string series)
|
||||||
|
lameConfig.ID3.UserDefinedText.Add("SERIES", series);
|
||||||
|
|
||||||
|
if (mp4File.AppleTags.AppleListBox.GetFreeformTagString(TagDomain, "PART") is string part)
|
||||||
|
lameConfig.ID3.UserDefinedText.Add("PART", part);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,6 @@ namespace AaxDecrypter
|
|||||||
public class NetworkFileStream : Stream, IUpdatable
|
public class NetworkFileStream : Stream, IUpdatable
|
||||||
{
|
{
|
||||||
public event EventHandler Updated;
|
public event EventHandler Updated;
|
||||||
public event EventHandler DownloadCompleted;
|
|
||||||
|
|
||||||
#region Public Properties
|
#region Public Properties
|
||||||
|
|
||||||
@ -41,6 +40,9 @@ namespace AaxDecrypter
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public bool IsCancelled => _cancellationSource.IsCancellationRequested;
|
public bool IsCancelled => _cancellationSource.IsCancellationRequested;
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public Task DownloadTask { get; private set; }
|
||||||
|
|
||||||
private long _speedLimit = 0;
|
private long _speedLimit = 0;
|
||||||
/// <summary>bytes per second</summary>
|
/// <summary>bytes per second</summary>
|
||||||
public long SpeedLimit { get => _speedLimit; set => _speedLimit = value <= 0 ? 0 : Math.Max(value, MIN_BYTES_PER_SECOND); }
|
public long SpeedLimit { get => _speedLimit; set => _speedLimit = value <= 0 ? 0 : Math.Max(value, MIN_BYTES_PER_SECOND); }
|
||||||
@ -52,7 +54,6 @@ namespace AaxDecrypter
|
|||||||
private FileStream _readFile { get; }
|
private FileStream _readFile { get; }
|
||||||
private CancellationTokenSource _cancellationSource { get; } = new();
|
private CancellationTokenSource _cancellationSource { get; } = new();
|
||||||
private EventWaitHandle _downloadedPiece { get; set; }
|
private EventWaitHandle _downloadedPiece { get; set; }
|
||||||
private Task _backgroundDownloadTask { get; set; }
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -128,7 +129,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
if (uriToSameFile.Host != Uri.Host)
|
if (uriToSameFile.Host != Uri.Host)
|
||||||
throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}");
|
throw new ArgumentException($"New uri to the same file must have the same host.\r\n Old Host :{Uri.Host}\r\nNew Host: {uriToSameFile.Host}");
|
||||||
if (_backgroundDownloadTask is not null)
|
if (DownloadTask is not null)
|
||||||
throw new InvalidOperationException("Cannot change Uri after download has started.");
|
throw new InvalidOperationException("Cannot change Uri after download has started.");
|
||||||
|
|
||||||
Uri = uriToSameFile;
|
Uri = uriToSameFile;
|
||||||
@ -141,7 +142,7 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
if (ContentLength != 0 && WritePosition == ContentLength)
|
if (ContentLength != 0 && WritePosition == ContentLength)
|
||||||
{
|
{
|
||||||
_backgroundDownloadTask = Task.CompletedTask;
|
DownloadTask = Task.CompletedTask;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,7 +168,8 @@ namespace AaxDecrypter
|
|||||||
_downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
|
_downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
|
||||||
|
|
||||||
//Download the file in the background.
|
//Download the file in the background.
|
||||||
_backgroundDownloadTask = Task.Run(() => DownloadFile(networkStream), _cancellationSource.Token);
|
|
||||||
|
DownloadTask = Task.Run(() => DownloadFile(networkStream), _cancellationSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary> Download <see cref="Uri"/> to <see cref="SaveFilePath"/>.</summary>
|
/// <summary> Download <see cref="Uri"/> to <see cref="SaveFilePath"/>.</summary>
|
||||||
@ -234,7 +236,6 @@ namespace AaxDecrypter
|
|||||||
_writeFile.Close();
|
_writeFile.Close();
|
||||||
_downloadedPiece.Set();
|
_downloadedPiece.Set();
|
||||||
OnUpdate();
|
OnUpdate();
|
||||||
DownloadCompleted?.Invoke(this, null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,7 +257,7 @@ namespace AaxDecrypter
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (_backgroundDownloadTask is null)
|
if (DownloadTask is null)
|
||||||
throw new InvalidOperationException($"Background downloader must first be started by calling {nameof(BeginDownloadingAsync)}");
|
throw new InvalidOperationException($"Background downloader must first be started by calling {nameof(BeginDownloadingAsync)}");
|
||||||
return ContentLength;
|
return ContentLength;
|
||||||
}
|
}
|
||||||
@ -280,7 +281,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
public override int Read(byte[] buffer, int offset, int count)
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
{
|
{
|
||||||
if (_backgroundDownloadTask is null)
|
if (DownloadTask is null)
|
||||||
throw new InvalidOperationException($"Background downloader must first be started by calling {nameof(BeginDownloadingAsync)}");
|
throw new InvalidOperationException($"Background downloader must first be started by calling {nameof(BeginDownloadingAsync)}");
|
||||||
|
|
||||||
var toRead = Math.Min(count, Length - Position);
|
var toRead = Math.Min(count, Length - Position);
|
||||||
@ -306,7 +307,7 @@ namespace AaxDecrypter
|
|||||||
private void WaitToPosition(long requiredPosition)
|
private void WaitToPosition(long requiredPosition)
|
||||||
{
|
{
|
||||||
while (WritePosition < requiredPosition
|
while (WritePosition < requiredPosition
|
||||||
&& _backgroundDownloadTask?.IsCompleted is false
|
&& DownloadTask?.IsCompleted is false
|
||||||
&& !IsCancelled)
|
&& !IsCancelled)
|
||||||
{
|
{
|
||||||
_downloadedPiece.WaitOne(50);
|
_downloadedPiece.WaitOne(50);
|
||||||
@ -326,7 +327,7 @@ namespace AaxDecrypter
|
|||||||
if (disposing && !disposed)
|
if (disposing && !disposed)
|
||||||
{
|
{
|
||||||
_cancellationSource.Cancel();
|
_cancellationSource.Cancel();
|
||||||
_backgroundDownloadTask?.GetAwaiter().GetResult();
|
DownloadTask?.GetAwaiter().GetResult();
|
||||||
_downloadedPiece?.Dispose();
|
_downloadedPiece?.Dispose();
|
||||||
_cancellationSource?.Dispose();
|
_cancellationSource?.Dispose();
|
||||||
_readFile.Dispose();
|
_readFile.Dispose();
|
||||||
|
|||||||
@ -26,11 +26,7 @@ namespace AaxDecrypter
|
|||||||
|
|
||||||
protected override async Task<bool> Step_DownloadAndDecryptAudiobookAsync()
|
protected override async Task<bool> Step_DownloadAndDecryptAudiobookAsync()
|
||||||
{
|
{
|
||||||
TaskCompletionSource completionSource = new();
|
await InputFileStream.DownloadTask;
|
||||||
|
|
||||||
InputFileStream.DownloadCompleted += (_, _) => completionSource.SetResult();
|
|
||||||
|
|
||||||
await completionSource.Task;
|
|
||||||
|
|
||||||
if (IsCanceled)
|
if (IsCanceled)
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -43,21 +43,6 @@ namespace AppScaffolding
|
|||||||
public static ReleaseIdentifier ReleaseIdentifier { get; private set; }
|
public static ReleaseIdentifier ReleaseIdentifier { get; private set; }
|
||||||
public static Variety Variety { get; private set; }
|
public static Variety Variety { get; private set; }
|
||||||
|
|
||||||
public static void SetReleaseIdentifier(Variety varietyType)
|
|
||||||
{
|
|
||||||
Variety = Enum.IsDefined(varietyType) ? varietyType : Variety.None;
|
|
||||||
|
|
||||||
var releaseID = (ReleaseIdentifier)((int)varietyType | (int)Configuration.OS | (int)RuntimeInformation.ProcessArchitecture);
|
|
||||||
|
|
||||||
if (Enum.IsDefined(releaseID))
|
|
||||||
ReleaseIdentifier = releaseID;
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ReleaseIdentifier = ReleaseIdentifier.None;
|
|
||||||
Serilog.Log.Logger.Warning("Unknown release identifier @{DebugInfo}", new { Variety = varietyType, Configuration.OS, RuntimeInformation.ProcessArchitecture });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppScaffolding
|
// AppScaffolding
|
||||||
private static Assembly _executingAssembly;
|
private static Assembly _executingAssembly;
|
||||||
private static Assembly ExecutingAssembly
|
private static Assembly ExecutingAssembly
|
||||||
@ -111,6 +96,22 @@ namespace AppScaffolding
|
|||||||
configureLogging(config);
|
configureLogging(config);
|
||||||
logStartupState(config);
|
logStartupState(config);
|
||||||
|
|
||||||
|
#region Determine Libation Variery and Release ID
|
||||||
|
|
||||||
|
Variety = File.Exists("System.Windows.Forms.dll") ? Variety.Classic : Variety.Chardonnay;
|
||||||
|
|
||||||
|
var releaseID = (ReleaseIdentifier)((int)Variety | (int)Configuration.OS | (int)RuntimeInformation.ProcessArchitecture);
|
||||||
|
|
||||||
|
if (Enum.IsDefined(releaseID))
|
||||||
|
ReleaseIdentifier = releaseID;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReleaseIdentifier = ReleaseIdentifier.None;
|
||||||
|
Serilog.Log.Logger.Warning("Unknown release identifier @{DebugInfo}", new { Variety, Configuration.OS, RuntimeInformation.ProcessArchitecture });
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
// all else should occur after logging
|
// all else should occur after logging
|
||||||
|
|
||||||
wireUpSystemEvents(config);
|
wireUpSystemEvents(config);
|
||||||
|
|||||||
@ -13,6 +13,10 @@ namespace DataLayer
|
|||||||
LC_64_22050_stereo = (64L << 18) | (22050 << 2) | 2,
|
LC_64_22050_stereo = (64L << 18) | (22050 << 2) | 2,
|
||||||
LC_64_44100_stereo = (64L << 18) | (44100 << 2) | 2,
|
LC_64_44100_stereo = (64L << 18) | (44100 << 2) | 2,
|
||||||
LC_128_44100_stereo = (128L << 18) | (44100 << 2) | 2,
|
LC_128_44100_stereo = (128L << 18) | (44100 << 2) | 2,
|
||||||
|
AAX_22_32 = LC_32_22050_stereo,
|
||||||
|
AAX_22_64 = LC_64_22050_stereo,
|
||||||
|
AAX_44_64 = LC_64_44100_stereo,
|
||||||
|
AAX_44_128 = LC_128_44100_stereo
|
||||||
}
|
}
|
||||||
|
|
||||||
public class AudioFormat : IComparable<AudioFormat>, IComparable
|
public class AudioFormat : IComparable<AudioFormat>, IComparable
|
||||||
|
|||||||
@ -121,8 +121,9 @@ namespace FileLiberator
|
|||||||
|
|
||||||
downloadValidation(libraryBook);
|
downloadValidation(libraryBook);
|
||||||
|
|
||||||
|
var quality = (AudibleApi.DownloadQuality)config.FileDownloadQuality;
|
||||||
var api = await libraryBook.GetApiAsync();
|
var api = await libraryBook.GetApiAsync();
|
||||||
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId);
|
var contentLic = await api.GetDownloadLicenseAsync(libraryBook.Book.AudibleProductId, quality);
|
||||||
using var dlOptions = BuildDownloadOptions(libraryBook, config, contentLic);
|
using var dlOptions = BuildDownloadOptions(libraryBook, config, contentLic);
|
||||||
|
|
||||||
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, dlOptions.OutputFormat.ToString().ToLower());
|
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, dlOptions.OutputFormat.ToString().ToLower());
|
||||||
@ -169,6 +170,9 @@ namespace FileLiberator
|
|||||||
? contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs
|
? contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
|
//Set the requested AudioFormat for use in file naming templates
|
||||||
|
libraryBook.Book.AudioFormat = AudioFormat.FromString(contentLic.ContentMetadata.ContentReference.ContentFormat);
|
||||||
|
|
||||||
var dlOptions = new DownloadOptions(config, libraryBook, contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl)
|
var dlOptions = new DownloadOptions(config, libraryBook, contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl)
|
||||||
{
|
{
|
||||||
AudibleKey = contentLic?.Voucher?.Key,
|
AudibleKey = contentLic?.Voucher?.Key,
|
||||||
|
|||||||
@ -21,6 +21,13 @@ namespace FileLiberator
|
|||||||
public TimeSpan RuntimeLength { get; init; }
|
public TimeSpan RuntimeLength { get; init; }
|
||||||
public OutputFormat OutputFormat { get; init; }
|
public OutputFormat OutputFormat { get; init; }
|
||||||
public ChapterInfo ChapterInfo { get; init; }
|
public ChapterInfo ChapterInfo { get; init; }
|
||||||
|
public string Title => LibraryBook.Book.Title;
|
||||||
|
public string Subtitle => LibraryBook.Book.Subtitle;
|
||||||
|
public string Publisher => LibraryBook.Book.Publisher;
|
||||||
|
public string Language => LibraryBook.Book.Language;
|
||||||
|
public string AudibleProductId => LibraryBookDto.AudibleProductId;
|
||||||
|
public string SeriesName => LibraryBookDto.SeriesName;
|
||||||
|
public float? SeriesNumber => LibraryBookDto.SeriesNumber;
|
||||||
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
public NAudio.Lame.LameConfig LameConfig { get; init; }
|
||||||
public string UserAgent => AudibleApi.Resources.Download_User_Agent;
|
public string UserAgent => AudibleApi.Resources.Download_User_Agent;
|
||||||
public bool TrimOutputToChapterLength => config.AllowLibationFixup && config.StripAudibleBrandAudio;
|
public bool TrimOutputToChapterLength => config.AllowLibationFixup && config.StripAudibleBrandAudio;
|
||||||
|
|||||||
@ -163,6 +163,11 @@ namespace FileManager
|
|||||||
|
|
||||||
public override string ToString() => Path;
|
public override string ToString() => Path;
|
||||||
|
|
||||||
|
public override int GetHashCode() => Path.GetHashCode();
|
||||||
|
public override bool Equals(object obj) => obj is LongPath other && Path == other.Path;
|
||||||
|
public static bool operator ==(LongPath path1, LongPath path2) => path1.Equals(path2);
|
||||||
|
public static bool operator !=(LongPath path1, LongPath path2) => !path1.Equals(path2);
|
||||||
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
private static extern int GetShortPathName([MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder shortPath, int shortPathLength);
|
private static extern int GetShortPathName([MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder shortPath, int shortPathLength);
|
||||||
|
|||||||
@ -67,13 +67,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
||||||
<PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc2.2" />
|
||||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||||
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" />
|
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-rc2.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
|
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
mc:Ignorable="d" d:DesignWidth="750" d:DesignHeight="600"
|
mc:Ignorable="d" d:DesignWidth="750" d:DesignHeight="650"
|
||||||
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
xmlns:controls="clr-namespace:LibationAvalonia.Controls"
|
||||||
xmlns:vm="clr-namespace:LibationAvalonia.ViewModels.Settings"
|
xmlns:vm="clr-namespace:LibationAvalonia.ViewModels.Settings"
|
||||||
x:DataType="vm:AudioSettingsVM"
|
x:DataType="vm:AudioSettingsVM"
|
||||||
@ -32,6 +32,18 @@
|
|||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="0">
|
Grid.Column="0">
|
||||||
|
|
||||||
|
<Grid ColumnDefinitions="*,Auto">
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Text="{CompiledBinding FileDownloadQualityText}" />
|
||||||
|
|
||||||
|
<controls:WheelComboBox
|
||||||
|
Margin="5,0,0,0"
|
||||||
|
Grid.Column="1"
|
||||||
|
ItemsSource="{CompiledBinding DownloadQualities}"
|
||||||
|
SelectedItem="{CompiledBinding FileDownloadQuality}"/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<CheckBox IsChecked="{CompiledBinding CreateCueSheet, Mode=TwoWay}">
|
<CheckBox IsChecked="{CompiledBinding CreateCueSheet, Mode=TwoWay}">
|
||||||
<TextBlock Text="{CompiledBinding CreateCueSheetText}" />
|
<TextBlock Text="{CompiledBinding CreateCueSheetText}" />
|
||||||
</CheckBox>
|
</CheckBox>
|
||||||
|
|||||||
@ -70,13 +70,13 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-rc2.2" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
|
||||||
<PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc2.2" />
|
||||||
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-rc1.1" />
|
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-rc2.2" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -48,12 +48,6 @@ namespace LibationAvalonia
|
|||||||
var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime());
|
var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime());
|
||||||
var appBuilderTask = Task.Run(BuildAvaloniaApp);
|
var appBuilderTask = Task.Run(BuildAvaloniaApp);
|
||||||
|
|
||||||
LibationScaffolding.SetReleaseIdentifier(Variety.Chardonnay);
|
|
||||||
|
|
||||||
if (LibationScaffolding.ReleaseIdentifier is ReleaseIdentifier.None)
|
|
||||||
return;
|
|
||||||
|
|
||||||
|
|
||||||
if (config.LibationSettingsAreValid)
|
if (config.LibationSettingsAreValid)
|
||||||
{
|
{
|
||||||
if (!RunDbMigrations(config))
|
if (!RunDbMigrations(config))
|
||||||
@ -81,7 +75,7 @@ namespace LibationAvalonia
|
|||||||
LibationScaffolding.RunPostConfigMigrations(config);
|
LibationScaffolding.RunPostConfigMigrations(config);
|
||||||
LibationScaffolding.RunPostMigrationScaffolding(config);
|
LibationScaffolding.RunPostMigrationScaffolding(config);
|
||||||
|
|
||||||
return true;
|
return LibationScaffolding.ReleaseIdentifier is not ReleaseIdentifier.None;
|
||||||
}
|
}
|
||||||
catch (Exception exDebug)
|
catch (Exception exDebug)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -48,6 +48,7 @@ namespace LibationAvalonia.ViewModels.Settings
|
|||||||
DownloadCoverArt = config.DownloadCoverArt;
|
DownloadCoverArt = config.DownloadCoverArt;
|
||||||
RetainAaxFile = config.RetainAaxFile;
|
RetainAaxFile = config.RetainAaxFile;
|
||||||
DownloadClipsBookmarks = config.DownloadClipsBookmarks;
|
DownloadClipsBookmarks = config.DownloadClipsBookmarks;
|
||||||
|
FileDownloadQuality = config.FileDownloadQuality;
|
||||||
ClipBookmarkFormat = config.ClipsBookmarksFileFormat;
|
ClipBookmarkFormat = config.ClipsBookmarksFileFormat;
|
||||||
SplitFilesByChapter = config.SplitFilesByChapter;
|
SplitFilesByChapter = config.SplitFilesByChapter;
|
||||||
MergeOpeningAndEndCredits = config.MergeOpeningAndEndCredits;
|
MergeOpeningAndEndCredits = config.MergeOpeningAndEndCredits;
|
||||||
@ -74,6 +75,7 @@ namespace LibationAvalonia.ViewModels.Settings
|
|||||||
config.DownloadCoverArt = DownloadCoverArt;
|
config.DownloadCoverArt = DownloadCoverArt;
|
||||||
config.RetainAaxFile = RetainAaxFile;
|
config.RetainAaxFile = RetainAaxFile;
|
||||||
config.DownloadClipsBookmarks = DownloadClipsBookmarks;
|
config.DownloadClipsBookmarks = DownloadClipsBookmarks;
|
||||||
|
config.FileDownloadQuality = FileDownloadQuality;
|
||||||
config.ClipsBookmarksFileFormat = ClipBookmarkFormat;
|
config.ClipsBookmarksFileFormat = ClipBookmarkFormat;
|
||||||
config.SplitFilesByChapter = SplitFilesByChapter;
|
config.SplitFilesByChapter = SplitFilesByChapter;
|
||||||
config.MergeOpeningAndEndCredits = MergeOpeningAndEndCredits;
|
config.MergeOpeningAndEndCredits = MergeOpeningAndEndCredits;
|
||||||
@ -93,7 +95,9 @@ namespace LibationAvalonia.ViewModels.Settings
|
|||||||
config.MaxSampleRate = SelectedSampleRate?.Value ?? config.MaxSampleRate;
|
config.MaxSampleRate = SelectedSampleRate?.Value ?? config.MaxSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<Configuration.DownloadQuality> DownloadQualities { get; } = new(Enum<Configuration.DownloadQuality>.GetValues());
|
||||||
public AvaloniaList<Configuration.ClipBookmarkFormat> ClipBookmarkFormats { get; } = new(Enum<Configuration.ClipBookmarkFormat>.GetValues());
|
public AvaloniaList<Configuration.ClipBookmarkFormat> ClipBookmarkFormats { get; } = new(Enum<Configuration.ClipBookmarkFormat>.GetValues());
|
||||||
|
public string FileDownloadQualityText { get; } = Configuration.GetDescription(nameof(Configuration.FileDownloadQuality));
|
||||||
public string CreateCueSheetText { get; } = Configuration.GetDescription(nameof(Configuration.CreateCueSheet));
|
public string CreateCueSheetText { get; } = Configuration.GetDescription(nameof(Configuration.CreateCueSheet));
|
||||||
public string AllowLibationFixupText { get; } = Configuration.GetDescription(nameof(Configuration.AllowLibationFixup));
|
public string AllowLibationFixupText { get; } = Configuration.GetDescription(nameof(Configuration.AllowLibationFixup));
|
||||||
public string DownloadCoverArtText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadCoverArt));
|
public string DownloadCoverArtText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadCoverArt));
|
||||||
@ -109,6 +113,7 @@ namespace LibationAvalonia.ViewModels.Settings
|
|||||||
public bool DownloadCoverArt { get; set; }
|
public bool DownloadCoverArt { get; set; }
|
||||||
public bool RetainAaxFile { get; set; }
|
public bool RetainAaxFile { get; set; }
|
||||||
public bool DownloadClipsBookmarks { get => _downloadClipsBookmarks; set => this.RaiseAndSetIfChanged(ref _downloadClipsBookmarks, value); }
|
public bool DownloadClipsBookmarks { get => _downloadClipsBookmarks; set => this.RaiseAndSetIfChanged(ref _downloadClipsBookmarks, value); }
|
||||||
|
public Configuration.DownloadQuality FileDownloadQuality { get; set; }
|
||||||
public Configuration.ClipBookmarkFormat ClipBookmarkFormat { get; set; }
|
public Configuration.ClipBookmarkFormat ClipBookmarkFormat { get; set; }
|
||||||
public bool MergeOpeningAndEndCredits { get; set; }
|
public bool MergeOpeningAndEndCredits { get; set; }
|
||||||
public bool StripAudibleBrandAudio { get; set; }
|
public bool StripAudibleBrandAudio { get; set; }
|
||||||
|
|||||||
76
Source/LibationCli/ConsoleProgressBar.cs
Normal file
76
Source/LibationCli/ConsoleProgressBar.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace LibationCli;
|
||||||
|
|
||||||
|
internal class ConsoleProgressBar
|
||||||
|
{
|
||||||
|
public TextWriter Output { get; }
|
||||||
|
public int MaxWidth { get; }
|
||||||
|
public char ProgressChar { get; }
|
||||||
|
public char NoProgressChar { get; }
|
||||||
|
|
||||||
|
public double? Progress
|
||||||
|
{
|
||||||
|
get => m_Progress;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_Progress = value ?? 0;
|
||||||
|
WriteProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimeSpan RemainingTime
|
||||||
|
{
|
||||||
|
get => m_RemainingTime;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
m_RemainingTime = value;
|
||||||
|
WriteProgress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double m_Progress;
|
||||||
|
private TimeSpan m_RemainingTime;
|
||||||
|
private int m_LastWriteLength = 0;
|
||||||
|
private const int MAX_ETA_LEN = 10;
|
||||||
|
private readonly int m_NumProgressPieces;
|
||||||
|
|
||||||
|
public ConsoleProgressBar(
|
||||||
|
TextWriter output,
|
||||||
|
int maxWidth = 80,
|
||||||
|
char progressCharr = '#',
|
||||||
|
char noProgressChar = '.')
|
||||||
|
{
|
||||||
|
Output = output;
|
||||||
|
MaxWidth = maxWidth;
|
||||||
|
ProgressChar = progressCharr;
|
||||||
|
NoProgressChar = noProgressChar;
|
||||||
|
m_NumProgressPieces = MaxWidth - MAX_ETA_LEN - 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteProgress()
|
||||||
|
{
|
||||||
|
var numCompleted = (int)Math.Round(double.Min(100, m_Progress) * m_NumProgressPieces / 100);
|
||||||
|
var numRemaining = m_NumProgressPieces - numCompleted;
|
||||||
|
var progressBar = $"[{new string(ProgressChar, numCompleted)}{new string(NoProgressChar, numRemaining)}] ";
|
||||||
|
|
||||||
|
progressBar += RemainingTime.TotalMinutes > 1000
|
||||||
|
? "ETA ∞"
|
||||||
|
: $"ETA {(int)RemainingTime.TotalMinutes}:{RemainingTime.Seconds:D2}";
|
||||||
|
|
||||||
|
Output.Write(new string('\b', m_LastWriteLength) + progressBar);
|
||||||
|
if (progressBar.Length < m_LastWriteLength)
|
||||||
|
{
|
||||||
|
var extra = m_LastWriteLength - progressBar.Length;
|
||||||
|
Output.Write(new string(' ', extra) + new string('\b', extra));
|
||||||
|
}
|
||||||
|
m_LastWriteLength = progressBar.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
=> Output.Write(
|
||||||
|
new string('\b', m_LastWriteLength) +
|
||||||
|
new string(' ', m_LastWriteLength) +
|
||||||
|
new string('\b', m_LastWriteLength));
|
||||||
|
}
|
||||||
48
Source/LibationCli/HelpVerb.cs
Normal file
48
Source/LibationCli/HelpVerb.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
using AppScaffolding;
|
||||||
|
using CommandLine;
|
||||||
|
using CommandLine.Text;
|
||||||
|
|
||||||
|
namespace LibationCli;
|
||||||
|
|
||||||
|
[Verb("help", HelpText = "Display more information on a specific command.")]
|
||||||
|
internal class HelpVerb
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the verb to get help about
|
||||||
|
/// </summary>
|
||||||
|
[Value(0, Default = "")]
|
||||||
|
public string HelpType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a base <see cref="HelpText"/> for <see cref="LibationCli"/>
|
||||||
|
/// </summary>
|
||||||
|
public static HelpText CreateHelpText() => new HelpText
|
||||||
|
{
|
||||||
|
AutoVersion = false,
|
||||||
|
AutoHelp = false,
|
||||||
|
Heading = $"LibationCli v{LibationScaffolding.BuildVersion.ToString(3)}",
|
||||||
|
AdditionalNewLineAfterOption = true,
|
||||||
|
MaximumDisplayWidth = 80
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the <see cref="HelpType"/>'s <see cref="HelpText"/>
|
||||||
|
/// </summary>
|
||||||
|
public HelpText GetHelpText()
|
||||||
|
{
|
||||||
|
var helpText = CreateHelpText();
|
||||||
|
var result = new Parser().ParseArguments(new string[] { HelpType }, Program.VerbTypes);
|
||||||
|
if (result.TypeInfo.Current == typeof(NullInstance))
|
||||||
|
{
|
||||||
|
//HelpType is not a defined verb so get LibationCli usage
|
||||||
|
helpText.AddVerbs(Program.VerbTypes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
helpText.AutoHelp = true;
|
||||||
|
helpText.AddDashesToOption = true;
|
||||||
|
helpText.AddOptions(result);
|
||||||
|
}
|
||||||
|
return helpText;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net7.0-windows</TargetFramework>
|
||||||
|
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
|
||||||
<PublishReadyToRun>true</PublishReadyToRun>
|
<PublishReadyToRun>true</PublishReadyToRun>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
using System;
|
using CommandLine;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommandLine;
|
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
using System;
|
using ApplicationServices;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using ApplicationServices;
|
|
||||||
using AudibleUtilities;
|
|
||||||
using CommandLine;
|
using CommandLine;
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
@ -29,26 +27,38 @@ namespace LibationCli
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
#endregion
|
#endregion
|
||||||
[Option(shortName: 'x', longName: "xlsx", SetName = "xlsx", Required = true)]
|
[Option(shortName: 'x', longName: "xlsx", HelpText = "Microsoft Excel Spreadsheet", SetName = "xlsx")]
|
||||||
public bool xlsx { get; set; }
|
public bool xlsx { get; set; }
|
||||||
|
|
||||||
[Option(shortName: 'c', longName: "csv", SetName = "csv", Required = true)]
|
[Option(shortName: 'c', longName: "csv", HelpText = "Comma-separated values", SetName = "csv")]
|
||||||
public bool csv { get; set; }
|
public bool csv { get; set; }
|
||||||
|
|
||||||
[Option(shortName: 'j', longName: "json", SetName = "json", Required = true)]
|
[Option(shortName: 'j', longName: "json", HelpText = "JavaScript Object Notation", SetName = "json")]
|
||||||
public bool json { get; set; }
|
public bool json { get; set; }
|
||||||
|
|
||||||
protected override Task ProcessAsync()
|
protected override Task ProcessAsync()
|
||||||
{
|
{
|
||||||
if (xlsx)
|
Action<string> exporter
|
||||||
LibraryExporter.ToXlsx(FilePath);
|
= csv ? LibraryExporter.ToCsv
|
||||||
if (csv)
|
: json ? LibraryExporter.ToJson
|
||||||
LibraryExporter.ToCsv(FilePath);
|
: xlsx ? LibraryExporter.ToXlsx
|
||||||
if (json)
|
: Path.GetExtension(FilePath)?.ToLower() switch
|
||||||
LibraryExporter.ToJson(FilePath);
|
{
|
||||||
|
".xlsx" => LibraryExporter.ToXlsx,
|
||||||
|
".csv" => LibraryExporter.ToCsv,
|
||||||
|
".json" => LibraryExporter.ToJson,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (exporter is null)
|
||||||
|
{
|
||||||
|
PrintVerbUsage($"Undefined export format for file type \"{Path.GetExtension(FilePath)}\"");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exporter(FilePath);
|
||||||
Console.WriteLine($"Library exported to: {FilePath}");
|
Console.WriteLine($"Library exported to: {FilePath}");
|
||||||
|
}
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
using System;
|
using CommandLine;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CommandLine;
|
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using FileLiberator;
|
using FileLiberator;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
using System;
|
using ApplicationServices;
|
||||||
|
using AudibleUtilities;
|
||||||
|
using CommandLine;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ApplicationServices;
|
|
||||||
using AudibleUtilities;
|
|
||||||
using CommandLine;
|
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
[Verb("scan", HelpText = "Scan library. Default: scan all accounts. Optional: use 'account' flag to specify a single account.")]
|
[Verb("scan", HelpText = "Scan library. Default: scan all accounts. Optional: use 'account' flag to specify a single account.")]
|
||||||
public class ScanOptions : OptionsBase
|
public class ScanOptions : OptionsBase
|
||||||
{
|
{
|
||||||
[Value(0, MetaName = "Accounts", HelpText = "Optional: nicknames of accounts to scan.", Required = false)]
|
[Value(0, MetaName = "Accounts", HelpText = "Optional: user ID or nicknames of accounts to scan.", Required = false)]
|
||||||
public IEnumerable<string> AccountNicknames { get; set; }
|
public IEnumerable<string> AccountNames { get; set; }
|
||||||
|
|
||||||
protected override async Task ProcessAsync()
|
protected override async Task ProcessAsync()
|
||||||
{
|
{
|
||||||
@ -42,13 +42,19 @@ namespace LibationCli
|
|||||||
private Account[] getAccounts()
|
private Account[] getAccounts()
|
||||||
{
|
{
|
||||||
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
|
||||||
var accounts = persister.AccountsSettings.GetAll().ToArray();
|
var allAccounts = persister.AccountsSettings.GetAll().ToArray();
|
||||||
|
|
||||||
if (!AccountNicknames.Any())
|
if (!AccountNames.Any())
|
||||||
return accounts;
|
return allAccounts;
|
||||||
|
|
||||||
var found = accounts.Where(acct => AccountNicknames.Contains(acct.AccountName)).ToArray();
|
var accountNames = AccountNames.Select(n => n.ToLower()).ToArray();
|
||||||
var notFound = AccountNicknames.Except(found.Select(f => f.AccountName)).ToArray();
|
|
||||||
|
var found
|
||||||
|
= allAccounts
|
||||||
|
.Where(acct => accountNames.Contains(acct.AccountName.ToLower()) || accountNames.Contains(acct.AccountId.ToLower()))
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
var notFound = allAccounts.Except(found).ToArray();
|
||||||
|
|
||||||
// no accounts found. do not continue
|
// no accounts found. do not continue
|
||||||
if (!found.Any())
|
if (!found.Any())
|
||||||
|
|||||||
56
Source/LibationCli/Options/SearchOptions.cs
Normal file
56
Source/LibationCli/Options/SearchOptions.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using ApplicationServices;
|
||||||
|
using CommandLine;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LibationCli.Options;
|
||||||
|
|
||||||
|
[Verb("search", HelpText = "Search for books in your library")]
|
||||||
|
internal class SearchOptions : OptionsBase
|
||||||
|
{
|
||||||
|
[Option('n', Default = 10, HelpText = "Number of search results per page")]
|
||||||
|
public int NumResultsPerPage { get; set; }
|
||||||
|
|
||||||
|
[Value(0, MetaName = "query", Required = true, HelpText = "Lucene search string")]
|
||||||
|
public IEnumerable<string> Query { get; set; }
|
||||||
|
|
||||||
|
protected override Task ProcessAsync()
|
||||||
|
{
|
||||||
|
var query = string.Join(" ", Query).Trim('\"');
|
||||||
|
var results = SearchEngineCommands.Search(query).Docs.ToList();
|
||||||
|
|
||||||
|
Console.WriteLine($"Found {results.Count} matching results.");
|
||||||
|
|
||||||
|
string nextPrompt = "Press any key for the next " + NumResultsPerPage + " results or Esc for all results";
|
||||||
|
bool waitForNextBatch = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < results.Count; i += NumResultsPerPage)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (int j = i; j < int.Min(results.Count, i + NumResultsPerPage); j++)
|
||||||
|
sb.AppendLine(getDocDisplay(results[j].Doc));
|
||||||
|
|
||||||
|
Console.Write(sb.ToString());
|
||||||
|
|
||||||
|
if (waitForNextBatch)
|
||||||
|
{
|
||||||
|
Console.Write(nextPrompt);
|
||||||
|
waitForNextBatch = Console.ReadKey(intercept: true).Key != ConsoleKey.Escape;
|
||||||
|
ReplaceConsoleText(Console.Out, nextPrompt.Length, "");
|
||||||
|
Console.CursorLeft = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string getDocDisplay(Lucene.Net.Documents.Document doc)
|
||||||
|
{
|
||||||
|
var title = doc.GetField("title");
|
||||||
|
var id = doc.GetField("_ID_");
|
||||||
|
return $"[{id.StringValue}] - {title.StringValue}";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,31 +1,63 @@
|
|||||||
using System;
|
using ApplicationServices;
|
||||||
|
using CommandLine;
|
||||||
|
using DataLayer;
|
||||||
|
using Dinah.Core;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ApplicationServices;
|
|
||||||
using AudibleUtilities;
|
|
||||||
using CommandLine;
|
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
[Verb("set-status", HelpText = """
|
[Verb("set-status", HelpText = """
|
||||||
Set download statuses throughout library based on whether each book's audio file can be found.
|
Set download statuses throughout library based on whether each book's audio file can be found.
|
||||||
Must include at least one flag: --downloaded , --not-downloaded.
|
|
||||||
Downloaded: If the audio file can be found, set download status to 'Downloaded'.
|
|
||||||
Not Downloaded: If the audio file cannot be found, set download status to 'Not Downloaded'
|
|
||||||
""")]
|
""")]
|
||||||
public class SetDownloadStatusOptions : OptionsBase
|
public class SetDownloadStatusOptions : OptionsBase
|
||||||
{
|
{
|
||||||
[Option(shortName: 'd', longName: "downloaded", Required = true)]
|
//https://github.com/commandlineparser/commandline/wiki/Option-Groups
|
||||||
|
[Option(shortName: 'd', longName: "downloaded", Group = "Download Status", HelpText = "set download status to 'Downloaded'")]
|
||||||
public bool SetDownloaded { get; set; }
|
public bool SetDownloaded { get; set; }
|
||||||
|
|
||||||
[Option(shortName: 'n', longName: "not-downloaded", Required = true)]
|
[Option(shortName: 'n', longName: "not-downloaded", Group = "Download Status", HelpText = "set download status to 'Not Downloaded'")]
|
||||||
public bool SetNotDownloaded { get; set; }
|
public bool SetNotDownloaded { get; set; }
|
||||||
|
|
||||||
|
[Option("force", HelpText = "Set the download status regardless of whether the book's audio file can be found. Only one download status option may be used with this option.")]
|
||||||
|
public bool Force { get; set; }
|
||||||
|
|
||||||
|
[Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books on which to set download status.")]
|
||||||
|
public IEnumerable<string> Asins { get; set; }
|
||||||
|
|
||||||
protected override async Task ProcessAsync()
|
protected override async Task ProcessAsync()
|
||||||
{
|
{
|
||||||
|
if (Force && SetDownloaded && SetNotDownloaded)
|
||||||
|
{
|
||||||
|
PrintVerbUsage("ERROR:\nWhen run with --force option, only one download status option may be used.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
|
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
|
||||||
|
|
||||||
|
if (Asins.Any())
|
||||||
|
{
|
||||||
|
var asins = Asins.Select(a => a.TrimStart('[').TrimEnd(']').ToLower()).ToArray();
|
||||||
|
libraryBooks = libraryBooks.Where(lb => lb.Book.AudibleProductId.ToLower().In(asins)).ToList();
|
||||||
|
|
||||||
|
if (libraryBooks.Count == 0)
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("Could not find any books matching asins");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Force)
|
||||||
|
{
|
||||||
|
var status = SetDownloaded ? LiberatedStatus.Liberated : LiberatedStatus.NotLiberated;
|
||||||
|
|
||||||
|
var num = libraryBooks.UpdateBookStatus(status);
|
||||||
|
Console.WriteLine($"Set LiberatedStatus to '{status}' on {"book".PluralizeWithCount(num)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
var bulkSetStatus = new BulkSetDownloadStatus(libraryBooks, SetDownloaded, SetNotDownloaded);
|
var bulkSetStatus = new BulkSetDownloadStatus(libraryBooks, SetDownloaded, SetNotDownloaded);
|
||||||
await Task.Run(() => bulkSetStatus.Discover());
|
await Task.Run(() => bulkSetStatus.Discover());
|
||||||
bulkSetStatus.Execute();
|
bulkSetStatus.Execute();
|
||||||
@ -34,4 +66,5 @@ namespace LibationCli
|
|||||||
Console.WriteLine(msg);
|
Console.WriteLine(msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
59
Source/LibationCli/Options/VersionOptions.cs
Normal file
59
Source/LibationCli/Options/VersionOptions.cs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
using AppScaffolding;
|
||||||
|
using CommandLine;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace LibationCli.Options;
|
||||||
|
|
||||||
|
[Verb("version", HelpText = "Display version information.")]
|
||||||
|
internal class VersionOptions : OptionsBase
|
||||||
|
{
|
||||||
|
[Option('c', "check", Required = false, HelpText = "Check if an upgrade is available")]
|
||||||
|
public bool CheckForUpgrade { get; set; }
|
||||||
|
|
||||||
|
protected override Task ProcessAsync()
|
||||||
|
{
|
||||||
|
const string checkingForUpgrade = "Checking for upgrade...";
|
||||||
|
Console.WriteLine($"Libation {LibationScaffolding.Variety} v{LibationScaffolding.BuildVersion.ToString(3)}");
|
||||||
|
|
||||||
|
if (CheckForUpgrade)
|
||||||
|
{
|
||||||
|
Console.Write(checkingForUpgrade);
|
||||||
|
|
||||||
|
var origColor = Console.ForegroundColor;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var upgradeProperties = LibationScaffolding.GetLatestRelease();
|
||||||
|
|
||||||
|
if (upgradeProperties is null)
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Green;
|
||||||
|
ReplaceConsoleText(Console.Out, checkingForUpgrade.Length, "No available upgrade");
|
||||||
|
Console.WriteLine();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = ConsoleColor.Red;
|
||||||
|
ReplaceConsoleText(Console.Out, checkingForUpgrade.Length, $"Upgrade Available: v{upgradeProperties.LatestRelease.ToString(3)}");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine(upgradeProperties.ZipUrl);
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("Release Notes");
|
||||||
|
Console.WriteLine("=============");
|
||||||
|
Console.WriteLine(upgradeProperties.Notes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine("ERROR CHECKING FOR UPGRADE");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Console.ForegroundColor = origColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,8 @@
|
|||||||
using System;
|
using CommandLine;
|
||||||
using System.Collections.Generic;
|
using System;
|
||||||
using System.Linq;
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommandLine;
|
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
@ -17,15 +17,34 @@ namespace LibationCli
|
|||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Environment.ExitCode = (int)ExitCode.RunTimeError;
|
Environment.ExitCode = (int)ExitCode.RunTimeError;
|
||||||
|
PrintVerbUsage(new string[]
|
||||||
Console.Error.WriteLine("ERROR");
|
{
|
||||||
Console.Error.WriteLine("=====");
|
"ERROR",
|
||||||
Console.Error.WriteLine(ex.Message);
|
"=====",
|
||||||
Console.Error.WriteLine();
|
ex.Message,
|
||||||
Console.Error.WriteLine(ex.StackTrace);
|
"",
|
||||||
|
ex.StackTrace
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void PrintVerbUsage(params string[] linesBeforeUsage)
|
||||||
|
{
|
||||||
|
var verb = GetType().GetCustomAttribute<VerbAttribute>().Name;
|
||||||
|
var helpText = new HelpVerb { HelpType = verb }.GetHelpText();
|
||||||
|
helpText.AddPreOptionsLines(linesBeforeUsage);
|
||||||
|
helpText.AddPreOptionsLine("");
|
||||||
|
helpText.AddPreOptionsLine($"{verb} Usage:");
|
||||||
|
Console.Error.WriteLine(helpText);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void ReplaceConsoleText(TextWriter writer, int previousLength, string newText)
|
||||||
|
{
|
||||||
|
writer.Write(new string('\b', previousLength));
|
||||||
|
writer.Write(newText);
|
||||||
|
writer.Write(new string(' ', int.Max(0, previousLength - newText.Length)));
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract Task ProcessAsync();
|
protected abstract Task ProcessAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,34 @@
|
|||||||
using System;
|
using ApplicationServices;
|
||||||
|
using CommandLine;
|
||||||
|
using DataLayer;
|
||||||
|
using Dinah.Core;
|
||||||
|
using FileLiberator;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ApplicationServices;
|
|
||||||
using CommandLine;
|
|
||||||
using DataLayer;
|
|
||||||
using FileLiberator;
|
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
public abstract class ProcessableOptionsBase : OptionsBase
|
public abstract class ProcessableOptionsBase : OptionsBase
|
||||||
{
|
{
|
||||||
|
|
||||||
|
[Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books to process.")]
|
||||||
|
public IEnumerable<string> Asins { get; set; }
|
||||||
|
|
||||||
protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook> completedAction = null)
|
protected static TProcessable CreateProcessable<TProcessable>(EventHandler<LibraryBook> completedAction = null)
|
||||||
where TProcessable : Processable, new()
|
where TProcessable : Processable, new()
|
||||||
{
|
{
|
||||||
|
var progressBar = new ConsoleProgressBar(Console.Out);
|
||||||
var strProc = new TProcessable();
|
var strProc = new TProcessable();
|
||||||
|
|
||||||
strProc.Begin += (o, e) => Console.WriteLine($"{typeof(TProcessable).Name} Begin: {e}");
|
strProc.Begin += (o, e) => Console.WriteLine($"{typeof(TProcessable).Name} Begin: {e}");
|
||||||
strProc.Completed += (o, e) => Console.WriteLine($"{typeof(TProcessable).Name} Completed: {e}");
|
|
||||||
|
strProc.Completed += (o, e) =>
|
||||||
|
{
|
||||||
|
progressBar.Clear();
|
||||||
|
Console.WriteLine($"{typeof(TProcessable).Name} Completed: {e}");
|
||||||
|
};
|
||||||
|
|
||||||
strProc.Completed += (s, e) =>
|
strProc.Completed += (s, e) =>
|
||||||
{
|
{
|
||||||
@ -32,13 +43,28 @@ namespace LibationCli
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
strProc.StreamingTimeRemaining += (_, e) => progressBar.RemainingTime = e;
|
||||||
|
strProc.StreamingProgressChanged += (_, e) => progressBar.Progress = e.ProgressPercentage;
|
||||||
|
|
||||||
return strProc;
|
return strProc;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static async Task RunAsync(Processable Processable)
|
protected async Task RunAsync(Processable Processable)
|
||||||
{
|
{
|
||||||
foreach (var libraryBook in Processable.GetValidLibraryBooks(DbContexts.GetLibrary_Flat_NoTracking()))
|
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
|
||||||
await ProcessOneAsync(Processable, libraryBook, false);
|
|
||||||
|
if (Asins.Any())
|
||||||
|
{
|
||||||
|
var asinsLower = Asins.Select(a => a.TrimStart('[').TrimEnd(']').ToLower()).ToArray();
|
||||||
|
|
||||||
|
foreach (var lb in libraryBooks.Where(lb => lb.Book.AudibleProductId.ToLower().In(asinsLower)))
|
||||||
|
await ProcessOneAsync(Processable, lb, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var lb in Processable.GetValidLibraryBooks(libraryBooks))
|
||||||
|
await ProcessOneAsync(Processable, lb, false);
|
||||||
|
}
|
||||||
|
|
||||||
var done = "Done. All books have been processed";
|
var done = "Done. All books have been processed";
|
||||||
Console.WriteLine(done);
|
Console.WriteLine(done);
|
||||||
|
|||||||
@ -1,12 +1,9 @@
|
|||||||
using System;
|
using CommandLine;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using CommandLine;
|
|
||||||
using CommandLine.Text;
|
using CommandLine.Text;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Dinah.Core.Collections;
|
using System;
|
||||||
using Dinah.Core.Collections.Generic;
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
@ -19,47 +16,63 @@ namespace LibationCli
|
|||||||
}
|
}
|
||||||
class Program
|
class Program
|
||||||
{
|
{
|
||||||
static async Task<int> Main(string[] args)
|
public readonly static Type[] VerbTypes = Setup.LoadVerbs();
|
||||||
|
static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
//***********************************************//
|
|
||||||
// //
|
|
||||||
// do not use Configuration before this line //
|
|
||||||
// //
|
|
||||||
//***********************************************//
|
|
||||||
Setup.Initialize();
|
|
||||||
|
|
||||||
var types = Setup.LoadVerbs();
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
string input = null;
|
string input = "";
|
||||||
|
|
||||||
|
//input = " set-status -n --force B017V4IM1G";
|
||||||
|
//input = " liberate B017V4IM1G";
|
||||||
|
//input = " convert B017V4IM1G";
|
||||||
|
//input = " search \"-liberated\"";
|
||||||
//input = " export --help";
|
//input = " export --help";
|
||||||
|
//input = " version --check";
|
||||||
//input = " scan rmcrackan";
|
//input = " scan rmcrackan";
|
||||||
|
//input = " help set-status";
|
||||||
//input = " liberate ";
|
//input = " liberate ";
|
||||||
|
|
||||||
|
|
||||||
// note: this hack will fail for quoted file paths with spaces because it will break on those spaces
|
// note: this hack will fail for quoted file paths with spaces because it will break on those spaces
|
||||||
if (!string.IsNullOrWhiteSpace(input))
|
if (!string.IsNullOrWhiteSpace(input))
|
||||||
args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
var setBreakPointHere = args;
|
var setBreakPointHere = args;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var result = Parser.Default.ParseArguments(args, types);
|
var result = new Parser(ConfigureParser).ParseArguments(args, VerbTypes);
|
||||||
|
|
||||||
|
if (result.Value is HelpVerb helper)
|
||||||
|
Console.Error.WriteLine(helper.GetHelpText());
|
||||||
|
else if (result.TypeInfo.Current == typeof(HelpVerb))
|
||||||
|
{
|
||||||
|
//Error parsing the command, but the verb type was identified as HelpVerb
|
||||||
|
//Print LibationCli usage
|
||||||
|
var helpText = HelpVerb.CreateHelpText();
|
||||||
|
helpText.AddVerbs(VerbTypes);
|
||||||
|
Console.Error.WriteLine(helpText);
|
||||||
|
}
|
||||||
|
else if (result.Errors.Any())
|
||||||
|
HandleErrors(result);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Everything parsed correctly, so execute the command
|
||||||
|
|
||||||
|
//***********************************************//
|
||||||
|
// //
|
||||||
|
// do not use Configuration before this line //
|
||||||
|
// //
|
||||||
|
//***********************************************//
|
||||||
|
Setup.Initialize();
|
||||||
|
|
||||||
// if successfully parsed
|
// if successfully parsed
|
||||||
// async: run parsed options
|
// async: run parsed options
|
||||||
await result.WithParsedAsync<OptionsBase>(opt => opt.Run());
|
await result.WithParsedAsync<OptionsBase>(opt => opt.Run());
|
||||||
|
}
|
||||||
// if not successfully parsed
|
|
||||||
// sync: handle parse errors
|
|
||||||
result.WithNotParsed(errors => HandleErrors(result, errors));
|
|
||||||
|
|
||||||
return Environment.ExitCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleErrors(ParserResult<object> result, IEnumerable<Error> errors)
|
private static void HandleErrors(ParserResult<object> result)
|
||||||
{
|
{
|
||||||
var errorsList = errors.ToList();
|
var errorsList = result.Errors.ToList();
|
||||||
if (errorsList.Any(e => e.Tag.In(ErrorType.HelpRequestedError, ErrorType.VersionRequestedError, ErrorType.HelpVerbRequestedError)))
|
if (errorsList.Any(e => e.Tag.In(ErrorType.HelpRequestedError, ErrorType.VersionRequestedError, ErrorType.HelpVerbRequestedError)))
|
||||||
{
|
{
|
||||||
Environment.ExitCode = (int)ExitCode.NonRunNonError;
|
Environment.ExitCode = (int)ExitCode.NonRunNonError;
|
||||||
@ -67,17 +80,36 @@ namespace LibationCli
|
|||||||
}
|
}
|
||||||
|
|
||||||
Environment.ExitCode = (int)ExitCode.ParseError;
|
Environment.ExitCode = (int)ExitCode.ParseError;
|
||||||
|
var helpText = HelpVerb.CreateHelpText();
|
||||||
|
|
||||||
if (errorsList.Any(e => e.Tag.In(ErrorType.NoVerbSelectedError)))
|
if (errorsList.OfType<NoVerbSelectedError>().Any())
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine("No verb selected");
|
//Print LibationCli usage
|
||||||
return;
|
helpText.AddPreOptionsLine("No verb selected");
|
||||||
|
helpText.AddVerbs(VerbTypes);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//print the specified verb's usage
|
||||||
|
helpText.AddDashesToOption = true;
|
||||||
|
helpText.AutoHelp = true;
|
||||||
|
|
||||||
|
if (!errorsList.OfType<UnknownOptionError>().Any(o => o.Token.ToLower() == "help"))
|
||||||
|
{
|
||||||
|
//verb was not executed with the "--help" option,
|
||||||
|
//so print verb option parsing error info.
|
||||||
|
helpText = HelpText.DefaultParsingErrorsHandler(result, helpText);
|
||||||
}
|
}
|
||||||
|
|
||||||
var helpText = HelpText.AutoBuild(result,
|
helpText.AddOptions(result);
|
||||||
h => HelpText.DefaultParsingErrorsHandler(result, h),
|
}
|
||||||
e => e);
|
Console.Error.WriteLine(helpText);
|
||||||
Console.WriteLine(helpText);
|
}
|
||||||
|
|
||||||
|
private static void ConfigureParser(ParserSettings settings)
|
||||||
|
{
|
||||||
|
settings.AutoVersion = false;
|
||||||
|
settings.AutoHelp = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,8 @@
|
|||||||
using System;
|
using AppScaffolding;
|
||||||
using System.Collections.Generic;
|
using CommandLine;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using AppScaffolding;
|
|
||||||
using CommandLine;
|
|
||||||
using CommandLine.Text;
|
|
||||||
using Dinah.Core;
|
|
||||||
using Dinah.Core.Collections;
|
|
||||||
using Dinah.Core.Collections.Generic;
|
|
||||||
|
|
||||||
namespace LibationCli
|
namespace LibationCli
|
||||||
{
|
{
|
||||||
@ -26,28 +20,6 @@ namespace LibationCli
|
|||||||
|
|
||||||
LibationScaffolding.RunPostConfigMigrations(config);
|
LibationScaffolding.RunPostConfigMigrations(config);
|
||||||
LibationScaffolding.RunPostMigrationScaffolding(config);
|
LibationScaffolding.RunPostMigrationScaffolding(config);
|
||||||
|
|
||||||
#if !DEBUG
|
|
||||||
checkForUpdate();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void checkForUpdate()
|
|
||||||
{
|
|
||||||
var upgradeProperties = LibationScaffolding.GetLatestRelease();
|
|
||||||
if (upgradeProperties is null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var origColor = Console.ForegroundColor;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = ConsoleColor.Red;
|
|
||||||
Console.WriteLine($"UPDATE AVAILABLE @ {upgradeProperties.ZipUrl}");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
Console.ForegroundColor = origColor;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type[] LoadVerbs() => Assembly.GetExecutingAssembly()
|
public static Type[] LoadVerbs() => Assembly.GetExecutingAssembly()
|
||||||
|
|||||||
@ -126,7 +126,16 @@ namespace LibationFileManager
|
|||||||
BookDirectoryFiles = new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
|
BookDirectoryFiles = new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
|
||||||
|
|
||||||
var regex = GetBookSearchRegex(productId);
|
var regex = GetBookSearchRegex(productId);
|
||||||
return BookDirectoryFiles.FindFiles(regex);
|
|
||||||
|
//Find all extant files matching the productId
|
||||||
|
//using both the file system and the file path cache
|
||||||
|
return
|
||||||
|
FilePathCache
|
||||||
|
.GetFiles(productId)
|
||||||
|
.Where(c => c.fileType == FileType.Audio && File.Exists(c.path))
|
||||||
|
.Select(c => c.path)
|
||||||
|
.Union(BookDirectoryFiles.FindFiles(regex))
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Refresh() => BookDirectoryFiles.RefreshFiles();
|
public void Refresh() => BookDirectoryFiles.RefreshFiles();
|
||||||
|
|||||||
@ -205,6 +205,16 @@ namespace LibationFileManager
|
|||||||
Added
|
Added
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[JsonConverter(typeof(StringEnumConverter))]
|
||||||
|
public enum DownloadQuality
|
||||||
|
{
|
||||||
|
High,
|
||||||
|
Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
[Description("Audio quality to request from Audible:")]
|
||||||
|
public DownloadQuality FileDownloadQuality { get => GetNonString(defaultValue: DownloadQuality.High); set => SetNonString(value); }
|
||||||
|
|
||||||
[Description("Set file \"created\" timestamp to:")]
|
[Description("Set file \"created\" timestamp to:")]
|
||||||
public DateTimeSource CreationTime { get => GetNonString(defaultValue: DateTimeSource.File); set => SetNonString(value); }
|
public DateTimeSource CreationTime { get => GetNonString(defaultValue: DateTimeSource.File); set => SetNonString(value); }
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
{
|
{
|
||||||
private void Load_AudioSettings(Configuration config)
|
private void Load_AudioSettings(Configuration config)
|
||||||
{
|
{
|
||||||
|
this.fileDownloadQualityLbl.Text = desc(nameof(config.FileDownloadQuality));
|
||||||
this.allowLibationFixupCbox.Text = desc(nameof(config.AllowLibationFixup));
|
this.allowLibationFixupCbox.Text = desc(nameof(config.AllowLibationFixup));
|
||||||
this.createCueSheetCbox.Text = desc(nameof(config.CreateCueSheet));
|
this.createCueSheetCbox.Text = desc(nameof(config.CreateCueSheet));
|
||||||
this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt));
|
this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt));
|
||||||
@ -19,6 +20,13 @@ namespace LibationWinForms.Dialogs
|
|||||||
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
|
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
|
||||||
this.moveMoovAtomCbox.Text = desc(nameof(config.MoveMoovToBeginning));
|
this.moveMoovAtomCbox.Text = desc(nameof(config.MoveMoovToBeginning));
|
||||||
|
|
||||||
|
fileDownloadQualityCb.Items.AddRange(
|
||||||
|
new object[]
|
||||||
|
{
|
||||||
|
Configuration.DownloadQuality.Normal,
|
||||||
|
Configuration.DownloadQuality.High
|
||||||
|
});
|
||||||
|
|
||||||
clipsBookmarksFormatCb.Items.AddRange(
|
clipsBookmarksFormatCb.Items.AddRange(
|
||||||
new object[]
|
new object[]
|
||||||
{
|
{
|
||||||
@ -44,6 +52,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
createCueSheetCbox.Checked = config.CreateCueSheet;
|
createCueSheetCbox.Checked = config.CreateCueSheet;
|
||||||
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
|
||||||
downloadClipsBookmarksCbox.Checked = config.DownloadClipsBookmarks;
|
downloadClipsBookmarksCbox.Checked = config.DownloadClipsBookmarks;
|
||||||
|
fileDownloadQualityCb.SelectedItem = config.FileDownloadQuality;
|
||||||
clipsBookmarksFormatCb.SelectedItem = config.ClipsBookmarksFileFormat;
|
clipsBookmarksFormatCb.SelectedItem = config.ClipsBookmarksFileFormat;
|
||||||
retainAaxFileCbox.Checked = config.RetainAaxFile;
|
retainAaxFileCbox.Checked = config.RetainAaxFile;
|
||||||
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
|
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
|
||||||
@ -87,6 +96,7 @@ namespace LibationWinForms.Dialogs
|
|||||||
config.CreateCueSheet = createCueSheetCbox.Checked;
|
config.CreateCueSheet = createCueSheetCbox.Checked;
|
||||||
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
|
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
|
||||||
config.DownloadClipsBookmarks = downloadClipsBookmarksCbox.Checked;
|
config.DownloadClipsBookmarks = downloadClipsBookmarksCbox.Checked;
|
||||||
|
config.FileDownloadQuality = (Configuration.DownloadQuality)fileDownloadQualityCb.SelectedItem;
|
||||||
config.ClipsBookmarksFileFormat = (Configuration.ClipBookmarkFormat)clipsBookmarksFormatCb.SelectedItem;
|
config.ClipsBookmarksFileFormat = (Configuration.ClipBookmarkFormat)clipsBookmarksFormatCb.SelectedItem;
|
||||||
config.RetainAaxFile = retainAaxFileCbox.Checked;
|
config.RetainAaxFile = retainAaxFileCbox.Checked;
|
||||||
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
|
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
|
||||||
@ -98,7 +108,6 @@ namespace LibationWinForms.Dialogs
|
|||||||
config.LameTargetBitrate = lameTargetBitrateRb.Checked;
|
config.LameTargetBitrate = lameTargetBitrateRb.Checked;
|
||||||
config.MaxSampleRate = ((EnumDiaplay<AAXClean.SampleRate>)maxSampleRateCb.SelectedItem).Value;
|
config.MaxSampleRate = ((EnumDiaplay<AAXClean.SampleRate>)maxSampleRateCb.SelectedItem).Value;
|
||||||
config.LameEncoderQuality = (NAudio.Lame.EncoderQuality)encoderQualityCb.SelectedItem;
|
config.LameEncoderQuality = (NAudio.Lame.EncoderQuality)encoderQualityCb.SelectedItem;
|
||||||
encoderQualityCb.SelectedItem = config.LameEncoderQuality;
|
|
||||||
config.LameDownsampleMono = lameDownsampleMonoCbox.Checked;
|
config.LameDownsampleMono = lameDownsampleMonoCbox.Checked;
|
||||||
config.LameBitrate = lameBitrateTb.Value;
|
config.LameBitrate = lameBitrateTb.Value;
|
||||||
config.LameConstantBitrate = lameConstantBitrateCbox.Checked;
|
config.LameConstantBitrate = lameConstantBitrateCbox.Checked;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -14,15 +14,6 @@ namespace LibationWinForms.Dialogs
|
|||||||
this.importEpisodesCb.Text = desc(nameof(config.ImportEpisodes));
|
this.importEpisodesCb.Text = desc(nameof(config.ImportEpisodes));
|
||||||
this.downloadEpisodesCb.Text = desc(nameof(config.DownloadEpisodes));
|
this.downloadEpisodesCb.Text = desc(nameof(config.DownloadEpisodes));
|
||||||
this.autoDownloadEpisodesCb.Text = desc(nameof(config.AutoDownloadEpisodes));
|
this.autoDownloadEpisodesCb.Text = desc(nameof(config.AutoDownloadEpisodes));
|
||||||
creationTimeLbl.Text = desc(nameof(config.CreationTime));
|
|
||||||
lastWriteTimeLbl.Text = desc(nameof(config.LastWriteTime));
|
|
||||||
|
|
||||||
var dateTimeSources = Enum.GetValues<Configuration.DateTimeSource>().Select(v => new EnumDiaplay<Configuration.DateTimeSource>(v)).ToArray();
|
|
||||||
creationTimeCb.Items.AddRange(dateTimeSources);
|
|
||||||
lastWriteTimeCb.Items.AddRange(dateTimeSources);
|
|
||||||
|
|
||||||
creationTimeCb.SelectedItem = dateTimeSources.SingleOrDefault(v => v.Value == config.CreationTime) ?? dateTimeSources[0];
|
|
||||||
lastWriteTimeCb.SelectedItem = dateTimeSources.SingleOrDefault(v => v.Value == config.LastWriteTime) ?? dateTimeSources[0];
|
|
||||||
|
|
||||||
autoScanCb.Checked = config.AutoScan;
|
autoScanCb.Checked = config.AutoScan;
|
||||||
showImportedStatsCb.Checked = config.ShowImportedStats;
|
showImportedStatsCb.Checked = config.ShowImportedStats;
|
||||||
@ -32,9 +23,6 @@ namespace LibationWinForms.Dialogs
|
|||||||
}
|
}
|
||||||
private void Save_ImportLibrary(Configuration config)
|
private void Save_ImportLibrary(Configuration config)
|
||||||
{
|
{
|
||||||
config.CreationTime = ((EnumDiaplay<Configuration.DateTimeSource>)creationTimeCb.SelectedItem).Value;
|
|
||||||
config.LastWriteTime = ((EnumDiaplay<Configuration.DateTimeSource>)lastWriteTimeCb.SelectedItem).Value;
|
|
||||||
|
|
||||||
config.AutoScan = autoScanCb.Checked;
|
config.AutoScan = autoScanCb.Checked;
|
||||||
config.ShowImportedStats = showImportedStatsCb.Checked;
|
config.ShowImportedStats = showImportedStatsCb.Checked;
|
||||||
config.ImportEpisodes = importEpisodesCb.Checked;
|
config.ImportEpisodes = importEpisodesCb.Checked;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationUiBase;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -25,6 +26,16 @@ namespace LibationWinForms.Dialogs
|
|||||||
betaOptInCbox.Text = desc(nameof(config.BetaOptIn));
|
betaOptInCbox.Text = desc(nameof(config.BetaOptIn));
|
||||||
saveEpisodesToSeriesFolderCbox.Text = desc(nameof(config.SavePodcastsToParentFolder));
|
saveEpisodesToSeriesFolderCbox.Text = desc(nameof(config.SavePodcastsToParentFolder));
|
||||||
overwriteExistingCbox.Text = desc(nameof(config.OverwriteExisting));
|
overwriteExistingCbox.Text = desc(nameof(config.OverwriteExisting));
|
||||||
|
creationTimeLbl.Text = desc(nameof(config.CreationTime));
|
||||||
|
lastWriteTimeLbl.Text = desc(nameof(config.LastWriteTime));
|
||||||
|
|
||||||
|
var dateTimeSources = Enum.GetValues<Configuration.DateTimeSource>().Select(v => new EnumDiaplay<Configuration.DateTimeSource>(v)).ToArray();
|
||||||
|
creationTimeCb.Items.AddRange(dateTimeSources);
|
||||||
|
lastWriteTimeCb.Items.AddRange(dateTimeSources);
|
||||||
|
|
||||||
|
creationTimeCb.SelectedItem = dateTimeSources.SingleOrDefault(v => v.Value == config.CreationTime) ?? dateTimeSources[0];
|
||||||
|
lastWriteTimeCb.SelectedItem = dateTimeSources.SingleOrDefault(v => v.Value == config.LastWriteTime) ?? dateTimeSources[0];
|
||||||
|
|
||||||
|
|
||||||
booksSelectControl.SetSearchTitle("books location");
|
booksSelectControl.SetSearchTitle("books location");
|
||||||
booksSelectControl.SetDirectoryItems(
|
booksSelectControl.SetDirectoryItems(
|
||||||
@ -82,6 +93,11 @@ namespace LibationWinForms.Dialogs
|
|||||||
config.OverwriteExisting = overwriteExistingCbox.Checked;
|
config.OverwriteExisting = overwriteExistingCbox.Checked;
|
||||||
|
|
||||||
config.BetaOptIn = betaOptInCbox.Checked;
|
config.BetaOptIn = betaOptInCbox.Checked;
|
||||||
|
|
||||||
|
|
||||||
|
config.CreationTime = ((EnumDiaplay<Configuration.DateTimeSource>)creationTimeCb.SelectedItem).Value;
|
||||||
|
config.LastWriteTime = ((EnumDiaplay<Configuration.DateTimeSource>)lastWriteTimeCb.SelectedItem).Value;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -30,8 +30,6 @@ namespace LibationWinForms
|
|||||||
|
|
||||||
ApplicationConfiguration.Initialize();
|
ApplicationConfiguration.Initialize();
|
||||||
|
|
||||||
AppScaffolding.LibationScaffolding.SetReleaseIdentifier(AppScaffolding.Variety.Classic);
|
|
||||||
|
|
||||||
//***********************************************//
|
//***********************************************//
|
||||||
// //
|
// //
|
||||||
// do not use Configuration before this line //
|
// do not use Configuration before this line //
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user