Merge pull request #280 from Mbucari/master
Add support for Audible's new hierarchical chapters
This commit is contained in:
commit
d3a9ff539e
@ -165,6 +165,8 @@ namespace FileLiberator
|
|||||||
LameConfig = GetLameOptions(config)
|
LameConfig = GetLameOptions(config)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var chapters = getChapters(contentLic.ContentMetadata.ChapterInfo.Chapters).OrderBy(c => c.StartOffsetMs).ToList();
|
||||||
|
|
||||||
if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3)
|
if (config.AllowLibationFixup || outputFormat == OutputFormat.Mp3)
|
||||||
{
|
{
|
||||||
long startMs = dlOptions.TrimOutputToChapterLength ?
|
long startMs = dlOptions.TrimOutputToChapterLength ?
|
||||||
@ -172,15 +174,15 @@ namespace FileLiberator
|
|||||||
|
|
||||||
dlOptions.ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(startMs));
|
dlOptions.ChapterInfo = new AAXClean.ChapterInfo(TimeSpan.FromMilliseconds(startMs));
|
||||||
|
|
||||||
for (int i = 0; i < contentLic.ContentMetadata.ChapterInfo.Chapters.Length; i++)
|
for (int i = 0; i < chapters.Count; i++)
|
||||||
{
|
{
|
||||||
var chapter = contentLic.ContentMetadata.ChapterInfo.Chapters[i];
|
var chapter = chapters[i];
|
||||||
long chapLenMs = chapter.LengthMs;
|
long chapLenMs = chapter.LengthMs;
|
||||||
|
|
||||||
if (i == 0)
|
if (i == 0)
|
||||||
chapLenMs -= startMs;
|
chapLenMs -= startMs;
|
||||||
|
|
||||||
if (config.StripAudibleBrandAudio && i == contentLic.ContentMetadata.ChapterInfo.Chapters.Length - 1)
|
if (config.StripAudibleBrandAudio && i == chapters.Count - 1)
|
||||||
chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs;
|
chapLenMs -= contentLic.ContentMetadata.ChapterInfo.BrandOutroDurationMs;
|
||||||
|
|
||||||
dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs));
|
dlOptions.ChapterInfo.AddChapter(chapter.Title, TimeSpan.FromMilliseconds(chapLenMs));
|
||||||
@ -190,6 +192,25 @@ namespace FileLiberator
|
|||||||
return dlOptions;
|
return dlOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<AudibleApi.Common.Chapter> getChapters(IEnumerable<AudibleApi.Common.Chapter> chapters)
|
||||||
|
{
|
||||||
|
List<AudibleApi.Common.Chapter> chaps = chapters.ToList();
|
||||||
|
|
||||||
|
foreach (var c in chapters)
|
||||||
|
{
|
||||||
|
if (c.Chapters is not null)
|
||||||
|
{
|
||||||
|
var children = getChapters(c.Chapters);
|
||||||
|
|
||||||
|
foreach (var child in children)
|
||||||
|
child.Title = string.IsNullOrEmpty(c.Title) ? child.Title : $"{c.Title}: {child.Title}";
|
||||||
|
|
||||||
|
chaps.AddRange(children);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chaps;
|
||||||
|
}
|
||||||
|
|
||||||
private static void downloadValidation(LibraryBook libraryBook)
|
private static void downloadValidation(LibraryBook libraryBook)
|
||||||
{
|
{
|
||||||
string errorString(string field)
|
string errorString(string field)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
@ -17,18 +18,39 @@ namespace FileManager
|
|||||||
/// <summary>Generate a valid path for this file or directory</summary>
|
/// <summary>Generate a valid path for this file or directory</summary>
|
||||||
public LongPath GetFilePath(bool returnFirstExisting = false)
|
public LongPath GetFilePath(bool returnFirstExisting = false)
|
||||||
{
|
{
|
||||||
int lastSlash = Template.LastIndexOf('\\');
|
string fileName = Template;
|
||||||
|
List<string> pathParts = new();
|
||||||
var directoryName = lastSlash >= 0 ? Template[..(lastSlash + 1)] : string.Empty;
|
|
||||||
var filename = lastSlash >= 0 ? Template[(lastSlash + 1)..] : Template;
|
|
||||||
|
|
||||||
List<StringBuilder> filenameParts = new();
|
|
||||||
|
|
||||||
var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value));
|
var paramReplacements = ParameterReplacements.ToDictionary(r => $"<{formatKey(r.Key)}>", r => formatValue(r.Value));
|
||||||
|
|
||||||
|
while (!string.IsNullOrEmpty(fileName))
|
||||||
|
{
|
||||||
|
var file = Path.GetFileName(fileName);
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(Template) && file == string.Empty)
|
||||||
|
{
|
||||||
|
pathParts.Add(fileName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file = replaceFileName(file, paramReplacements);
|
||||||
|
fileName = Path.GetDirectoryName(fileName);
|
||||||
|
pathParts.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pathParts.Reverse();
|
||||||
|
|
||||||
|
return FileUtility.GetValidFilename(Path.Join(pathParts.ToArray()), IllegalCharacterReplacements, returnFirstExisting);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string replaceFileName(string filename, Dictionary<string,string> paramReplacements)
|
||||||
|
{
|
||||||
|
List<StringBuilder> filenameParts = new();
|
||||||
//Build the filename in parts, replacing replacement parameters with
|
//Build the filename in parts, replacing replacement parameters with
|
||||||
//their values, and storing the parts in a list.
|
//their values, and storing the parts in a list.
|
||||||
while(!string.IsNullOrEmpty(filename))
|
while (!string.IsNullOrEmpty(filename))
|
||||||
{
|
{
|
||||||
int openIndex = filename.IndexOf('<');
|
int openIndex = filename.IndexOf('<');
|
||||||
int closeIndex = filename.IndexOf('>');
|
int closeIndex = filename.IndexOf('>');
|
||||||
@ -59,17 +81,14 @@ namespace FileManager
|
|||||||
|
|
||||||
//Remove 1 character from the end of the longest filename part until
|
//Remove 1 character from the end of the longest filename part until
|
||||||
//the total filename is less than max filename length
|
//the total filename is less than max filename length
|
||||||
while(filenameParts.Sum(p => p.Length) > LongPath.MaxFilenameLength)
|
while (filenameParts.Sum(p => p.Length) > LongPath.MaxFilenameLength)
|
||||||
{
|
{
|
||||||
int maxLength = filenameParts.Max(p => p.Length);
|
int maxLength = filenameParts.Max(p => p.Length);
|
||||||
var maxEntry = filenameParts.First(p => p.Length == maxLength);
|
var maxEntry = filenameParts.First(p => p.Length == maxLength);
|
||||||
|
|
||||||
maxEntry.Remove(maxLength - 1, 1);
|
maxEntry.Remove(maxLength - 1, 1);
|
||||||
}
|
}
|
||||||
|
return string.Join("", filenameParts);
|
||||||
filename = string.Join("", filenameParts);
|
|
||||||
|
|
||||||
return FileUtility.GetValidFilename(directoryName + filename, IllegalCharacterReplacements, returnFirstExisting);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string formatValue(object value)
|
private string formatValue(object value)
|
||||||
|
|||||||
@ -16,7 +16,6 @@ namespace FileManager
|
|||||||
|
|
||||||
private const int MAX_PATH = 260;
|
private const int MAX_PATH = 260;
|
||||||
private const string LONG_PATH_PREFIX = "\\\\?\\";
|
private const string LONG_PATH_PREFIX = "\\\\?\\";
|
||||||
private static readonly StringBuilder longPathBuffer = new(MaxPathLength);
|
|
||||||
|
|
||||||
public string Path { get; init; }
|
public string Path { get; init; }
|
||||||
public override string ToString() => Path;
|
public override string ToString() => Path;
|
||||||
@ -71,18 +70,24 @@ namespace FileManager
|
|||||||
//
|
//
|
||||||
// "Based on the above settings, 8dot3 name creation is [enabled/disabled] on c:"
|
// "Based on the above settings, 8dot3 name creation is [enabled/disabled] on c:"
|
||||||
//
|
//
|
||||||
//To enable short names on all volumes on the system, run the following command
|
//To enable short names on a volume on the system, run the following command
|
||||||
//from an elevated command prompt:
|
//from an elevated command prompt:
|
||||||
//
|
//
|
||||||
// fsutil 8dot3name set c: 0
|
// fsutil 8dot3name set c: 0
|
||||||
//
|
//
|
||||||
|
//or for all volumes on the system:
|
||||||
|
//
|
||||||
|
// fsutil 8dot3name set 0
|
||||||
|
//
|
||||||
//Note that after enabling 8dot3 names on a volume, they will only be available
|
//Note that after enabling 8dot3 names on a volume, they will only be available
|
||||||
//for newly-created entries in ther file system. Existing entries made while
|
//for newly-created entries in ther file system. Existing entries made while
|
||||||
//8dot3 names were disabled will not be reachable by short paths.
|
//8dot3 names were disabled will not be reachable by short paths.
|
||||||
|
|
||||||
if (Path is null) return null;
|
if (Path is null) return null;
|
||||||
GetShortPathName(Path, longPathBuffer, MaxPathLength);
|
|
||||||
return longPathBuffer.ToString();
|
StringBuilder shortPathBuffer = new(MaxPathLength);
|
||||||
|
GetShortPathName(Path, shortPathBuffer, MaxPathLength);
|
||||||
|
return shortPathBuffer.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +97,8 @@ namespace FileManager
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (Path is null) return null;
|
if (Path is null) return null;
|
||||||
|
|
||||||
|
StringBuilder longPathBuffer = new(MaxPathLength);
|
||||||
GetLongPathName(Path, longPathBuffer, MaxPathLength);
|
GetLongPathName(Path, longPathBuffer, MaxPathLength);
|
||||||
return longPathBuffer.ToString();
|
return longPathBuffer.ToString();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -140,12 +140,11 @@ namespace LibationWinForms.Dialogs
|
|||||||
richTextBox1.SelectionFont = reg;
|
richTextBox1.SelectionFont = reg;
|
||||||
|
|
||||||
if (isChapterTitle)
|
if (isChapterTitle)
|
||||||
|
{
|
||||||
richTextBox1.SelectionFont = bold;
|
richTextBox1.SelectionFont = bold;
|
||||||
|
richTextBox1.AppendText(chapterTitle);
|
||||||
richTextBox1.AppendText(chapterTitle);
|
|
||||||
|
|
||||||
if (isChapterTitle)
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
richTextBox1.AppendText(slashWrap(books));
|
richTextBox1.AppendText(slashWrap(books));
|
||||||
richTextBox1.AppendText(sing);
|
richTextBox1.AppendText(sing);
|
||||||
|
|||||||
@ -212,7 +212,8 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
private async void cancelAllBtn_Click(object sender, EventArgs e)
|
private async void cancelAllBtn_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
Queue.ClearQueue();
|
Queue.ClearQueue();
|
||||||
await Queue.Current?.CancelAsync();
|
if (Queue.Current is not null)
|
||||||
|
await Queue.Current.CancelAsync();
|
||||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||||
UpdateAllControls();
|
UpdateAllControls();
|
||||||
}
|
}
|
||||||
@ -331,7 +332,8 @@ namespace LibationWinForms.ProcessQueue
|
|||||||
ProcessBook item = Queue[queueIndex];
|
ProcessBook item = Queue[queueIndex];
|
||||||
if (buttonName == nameof(panelClicked.cancelBtn))
|
if (buttonName == nameof(panelClicked.cancelBtn))
|
||||||
{
|
{
|
||||||
await item.CancelAsync();
|
if (item is not null)
|
||||||
|
await item.CancelAsync();
|
||||||
Queue.RemoveQueued(item);
|
Queue.RemoveQueued(item);
|
||||||
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
virtualFlowControl2.VirtualControlCount = Queue.Count;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user