Increase tag template options for contributor and series types
- Add template tag support for multiple series - Add series ID and contributor ID to template tags - <first author> and <first narrator> are now name types with name formatter support - Properly import contributor IDs into database - Updated docs
This commit is contained in:
parent
0a9e489f48
commit
7d806e0f3e
@ -17,6 +17,9 @@ These templates apply to both GUI and CLI.
|
|||||||
- [Conditional Tags](#conditional-tags)
|
- [Conditional Tags](#conditional-tags)
|
||||||
- [Tag Formatters](#tag-formatters)
|
- [Tag Formatters](#tag-formatters)
|
||||||
- [Text Formatters](#text-formatters)
|
- [Text Formatters](#text-formatters)
|
||||||
|
- [Series Formatters](#series-formatters)
|
||||||
|
- [Series List Formatters](#series-list-formatters)
|
||||||
|
- [Name Formatters](#name-formatters)
|
||||||
- [Name List Formatters](#name-list-formatters)
|
- [Name List Formatters](#name-list-formatters)
|
||||||
- [Number Formatters](#number-formatters)
|
- [Number Formatters](#number-formatters)
|
||||||
- [Date Formatters](#date-formatters)
|
- [Date Formatters](#date-formatters)
|
||||||
@ -32,32 +35,33 @@ These tags will be replaced in the template with the audiobook's values.
|
|||||||
|Tag|Description|Type|
|
|Tag|Description|Type|
|
||||||
|-|-|-|
|
|-|-|-|
|
||||||
|\<id\> **†**|Audible book ID (ASIN)|Text|
|
|\<id\> **†**|Audible book ID (ASIN)|Text|
|
||||||
|\<title\>|Full title with subtitle|Text|
|
|\<title\>|Full title with subtitle|[Text](#text-formatters)|
|
||||||
|\<title short\>|Title. Stop at first colon|Text|
|
|\<title short\>|Title. Stop at first colon|[Text](#text-formatters)|
|
||||||
|\<audible title\>|Audible's title (does not include subtitle)|Text|
|
|\<audible title\>|Audible's title (does not include subtitle)|[Text](#text-formatters)|
|
||||||
|\<audible subtitle\>|Audible's subtitle|Text|
|
|\<audible subtitle\>|Audible's subtitle|[Text](#text-formatters)|
|
||||||
|\<author\>|Author(s)|Name List|
|
|\<author\>|Author(s)|[Name List](#name-list-formatters)|
|
||||||
|\<first author\>|First author|Text|
|
|\<first author\>|First author|[Name](#name-formatters)|
|
||||||
|\<narrator\>|Narrator(s)|Name List|
|
|\<narrator\>|Narrator(s)|[Name List](#name-list-formatters)|
|
||||||
|\<first narrator\>|First narrator|Text|
|
|\<first narrator\>|First narrator|[Name](#name-formatters)|
|
||||||
|\<series\>|Name of series|Text|
|
|\<series\>|All series to which the book belongs (if any)|[Series List](#series-list-formatters)|
|
||||||
|\<series#\>|Number order in series|Number|
|
|\<first series\>|First series|[Series](#series-formatters)|
|
||||||
|\<bitrate\>|File's original bitrate (Kbps)|Number|
|
|\<series#\>|Number order in series (alias for \<first series[{#}]\>|[Number](#number-formatters)|
|
||||||
|\<samplerate\>|File's original audio sample rate|Number|
|
|\<bitrate\>|File's original bitrate (Kbps)|[Number](#number-formatters)|
|
||||||
|\<channels\>|Number of audio channels|Number|
|
|\<samplerate\>|File's original audio sample rate|[Number](#number-formatters)|
|
||||||
|\<account\>|Audible account of this book|Text|
|
|\<channels\>|Number of audio channels|[Number](#number-formatters)|
|
||||||
|\<account nickname\>|Audible account nickname of this book|Text|
|
|\<account\>|Audible account of this book|[Text](#text-formatters)|
|
||||||
|\<locale\>|Region/country|Text|
|
|\<account nickname\>|Audible account nickname of this book|[Text](#text-formatters)|
|
||||||
|\<year\>|Year published|Number|
|
|\<locale\>|Region/country|[Text](#text-formatters)|
|
||||||
|\<language\>|Book's language|Text|
|
|\<year\>|Year published|[Number](#number-formatters)|
|
||||||
|
|\<language\>|Book's language|[Text](#text-formatters)|
|
||||||
|\<language short\> **†**|Book's language abbreviated. Eg: ENG|Text|
|
|\<language short\> **†**|Book's language abbreviated. Eg: ENG|Text|
|
||||||
|\<file date\>|File creation date/time.|DateTime|
|
|\<file date\>|File creation date/time.|[DateTime](#date-formatters)|
|
||||||
|\<pub date\>|Audiobook publication date|DateTime|
|
|\<pub date\>|Audiobook publication date|[DateTime](#date-formatters)|
|
||||||
|\<date added\>|Date the book added to your Audible account|DateTime|
|
|\<date added\>|Date the book added to your Audible account|[DateTime](#date-formatters)|
|
||||||
|\<ch count\> **‡**|Number of chapters|Number|
|
|\<ch count\> **‡**|Number of chapters|[Number](#number-formatters)|
|
||||||
|\<ch title\> **‡**|Chapter title|Text|
|
|\<ch title\> **‡**|Chapter title|[Text](#text-formatters)|
|
||||||
|\<ch#\> **‡**|Chapter number|Number|
|
|\<ch#\> **‡**|Chapter number|[Number](#number-formatters)|
|
||||||
|\<ch# 0\> **‡**|Chapter number with leading zeros|Number|
|
|\<ch# 0\> **‡**|Chapter number with leading zeros|[Number](#number-formatters)|
|
||||||
|
|
||||||
**†** Does not support custom formatting
|
**†** Does not support custom formatting
|
||||||
|
|
||||||
@ -95,11 +99,28 @@ As an example, this folder template will place all Liberated podcasts into a "Po
|
|||||||
|L|Converts text to lowercase|\<title[L]\>|a study in scarlet꞉ a sherlock holmes novel|
|
|L|Converts text to lowercase|\<title[L]\>|a study in scarlet꞉ a sherlock holmes novel|
|
||||||
|U|Converts text to uppercase|\<title short[U]\>|A STUDY IN SCARLET|
|
|U|Converts text to uppercase|\<title short[U]\>|A STUDY IN SCARLET|
|
||||||
|
|
||||||
|
## Series Formatters
|
||||||
|
|Formatter|Description|Example Usage|Example Result|
|
||||||
|
|-|-|-|-|
|
||||||
|
|\{N \| # \| ID\}|Formats the series using<br>the series part tags.<br>\{N\} = Series Name<br>\{#\} = Number order in series<br>\{ID\} = Audible Series ID<br><br>Default is \{N\}|`<first series>`<hr>`<first series[{N}]>`<hr>`<first series[{N}, {#}, {ID}]>`|Sherlock Holmes<hr>Sherlock Holmes<hr>Sherlock Holmes, 1, B08376S3R2|
|
||||||
|
|
||||||
|
## Series List Formatters
|
||||||
|
|Formatter|Description|Example Usage|Example Result|
|
||||||
|
|-|-|-|-|
|
||||||
|
|separator()|Speficy the text used to join<br>multiple series names.<br><br>Default is ", "|`<series[separator(; )]>`|Sherlock Holmes; Some Other Series|
|
||||||
|
|format(\{N \| # \| ID\})|Formats the series properties<br>using the name series tags.<br>See [Series Formatter Usage](#series-formatters) above.|`<series[format({N}, {#})`<br>`separator(; )]>`<hr>`<author[format({L}, {ID}) separator(; )]>`|Sherlock Holmes, 1; Some Other Series, 1<hr>herlock Holmes, B08376S3R2; Some Other Series, B000000000|
|
||||||
|
|max(#)|Only use the first # of series<br><br>Default is all series|`<series[max(1)]>`|Sherlock Holmes|
|
||||||
|
|
||||||
|
## Name Formatters
|
||||||
|
|Formatter|Description|Example Usage|Example Result|
|
||||||
|
|-|-|-|-|
|
||||||
|
|\{T \| F \| M \| L \| S \| ID\}|Formats the human name using<br>the name part tags.<br>\{T\} = Title (e.g. "Dr.")<br>\{F\} = First name<br>\{M\} = Middle name<br>\{L\} = Last Name<br>\{S\} = Suffix (e.g. "PhD")<br>\{ID\} = Audible Contributor ID<br><br>Default is \{P\} \{F\} \{M\} \{L\} \{S\}|`<first narrator[{L}, {F}]>`<hr>`<first author[{L}, {F} _{ID}_]>`|Fry, Stephen<hr>Doyle, Arthur \_B000AQ43GQ\_;<br>Fry, Stephen \_B000APAGVS\_|
|
||||||
|
|
||||||
## Name List Formatters
|
## Name List Formatters
|
||||||
|Formatter|Description|Example Usage|Example Result|
|
|Formatter|Description|Example Usage|Example Result|
|
||||||
|-|-|-|-|
|
|-|-|-|-|
|
||||||
|separator()|Speficy the text used to join<br>multiple people's names.<br><br>Default is ", "|`<author[separator(; )]>`|Arthur Conan Doyle; Stephen Fry|
|
|separator()|Speficy the text used to join<br>multiple people's names.<br><br>Default is ", "|`<author[separator(; )]>`|Arthur Conan Doyle; Stephen Fry|
|
||||||
|format(\{T \| F \| M \| L \| S\})|Formats the human name using<br>the name part tags.<br>\{T\} = Title (e.g. "Dr.")<br>\{F\} = First name<br>\{M\} = Middle name<br>\{L\} = Last Name<br>\{S\} = Suffix (e.g. "PhD")<br><br>Default is \{P\} \{F\} \{M\} \{L\} \{S\}|`<author[format({L}, {F})`<br>`separator(; )]>`|Doyle, Arthur; Fry, Stephen|
|
|format(\{T \| F \| M \| L \| S \| ID\})|Formats the human name using<br>the name part tags.<br>See [Name Formatter Usage](#name-formatters) above.|`<author[format({L}, {F})`<br>`separator(; )]>`<hr>`<author[format({L}, {F}`<br>`_{ID}_) separator(; )]>`|Doyle, Arthur; Fry, Stephen<hr>Doyle, Arthur \_B000AQ43GQ\_;<br>Fry, Stephen \_B000APAGVS\_|
|
||||||
|sort(F \| M \| L)|Sorts the names by first, middle,<br>or last name<br><br>Default is unsorted|`<author[sort(M)]>`|Stephen Fry, Arthur Conan Doyle|
|
|sort(F \| M \| L)|Sorts the names by first, middle,<br>or last name<br><br>Default is unsorted|`<author[sort(M)]>`|Stephen Fry, Arthur Conan Doyle|
|
||||||
|max(#)|Only use the first # of names<br><br>Default is all names|`<author[max(1)]>`|Arthur Conan Doyle|
|
|max(#)|Only use the first # of names<br><br>Default is all names|`<author[max(1)]>`|Arthur Conan Doyle|
|
||||||
|
|
||||||
|
|||||||
@ -418,7 +418,6 @@ namespace AppScaffolding
|
|||||||
public List<string> Filters { get; set; } = new();
|
public List<string> Filters { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void migrate_to_v12_0_1(Configuration config)
|
public static void migrate_to_v12_0_1(Configuration config)
|
||||||
{
|
{
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|||||||
@ -43,5 +43,7 @@ namespace DataLayer
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => Name;
|
public override string ToString() => Name;
|
||||||
|
public void SetAudibleContributorId(string audibleContributorId)
|
||||||
|
=> AudibleContributorId = audibleContributorId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,19 +61,19 @@ namespace DtoImporterService
|
|||||||
|
|
||||||
private int upsertPeople(List<Person> people)
|
private int upsertPeople(List<Person> people)
|
||||||
{
|
{
|
||||||
var hash = people
|
var qtyNew = 0;
|
||||||
// new people only
|
foreach (var person in people)
|
||||||
.Where(p => !Cache.ContainsKey(p.Name))
|
|
||||||
// remove duplicates by Name. first in wins
|
|
||||||
.ToDictionarySafe(p => p.Name);
|
|
||||||
|
|
||||||
foreach (var kvp in hash)
|
|
||||||
{
|
{
|
||||||
var person = kvp.Value;
|
if (!Cache.TryGetValue(person.Name, out var contributor))
|
||||||
addContributor(person.Name, person.Asin);
|
{
|
||||||
|
contributor = createContributor(person.Name, person.Asin);
|
||||||
|
qtyNew++;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateContributor(person, contributor);
|
||||||
}
|
}
|
||||||
|
|
||||||
return hash.Count;
|
return qtyNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
// only use after loading contributors => local
|
// only use after loading contributors => local
|
||||||
@ -86,16 +86,22 @@ namespace DtoImporterService
|
|||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
|
|
||||||
foreach (var pub in hash)
|
foreach (var pub in hash)
|
||||||
addContributor(pub);
|
createContributor(pub);
|
||||||
|
|
||||||
return hash.Count;
|
return hash.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Contributor addContributor(string name, string id = null)
|
private void updateContributor(Person person, Contributor contributor)
|
||||||
|
{
|
||||||
|
if (person.Asin != contributor.AudibleContributorId)
|
||||||
|
contributor.SetAudibleContributorId(person.Asin);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Contributor createContributor(string name, string id = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var newContrib = new Contributor(name);
|
var newContrib = new Contributor(name, id);
|
||||||
|
|
||||||
var entityEntry = DbContext.Contributors.Add(newContrib);
|
var entityEntry = DbContext.Contributors.Add(newContrib);
|
||||||
var entity = entityEntry.Entity;
|
var entity = entityEntry.Entity;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
|
|
||||||
namespace FileLiberator
|
namespace FileLiberator
|
||||||
{
|
{
|
||||||
|
|||||||
@ -158,7 +158,7 @@ namespace FileLiberator
|
|||||||
|
|
||||||
if (success && config.SaveMetadataToFile)
|
if (success && config.SaveMetadataToFile)
|
||||||
{
|
{
|
||||||
var metadataFile = Templates.File.GetFilename(dlOptions.LibraryBookDto, Path.GetDirectoryName(outFileName), ".metadata.json");
|
var metadataFile = LibationFileManager.Templates.Templates.File.GetFilename(dlOptions.LibraryBookDto, Path.GetDirectoryName(outFileName), ".metadata.json");
|
||||||
|
|
||||||
var item = await api.GetCatalogProductAsync(libraryBook.Book.AudibleProductId, AudibleApi.CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
|
var item = await api.GetCatalogProductAsync(libraryBook.Book.AudibleProductId, AudibleApi.CatalogOptions.ResponseGroupOptions.ALL_OPTIONS);
|
||||||
item.SourceJson.Add(nameof(ContentMetadata.ChapterInfo), Newtonsoft.Json.Linq.JObject.FromObject(contentLic.ContentMetadata.ChapterInfo));
|
item.SourceJson.Add(nameof(ContentMetadata.ChapterInfo), Newtonsoft.Json.Linq.JObject.FromObject(contentLic.ContentMetadata.ChapterInfo));
|
||||||
|
|||||||
@ -7,6 +7,7 @@ using System.Threading.Tasks;
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using ApplicationServices;
|
using ApplicationServices;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
|
|
||||||
namespace FileLiberator
|
namespace FileLiberator
|
||||||
{
|
{
|
||||||
@ -26,8 +27,8 @@ namespace FileLiberator
|
|||||||
public string Publisher => LibraryBook.Book.Publisher;
|
public string Publisher => LibraryBook.Book.Publisher;
|
||||||
public string Language => LibraryBook.Book.Language;
|
public string Language => LibraryBook.Book.Language;
|
||||||
public string AudibleProductId => LibraryBookDto.AudibleProductId;
|
public string AudibleProductId => LibraryBookDto.AudibleProductId;
|
||||||
public string SeriesName => LibraryBookDto.SeriesName;
|
public string SeriesName => LibraryBookDto.FirstSeries?.Name;
|
||||||
public float? SeriesNumber => LibraryBookDto.SeriesNumber;
|
public float? SeriesNumber => LibraryBookDto.FirstSeries?.Number;
|
||||||
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;
|
||||||
|
|||||||
@ -5,8 +5,9 @@ using System.Threading.Tasks;
|
|||||||
using AudibleUtilities;
|
using AudibleUtilities;
|
||||||
using DataLayer;
|
using DataLayer;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using LibationFileManager;
|
using LibationFileManager.Templates;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
namespace FileLiberator
|
namespace FileLiberator
|
||||||
{
|
{
|
||||||
public static class UtilityExtensions
|
public static class UtilityExtensions
|
||||||
@ -47,12 +48,10 @@ namespace FileLiberator
|
|||||||
YearPublished = libraryBook.Book.DatePublished?.Year,
|
YearPublished = libraryBook.Book.DatePublished?.Year,
|
||||||
DatePublished = libraryBook.Book.DatePublished,
|
DatePublished = libraryBook.Book.DatePublished,
|
||||||
|
|
||||||
Authors = libraryBook.Book.Authors.Select(c => c.Name).ToList(),
|
Authors = libraryBook.Book.Authors.Select(c => new ContributorDto(c.Name, c.AudibleContributorId)).ToList(),
|
||||||
|
Narrators = libraryBook.Book.Narrators.Select(c => new ContributorDto(c.Name, c.AudibleContributorId)).ToList(),
|
||||||
|
|
||||||
Narrators = libraryBook.Book.Narrators.Select(c => c.Name).ToList(),
|
Series = getSeries(libraryBook.Book.SeriesLink),
|
||||||
|
|
||||||
SeriesName = libraryBook.Book.SeriesLink.FirstOrDefault()?.Series.Name,
|
|
||||||
SeriesNumber = libraryBook.Book.SeriesLink.FirstOrDefault()?.Index,
|
|
||||||
IsPodcastParent = libraryBook.Book.IsEpisodeParent(),
|
IsPodcastParent = libraryBook.Book.IsEpisodeParent(),
|
||||||
IsPodcast = libraryBook.Book.IsEpisodeChild() || libraryBook.Book.IsEpisodeParent(),
|
IsPodcast = libraryBook.Book.IsEpisodeChild() || libraryBook.Book.IsEpisodeParent(),
|
||||||
|
|
||||||
@ -62,5 +61,21 @@ namespace FileLiberator
|
|||||||
Language = libraryBook.Book.Language
|
Language = libraryBook.Book.Language
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static List<SeriesDto>? getSeries(IEnumerable<SeriesBook> seriesBooks)
|
||||||
|
{
|
||||||
|
if (!seriesBooks.Any())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
//I don't remember why or if there was a good reason not to have series numbers for
|
||||||
|
//podcast parents, but preserving the behavior for backwards compatibility.
|
||||||
|
return seriesBooks
|
||||||
|
.Select(sb
|
||||||
|
=> new SeriesDto(
|
||||||
|
sb.Series.Name,
|
||||||
|
sb.Book.IsEpisodeParent() ? null : sb.Index,
|
||||||
|
sb.Series.AudibleSeriesId)
|
||||||
|
).ToList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ using Avalonia.Controls;
|
|||||||
using LibationAvalonia.Dialogs;
|
using LibationAvalonia.Dialogs;
|
||||||
using LibationAvalonia.ViewModels.Settings;
|
using LibationAvalonia.ViewModels.Settings;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationAvalonia.Controls.Settings
|
namespace LibationAvalonia.Controls.Settings
|
||||||
|
|||||||
@ -2,6 +2,7 @@ using Avalonia.Controls;
|
|||||||
using LibationAvalonia.Dialogs;
|
using LibationAvalonia.Dialogs;
|
||||||
using LibationAvalonia.ViewModels.Settings;
|
using LibationAvalonia.ViewModels.Settings;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace LibationAvalonia.Controls.Settings
|
namespace LibationAvalonia.Controls.Settings
|
||||||
|
|||||||
@ -5,6 +5,7 @@ using Avalonia.Media;
|
|||||||
using Avalonia.Styling;
|
using Avalonia.Styling;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|||||||
@ -12,6 +12,7 @@ using LibationAvalonia.Controls;
|
|||||||
using LibationAvalonia.Dialogs;
|
using LibationAvalonia.Dialogs;
|
||||||
using LibationAvalonia.ViewModels;
|
using LibationAvalonia.ViewModels;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
using LibationUiBase.GridView;
|
using LibationUiBase.GridView;
|
||||||
using ReactiveUI;
|
using ReactiveUI;
|
||||||
using System;
|
using System;
|
||||||
@ -350,7 +351,7 @@ namespace LibationAvalonia.Views
|
|||||||
#region Edit Templates (Single book only)
|
#region Edit Templates (Single book only)
|
||||||
|
|
||||||
async Task editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
|
async Task editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
|
||||||
where T : Templates, LibationFileManager.ITemplate, new()
|
where T : Templates, LibationFileManager.Templates.ITemplate, new()
|
||||||
{
|
{
|
||||||
var template = ctx.CreateTemplateEditor<T>(libraryBook, existingTemplate);
|
var template = ctx.CreateTemplateEditor<T>(libraryBook, existingTemplate);
|
||||||
var form = new EditTemplateDialog(template);
|
var form = new EditTemplateDialog(template);
|
||||||
|
|||||||
@ -306,41 +306,41 @@ namespace LibationFileManager
|
|||||||
[Description("How to format the folders in which files will be saved")]
|
[Description("How to format the folders in which files will be saved")]
|
||||||
public string FolderTemplate
|
public string FolderTemplate
|
||||||
{
|
{
|
||||||
get => getTemplate<Templates.FolderTemplate>();
|
get => getTemplate<Templates.Templates.FolderTemplate>();
|
||||||
set => setTemplate<Templates.FolderTemplate>(value);
|
set => setTemplate<Templates.Templates.FolderTemplate>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Description("How to format the saved pdf and audio files")]
|
[Description("How to format the saved pdf and audio files")]
|
||||||
public string FileTemplate
|
public string FileTemplate
|
||||||
{
|
{
|
||||||
get => getTemplate<Templates.FileTemplate>();
|
get => getTemplate<Templates.Templates.FileTemplate>();
|
||||||
set => setTemplate<Templates.FileTemplate>(value);
|
set => setTemplate<Templates.Templates.FileTemplate>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Description("How to format the saved audio files when split by chapters")]
|
[Description("How to format the saved audio files when split by chapters")]
|
||||||
public string ChapterFileTemplate
|
public string ChapterFileTemplate
|
||||||
{
|
{
|
||||||
get => getTemplate<Templates.ChapterFileTemplate>();
|
get => getTemplate<Templates.Templates.ChapterFileTemplate>();
|
||||||
set => setTemplate<Templates.ChapterFileTemplate>(value);
|
set => setTemplate<Templates.Templates.ChapterFileTemplate>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Description("How to format the file's Title stored in metadata")]
|
[Description("How to format the file's Title stored in metadata")]
|
||||||
public string ChapterTitleTemplate
|
public string ChapterTitleTemplate
|
||||||
{
|
{
|
||||||
get => getTemplate<Templates.ChapterTitleTemplate>();
|
get => getTemplate<Templates.Templates.ChapterTitleTemplate>();
|
||||||
set => setTemplate<Templates.ChapterTitleTemplate>(value);
|
set => setTemplate<Templates.Templates.ChapterTitleTemplate>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private string getTemplate<T>([CallerMemberName] string propertyName = "")
|
private string getTemplate<T>([CallerMemberName] string propertyName = "")
|
||||||
where T : Templates, ITemplate, new()
|
where T : Templates.Templates, Templates.ITemplate, new()
|
||||||
{
|
{
|
||||||
return Templates.GetTemplate<T>(GetString(defaultValue: T.DefaultTemplate, propertyName)).TemplateText;
|
return Templates.Templates.GetTemplate<T>(GetString(defaultValue: T.DefaultTemplate, propertyName)).TemplateText;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setTemplate<T>(string newValue, [CallerMemberName] string propertyName = "")
|
private void setTemplate<T>(string newValue, [CallerMemberName] string propertyName = "")
|
||||||
where T : Templates, ITemplate, new()
|
where T : Templates.Templates, Templates.ITemplate, new()
|
||||||
{
|
{
|
||||||
SetString(Templates.GetTemplate<T>(newValue).TemplateText, propertyName);
|
SetString(Templates.Templates.GetTemplate<T>(newValue).TemplateText, propertyName);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
namespace LibationFileManager
|
|
||||||
{
|
|
||||||
public class BookDto
|
|
||||||
{
|
|
||||||
public string? AudibleProductId { get; set; }
|
|
||||||
public string? Title { get; set; }
|
|
||||||
public string? Subtitle { get; set; }
|
|
||||||
public string? TitleWithSubtitle { get; set; }
|
|
||||||
public string? Locale { get; set; }
|
|
||||||
public int? YearPublished { get; set; }
|
|
||||||
|
|
||||||
public IEnumerable<string>? Authors { get; set; }
|
|
||||||
public string? AuthorNames => Authors is null ? null : string.Join(", ", Authors);
|
|
||||||
public string? FirstAuthor => Authors?.FirstOrDefault();
|
|
||||||
|
|
||||||
public IEnumerable<string>? Narrators { get; set; }
|
|
||||||
public string? NarratorNames => Narrators is null? null: string.Join(", ", Narrators);
|
|
||||||
public string? FirstNarrator => Narrators?.FirstOrDefault();
|
|
||||||
|
|
||||||
public string? SeriesName { get; set; }
|
|
||||||
public float? SeriesNumber { get; set; }
|
|
||||||
public bool IsSeries => !string.IsNullOrEmpty(SeriesName);
|
|
||||||
public bool IsPodcastParent { get; set; }
|
|
||||||
public bool IsPodcast { get; set; }
|
|
||||||
|
|
||||||
public int BitRate { get; set; }
|
|
||||||
public int SampleRate { get; set; }
|
|
||||||
public int Channels { get; set; }
|
|
||||||
public DateTime FileDate { get; set; } = DateTime.Now;
|
|
||||||
public DateTime? DatePublished { get; set; }
|
|
||||||
public string? Language { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class LibraryBookDto : BookDto
|
|
||||||
{
|
|
||||||
public DateTime? DateAdded { get; set; }
|
|
||||||
public string? Account { get; set; }
|
|
||||||
public string? AccountNickname { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,98 +0,0 @@
|
|||||||
using FileManager.NamingTemplate;
|
|
||||||
using NameParser;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
namespace LibationFileManager
|
|
||||||
{
|
|
||||||
internal partial class NameListFormat
|
|
||||||
{
|
|
||||||
public static string Formatter(ITemplateTag _, IEnumerable<string>? names, string formatString)
|
|
||||||
{
|
|
||||||
if (names is null) return "";
|
|
||||||
|
|
||||||
var humanNames = names.Select(n => new HumanName(RemoveSuffix(n), Prefer.FirstOverPrefix));
|
|
||||||
|
|
||||||
var sortedNames = Sort(humanNames, formatString);
|
|
||||||
var nameFormatString = Format(formatString, defaultValue: "{T} {F} {M} {L} {S}");
|
|
||||||
var separatorString = Separator(formatString, defaultValue: ", ");
|
|
||||||
var maxNames = Max(formatString, defaultValue: humanNames.Count());
|
|
||||||
|
|
||||||
var formattedNames = string.Join(separatorString, sortedNames.Take(maxNames).Select(n => FormatName(n, nameFormatString)));
|
|
||||||
|
|
||||||
while (formattedNames.Contains(" "))
|
|
||||||
formattedNames = formattedNames.Replace(" ", " ");
|
|
||||||
|
|
||||||
return formattedNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string RemoveSuffix(string namesString)
|
|
||||||
{
|
|
||||||
namesString = namesString.Replace('’', '\'').Replace(" - Ret.", ", Ret.");
|
|
||||||
int dashIndex = namesString.IndexOf(" - ");
|
|
||||||
return (dashIndex > 0 ? namesString[..dashIndex] : namesString).Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static IEnumerable<HumanName> Sort(IEnumerable<HumanName> humanNames, string formatString)
|
|
||||||
{
|
|
||||||
var sortMatch = SortRegex().Match(formatString);
|
|
||||||
return
|
|
||||||
sortMatch.Success
|
|
||||||
? sortMatch.Groups[1].Value == "F" ? humanNames.OrderBy(n => n.First)
|
|
||||||
: sortMatch.Groups[1].Value == "M" ? humanNames.OrderBy(n => n.Middle)
|
|
||||||
: sortMatch.Groups[1].Value == "L" ? humanNames.OrderBy(n => n.Last)
|
|
||||||
: humanNames
|
|
||||||
: humanNames;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string Format(string formatString, string defaultValue)
|
|
||||||
{
|
|
||||||
var formatMatch = FormatRegex().Match(formatString);
|
|
||||||
return formatMatch.Success ? formatMatch.Groups[1].Value : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string Separator(string formatString, string defaultValue)
|
|
||||||
{
|
|
||||||
var separatorMatch = SeparatorRegex().Match(formatString);
|
|
||||||
return separatorMatch.Success ? separatorMatch.Groups[1].Value : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int Max(string formatString, int defaultValue)
|
|
||||||
{
|
|
||||||
var maxMatch = MaxRegex().Match(formatString);
|
|
||||||
return maxMatch.Success && int.TryParse(maxMatch.Groups[1].Value, out var max) ? int.Max(1, max) : defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatName(HumanName humanName, string nameFormatString)
|
|
||||||
{
|
|
||||||
//Single-word names parse as first names. Use it as last name.
|
|
||||||
var lastName = string.IsNullOrWhiteSpace(humanName.Last) ? humanName.First : humanName.Last;
|
|
||||||
|
|
||||||
nameFormatString
|
|
||||||
= nameFormatString
|
|
||||||
.Replace("{T}", "{0}")
|
|
||||||
.Replace("{F}", "{1}")
|
|
||||||
.Replace("{M}", "{2}")
|
|
||||||
.Replace("{L}", "{3}")
|
|
||||||
.Replace("{S}", "{4}");
|
|
||||||
|
|
||||||
return string.Format(nameFormatString, humanName.Title, humanName.First, humanName.Middle, lastName, humanName.Suffix).Trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary> Sort must have exactly one of the characters F, M, or L </summary>
|
|
||||||
[GeneratedRegex(@"[Ss]ort\(\s*?([FML])\s*?\)")]
|
|
||||||
private static partial Regex SortRegex();
|
|
||||||
/// <summary> Format must have at least one of the string {T}, {F}, {M}, {L}, or {S} </summary>
|
|
||||||
[GeneratedRegex(@"[Ff]ormat\((.*?(?:{[TFMLS]})+.*?)\)")]
|
|
||||||
private static partial Regex FormatRegex();
|
|
||||||
/// <summary> Separator can be anything </summary>
|
|
||||||
[GeneratedRegex(@"[Ss]eparator\((.*?)\)")]
|
|
||||||
private static partial Regex SeparatorRegex();
|
|
||||||
/// <summary> Max must have a 1 or 2-digit number </summary>
|
|
||||||
[GeneratedRegex(@"[Mm]ax\(\s*?(\d{1,2})\s*?\)")]
|
|
||||||
private static partial Regex MaxRegex();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
44
Source/LibationFileManager/Templates/ContributorDto.cs
Normal file
44
Source/LibationFileManager/Templates/ContributorDto.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using NameParser;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
namespace LibationFileManager.Templates;
|
||||||
|
|
||||||
|
public class ContributorDto : IFormattable
|
||||||
|
{
|
||||||
|
public HumanName HumanName { get; }
|
||||||
|
public string? AudibleContributorId { get; }
|
||||||
|
public ContributorDto(string name, string? audibleContributorId)
|
||||||
|
{
|
||||||
|
HumanName = new HumanName(RemoveSuffix(name), Prefer.FirstOverPrefix);
|
||||||
|
AudibleContributorId = audibleContributorId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> ToString("{T} {F} {M} {L} {S}", null);
|
||||||
|
|
||||||
|
public string ToString(string? format, IFormatProvider? _)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(format))
|
||||||
|
return ToString();
|
||||||
|
|
||||||
|
//Single-word names parse as first names. Use it as last name.
|
||||||
|
var lastName = string.IsNullOrWhiteSpace(HumanName.Last) ? HumanName.First : HumanName.Last;
|
||||||
|
|
||||||
|
return format
|
||||||
|
.Replace("{T}", HumanName.Title)
|
||||||
|
.Replace("{F}", HumanName.First)
|
||||||
|
.Replace("{M}", HumanName.Middle)
|
||||||
|
.Replace("{L}", lastName)
|
||||||
|
.Replace("{S}", HumanName.Suffix)
|
||||||
|
.Replace("{ID}", AudibleContributorId)
|
||||||
|
.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string RemoveSuffix(string namesString)
|
||||||
|
{
|
||||||
|
namesString = namesString.Replace('’', '\'').Replace(" - Ret.", ", Ret.");
|
||||||
|
int dashIndex = namesString.IndexOf(" - ");
|
||||||
|
return (dashIndex > 0 ? namesString[..dashIndex] : namesString).Trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Source/LibationFileManager/Templates/IListFormat[TList].cs
Normal file
53
Source/LibationFileManager/Templates/IListFormat[TList].cs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
namespace LibationFileManager.Templates;
|
||||||
|
|
||||||
|
internal partial interface IListFormat<TList> where TList : IListFormat<TList>
|
||||||
|
{
|
||||||
|
static string Join<T>(string formatString, IEnumerable<T> items)
|
||||||
|
where T : IFormattable
|
||||||
|
{
|
||||||
|
var itemFormatter = Formatter(formatString);
|
||||||
|
var separatorString = Separator(formatString) ?? ", ";
|
||||||
|
var maxValues = Max(formatString) ?? items.Count();
|
||||||
|
|
||||||
|
var formattedValues = string.Join(separatorString, items.Take(maxValues).Select(n => n.ToString(itemFormatter, null)));
|
||||||
|
|
||||||
|
while (formattedValues.Contains(" "))
|
||||||
|
formattedValues = formattedValues.Replace(" ", " ");
|
||||||
|
|
||||||
|
return formattedValues;
|
||||||
|
|
||||||
|
static string? Formatter(string formatString)
|
||||||
|
{
|
||||||
|
var formatMatch = TList.FormatRegex().Match(formatString);
|
||||||
|
return formatMatch.Success ? formatMatch.Groups[1].Value : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int? Max(string formatString)
|
||||||
|
{
|
||||||
|
var maxMatch = MaxRegex().Match(formatString);
|
||||||
|
return maxMatch.Success && int.TryParse(maxMatch.Groups[1].Value, out var max) ? int.Max(1, max) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static string? Separator(string formatString)
|
||||||
|
{
|
||||||
|
var separatorMatch = SeparatorRegex().Match(formatString);
|
||||||
|
return separatorMatch.Success ? separatorMatch.Groups[1].Value : ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static abstract Regex FormatRegex();
|
||||||
|
|
||||||
|
/// <summary> Separator can be anything </summary>
|
||||||
|
[GeneratedRegex(@"[Ss]eparator\((.*?)\)")]
|
||||||
|
private static partial Regex SeparatorRegex();
|
||||||
|
|
||||||
|
/// <summary> Max must have a 1 or 2-digit number </summary>
|
||||||
|
[GeneratedRegex(@"[Mm]ax\(\s*?(\d{1,2})\s*?\)")]
|
||||||
|
private static partial Regex MaxRegex();
|
||||||
|
}
|
||||||
43
Source/LibationFileManager/Templates/LibraryBookDto.cs
Normal file
43
Source/LibationFileManager/Templates/LibraryBookDto.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
namespace LibationFileManager.Templates;
|
||||||
|
|
||||||
|
public class BookDto
|
||||||
|
{
|
||||||
|
public string? AudibleProductId { get; set; }
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public string? Subtitle { get; set; }
|
||||||
|
public string? TitleWithSubtitle { get; set; }
|
||||||
|
public string? Locale { get; set; }
|
||||||
|
public int? YearPublished { get; set; }
|
||||||
|
|
||||||
|
public IEnumerable<ContributorDto>? Authors { get; set; }
|
||||||
|
public ContributorDto? FirstAuthor => Authors?.FirstOrDefault();
|
||||||
|
|
||||||
|
public IEnumerable<ContributorDto>? Narrators { get; set; }
|
||||||
|
public ContributorDto? FirstNarrator => Narrators?.FirstOrDefault();
|
||||||
|
|
||||||
|
public IEnumerable<SeriesDto>? Series { get; set; }
|
||||||
|
public SeriesDto? FirstSeries => Series?.FirstOrDefault();
|
||||||
|
|
||||||
|
public bool IsSeries => Series is not null;
|
||||||
|
public bool IsPodcastParent { get; set; }
|
||||||
|
public bool IsPodcast { get; set; }
|
||||||
|
|
||||||
|
public int BitRate { get; set; }
|
||||||
|
public int SampleRate { get; set; }
|
||||||
|
public int Channels { get; set; }
|
||||||
|
public DateTime FileDate { get; set; } = DateTime.Now;
|
||||||
|
public DateTime? DatePublished { get; set; }
|
||||||
|
public string? Language { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LibraryBookDto : BookDto
|
||||||
|
{
|
||||||
|
public DateTime? DateAdded { get; set; }
|
||||||
|
public string? Account { get; set; }
|
||||||
|
public string? AccountNickname { get; set; }
|
||||||
|
}
|
||||||
33
Source/LibationFileManager/Templates/NameListFormat.cs
Normal file
33
Source/LibationFileManager/Templates/NameListFormat.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using FileManager.NamingTemplate;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
namespace LibationFileManager.Templates;
|
||||||
|
|
||||||
|
internal partial class NameListFormat : IListFormat<NameListFormat>
|
||||||
|
{
|
||||||
|
public static string Formatter(ITemplateTag _, IEnumerable<ContributorDto>? names, string formatString)
|
||||||
|
=> names is null ? string.Empty
|
||||||
|
: IListFormat<NameListFormat>.Join(formatString, Sort(names, formatString));
|
||||||
|
|
||||||
|
private static IEnumerable<ContributorDto> Sort(IEnumerable<ContributorDto> names, string formatString)
|
||||||
|
{
|
||||||
|
var sortMatch = SortRegex().Match(formatString);
|
||||||
|
return
|
||||||
|
sortMatch.Success
|
||||||
|
? sortMatch.Groups[1].Value == "F" ? names.OrderBy(n => n.HumanName.First)
|
||||||
|
: sortMatch.Groups[1].Value == "M" ? names.OrderBy(n => n.HumanName.Middle)
|
||||||
|
: sortMatch.Groups[1].Value == "L" ? names.OrderBy(n => n.HumanName.Last)
|
||||||
|
: names
|
||||||
|
: names;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Sort must have exactly one of the characters F, M, or L </summary>
|
||||||
|
[GeneratedRegex(@"[Ss]ort\(\s*?([FML])\s*?\)")]
|
||||||
|
private static partial Regex SortRegex();
|
||||||
|
/// <summary> Format must have at least one of the string {T}, {F}, {M}, {L}, {S}, or {ID} </summary>
|
||||||
|
[GeneratedRegex(@"[Ff]ormat\((.*?(?:{[TFMLS]}|{ID})+.*?)\)")]
|
||||||
|
public static partial Regex FormatRegex();
|
||||||
|
}
|
||||||
27
Source/LibationFileManager/Templates/SeriesDto.cs
Normal file
27
Source/LibationFileManager/Templates/SeriesDto.cs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
namespace LibationFileManager.Templates;
|
||||||
|
|
||||||
|
public record SeriesDto : IFormattable
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
|
||||||
|
public float? Number { get; }
|
||||||
|
public string AudibleSeriesId { get; }
|
||||||
|
public SeriesDto(string name, float? number, string audibleSeriesId)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Number = number;
|
||||||
|
AudibleSeriesId = audibleSeriesId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string ToString() => Name.Trim();
|
||||||
|
public string ToString(string? format, IFormatProvider? _)
|
||||||
|
=> string.IsNullOrWhiteSpace(format) ? ToString()
|
||||||
|
: format
|
||||||
|
.Replace("{N}", Name)
|
||||||
|
.Replace("{#}", Number?.ToString())
|
||||||
|
.Replace("{ID}", AudibleSeriesId)
|
||||||
|
.Trim();
|
||||||
|
}
|
||||||
17
Source/LibationFileManager/Templates/SeriesListFormat.cs
Normal file
17
Source/LibationFileManager/Templates/SeriesListFormat.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using FileManager.NamingTemplate;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
namespace LibationFileManager.Templates;
|
||||||
|
|
||||||
|
internal partial class SeriesListFormat : IListFormat<SeriesListFormat>
|
||||||
|
{
|
||||||
|
public static string Formatter(ITemplateTag _, IEnumerable<SeriesDto>? series, string formatString)
|
||||||
|
=> series is null ? string.Empty
|
||||||
|
: IListFormat<SeriesListFormat>.Join(formatString, series);
|
||||||
|
|
||||||
|
/// <summary> Format must have at least one of the string {N}, {#}, {ID} </summary>
|
||||||
|
[GeneratedRegex(@"[Ff]ormat\((.*?(?:{[N#]}|{ID})+.*?)\)")]
|
||||||
|
public static partial Regex FormatRegex();
|
||||||
|
}
|
||||||
@ -1,11 +1,10 @@
|
|||||||
using AaxDecrypter;
|
using AaxDecrypter;
|
||||||
using FileManager;
|
using FileManager;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager.Templates
|
||||||
{
|
{
|
||||||
public interface ITemplateEditor
|
public interface ITemplateEditor
|
||||||
{
|
{
|
||||||
@ -61,16 +60,15 @@ namespace LibationFileManager
|
|||||||
AccountNickname = "my account",
|
AccountNickname = "my account",
|
||||||
DateAdded = new DateTime(2022, 6, 9, 0, 0, 0),
|
DateAdded = new DateTime(2022, 6, 9, 0, 0, 0),
|
||||||
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
|
DatePublished = new DateTime(2017, 2, 27, 0, 0, 0),
|
||||||
AudibleProductId = "123456789",
|
AudibleProductId = "B06WLMWF2S",
|
||||||
Title = "A Study in Scarlet",
|
Title = "A Study in Scarlet",
|
||||||
TitleWithSubtitle = "A Study in Scarlet: A Sherlock Holmes Novel",
|
TitleWithSubtitle = "A Study in Scarlet: A Sherlock Holmes Novel",
|
||||||
Subtitle = "A Sherlock Holmes Novel",
|
Subtitle = "A Sherlock Holmes Novel",
|
||||||
Locale = "us",
|
Locale = "us",
|
||||||
YearPublished = 2017,
|
YearPublished = 2017,
|
||||||
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
|
Authors = [new("Arthur Conan Doyle", "B000AQ43GQ"), new("Stephen Fry - introductions", "B000APAGVS")],
|
||||||
Narrators = new List<string> { "Stephen Fry" },
|
Narrators = [new("Stephen Fry", null)],
|
||||||
SeriesName = "Sherlock Holmes",
|
Series = [new("Sherlock Holmes", 1, "B08376S3R2"), new("Some Other Series", 1, "B000000000")],
|
||||||
SeriesNumber = 1,
|
|
||||||
BitRate = 128,
|
BitRate = 128,
|
||||||
SampleRate = 44100,
|
SampleRate = 44100,
|
||||||
Channels = 2,
|
Channels = 2,
|
||||||
@ -131,7 +129,7 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
if (!templateEditor.IsFolder && !templateEditor.IsFilePath)
|
if (!templateEditor.IsFolder && !templateEditor.IsFilePath)
|
||||||
throw new InvalidOperationException($"This method is only for File and Folder templates. Use {nameof(CreateNameEditor)} for name templates");
|
throw new InvalidOperationException($"This method is only for File and Folder templates. Use {nameof(CreateNameEditor)} for name templates");
|
||||||
|
|
||||||
if (templateEditor.IsFolder)
|
if (templateEditor.IsFolder)
|
||||||
templateEditor.File = Templates.File;
|
templateEditor.File = Templates.File;
|
||||||
else
|
else
|
||||||
@ -1,7 +1,7 @@
|
|||||||
using FileManager.NamingTemplate;
|
using FileManager.NamingTemplate;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager.Templates
|
||||||
{
|
{
|
||||||
public sealed class TemplateTags : ITemplateTag
|
public sealed class TemplateTags : ITemplateTag
|
||||||
{
|
{
|
||||||
@ -33,15 +33,15 @@ namespace LibationFileManager
|
|||||||
public static TemplateTags FirstAuthor { get; } = new TemplateTags("first author", "First author");
|
public static TemplateTags FirstAuthor { get; } = new TemplateTags("first author", "First author");
|
||||||
public static TemplateTags Narrator { get; } = new TemplateTags("narrator", "Narrator(s)");
|
public static TemplateTags Narrator { get; } = new TemplateTags("narrator", "Narrator(s)");
|
||||||
public static TemplateTags FirstNarrator { get; } = new TemplateTags("first narrator", "First narrator");
|
public static TemplateTags FirstNarrator { get; } = new TemplateTags("first narrator", "First narrator");
|
||||||
public static TemplateTags Series { get; } = new TemplateTags("series", "Name of series");
|
public static TemplateTags Series { get; } = new TemplateTags("series", "All series to which the book belongs (if any)");
|
||||||
// can't also have a leading zeros version. Too many weird edge cases. Eg: "1-4"
|
public static TemplateTags FirstSeries { get; } = new TemplateTags("first series", "First series");
|
||||||
public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series");
|
public static TemplateTags SeriesNumber { get; } = new TemplateTags("series#", "Number order in series (alias for <first series[{#}]>");
|
||||||
public static TemplateTags Bitrate { get; } = new TemplateTags("bitrate", "File's orig. bitrate");
|
public static TemplateTags Bitrate { get; } = new TemplateTags("bitrate", "File's orig. bitrate");
|
||||||
public static TemplateTags SampleRate { get; } = new TemplateTags("samplerate", "File's orig. sample rate");
|
public static TemplateTags SampleRate { get; } = new TemplateTags("samplerate", "File's orig. sample rate");
|
||||||
public static TemplateTags Channels { get; } = new TemplateTags("channels", "Number of audio channels");
|
public static TemplateTags Channels { get; } = new TemplateTags("channels", "Number of audio channels");
|
||||||
public static TemplateTags Account { get; } = new TemplateTags("account", "Audible account of this book");
|
public static TemplateTags Account { get; } = new TemplateTags("account", "Audible account of this book");
|
||||||
public static TemplateTags AccountNickname { get; } = new TemplateTags("account nickname", "Audible account nickname of this book");
|
public static TemplateTags AccountNickname { get; } = new TemplateTags("account nickname", "Audible account nickname of this book");
|
||||||
public static TemplateTags Locale { get; } = new ("locale", "Region/country");
|
public static TemplateTags Locale { get; } = new("locale", "Region/country");
|
||||||
public static TemplateTags YearPublished { get; } = new("year", "Year published");
|
public static TemplateTags YearPublished { get; } = new("year", "Year published");
|
||||||
public static TemplateTags Language { get; } = new("language", "Book's language");
|
public static TemplateTags Language { get; } = new("language", "Book's language");
|
||||||
public static TemplateTags LanguageShort { get; } = new("language short", "Book's language abbreviated. Eg: ENG");
|
public static TemplateTags LanguageShort { get; } = new("language short", "Book's language abbreviated. Eg: ENG");
|
||||||
@ -10,7 +10,7 @@ using FileManager.NamingTemplate;
|
|||||||
using NameParser;
|
using NameParser;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
namespace LibationFileManager
|
namespace LibationFileManager.Templates
|
||||||
{
|
{
|
||||||
public interface ITemplate
|
public interface ITemplate
|
||||||
{
|
{
|
||||||
@ -58,19 +58,19 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
Configuration.Instance.PropertyChanged +=
|
Configuration.Instance.PropertyChanged +=
|
||||||
[PropertyChangeFilter(nameof(Configuration.FolderTemplate))]
|
[PropertyChangeFilter(nameof(Configuration.FolderTemplate))]
|
||||||
(_,e) => _folder = GetTemplate<FolderTemplate>(e.NewValue as string);
|
(_, e) => _folder = GetTemplate<FolderTemplate>(e.NewValue as string);
|
||||||
|
|
||||||
Configuration.Instance.PropertyChanged +=
|
Configuration.Instance.PropertyChanged +=
|
||||||
[PropertyChangeFilter(nameof(Configuration.FileTemplate))]
|
[PropertyChangeFilter(nameof(Configuration.FileTemplate))]
|
||||||
(_, e) => _file = GetTemplate<FileTemplate>(e.NewValue as string);
|
(_, e) => _file = GetTemplate<FileTemplate>(e.NewValue as string);
|
||||||
|
|
||||||
Configuration.Instance.PropertyChanged +=
|
Configuration.Instance.PropertyChanged +=
|
||||||
[PropertyChangeFilter(nameof(Configuration.ChapterFileTemplate))]
|
[PropertyChangeFilter(nameof(Configuration.ChapterFileTemplate))]
|
||||||
(_, e) => _chapterFile = GetTemplate<ChapterFileTemplate>(e.NewValue as string);
|
(_, e) => _chapterFile = GetTemplate<ChapterFileTemplate>(e.NewValue as string);
|
||||||
|
|
||||||
Configuration.Instance.PropertyChanged +=
|
Configuration.Instance.PropertyChanged +=
|
||||||
[PropertyChangeFilter(nameof(Configuration.ChapterTitleTemplate))]
|
[PropertyChangeFilter(nameof(Configuration.ChapterTitleTemplate))]
|
||||||
(_, e) => _chapterTitle = GetTemplate<ChapterTitleTemplate>(e.NewValue as string);
|
(_, e) => _chapterTitle = GetTemplate<ChapterTitleTemplate>(e.NewValue as string);
|
||||||
|
|
||||||
HumanName.Suffixes.Add("ret");
|
HumanName.Suffixes.Add("ret");
|
||||||
HumanName.Titles.Add("professor");
|
HumanName.Titles.Add("professor");
|
||||||
@ -121,7 +121,7 @@ namespace LibationFileManager
|
|||||||
ArgumentValidator.EnsureNotNull(fileExtension, nameof(fileExtension));
|
ArgumentValidator.EnsureNotNull(fileExtension, nameof(fileExtension));
|
||||||
|
|
||||||
replacements ??= Configuration.Instance.ReplacementCharacters;
|
replacements ??= Configuration.Instance.ReplacementCharacters;
|
||||||
return GetFilename(baseDir, fileExtension,replacements, returnFirstExisting, libraryBookDto);
|
return GetFilename(baseDir, fileExtension, replacements, returnFirstExisting, libraryBookDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LongPath GetFilename(LibraryBookDto libraryBookDto, MultiConvertFileProperties multiChapProps, string baseDir, string fileExtension, ReplacementCharacters? replacements = null, bool returnFirstExisting = false)
|
public LongPath GetFilename(LibraryBookDto libraryBookDto, MultiConvertFileProperties multiChapProps, string baseDir, string fileExtension, ReplacementCharacters? replacements = null, bool returnFirstExisting = false)
|
||||||
@ -154,7 +154,7 @@ namespace LibationFileManager
|
|||||||
//If file already exists, GetValidFilename will append " (n)" to the filename.
|
//If file already exists, GetValidFilename will append " (n)" to the filename.
|
||||||
//This could cause the filename length to exceed MaxFilenameLength, so reduce
|
//This could cause the filename length to exceed MaxFilenameLength, so reduce
|
||||||
//allowable filename length by 5 chars, allowing for up to 99 duplicates.
|
//allowable filename length by 5 chars, allowing for up to 99 duplicates.
|
||||||
var maxFilenameLength = LongPath.MaxFilenameLength -
|
var maxFilenameLength = LongPath.MaxFilenameLength -
|
||||||
(i < pathParts.Count - 1 || string.IsNullOrEmpty(fileExtension) ? 0 : fileExtension.Length + 5);
|
(i < pathParts.Count - 1 || string.IsNullOrEmpty(fileExtension) ? 0 : fileExtension.Length + 5);
|
||||||
|
|
||||||
while (part.Sum(LongPath.GetFilesystemStringLength) > maxFilenameLength)
|
while (part.Sum(LongPath.GetFilesystemStringLength) > maxFilenameLength)
|
||||||
@ -170,7 +170,7 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
var fullPath = Path.Combine(pathParts.Select(fileParts => string.Concat(fileParts)).Prepend(baseDir).ToArray());
|
var fullPath = Path.Combine(pathParts.Select(fileParts => string.Concat(fileParts)).Prepend(baseDir).ToArray());
|
||||||
|
|
||||||
return FileUtility.GetValidFilename(fullPath, replacements, fileExtension, returnFirstExisting);
|
return FileUtility.GetValidFilename(fullPath, replacements, fileExtension, returnFirstExisting);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -186,7 +186,7 @@ namespace LibationFileManager
|
|||||||
foreach (var part in templateParts)
|
foreach (var part in templateParts)
|
||||||
{
|
{
|
||||||
int slashIndex, lastIndex = 0;
|
int slashIndex, lastIndex = 0;
|
||||||
while((slashIndex = part.IndexOf(Path.DirectorySeparatorChar, lastIndex)) > -1)
|
while ((slashIndex = part.IndexOf(Path.DirectorySeparatorChar, lastIndex)) > -1)
|
||||||
{
|
{
|
||||||
dir.Add(part[lastIndex..slashIndex]);
|
dir.Add(part[lastIndex..slashIndex]);
|
||||||
RemoveSpaces(dir);
|
RemoveSpaces(dir);
|
||||||
@ -229,7 +229,7 @@ namespace LibationFileManager
|
|||||||
{
|
{
|
||||||
original = parts[i];
|
original = parts[i];
|
||||||
parts[i] = original.Replace(" ", " ");
|
parts[i] = original.Replace(" ", " ");
|
||||||
}while(original.Length != parts[i].Length);
|
} while (original.Length != parts[i].Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Remove instances of double spaces at part boundaries
|
//Remove instances of double spaces at part boundaries
|
||||||
@ -262,11 +262,12 @@ namespace LibationFileManager
|
|||||||
{ TemplateTags.AudibleTitle, lb => lb.Title },
|
{ TemplateTags.AudibleTitle, lb => lb.Title },
|
||||||
{ TemplateTags.AudibleSubtitle, lb => lb.Subtitle },
|
{ TemplateTags.AudibleSubtitle, lb => lb.Subtitle },
|
||||||
{ TemplateTags.Author, lb => lb.Authors, NameListFormat.Formatter },
|
{ TemplateTags.Author, lb => lb.Authors, NameListFormat.Formatter },
|
||||||
{ TemplateTags.FirstAuthor, lb => lb.FirstAuthor },
|
{ TemplateTags.FirstAuthor, lb => lb.FirstAuthor, FormattableFormatter },
|
||||||
{ TemplateTags.Narrator, lb => lb.Narrators, NameListFormat.Formatter },
|
{ TemplateTags.Narrator, lb => lb.Narrators, NameListFormat.Formatter },
|
||||||
{ TemplateTags.FirstNarrator, lb => lb.FirstNarrator },
|
{ TemplateTags.FirstNarrator, lb => lb.FirstNarrator, FormattableFormatter },
|
||||||
{ TemplateTags.Series, lb => lb.SeriesName },
|
{ TemplateTags.Series, lb => lb.Series, SeriesListFormat.Formatter },
|
||||||
{ TemplateTags.SeriesNumber, lb => lb.IsPodcastParent ? null : lb.SeriesNumber },
|
{ TemplateTags.FirstSeries, lb => lb.FirstSeries, FormattableFormatter },
|
||||||
|
{ TemplateTags.SeriesNumber, lb => lb.FirstSeries?.Number },
|
||||||
{ TemplateTags.Language, lb => lb.Language },
|
{ TemplateTags.Language, lb => lb.Language },
|
||||||
//Don't allow formatting of LanguageShort
|
//Don't allow formatting of LanguageShort
|
||||||
{ TemplateTags.LanguageShort, lb =>lb.Language, getLanguageShort },
|
{ TemplateTags.LanguageShort, lb =>lb.Language, getLanguageShort },
|
||||||
@ -280,7 +281,7 @@ namespace LibationFileManager
|
|||||||
{ TemplateTags.DatePublished, lb => lb.DatePublished },
|
{ TemplateTags.DatePublished, lb => lb.DatePublished },
|
||||||
{ TemplateTags.DateAdded, lb => lb.DateAdded },
|
{ TemplateTags.DateAdded, lb => lb.DateAdded },
|
||||||
{ TemplateTags.FileDate, lb => lb.FileDate },
|
{ TemplateTags.FileDate, lb => lb.FileDate },
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly List<TagCollection> chapterPropertyTags = new()
|
private static readonly List<TagCollection> chapterPropertyTags = new()
|
||||||
{
|
{
|
||||||
@ -290,7 +291,8 @@ namespace LibationFileManager
|
|||||||
{ TemplateTags.TitleShort, lb => getTitleShort(lb.Title) },
|
{ TemplateTags.TitleShort, lb => getTitleShort(lb.Title) },
|
||||||
{ TemplateTags.AudibleTitle, lb => lb.Title },
|
{ TemplateTags.AudibleTitle, lb => lb.Title },
|
||||||
{ TemplateTags.AudibleSubtitle, lb => lb.Subtitle },
|
{ TemplateTags.AudibleSubtitle, lb => lb.Subtitle },
|
||||||
{ TemplateTags.Series, lb => lb.SeriesName },
|
{ TemplateTags.Series, lb => lb.Series, SeriesListFormat.Formatter },
|
||||||
|
{ TemplateTags.FirstSeries, lb => lb.FirstSeries, FormattableFormatter },
|
||||||
},
|
},
|
||||||
new PropertyTagCollection<MultiConvertFileProperties>(caseSensative: true, StringFormatter, IntegerFormatter, DateTimeFormatter)
|
new PropertyTagCollection<MultiConvertFileProperties>(caseSensative: true, StringFormatter, IntegerFormatter, DateTimeFormatter)
|
||||||
{
|
{
|
||||||
@ -332,6 +334,9 @@ namespace LibationFileManager
|
|||||||
return language[..3].ToUpper();
|
return language[..3].ToUpper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormattableFormatter(ITemplateTag templateTag, IFormattable? value, string formatString)
|
||||||
|
=> value?.ToString(formatString, null) ?? "";
|
||||||
|
|
||||||
private static string StringFormatter(ITemplateTag templateTag, string value, string formatString)
|
private static string StringFormatter(ITemplateTag templateTag, string value, string formatString)
|
||||||
{
|
{
|
||||||
if (value is null) return "";
|
if (value is null) return "";
|
||||||
@ -368,7 +373,7 @@ namespace LibationFileManager
|
|||||||
|
|
||||||
public class FolderTemplate : Templates, ITemplate
|
public class FolderTemplate : Templates, ITemplate
|
||||||
{
|
{
|
||||||
public static string Name { get; }= "Folder Template";
|
public static string Name { get; } = "Folder Template";
|
||||||
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.FolderTemplate)) ?? "";
|
public static string Description { get; } = Configuration.GetDescription(nameof(Configuration.FolderTemplate)) ?? "";
|
||||||
public static string DefaultTemplate { get; } = "<title short> [<id>]";
|
public static string DefaultTemplate { get; } = "<title short> [<id>]";
|
||||||
public static IEnumerable<TagCollection> TagCollections
|
public static IEnumerable<TagCollection> TagCollections
|
||||||
@ -2,6 +2,7 @@
|
|||||||
using DataLayer;
|
using DataLayer;
|
||||||
using FileLiberator;
|
using FileLiberator;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -25,7 +26,7 @@ public class GridContextMenu
|
|||||||
public string FolderTemplateText => "Folder Template";
|
public string FolderTemplateText => "Folder Template";
|
||||||
public string FileTemplateText => "File Template";
|
public string FileTemplateText => "File Template";
|
||||||
public string MultipartTemplateText => "Multipart File Template";
|
public string MultipartTemplateText => "Multipart File Template";
|
||||||
public string ViewBookmarksText => "View _Bookmarks/Clips";
|
public string ViewBookmarksText => $"View {Accelerator}Bookmarks/Clips";
|
||||||
public string ViewSeriesText => GridEntries[0].Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series";
|
public string ViewSeriesText => GridEntries[0].Liberate.IsSeries ? "View All Episodes in Series" : "View All Books in Series";
|
||||||
|
|
||||||
public bool LiberateEpisodesEnabled => GridEntries.OfType<ISeriesEntry>().Any(sEntry => sEntry.Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload));
|
public bool LiberateEpisodesEnabled => GridEntries.OfType<ISeriesEntry>().Any(sEntry => sEntry.Children.Any(c => c.Liberate.BookStatus is LiberatedStatus.NotLiberated or LiberatedStatus.PartialDownload));
|
||||||
|
|||||||
@ -4,6 +4,7 @@ using System.IO;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
|
|
||||||
namespace LibationWinForms.Dialogs
|
namespace LibationWinForms.Dialogs
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using LibationUiBase;
|
using LibationUiBase;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
|
|
||||||
namespace LibationWinForms.Dialogs
|
namespace LibationWinForms.Dialogs
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
|
|
||||||
namespace LibationWinForms.Dialogs
|
namespace LibationWinForms.Dialogs
|
||||||
{
|
{
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
|
|
||||||
namespace LibationWinForms.Dialogs
|
namespace LibationWinForms.Dialogs
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using AudibleUtilities;
|
|||||||
using DataLayer;
|
using DataLayer;
|
||||||
using FileLiberator;
|
using FileLiberator;
|
||||||
using LibationFileManager;
|
using LibationFileManager;
|
||||||
|
using LibationFileManager.Templates;
|
||||||
using LibationUiBase.GridView;
|
using LibationUiBase.GridView;
|
||||||
using LibationWinForms.Dialogs;
|
using LibationWinForms.Dialogs;
|
||||||
using LibationWinForms.SeriesView;
|
using LibationWinForms.SeriesView;
|
||||||
@ -258,7 +259,7 @@ namespace LibationWinForms.GridView
|
|||||||
#region Edit Templates (Single book only)
|
#region Edit Templates (Single book only)
|
||||||
|
|
||||||
void editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
|
void editTemplate<T>(LibraryBook libraryBook, string existingTemplate, Action<string> setNewTemplate)
|
||||||
where T : Templates, LibationFileManager.ITemplate, new()
|
where T : Templates, ITemplate, new()
|
||||||
{
|
{
|
||||||
var template = ctx.CreateTemplateEditor<T>(libraryBook, existingTemplate);
|
var template = ctx.CreateTemplateEditor<T>(libraryBook, existingTemplate);
|
||||||
var form = new EditTemplateDialog(template);
|
var form = new EditTemplateDialog(template);
|
||||||
@ -280,8 +281,8 @@ namespace LibationWinForms.GridView
|
|||||||
var editTemplatesMenuItem = new ToolStripMenuItem { Text = ctx.EditTemplatesText };
|
var editTemplatesMenuItem = new ToolStripMenuItem { Text = ctx.EditTemplatesText };
|
||||||
editTemplatesMenuItem.DropDownItems.AddRange(new[] { folderTemplateMenuItem, fileTemplateMenuItem, multiFileTemplateMenuItem });
|
editTemplatesMenuItem.DropDownItems.AddRange(new[] { folderTemplateMenuItem, fileTemplateMenuItem, multiFileTemplateMenuItem });
|
||||||
|
|
||||||
ctxMenu.Items.Add(new ToolStripSeparator());
|
|
||||||
ctxMenu.Items.Add(editTemplatesMenuItem);
|
ctxMenu.Items.Add(editTemplatesMenuItem);
|
||||||
|
ctxMenu.Items.Add(new ToolStripSeparator());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
@ -6,7 +6,7 @@ using Dinah.Core;
|
|||||||
using FileManager;
|
using FileManager;
|
||||||
using FileManager.NamingTemplate;
|
using FileManager.NamingTemplate;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using LibationFileManager;
|
using LibationFileManager.Templates;
|
||||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
|
|
||||||
using static TemplatesTests.Shared;
|
using static TemplatesTests.Shared;
|
||||||
@ -23,7 +23,10 @@ namespace TemplatesTests
|
|||||||
|
|
||||||
public static class Shared
|
public static class Shared
|
||||||
{
|
{
|
||||||
public static LibraryBookDto GetLibraryBook(string seriesName = "Sherlock Holmes")
|
public static LibraryBookDto GetLibraryBook()
|
||||||
|
=> GetLibraryBook([new SeriesDto("Sherlock Holmes", 1, "B08376S3R2")]);
|
||||||
|
|
||||||
|
public static LibraryBookDto GetLibraryBook(IEnumerable<SeriesDto> series)
|
||||||
=> new()
|
=> new()
|
||||||
{
|
{
|
||||||
Account = "myaccount@example.co",
|
Account = "myaccount@example.co",
|
||||||
@ -35,10 +38,9 @@ namespace TemplatesTests
|
|||||||
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
|
Title = "A Study in Scarlet: A Sherlock Holmes Novel",
|
||||||
Locale = "us",
|
Locale = "us",
|
||||||
YearPublished = 2017,
|
YearPublished = 2017,
|
||||||
Authors = new List<string> { "Arthur Conan Doyle", "Stephen Fry - introductions" },
|
Authors = [new("Arthur Conan Doyle", "B000AQ43GQ"), new("Stephen Fry - introductions", "B000APAGVS")],
|
||||||
Narrators = new List<string> { "Stephen Fry" },
|
Narrators = [new("Stephen Fry", "B000APAGVS"), new("Some Narrator", "B000000000")],
|
||||||
SeriesName = seriesName ?? "",
|
Series = series,
|
||||||
SeriesNumber = 1,
|
|
||||||
BitRate = 128,
|
BitRate = 128,
|
||||||
SampleRate = 44100,
|
SampleRate = 44100,
|
||||||
Channels = 2,
|
Channels = 2,
|
||||||
@ -253,7 +255,6 @@ namespace TemplatesTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow("<filedate[yy-MM-dd]> <date added[yy-MM-dd]> <pubdate[yy-MM]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\23-01-28.m4b")]
|
[DataRow("<filedate[yy-MM-dd]> <date added[yy-MM-dd]> <pubdate[yy-MM]>", @"C:\foo\bar", ".m4b", @"C:\foo\bar\23-01-28.m4b")]
|
||||||
public void DateFormat_null(string template, string dirFullPath, string extension, string expected)
|
public void DateFormat_null(string template, string dirFullPath, string extension, string expected)
|
||||||
@ -308,7 +309,7 @@ namespace TemplatesTests
|
|||||||
public void NameFormat_unusual(string author, string expected)
|
public void NameFormat_unusual(string author, string expected)
|
||||||
{
|
{
|
||||||
var bookDto = GetLibraryBook();
|
var bookDto = GetLibraryBook();
|
||||||
bookDto.Authors = new List<string> { author };
|
bookDto.Authors = [new(author, null)];
|
||||||
Templates.TryGetTemplate<Templates.FileTemplate>("<author[format(Title={T}, First={F}, Middle={M} Last={L}, Suffix={S})]>", out var fileTemplate).Should().BeTrue();
|
Templates.TryGetTemplate<Templates.FileTemplate>("<author[format(Title={T}, First={F}, Middle={M} Last={L}, Suffix={S})]>", out var fileTemplate).Should().BeTrue();
|
||||||
fileTemplate
|
fileTemplate
|
||||||
.GetFilename(bookDto, "", "", Replacements)
|
.GetFilename(bookDto, "", "", Replacements)
|
||||||
@ -329,6 +330,11 @@ namespace TemplatesTests
|
|||||||
[DataRow("<author[max(2)]>", "Jill Conner Browne, Charles E. Gannon")]
|
[DataRow("<author[max(2)]>", "Jill Conner Browne, Charles E. Gannon")]
|
||||||
[DataRow("<author[max(3)]>", "Jill Conner Browne, Charles E. Gannon, Christopher John Fetherolf")]
|
[DataRow("<author[max(3)]>", "Jill Conner Browne, Charles E. Gannon, Christopher John Fetherolf")]
|
||||||
[DataRow("<author[format({L}, {F})]>", "Browne, Jill, Gannon, Charles, Fetherolf, Christopher, Montgomery, Lucy, Bon Jovi, Jon, Van Doren, Paul")]
|
[DataRow("<author[format({L}, {F})]>", "Browne, Jill, Gannon, Charles, Fetherolf, Christopher, Montgomery, Lucy, Bon Jovi, Jon, Van Doren, Paul")]
|
||||||
|
[DataRow("<author[format({L}, {F} {ID})]>", "Browne, Jill B1, Gannon, Charles B2, Fetherolf, Christopher B3, Montgomery, Lucy B4, Bon Jovi, Jon B5, Van Doren, Paul B6")]
|
||||||
|
[DataRow("<author[format({ID})]>", "B1, B2, B3, B4, B5, B6")]
|
||||||
|
[DataRow("<author[format({Id})]>", "Jill Conner Browne, Charles E. Gannon, Christopher John Fetherolf, Lucy Maud Montgomery, Jon Bon Jovi, Paul Van Doren")]
|
||||||
|
[DataRow("<author[format({iD})]>", "Jill Conner Browne, Charles E. Gannon, Christopher John Fetherolf, Lucy Maud Montgomery, Jon Bon Jovi, Paul Van Doren")]
|
||||||
|
[DataRow("<author[format({id})]>", "Jill Conner Browne, Charles E. Gannon, Christopher John Fetherolf, Lucy Maud Montgomery, Jon Bon Jovi, Paul Van Doren")]
|
||||||
[DataRow("<author[format({f}, {l})]>", "Jill Conner Browne, Charles E. Gannon, Christopher John Fetherolf, Lucy Maud Montgomery, Jon Bon Jovi, Paul Van Doren")]
|
[DataRow("<author[format({f}, {l})]>", "Jill Conner Browne, Charles E. Gannon, Christopher John Fetherolf, Lucy Maud Montgomery, Jon Bon Jovi, Paul Van Doren")]
|
||||||
[DataRow("<author[format(First={F}, Last={L})]>", "First=Jill, Last=Browne, First=Charles, Last=Gannon, First=Christopher, Last=Fetherolf, First=Lucy, Last=Montgomery, First=Jon, Last=Bon Jovi, First=Paul, Last=Van Doren")]
|
[DataRow("<author[format(First={F}, Last={L})]>", "First=Jill, Last=Browne, First=Charles, Last=Gannon, First=Christopher, Last=Fetherolf, First=Lucy, Last=Montgomery, First=Jon, Last=Bon Jovi, First=Paul, Last=Van Doren")]
|
||||||
[DataRow("<author[format({L}, {F}) separator( - ) max(3)]>", "Browne, Jill - Gannon, Charles - Fetherolf, Christopher")]
|
[DataRow("<author[format({L}, {F}) separator( - ) max(3)]>", "Browne, Jill - Gannon, Charles - Fetherolf, Christopher")]
|
||||||
@ -337,18 +343,21 @@ namespace TemplatesTests
|
|||||||
//Jon Bon Jovi and Paul Van Doren don't have middle names, so they are sorted to the top.
|
//Jon Bon Jovi and Paul Van Doren don't have middle names, so they are sorted to the top.
|
||||||
//Since only the middle names of the first 2 names are to be displayed, the name string is empty.
|
//Since only the middle names of the first 2 names are to be displayed, the name string is empty.
|
||||||
[DataRow("<author[sort(M) max(2) separator(; ) format({M})]>", ";")]
|
[DataRow("<author[sort(M) max(2) separator(; ) format({M})]>", ";")]
|
||||||
|
[DataRow("<first author>", "Jill Conner Browne")]
|
||||||
|
[DataRow("<first author[]>", "Jill Conner Browne")]
|
||||||
|
[DataRow("<first author[{L}, {F}]>", "Browne, Jill")]
|
||||||
public void NameFormat_formatters(string template, string expected)
|
public void NameFormat_formatters(string template, string expected)
|
||||||
{
|
{
|
||||||
var bookDto = GetLibraryBook();
|
var bookDto = GetLibraryBook();
|
||||||
bookDto.Authors = new List<string>
|
bookDto.Authors =
|
||||||
{
|
[
|
||||||
"Jill Conner Browne",
|
new("Jill Conner Browne", "B1"),
|
||||||
"Charles E. Gannon",
|
new("Charles E. Gannon", "B2"),
|
||||||
"Christopher John Fetherolf",
|
new("Christopher John Fetherolf", "B3"),
|
||||||
"Lucy Maud Montgomery",
|
new("Lucy Maud Montgomery", "B4"),
|
||||||
"Jon Bon Jovi",
|
new("Jon Bon Jovi", "B5"),
|
||||||
"Paul Van Doren"
|
new("Paul Van Doren", "B6")
|
||||||
};
|
];
|
||||||
|
|
||||||
Templates.TryGetTemplate<Templates.FileTemplate>(template, out var fileTemplate).Should().BeTrue();
|
Templates.TryGetTemplate<Templates.FileTemplate>(template, out var fileTemplate).Should().BeTrue();
|
||||||
fileTemplate
|
fileTemplate
|
||||||
@ -358,6 +367,35 @@ namespace TemplatesTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[TestMethod]
|
||||||
|
[DataRow("<series>", "Series A, Series B, Series C")]
|
||||||
|
[DataRow("<series[]>", "Series A, Series B, Series C")]
|
||||||
|
[DataRow("<series[max(1)]>", "Series A")]
|
||||||
|
[DataRow("<series[max(2)]>", "Series A, Series B")]
|
||||||
|
[DataRow("<series[max(3)]>", "Series A, Series B, Series C")]
|
||||||
|
[DataRow("<series[format({N}, {#}, {ID}) separator(; )]>", "Series A, 1, B1; Series B, 6, B2; Series C, 2, B3")]
|
||||||
|
[DataRow("<series[format({N}, {#}, {ID}) separator(; ) max(3)]>", "Series A, 1, B1; Series B, 6, B2; Series C, 2, B3")]
|
||||||
|
[DataRow("<series[format({N}, {#}, {ID}) separator(; ) max(2)]>", "Series A, 1, B1; Series B, 6, B2")]
|
||||||
|
[DataRow("<first series>", "Series A")]
|
||||||
|
[DataRow("<first series[]>", "Series A")]
|
||||||
|
[DataRow("<first series[{N}, {#}, {ID}]>", "Series A, 1, B1")]
|
||||||
|
public void SeriesFormat_formatters(string template, string expected)
|
||||||
|
{
|
||||||
|
var bookDto = GetLibraryBook();
|
||||||
|
bookDto.Series =
|
||||||
|
[
|
||||||
|
new("Series A", 1, "B1"),
|
||||||
|
new("Series B", 6, "B2"),
|
||||||
|
new("Series C", 2, "B3")
|
||||||
|
];
|
||||||
|
|
||||||
|
Templates.TryGetTemplate<Templates.FileTemplate>(template, out var fileTemplate).Should().BeTrue();
|
||||||
|
fileTemplate
|
||||||
|
.GetFilename(bookDto, "", "", Replacements)
|
||||||
|
.PathWithoutPrefix
|
||||||
|
.Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
[TestMethod]
|
[TestMethod]
|
||||||
[DataRow(@"C:\a\b", @"C:\a\b\foobar.ext", PlatformID.Win32NT)]
|
[DataRow(@"C:\a\b", @"C:\a\b\foobar.ext", PlatformID.Win32NT)]
|
||||||
[DataRow(@"/a/b", @"/a/b/foobar.ext", PlatformID.Unix)]
|
[DataRow(@"/a/b", @"/a/b/foobar.ext", PlatformID.Unix)]
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user