Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Source/LibationFileManager/Templates/TemplateTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ private TemplateTags(string tagName, string description, string? defaultValue =
public static TemplateTags Id { get; } = new("id", "Audible ID");
public static TemplateTags Title { get; } = new("title", "Full title with subtitle");
public static TemplateTags TitleShort { get; } = new("title short", "Title. Stop at first colon");
public static TemplateTags TitleSort { get; } = new("title sort", "Full title with subtitle, leading article (A/An/The) removed");
public static TemplateTags TitleShortSort { get; } = new("title short sort", "Title without subtitle, leading article (A/An/The) removed");
public static TemplateTags AudibleTitle { get; } = new("audible title", "Audible's title (does not include subtitle)");
public static TemplateTags AudibleSubtitle { get; } = new("audible subtitle", "Audible's subtitle");
public static TemplateTags Author { get; } = new("author", "Author(s)");
Expand All @@ -33,6 +35,7 @@ private TemplateTags(string tagName, string description, string? defaultValue =
public static TemplateTags FirstNarrator { get; } = new("first narrator", "First narrator");
public static TemplateTags Series { get; } = new("series", "All series to which the book belongs (if any)");
public static TemplateTags FirstSeries { get; } = new("first series", "First series");
public static TemplateTags FirstSeriesSort { get; } = new("first series sort", "First series name, leading article (A/An/The) removed");
public static TemplateTags SeriesNumber { get; } = new("series#", "Number order in series (alias for <first series[{#}]>");
public static TemplateTags Minutes { get; } = new("minutes", "Length in minutes");
public static TemplateTags Bitrate { get; } = new("bitrate", "Bitrate (kbps) of the last downloaded audiobook");
Expand Down
17 changes: 17 additions & 0 deletions Source/LibationFileManager/Templates/Templates.cs
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,8 @@ private static void RemoveSpaces(List<string> parts)
{ TemplateTags.Id, lb => lb.AudibleProductId, v => v },
{ TemplateTags.Title, lb => lb.TitleWithSubtitle },
{ TemplateTags.TitleShort, lb => GetTitleShort(lb.Title) },
{ TemplateTags.TitleSort, lb => StripLeadingArticle(lb.TitleWithSubtitle) },
{ TemplateTags.TitleShortSort, lb => StripLeadingArticle(GetTitleShort(lb.Title)) },
{ TemplateTags.AudibleTitle, lb => lb.Title },
{ TemplateTags.AudibleSubtitle, lb => lb.Subtitle },
{ TemplateTags.Author, lb => lb.Authors, NameListFormat.Formatter, NameListFormat.Finalizer },
Expand All @@ -277,6 +279,7 @@ private static void RemoveSpaces(List<string> parts)
{ TemplateTags.FirstNarrator, lb => lb.FirstNarrator, CommonFormatters.FormattableFormatter },
{ TemplateTags.Series, lb => lb.Series, SeriesListFormat.Formatter, SeriesListFormat.Finalizer },
{ TemplateTags.FirstSeries, lb => lb.FirstSeries, CommonFormatters.FormattableFormatter },
{ TemplateTags.FirstSeriesSort, lb => StripLeadingArticle(lb.FirstSeries?.ToString()) },
{ TemplateTags.SeriesNumber, lb => lb.FirstSeries?.Order, CommonFormatters.FormattableFormatter },
{ TemplateTags.Language, lb => lb.Language, CommonFormatters.FormattableFormatter },
//Don't allow formatting of LanguageShort
Expand Down Expand Up @@ -312,10 +315,13 @@ private static void RemoveSpaces(List<string> parts)
{
{ TemplateTags.Title, lb => lb.TitleWithSubtitle },
{ TemplateTags.TitleShort, lb => GetTitleShort(lb.Title) },
{ TemplateTags.TitleSort, lb => StripLeadingArticle(lb.TitleWithSubtitle) },
{ TemplateTags.TitleShortSort, lb => StripLeadingArticle(GetTitleShort(lb.Title)) },
{ TemplateTags.AudibleTitle, lb => lb.Title },
{ TemplateTags.AudibleSubtitle, lb => lb.Subtitle },
{ TemplateTags.Series, lb => lb.Series, SeriesListFormat.Formatter, SeriesListFormat.Finalizer },
{ TemplateTags.FirstSeries, lb => lb.FirstSeries, CommonFormatters.FormattableFormatter },
{ TemplateTags.FirstSeriesSort, lb => StripLeadingArticle(lb.FirstSeries?.ToString()) },
},
new PropertyTagCollection<MultiConvertFileProperties>(caseSensitive: true, CommonFormatters.StringFormatter, CommonFormatters.IntegerFormatter, CommonFormatters.DateTimeFormatter)
{
Expand Down Expand Up @@ -397,6 +403,17 @@ private static bool HasValue(object? value, object? _, CultureInfo? culture)
? title[..i]
: title;

private static readonly string[] _sortArticles = ["The ", "A ", "An "];

private static string? StripLeadingArticle(string? value)
{
if (value is null) return null;
foreach (var article in _sortArticles)
if (value.StartsWith(article, StringComparison.OrdinalIgnoreCase))
return value[article.Length..];
return value;
}

#endregion

public class FolderTemplate : Templates, ITemplate
Expand Down
56 changes: 56 additions & 0 deletions Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1517,4 +1517,60 @@ public void Tests(string template, string dir, string ext, int pos, int total, s
.Should().Be(expected);
}
}

[TestClass]
public class SortTags
{
static LibraryBookDto Book(string titleWithSubtitle, string title, string? seriesName = null)
{
var dto = GetLibraryBook(seriesName is null ? null : [new SeriesDto(seriesName, "1", "sid")]);
dto.Title = title;
dto.TitleWithSubtitle = titleWithSubtitle;
return dto;
}

private static string Evaluate(string template, LibraryBookDto dto)
{
Templates.TryGetTemplate<Templates.FileTemplate>(template, out var t).Should().BeTrue();
return t.GetName(dto, new MultiConvertFileProperties { OutputFileName = string.Empty });
}

[TestMethod]
[DataRow("The Hobbit", "The Hobbit", "Hobbit")]
[DataRow("A Tale of Two Cities", "A Tale of Two Cities", "Tale of Two Cities")]
[DataRow("An American in Paris", "An American in Paris", "American in Paris")]
[DataRow("Foundation", "Foundation", "Foundation")] // no article — unchanged
[DataRow("Theatre of War", "Theatre of War", "Theatre of War")] // "The" must be whole word
public void TitleSort_strips_leading_article(string titleWithSubtitle, string title, string expected)
=> Evaluate("<title sort>", Book(titleWithSubtitle, title)).Should().Be(expected);

[TestMethod]
[DataRow("The Hobbit: There and Back Again", "The Hobbit", "Hobbit")]
[DataRow("A Tale: Subtitle", "A Tale", "Tale")]
[DataRow("Foundation: Prelude", "Foundation", "Foundation")] // no article
public void TitleShortSort_strips_leading_article(string titleWithSubtitle, string title, string expected)
=> Evaluate("<title short sort>", Book(titleWithSubtitle, title)).Should().Be(expected);

[TestMethod]
[DataRow("The Lord of the Rings", "Lord of the Rings")]
[DataRow("A Series of Unfortunate Events", "Series of Unfortunate Events")]
[DataRow("An Inspector Calls", "Inspector Calls")]
[DataRow("Sherlock Holmes", "Sherlock Holmes")] // no article — unchanged
public void FirstSeriesSort_strips_leading_article(string seriesName, string expected)
=> Evaluate("<first series sort>", Book("any", "any", seriesName)).Should().Be(expected);

[TestMethod]
public void TitleSort_case_insensitive()
=> Evaluate("<title sort>", Book("the hobbit", "the hobbit")).Should().Be("hobbit");

[TestMethod]
public void TitleSort_available_in_chapter_template()
{
Templates.TryGetTemplate<Templates.ChapterFileTemplate>("<title sort>", out var t).Should().BeTrue();
var dto = GetLibraryBook();
dto.TitleWithSubtitle = "The Silmarillion";
t.GetName(dto, new MultiConvertFileProperties { OutputFileName = string.Empty })
.Should().Be("Silmarillion");
}
}
}
Loading