Compare commits

...

10 Commits

Author SHA1 Message Date
Robert McRackan
03d30ff6af incr. ver. 2023-07-03 22:06:00 -04:00
rmcrackan
ecfe0dc033 Merge pull request #651 from Mbucari/master
Overhaul LibationCli and add Download Quality Option
2023-07-03 21:57:04 -04:00
Mbucari
f2d475a9b0 Add audiobookshelf tags for m4b and mp3
Fix the following tag fields so they are correctly parsed and displayed in audiobookshelf:
Language
Publisher
Series name and number
ASIN
2023-07-03 15:57:11 -06:00
Mbucari
86124fc609 Address comments 2023-07-03 10:01:25 -06:00
Mbucari
db2b10d2a4 Performance improvement 2023-07-03 07:04:29 -06:00
Mbucari
83402028fd Update Avalonia 2023-07-02 19:27:58 -06:00
Mbucari
423b5312f7 Add setting to choose downloaded audio quality ((#648) 2023-07-02 19:19:28 -06:00
Mbucari
3be7d8e825 Minor cli edits and fix potential deadlock 2023-07-02 18:29:36 -06:00
Mbucari
29803c6ba0 Overhaul LibationCli
Add version verb with option to check for upgrade
Add Search verb to search the library
Add export file type inference
Add more set-status options
Add console progress bar and ETA
Add processable option to liberate specific book IDs
Scan accounts by nickname or account ID
Improve startup performance for halp and on parsing error
More useful error messages
2023-07-02 15:01:10 -06:00
Mbucari
bb05847b25 Improve finding audio file by ID 2023-07-02 14:08:27 -06:00
38 changed files with 995 additions and 428 deletions

View File

@@ -13,7 +13,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AAXClean.Codecs" Version="1.0.4" />
<PackageReference Include="AAXClean.Codecs" Version="1.1.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -51,6 +51,34 @@ namespace AaxDecrypter
if (!string.IsNullOrWhiteSpace(AaxFile.AppleTags.Copyright))
AaxFile.AppleTags.Copyright = AaxFile.AppleTags.Copyright.Replace("(P)", "℗").Replace("&#169;", "©");
//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.

View File

@@ -21,7 +21,14 @@ namespace AaxDecrypter
long DownloadSpeedBps { get; }
ChapterInfo ChapterInfo { get; }
bool FixupFile { get; }
NAudio.Lame.LameConfig LameConfig { 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; }
bool Downsample { get; }
bool MatchSourceBitrate { get; }
bool MoveMoovToBeginning { get; }

View File

@@ -1,4 +1,5 @@
using AAXClean;
using AAXClean.Codecs;
using NAudio.Lame;
using System;
@@ -6,6 +7,7 @@ namespace AaxDecrypter
{
public static class MpegUtil
{
private const string TagDomain = "com.pilabor.tone";
public static void ConfigureLameOptions(Mp4File mp4File, LameConfig lameConfig, bool downsample, bool matchSourceBitrate)
{
double bitrateMultiple = 1;
@@ -36,6 +38,21 @@ namespace AaxDecrypter
else if (lameConfig.VBR == VBRMode.ABR)
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);
}
}
}

View File

@@ -14,7 +14,6 @@ namespace AaxDecrypter
public class NetworkFileStream : Stream, IUpdatable
{
public event EventHandler Updated;
public event EventHandler DownloadCompleted;
#region Public Properties
@@ -41,6 +40,9 @@ namespace AaxDecrypter
[JsonIgnore]
public bool IsCancelled => _cancellationSource.IsCancellationRequested;
[JsonIgnore]
public Task DownloadTask { get; private set; }
private long _speedLimit = 0;
/// <summary>bytes per second</summary>
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 CancellationTokenSource _cancellationSource { get; } = new();
private EventWaitHandle _downloadedPiece { get; set; }
private Task _backgroundDownloadTask { get; set; }
#endregion
@@ -128,7 +129,7 @@ namespace AaxDecrypter
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}");
if (_backgroundDownloadTask is not null)
if (DownloadTask is not null)
throw new InvalidOperationException("Cannot change Uri after download has started.");
Uri = uriToSameFile;
@@ -141,7 +142,7 @@ namespace AaxDecrypter
{
if (ContentLength != 0 && WritePosition == ContentLength)
{
_backgroundDownloadTask = Task.CompletedTask;
DownloadTask = Task.CompletedTask;
return;
}
@@ -167,7 +168,8 @@ namespace AaxDecrypter
_downloadedPiece = new EventWaitHandle(false, EventResetMode.AutoReset);
//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>
@@ -234,7 +236,6 @@ namespace AaxDecrypter
_writeFile.Close();
_downloadedPiece.Set();
OnUpdate();
DownloadCompleted?.Invoke(this, null);
}
}
@@ -256,7 +257,7 @@ namespace AaxDecrypter
{
get
{
if (_backgroundDownloadTask is null)
if (DownloadTask is null)
throw new InvalidOperationException($"Background downloader must first be started by calling {nameof(BeginDownloadingAsync)}");
return ContentLength;
}
@@ -280,7 +281,7 @@ namespace AaxDecrypter
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)}");
var toRead = Math.Min(count, Length - Position);
@@ -306,7 +307,7 @@ namespace AaxDecrypter
private void WaitToPosition(long requiredPosition)
{
while (WritePosition < requiredPosition
&& _backgroundDownloadTask?.IsCompleted is false
&& DownloadTask?.IsCompleted is false
&& !IsCancelled)
{
_downloadedPiece.WaitOne(50);
@@ -326,7 +327,7 @@ namespace AaxDecrypter
if (disposing && !disposed)
{
_cancellationSource.Cancel();
_backgroundDownloadTask?.GetAwaiter().GetResult();
DownloadTask?.GetAwaiter().GetResult();
_downloadedPiece?.Dispose();
_cancellationSource?.Dispose();
_readFile.Dispose();

View File

@@ -26,11 +26,7 @@ namespace AaxDecrypter
protected override async Task<bool> Step_DownloadAndDecryptAudiobookAsync()
{
TaskCompletionSource completionSource = new();
InputFileStream.DownloadCompleted += (_, _) => completionSource.SetResult();
await completionSource.Task;
await InputFileStream.DownloadTask;
if (IsCanceled)
return false;

View File

@@ -2,7 +2,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<Version>10.4.3.1</Version>
<Version>10.5.0.1</Version>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Octokit" Version="6.0.0" />

View File

@@ -43,21 +43,6 @@ namespace AppScaffolding
public static ReleaseIdentifier ReleaseIdentifier { 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
private static Assembly _executingAssembly;
private static Assembly ExecutingAssembly
@@ -111,6 +96,22 @@ namespace AppScaffolding
configureLogging(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
wireUpSystemEvents(config);

View File

@@ -13,7 +13,11 @@ namespace DataLayer
LC_64_22050_stereo = (64L << 18) | (22050 << 2) | 2,
LC_64_44100_stereo = (64L << 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
{

View File

@@ -121,8 +121,9 @@ namespace FileLiberator
downloadValidation(libraryBook);
var quality = (AudibleApi.DownloadQuality)config.FileDownloadQuality;
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);
var outFileName = AudibleFileStorage.Audio.GetInProgressFilename(libraryBook, dlOptions.OutputFormat.ToString().ToLower());
@@ -169,7 +170,10 @@ namespace FileLiberator
? contentLic.ContentMetadata.ChapterInfo.BrandIntroDurationMs
: 0;
var dlOptions = new DownloadOptions(config, libraryBook, contentLic?.ContentMetadata?.ContentUrl?.OfflineUrl)
//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)
{
AudibleKey = contentLic?.Voucher?.Key,
AudibleIV = contentLic?.Voucher?.Iv,

View File

@@ -21,6 +21,13 @@ namespace FileLiberator
public TimeSpan RuntimeLength { get; init; }
public OutputFormat OutputFormat { 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 string UserAgent => AudibleApi.Resources.Download_User_Agent;
public bool TrimOutputToChapterLength => config.AllowLibationFixup && config.StripAudibleBrandAudio;

View File

@@ -163,6 +163,11 @@ namespace FileManager
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)]
private static extern int GetShortPathName([MarshalAs(UnmanagedType.LPWStr)] string path, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder shortPath, int shortPathLength);

View File

@@ -67,13 +67,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia" Version="11.0.0-rc2.2" />
<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.-->
<PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Themes.Fluent" 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-rc2.2" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-rc2.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-rc2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\HangoverBase\HangoverBase.csproj" />

View File

@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
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:vm="clr-namespace:LibationAvalonia.ViewModels.Settings"
x:DataType="vm:AudioSettingsVM"
@@ -32,6 +32,18 @@
Grid.Row="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}">
<TextBlock Text="{CompiledBinding CreateCueSheetText}" />
</CheckBox>

View File

@@ -70,13 +70,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-rc1.1" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-rc1.1" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.0-rc2.2" Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'" />
<PackageReference Include="Avalonia" Version="11.0.0-rc2.2" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.0-rc2.2" />
<PackageReference Include="Avalonia.Controls.ItemsRepeater" Version="11.0.0-rc2.2" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.0-rc2.2" />
<PackageReference Include="Avalonia.ReactiveUI" Version="11.0.0-rc2.2" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.0-rc2.2" />
</ItemGroup>
<ItemGroup>

View File

@@ -48,12 +48,6 @@ namespace LibationAvalonia
var classicLifetimeTask = Task.Run(() => new ClassicDesktopStyleApplicationLifetime());
var appBuilderTask = Task.Run(BuildAvaloniaApp);
LibationScaffolding.SetReleaseIdentifier(Variety.Chardonnay);
if (LibationScaffolding.ReleaseIdentifier is ReleaseIdentifier.None)
return;
if (config.LibationSettingsAreValid)
{
if (!RunDbMigrations(config))
@@ -81,7 +75,7 @@ namespace LibationAvalonia
LibationScaffolding.RunPostConfigMigrations(config);
LibationScaffolding.RunPostMigrationScaffolding(config);
return true;
return LibationScaffolding.ReleaseIdentifier is not ReleaseIdentifier.None;
}
catch (Exception exDebug)
{

View File

@@ -48,6 +48,7 @@ namespace LibationAvalonia.ViewModels.Settings
DownloadCoverArt = config.DownloadCoverArt;
RetainAaxFile = config.RetainAaxFile;
DownloadClipsBookmarks = config.DownloadClipsBookmarks;
FileDownloadQuality = config.FileDownloadQuality;
ClipBookmarkFormat = config.ClipsBookmarksFileFormat;
SplitFilesByChapter = config.SplitFilesByChapter;
MergeOpeningAndEndCredits = config.MergeOpeningAndEndCredits;
@@ -74,6 +75,7 @@ namespace LibationAvalonia.ViewModels.Settings
config.DownloadCoverArt = DownloadCoverArt;
config.RetainAaxFile = RetainAaxFile;
config.DownloadClipsBookmarks = DownloadClipsBookmarks;
config.FileDownloadQuality = FileDownloadQuality;
config.ClipsBookmarksFileFormat = ClipBookmarkFormat;
config.SplitFilesByChapter = SplitFilesByChapter;
config.MergeOpeningAndEndCredits = MergeOpeningAndEndCredits;
@@ -93,7 +95,9 @@ namespace LibationAvalonia.ViewModels.Settings
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 string FileDownloadQualityText { get; } = Configuration.GetDescription(nameof(Configuration.FileDownloadQuality));
public string CreateCueSheetText { get; } = Configuration.GetDescription(nameof(Configuration.CreateCueSheet));
public string AllowLibationFixupText { get; } = Configuration.GetDescription(nameof(Configuration.AllowLibationFixup));
public string DownloadCoverArtText { get; } = Configuration.GetDescription(nameof(Configuration.DownloadCoverArt));
@@ -109,6 +113,7 @@ namespace LibationAvalonia.ViewModels.Settings
public bool DownloadCoverArt { get; set; }
public bool RetainAaxFile { get; set; }
public bool DownloadClipsBookmarks { get => _downloadClipsBookmarks; set => this.RaiseAndSetIfChanged(ref _downloadClipsBookmarks, value); }
public Configuration.DownloadQuality FileDownloadQuality { get; set; }
public Configuration.ClipBookmarkFormat ClipBookmarkFormat { get; set; }
public bool MergeOpeningAndEndCredits { get; set; }
public bool StripAudibleBrandAudio { get; set; }

View 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));
}

View 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;
}
}

View File

@@ -3,7 +3,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net7.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishReadyToRun>true</PublishReadyToRun>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>

View File

@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CommandLine;
using System.Threading.Tasks;
using CommandLine;
namespace LibationCli
{

View File

@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using AudibleUtilities;
using ApplicationServices;
using CommandLine;
using System;
using System.IO;
using System.Threading.Tasks;
namespace LibationCli
{
@@ -29,26 +27,38 @@ namespace LibationCli
}
*/
#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; }
[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; }
[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; }
protected override Task ProcessAsync()
{
if (xlsx)
LibraryExporter.ToXlsx(FilePath);
if (csv)
LibraryExporter.ToCsv(FilePath);
if (json)
LibraryExporter.ToJson(FilePath);
Console.WriteLine($"Library exported to: {FilePath}");
Action<string> exporter
= csv ? LibraryExporter.ToCsv
: json ? LibraryExporter.ToJson
: xlsx ? LibraryExporter.ToXlsx
: Path.GetExtension(FilePath)?.ToLower() switch
{
".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}");
}
return Task.CompletedTask;
}
}

View File

@@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommandLine;
using CommandLine;
using DataLayer;
using FileLiberator;
using System.Threading.Tasks;
namespace LibationCli
{

View File

@@ -1,18 +1,18 @@
using System;
using ApplicationServices;
using AudibleUtilities;
using CommandLine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using AudibleUtilities;
using CommandLine;
namespace LibationCli
{
[Verb("scan", HelpText = "Scan library. Default: scan all accounts. Optional: use 'account' flag to specify a single account.")]
public class ScanOptions : OptionsBase
{
[Value(0, MetaName = "Accounts", HelpText = "Optional: nicknames of accounts to scan.", Required = false)]
public IEnumerable<string> AccountNicknames { get; set; }
[Value(0, MetaName = "Accounts", HelpText = "Optional: user ID or nicknames of accounts to scan.", Required = false)]
public IEnumerable<string> AccountNames { get; set; }
protected override async Task ProcessAsync()
{
@@ -42,13 +42,19 @@ namespace LibationCli
private Account[] getAccounts()
{
using var persister = AudibleApiStorage.GetAccountsSettingsPersister();
var accounts = persister.AccountsSettings.GetAll().ToArray();
var allAccounts = persister.AccountsSettings.GetAll().ToArray();
if (!AccountNicknames.Any())
return accounts;
if (!AccountNames.Any())
return allAccounts;
var found = accounts.Where(acct => AccountNicknames.Contains(acct.AccountName)).ToArray();
var notFound = AccountNicknames.Except(found.Select(f => f.AccountName)).ToArray();
var accountNames = AccountNames.Select(n => n.ToLower()).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
if (!found.Any())

View 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}";
}
}

View File

@@ -1,37 +1,70 @@
using System;
using ApplicationServices;
using CommandLine;
using DataLayer;
using Dinah.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using AudibleUtilities;
using CommandLine;
namespace LibationCli
{
[Verb("set-status", HelpText = """
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'
[Verb("set-status", HelpText = """
Set download statuses throughout library based on whether each book's audio file can be found.
""")]
public class SetDownloadStatusOptions : OptionsBase
{
[Option(shortName: 'd', longName: "downloaded", Required = true)]
public bool SetDownloaded { get; set; }
public class SetDownloadStatusOptions : OptionsBase
{
//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; }
[Option(shortName: 'n', longName: "not-downloaded", Required = true)]
public bool SetNotDownloaded { get; set; }
[Option(shortName: 'n', longName: "not-downloaded", Group = "Download Status", HelpText = "set download status to 'Not Downloaded'")]
public bool SetNotDownloaded { get; set; }
protected override async Task ProcessAsync()
{
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
[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; }
var bulkSetStatus = new BulkSetDownloadStatus(libraryBooks, SetDownloaded, SetNotDownloaded);
await Task.Run(() => bulkSetStatus.Discover());
bulkSetStatus.Execute();
[Value(0, MetaName = "[asins]", HelpText = "Optional product IDs of books on which to set download status.")]
public IEnumerable<string> Asins { get; set; }
foreach (var msg in bulkSetStatus.Messages)
Console.WriteLine(msg);
}
}
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();
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);
await Task.Run(() => bulkSetStatus.Discover());
bulkSetStatus.Execute();
foreach (var msg in bulkSetStatus.Messages)
Console.WriteLine(msg);
}
}
}
}

View 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;
}
}

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using CommandLine;
using System;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using CommandLine;
namespace LibationCli
{
@@ -17,15 +17,34 @@ namespace LibationCli
catch (Exception ex)
{
Environment.ExitCode = (int)ExitCode.RunTimeError;
Console.Error.WriteLine("ERROR");
Console.Error.WriteLine("=====");
Console.Error.WriteLine(ex.Message);
Console.Error.WriteLine();
Console.Error.WriteLine(ex.StackTrace);
PrintVerbUsage(new string[]
{
"ERROR",
"=====",
ex.Message,
"",
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();
}
}

View File

@@ -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.Linq;
using System.Threading.Tasks;
using ApplicationServices;
using CommandLine;
using DataLayer;
using FileLiberator;
namespace LibationCli
{
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)
where TProcessable : Processable, new()
{
var progressBar = new ConsoleProgressBar(Console.Out);
var strProc = new TProcessable();
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) =>
{
@@ -32,13 +43,28 @@ namespace LibationCli
}
};
strProc.StreamingTimeRemaining += (_, e) => progressBar.RemainingTime = e;
strProc.StreamingProgressChanged += (_, e) => progressBar.Progress = e.ProgressPercentage;
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()))
await ProcessOneAsync(Processable, libraryBook, false);
var libraryBooks = DbContexts.GetLibrary_Flat_NoTracking();
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";
Console.WriteLine(done);

View File

@@ -1,12 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CommandLine;
using CommandLine;
using CommandLine.Text;
using Dinah.Core;
using Dinah.Core.Collections;
using Dinah.Core.Collections.Generic;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace LibationCli
{
@@ -19,47 +16,63 @@ namespace LibationCli
}
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
string input = null;
string input = "";
//input = " set-status -n --force B017V4IM1G";
//input = " liberate B017V4IM1G";
//input = " convert B017V4IM1G";
//input = " search \"-liberated\"";
//input = " export --help";
//input = " version --check";
//input = " scan rmcrackan";
//input = " help set-status";
//input = " liberate ";
// note: this hack will fail for quoted file paths with spaces because it will break on those spaces
if (!string.IsNullOrWhiteSpace(input))
args = input.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var setBreakPointHere = args;
#endif
var result = Parser.Default.ParseArguments(args, types);
var result = new Parser(ConfigureParser).ParseArguments(args, VerbTypes);
// if successfully parsed
// async: run parsed options
await result.WithParsedAsync<OptionsBase>(opt => opt.Run());
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
// if not successfully parsed
// sync: handle parse errors
result.WithNotParsed(errors => HandleErrors(result, errors));
//***********************************************//
// //
// do not use Configuration before this line //
// //
//***********************************************//
Setup.Initialize();
return Environment.ExitCode;
// if successfully parsed
// async: run parsed options
await result.WithParsedAsync<OptionsBase>(opt => opt.Run());
}
}
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)))
{
Environment.ExitCode = (int)ExitCode.NonRunNonError;
@@ -67,17 +80,36 @@ namespace LibationCli
}
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");
return;
//Print LibationCli usage
helpText.AddPreOptionsLine("No verb selected");
helpText.AddVerbs(VerbTypes);
}
else
{
//print the specified verb's usage
helpText.AddDashesToOption = true;
helpText.AutoHelp = true;
var helpText = HelpText.AutoBuild(result,
h => HelpText.DefaultParsingErrorsHandler(result, h),
e => e);
Console.WriteLine(helpText);
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);
}
helpText.AddOptions(result);
}
Console.Error.WriteLine(helpText);
}
private static void ConfigureParser(ParserSettings settings)
{
settings.AutoVersion = false;
settings.AutoHelp = false;
}
}
}

View File

@@ -1,14 +1,8 @@
using System;
using System.Collections.Generic;
using AppScaffolding;
using CommandLine;
using System;
using System.Linq;
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
{
@@ -26,28 +20,6 @@ namespace LibationCli
LibationScaffolding.RunPostConfigMigrations(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()

View File

@@ -126,7 +126,16 @@ namespace LibationFileManager
BookDirectoryFiles = new BackgroundFileSystem(BooksDirectory, "*.*", SearchOption.AllDirectories);
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();

View File

@@ -205,6 +205,16 @@ namespace LibationFileManager
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:")]
public DateTimeSource CreationTime { get => GetNonString(defaultValue: DateTimeSource.File); set => SetNonString(value); }

View File

@@ -9,6 +9,7 @@ namespace LibationWinForms.Dialogs
{
private void Load_AudioSettings(Configuration config)
{
this.fileDownloadQualityLbl.Text = desc(nameof(config.FileDownloadQuality));
this.allowLibationFixupCbox.Text = desc(nameof(config.AllowLibationFixup));
this.createCueSheetCbox.Text = desc(nameof(config.CreateCueSheet));
this.downloadCoverArtCbox.Text = desc(nameof(config.DownloadCoverArt));
@@ -19,6 +20,13 @@ namespace LibationWinForms.Dialogs
this.stripUnabridgedCbox.Text = desc(nameof(config.StripUnabridged));
this.moveMoovAtomCbox.Text = desc(nameof(config.MoveMoovToBeginning));
fileDownloadQualityCb.Items.AddRange(
new object[]
{
Configuration.DownloadQuality.Normal,
Configuration.DownloadQuality.High
});
clipsBookmarksFormatCb.Items.AddRange(
new object[]
{
@@ -44,6 +52,7 @@ namespace LibationWinForms.Dialogs
createCueSheetCbox.Checked = config.CreateCueSheet;
downloadCoverArtCbox.Checked = config.DownloadCoverArt;
downloadClipsBookmarksCbox.Checked = config.DownloadClipsBookmarks;
fileDownloadQualityCb.SelectedItem = config.FileDownloadQuality;
clipsBookmarksFormatCb.SelectedItem = config.ClipsBookmarksFileFormat;
retainAaxFileCbox.Checked = config.RetainAaxFile;
splitFilesByChapterCbox.Checked = config.SplitFilesByChapter;
@@ -87,6 +96,7 @@ namespace LibationWinForms.Dialogs
config.CreateCueSheet = createCueSheetCbox.Checked;
config.DownloadCoverArt = downloadCoverArtCbox.Checked;
config.DownloadClipsBookmarks = downloadClipsBookmarksCbox.Checked;
config.FileDownloadQuality = (Configuration.DownloadQuality)fileDownloadQualityCb.SelectedItem;
config.ClipsBookmarksFileFormat = (Configuration.ClipBookmarkFormat)clipsBookmarksFormatCb.SelectedItem;
config.RetainAaxFile = retainAaxFileCbox.Checked;
config.SplitFilesByChapter = splitFilesByChapterCbox.Checked;
@@ -98,7 +108,6 @@ namespace LibationWinForms.Dialogs
config.LameTargetBitrate = lameTargetBitrateRb.Checked;
config.MaxSampleRate = ((EnumDiaplay<AAXClean.SampleRate>)maxSampleRateCb.SelectedItem).Value;
config.LameEncoderQuality = (NAudio.Lame.EncoderQuality)encoderQualityCb.SelectedItem;
encoderQualityCb.SelectedItem = config.LameEncoderQuality;
config.LameDownsampleMono = lameDownsampleMonoCbox.Checked;
config.LameBitrate = lameBitrateTb.Value;
config.LameConstantBitrate = lameConstantBitrateCbox.Checked;

View File

File diff suppressed because it is too large Load Diff

View File

@@ -14,15 +14,6 @@ namespace LibationWinForms.Dialogs
this.importEpisodesCb.Text = desc(nameof(config.ImportEpisodes));
this.downloadEpisodesCb.Text = desc(nameof(config.DownloadEpisodes));
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;
showImportedStatsCb.Checked = config.ShowImportedStats;
@@ -32,9 +23,6 @@ namespace LibationWinForms.Dialogs
}
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.ShowImportedStats = showImportedStatsCb.Checked;
config.ImportEpisodes = importEpisodesCb.Checked;

View File

@@ -1,6 +1,7 @@
using Dinah.Core;
using FileManager;
using LibationFileManager;
using LibationUiBase;
using System;
using System.IO;
using System.Linq;
@@ -25,6 +26,16 @@ namespace LibationWinForms.Dialogs
betaOptInCbox.Text = desc(nameof(config.BetaOptIn));
saveEpisodesToSeriesFolderCbox.Text = desc(nameof(config.SavePodcastsToParentFolder));
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.SetDirectoryItems(
@@ -82,6 +93,11 @@ namespace LibationWinForms.Dialogs
config.OverwriteExisting = overwriteExistingCbox.Checked;
config.BetaOptIn = betaOptInCbox.Checked;
config.CreationTime = ((EnumDiaplay<Configuration.DateTimeSource>)creationTimeCb.SelectedItem).Value;
config.LastWriteTime = ((EnumDiaplay<Configuration.DateTimeSource>)lastWriteTimeCb.SelectedItem).Value;
}

View File

@@ -30,8 +30,6 @@ namespace LibationWinForms
ApplicationConfiguration.Initialize();
AppScaffolding.LibationScaffolding.SetReleaseIdentifier(AppScaffolding.Variety.Classic);
//***********************************************//
// //
// do not use Configuration before this line //