diff --git a/Source/FileLiberator/DownloadOptions.cs b/Source/FileLiberator/DownloadOptions.cs
index 9efb9fb0..a79678d2 100644
--- a/Source/FileLiberator/DownloadOptions.cs
+++ b/Source/FileLiberator/DownloadOptions.cs
@@ -35,7 +35,11 @@ namespace FileLiberator
public bool MoveMoovToBeginning => config.MoveMoovToBeginning;
public string GetMultipartFileName(MultiConvertFileProperties props)
- => Templates.ChapterFile.GetFilename(LibraryBookDto, props);
+ {
+ var baseDir = Path.GetDirectoryName(props.OutputFileName);
+ var extension = Path.GetExtension(props.OutputFileName);
+ return Templates.ChapterFile.GetFilename(LibraryBookDto, props, baseDir, extension);
+ }
public string GetMultipartTitle(MultiConvertFileProperties props)
=> Templates.ChapterTitle.GetName(LibraryBookDto, props);
diff --git a/Source/FileLiberator/UtilityExtensions.cs b/Source/FileLiberator/UtilityExtensions.cs
index 22a728d9..b85eee0c 100644
--- a/Source/FileLiberator/UtilityExtensions.cs
+++ b/Source/FileLiberator/UtilityExtensions.cs
@@ -40,7 +40,7 @@ namespace FileLiberator
Narrators = libraryBook.Book.Narrators.Select(c => c.Name).ToList(),
SeriesName = libraryBook.Book.SeriesLink.FirstOrDefault()?.Series.Name,
- SeriesNumber = libraryBook.Book.SeriesLink.FirstOrDefault()?.Order,
+ SeriesNumber = (int?)libraryBook.Book.SeriesLink.FirstOrDefault()?.Index,
IsPodcast = libraryBook.Book.IsEpisodeChild(),
BitRate = libraryBook.Book.AudioFormat.Bitrate,
diff --git a/Source/FileManager/NamingTemplate/NamingTemplate.cs b/Source/FileManager/NamingTemplate/NamingTemplate.cs
index 4d5016da..17ee7ec3 100644
--- a/Source/FileManager/NamingTemplate/NamingTemplate.cs
+++ b/Source/FileManager/NamingTemplate/NamingTemplate.cs
@@ -47,7 +47,7 @@ public class NamingTemplate
/// Parse a template string to a
/// The template string to parse
- /// A collection of with
+ /// A collection of with
/// properties registered to match to the
public static NamingTemplate Parse(string template, IEnumerable tagClasses)
{
@@ -111,10 +111,10 @@ public class NamingTemplate
checkAndAddLiterals();
if (propertyTag is IClosingPropertyTag)
- currentNode = AddNewNode(currentNode, BinaryNode.CreateConditional(propertyTag.TemplateTag, valueExpression));
+ currentNode = currentNode.AddNewNode(BinaryNode.CreateConditional(propertyTag.TemplateTag, valueExpression));
else
{
- currentNode = AddNewNode(currentNode, BinaryNode.CreateValue(propertyTag.TemplateTag, valueExpression));
+ currentNode = currentNode.AddNewNode(BinaryNode.CreateValue(propertyTag.TemplateTag, valueExpression));
_tagsInUse.Add(propertyTag.TemplateTag);
}
@@ -170,7 +170,7 @@ public class NamingTemplate
{
if (literalChars.Count != 0)
{
- currentNode = AddNewNode(currentNode, BinaryNode.CreateValue(new string(literalChars.ToArray())));
+ currentNode = currentNode.AddNewNode(BinaryNode.CreateValue(new string(literalChars.ToArray())));
literalChars.Clear();
}
}
@@ -201,34 +201,12 @@ public class NamingTemplate
return false;
}
- private static BinaryNode AddNewNode(BinaryNode currentNode, BinaryNode newNode)
- {
- if (currentNode.LeftChild is null)
- {
- newNode.Parent = currentNode;
- currentNode.LeftChild = newNode;
- }
- else if (currentNode.RightChild is null)
- {
- newNode.Parent = currentNode;
- currentNode.RightChild = newNode;
- }
- else
- {
- currentNode.RightChild = BinaryNode.CreateConcatenation(currentNode.RightChild, newNode);
- currentNode.RightChild.Parent = currentNode;
- currentNode = currentNode.RightChild;
- }
-
- return newNode.IsConditional ? newNode : currentNode;
- }
-
private class BinaryNode
{
public string Name { get; }
- public BinaryNode Parent { get; set; }
- public BinaryNode RightChild { get; set; }
- public BinaryNode LeftChild { get; set; }
+ public BinaryNode Parent { get; private set; }
+ public BinaryNode RightChild { get; private set; }
+ public BinaryNode LeftChild { get; private set; }
public Expression Expression { get; private init; }
public bool IsConditional { get; private init; } = false;
public bool IsValue { get; private init; } = false;
@@ -253,7 +231,7 @@ public class NamingTemplate
Expression = property
};
- public static BinaryNode CreateConcatenation(BinaryNode left, BinaryNode right)
+ private static BinaryNode CreateConcatenation(BinaryNode left, BinaryNode right)
{
var newNode = new BinaryNode("Concatenation")
{
@@ -267,5 +245,29 @@ public class NamingTemplate
private BinaryNode(string name) => Name = name;
public override string ToString() => Name;
+
+ public BinaryNode AddNewNode(BinaryNode newNode)
+ {
+ BinaryNode currentNode = this;
+
+ if (LeftChild is null)
+ {
+ newNode.Parent = currentNode;
+ LeftChild = newNode;
+ }
+ else if (RightChild is null)
+ {
+ newNode.Parent = currentNode;
+ RightChild = newNode;
+ }
+ else
+ {
+ RightChild = CreateConcatenation(RightChild, newNode);
+ RightChild.Parent = currentNode;
+ currentNode = RightChild;
+ }
+
+ return newNode.IsConditional ? newNode : currentNode;
+ }
}
}
diff --git a/Source/FileManager/NamingTemplate/PropertyTagClass[TClass].cs b/Source/FileManager/NamingTemplate/PropertyTagClass[TClass].cs
index 1d53f7ae..04eac040 100644
--- a/Source/FileManager/NamingTemplate/PropertyTagClass[TClass].cs
+++ b/Source/FileManager/NamingTemplate/PropertyTagClass[TClass].cs
@@ -19,7 +19,7 @@ public class PropertyTagClass : TagClass
/// Optional formatting function that accepts the property and a formatting string and returnes the value formatted to string
public void RegisterProperty(ITemplateTag templateTag, Func propertyGetter, PropertyFormatter formatter = null)
where U : struct
- => RegisterProperty(templateTag, propertyGetter, formatter?.Method);
+ => RegisterPropertyInternal(templateTag, propertyGetter, formatter);
///
/// Register a non-nullable value type property
@@ -29,7 +29,7 @@ public class PropertyTagClass : TagClass
/// Optional formatting function that accepts the property and a formatting string and returnes the value formatted to string
public void RegisterProperty(ITemplateTag templateTag, Func propertyGetter, PropertyFormatter formatter = null)
where U : struct
- => RegisterProperty(templateTag, propertyGetter, formatter?.Method);
+ => RegisterPropertyInternal(templateTag, propertyGetter, formatter);
///
/// Register a string type property.
@@ -37,13 +37,16 @@ public class PropertyTagClass : TagClass
/// A Func to get the string property from
/// Optional formatting function that accepts the string property and a formatting string and returnes the value formatted to string
public void RegisterProperty(ITemplateTag templateTag, Func propertyGetter, PropertyFormatter formatter = null)
- => RegisterProperty(templateTag, propertyGetter, formatter?.Method);
+ => RegisterPropertyInternal(templateTag, propertyGetter, formatter);
- private void RegisterProperty(ITemplateTag templateTag, Delegate propertyGetter, MethodInfo formatter)
+ private void RegisterPropertyInternal(ITemplateTag templateTag, Delegate propertyGetter, Delegate formatter)
{
+ if (formatter?.Target is not null)
+ throw new ArgumentException($"{nameof(formatter)} must be a static method");
+
var expr = Expression.Call(Expression.Constant(propertyGetter.Target), propertyGetter.Method, Parameter);
- AddPropertyTag(new PropertyTag(templateTag, Options, expr, formatter));
+ AddPropertyTag(new PropertyTag(templateTag, Options, expr, formatter?.Method));
}
private class PropertyTag : TagBase
diff --git a/Source/LibationFileManager/LibraryBookDto.cs b/Source/LibationFileManager/LibraryBookDto.cs
index 60114040..859f18c9 100644
--- a/Source/LibationFileManager/LibraryBookDto.cs
+++ b/Source/LibationFileManager/LibraryBookDto.cs
@@ -20,7 +20,8 @@ namespace LibationFileManager
public string FirstNarrator => Narrators.FirstOrDefault();
public string SeriesName { get; set; }
- public string SeriesNumber { get; set; }
+ public int? SeriesNumber { get; set; }
+ public bool IsSeries => !string.IsNullOrEmpty(SeriesName);
public bool IsPodcast { get; set; }
public int BitRate { get; set; }
diff --git a/Source/LibationFileManager/TemplateEditor[T].cs b/Source/LibationFileManager/TemplateEditor[T].cs
index d20663b9..a7226de2 100644
--- a/Source/LibationFileManager/TemplateEditor[T].cs
+++ b/Source/LibationFileManager/TemplateEditor[T].cs
@@ -58,7 +58,7 @@ namespace LibationFileManager
Authors = new List { "Arthur Conan Doyle", "Stephen Fry - introductions" },
Narrators = new List { "Stephen Fry" },
SeriesName = "Sherlock Holmes",
- SeriesNumber = "1",
+ SeriesNumber = 1,
BitRate = 128,
SampleRate = 44100,
Channels = 2,
diff --git a/Source/LibationFileManager/TemplateTags.cs b/Source/LibationFileManager/TemplateTags.cs
index 879363a0..8adcd4d6 100644
--- a/Source/LibationFileManager/TemplateTags.cs
+++ b/Source/LibationFileManager/TemplateTags.cs
@@ -45,7 +45,8 @@ namespace LibationFileManager
public static TemplateTags FileDate { get; } = new TemplateTags("file date", "File date/time. e.g. yyyy-MM-dd HH-mm", $"", "");
public static TemplateTags DatePublished { get; } = new TemplateTags("pub date", "Publication date. e.g. yyyy-MM-dd", $"", "");
public static TemplateTags DateAdded { get; } = new TemplateTags("date added", "Date added to your Audible account. e.g. yyyy-MM-dd", $"", "");
- public static TemplateTags IfSeries { get; } = new TemplateTags("if series", "Only include if part of a series", "<-if series>", "...<-if series>");
+ public static TemplateTags IfSeries { get; } = new TemplateTags("if series", "Only include if part of a book series or podcast", "<-if series>", "...<-if series>");
public static TemplateTags IfPodcast { get; } = new TemplateTags("if podcast", "Only include if part of a podcast", "<-if podcast>", "...<-if podcast>");
+ public static TemplateTags IfBookseries { get; } = new TemplateTags("if bookseries", "Only include if part of a book series", "<-if bookseries>", "...<-if bookseries>");
}
}
diff --git a/Source/LibationFileManager/Templates.cs b/Source/LibationFileManager/Templates.cs
index d42e70a0..e62a62b2 100644
--- a/Source/LibationFileManager/Templates.cs
+++ b/Source/LibationFileManager/Templates.cs
@@ -6,6 +6,7 @@ using AaxDecrypter;
using Dinah.Core;
using FileManager;
using FileManager.NamingTemplate;
+using Serilog.Formatting;
namespace LibationFileManager
{
@@ -105,24 +106,24 @@ namespace LibationFileManager
ArgumentValidator.EnsureNotNull(fileExtension, nameof(fileExtension));
replacements ??= Configuration.Instance.ReplacementCharacters;
- return GetFilename(baseDir, fileExtension, returnFirstExisting, replacements, libraryBookDto);
+ return GetFilename(baseDir, fileExtension,replacements, returnFirstExisting, libraryBookDto);
}
- public LongPath GetFilename(LibraryBookDto libraryBookDto, MultiConvertFileProperties multiChapProps, string baseDir = "", string fileExtension = null, ReplacementCharacters replacements = null)
+ public LongPath GetFilename(LibraryBookDto libraryBookDto, MultiConvertFileProperties multiChapProps, string baseDir, string fileExtension, ReplacementCharacters replacements = null, bool returnFirstExisting = false)
{
ArgumentValidator.EnsureNotNull(libraryBookDto, nameof(libraryBookDto));
ArgumentValidator.EnsureNotNull(multiChapProps, nameof(multiChapProps));
ArgumentValidator.EnsureNotNull(baseDir, nameof(baseDir));
+ ArgumentValidator.EnsureNotNull(fileExtension, nameof(fileExtension));
replacements ??= Configuration.Instance.ReplacementCharacters;
- fileExtension ??= Path.GetExtension(multiChapProps.OutputFileName);
- return GetFilename(baseDir, fileExtension, false, replacements, libraryBookDto, multiChapProps);
+ return GetFilename(baseDir, fileExtension, replacements, returnFirstExisting, libraryBookDto, multiChapProps);
}
protected virtual IEnumerable GetTemplatePartsStrings(List parts, ReplacementCharacters replacements)
=> parts.Select(p => replacements.ReplaceFilenameChars(p.Value));
- private LongPath GetFilename(string baseDir, string fileExtension, bool returnFirstExisting, ReplacementCharacters replacements, params object[] dtos)
+ private LongPath GetFilename(string baseDir, string fileExtension, ReplacementCharacters replacements, bool returnFirstExisting, params object[] dtos)
{
fileExtension = FileUtility.GetStandardizedExtension(fileExtension);
@@ -151,14 +152,15 @@ namespace LibationFileManager
part.Insert(maxIndex, maxEntry.Remove(maxLength - 1, 1));
}
}
-
- var fullPath = Path.Combine(pathParts.Select(p => string.Join("", p)).Prepend(baseDir).ToArray());
+ //Any
+ var fullPath = Path.Combine(pathParts.Select(fileParts => string.Join("", fileParts)).Prepend(baseDir).ToArray());
return FileUtility.GetValidFilename(fullPath, replacements, fileExtension, returnFirstExisting);
}
///
- /// Organize template parts into directories.
+ /// Organize template parts into directories. Any Extra slashes will be
+ /// returned as empty directories and are taken care of by Path.Combine()
///
/// A List of template directories. Each directory is a list of template part strings
private List> GetPathParts(IEnumerable templateParts)
@@ -196,8 +198,9 @@ namespace LibationFileManager
{
ConditionalTagClass lbConditions = new();
- lbConditions.RegisterCondition(TemplateTags.IfSeries, lb => !string.IsNullOrWhiteSpace(lb.SeriesName));
+ lbConditions.RegisterCondition(TemplateTags.IfSeries, lb => lb.IsSeries);
lbConditions.RegisterCondition(TemplateTags.IfPodcast, lb => lb.IsPodcast);
+ lbConditions.RegisterCondition(TemplateTags.IfBookseries, lb => lb.IsSeries && !lb.IsPodcast);
return lbConditions;
}
@@ -206,16 +209,16 @@ namespace LibationFileManager
{
PropertyTagClass lbProperties = new();
lbProperties.RegisterProperty(TemplateTags.Id, lb => lb.AudibleProductId);
- lbProperties.RegisterProperty(TemplateTags.Title, lb => lb.Title ?? "", StringFormatter);
+ lbProperties.RegisterProperty(TemplateTags.Title, lb => lb.Title, StringFormatter);
lbProperties.RegisterProperty(TemplateTags.TitleShort, lb => lb.Title.IndexOf(':') < 1 ? lb.Title : lb.Title.Substring(0, lb.Title.IndexOf(':')), StringFormatter);
lbProperties.RegisterProperty(TemplateTags.Author, lb => lb.AuthorNames, StringFormatter);
lbProperties.RegisterProperty(TemplateTags.FirstAuthor, lb => lb.FirstAuthor, StringFormatter);
lbProperties.RegisterProperty(TemplateTags.Narrator, lb => lb.NarratorNames, StringFormatter);
lbProperties.RegisterProperty(TemplateTags.FirstNarrator, lb => lb.FirstNarrator, StringFormatter);
- lbProperties.RegisterProperty(TemplateTags.Series, lb => lb.SeriesName ?? "", StringFormatter);
- lbProperties.RegisterProperty(TemplateTags.SeriesNumber, lb => lb.SeriesNumber);
- lbProperties.RegisterProperty(TemplateTags.Language, lb => lb.Language);
- lbProperties.RegisterProperty(TemplateTags.LanguageShort, lb => getLanguageShort(lb.Language));
+ lbProperties.RegisterProperty(TemplateTags.Series, lb => lb.SeriesName, StringFormatter);
+ lbProperties.RegisterProperty(TemplateTags.SeriesNumber, lb => lb.SeriesNumber, IntegerFormatter);
+ lbProperties.RegisterProperty(TemplateTags.Language, lb => lb.Language, StringFormatter);
+ lbProperties.RegisterProperty(TemplateTags.LanguageShort, lb => getLanguageShort(lb.Language), StringFormatter);
lbProperties.RegisterProperty(TemplateTags.Bitrate, lb => lb.BitRate, IntegerFormatter);
lbProperties.RegisterProperty(TemplateTags.SampleRate, lb => lb.SampleRate, IntegerFormatter);
lbProperties.RegisterProperty(TemplateTags.Channels, lb => lb.Channels, IntegerFormatter);
@@ -233,14 +236,15 @@ namespace LibationFileManager
PropertyTagClass lbProperties = new();
PropertyTagClass multiConvertProperties = new();
- lbProperties.RegisterProperty(TemplateTags.Title, lb => lb.Title ?? "");
- lbProperties.RegisterProperty(TemplateTags.TitleShort, lb => lb.Title.IndexOf(':') < 1 ? lb.Title : lb.Title.Substring(0, lb.Title.IndexOf(':')));
- lbProperties.RegisterProperty(TemplateTags.Series, lb => lb.SeriesName ?? "");
+ lbProperties.RegisterProperty(TemplateTags.Title, lb => lb.Title, StringFormatter);
+ lbProperties.RegisterProperty(TemplateTags.TitleShort, lb => lb?.Title?.IndexOf(':') > 0 ? lb.Title.Substring(0, lb.Title.IndexOf(':')) : lb.Title, StringFormatter);
+ lbProperties.RegisterProperty(TemplateTags.Series, lb => lb.SeriesName, StringFormatter);
multiConvertProperties.RegisterProperty(TemplateTags.ChCount, lb => lb.PartsTotal, IntegerFormatter);
multiConvertProperties.RegisterProperty(TemplateTags.ChNumber, lb => lb.PartsPosition, IntegerFormatter);
multiConvertProperties.RegisterProperty(TemplateTags.ChNumber0, m => m.PartsPosition.ToString("D" + ((int)Math.Log10(m.PartsTotal) + 1)));
- multiConvertProperties.RegisterProperty(TemplateTags.ChTitle, m => m.Title ?? "", StringFormatter);
+ multiConvertProperties.RegisterProperty(TemplateTags.ChTitle, m => m.Title, StringFormatter);
+ multiConvertProperties.RegisterProperty(TemplateTags.FileDate, lb => lb.FileDate, DateTimeFormatter);
return new List { lbProperties, multiConvertProperties };
}
@@ -303,15 +307,6 @@ namespace LibationFileManager
else
tp.Value = replacements.ReplaceFilenameChars(tp.Value);
}
- if (parts.Count > 0)
- {
- //Remove DirectorySeparatorChar at beginning and end of template
- if (parts[0].Value.Length > 0 && parts[0].Value[0] == Path.DirectorySeparatorChar)
- parts[0].Value = parts[0].Value.Remove(0,1);
-
- if (parts[^1].Value.Length > 0 && parts[^1].Value[^1] == Path.DirectorySeparatorChar)
- parts[^1].Value = parts[^1].Value.Remove(parts[^1].Value.Length - 1, 1);
- }
return parts.Select(p => p.Value).ToList();
}
}
diff --git a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs
index 46398993..394c7ecc 100644
--- a/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs
+++ b/Source/_Tests/LibationFileManager.Tests/TemplatesTests.cs
@@ -37,7 +37,7 @@ namespace TemplatesTests
Authors = new List { "Arthur Conan Doyle", "Stephen Fry - introductions" },
Narrators = new List { "Stephen Fry" },
SeriesName = seriesName ?? "",
- SeriesNumber = "1",
+ SeriesNumber = 1,
BitRate = 128,
SampleRate = 44100,
Channels = 2,
@@ -297,8 +297,8 @@ namespace Templates_Other
static ReplacementCharacters Replacements = ReplacementCharacters.Default;
[TestMethod]
- [DataRow(@"C:\foo\bar", @"\Folder\\[]\", @"C:\foo\bar\Folder\my꞉ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\[ID123456].txt", PlatformID.Win32NT)]
- [DataRow("/foo/bar", "/Folder//[]/", @" / foo/bar/Folder/my: book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/[ID123456].txt", PlatformID.Unix)]
+ [DataRow(@"C:\foo\bar", @"\\Folder\\[]\\", @"C:\foo\bar\Folder\my꞉ book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\[ID123456].txt", PlatformID.Win32NT)]
+ [DataRow("/foo/bar", "/Folder//[]/", @"/foo/bar/Folder/my: book 000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000/[ID123456].txt", PlatformID.Unix)]
[DataRow(@"C:\foo\bar", @"\Folder\ []", @"C:\foo\bar\Folder\my꞉ book 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [ID123456].txt", PlatformID.Win32NT)]
[DataRow("/foo/bar", "/Folder/ []", @"/foo/bar/Folder/my: book 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 [ID123456].txt", PlatformID.Unix)]
[DataRow(@"C:\foo\bar", @"\Folder\ []", @"C:\foo\bar\Folder\my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 0000000000000000 my꞉ book 00000000000000000 my꞉ book 00000000000000000 [ID123456].txt", PlatformID.Win32NT)]