Merge pull request #26 from Mbucari/master

Added support for downloaded chapter titles.
This commit is contained in:
rmcrackan 2021-06-17 13:37:15 -04:00 committed by GitHub
commit 2217fe6948
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 138 additions and 70 deletions

View File

@ -0,0 +1,40 @@
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Dinah.Core.Diagnostics;
namespace AaxDecrypter
{
public class AAXChapters : Chapters
{
public AAXChapters(string file)
{
var info = new ProcessStartInfo
{
FileName = DecryptSupportLibraries.ffprobePath,
Arguments = "-loglevel panic -show_chapters -print_format xml \"" + file + "\""
};
var xml = info.RunHidden().Output;
var xmlDocument = new System.Xml.XmlDocument();
xmlDocument.LoadXml(xml);
var chaptersXml = xmlDocument.SelectNodes("/ffprobe/chapters/chapter")
.Cast<System.Xml.XmlNode>()
.Where(n => n.Name == "chapter");
foreach (var cnode in chaptersXml)
{
double startTime = double.Parse(cnode.Attributes["start_time"].Value.Replace(",", "."), CultureInfo.InvariantCulture);
double endTime = double.Parse(cnode.Attributes["end_time"].Value.Replace(",", "."), CultureInfo.InvariantCulture);
string chapterTitle = cnode.ChildNodes
.Cast<System.Xml.XmlNode>()
.Where(childnode => childnode.Attributes["key"].Value == "title")
.Select(childnode => childnode.Attributes["value"].Value)
.FirstOrDefault();
AddChapter(new Chapter(startTime, endTime, chapterTitle));
}
}
}
}

View File

@ -62,9 +62,10 @@ namespace AaxDecrypter
public Tags tags { get; private set; } public Tags tags { get; private set; }
public EncodingInfo encodingInfo { get; private set; } public EncodingInfo encodingInfo { get; private set; }
public static async Task<AaxToM4bConverter> CreateAsync(string inputFile, string decryptKey) public static async Task<AaxToM4bConverter> CreateAsync(string inputFile, string decryptKey, Chapters chapters = null)
{ {
var converter = new AaxToM4bConverter(inputFile, decryptKey); var converter = new AaxToM4bConverter(inputFile, decryptKey);
converter.chapters = chapters ?? new AAXChapters(inputFile);
await converter.prelimProcessing(); await converter.prelimProcessing();
converter.printPrelim(); converter.printPrelim();
@ -98,7 +99,6 @@ namespace AaxDecrypter
{ {
tags = new Tags(inputFileName); tags = new Tags(inputFileName);
encodingInfo = new EncodingInfo(inputFileName); encodingInfo = new EncodingInfo(inputFileName);
chapters = new Chapters(inputFileName, tags.duration.TotalSeconds);
var defaultFilename = Path.Combine( var defaultFilename = Path.Combine(
Path.GetDirectoryName(inputFileName), Path.GetDirectoryName(inputFileName),
@ -278,9 +278,9 @@ namespace AaxDecrypter
public bool Step3_Chapterize() public bool Step3_Chapterize()
{ {
var str1 = ""; var str1 = "";
if (chapters.FirstChapterStart != 0.0) if (chapters.FirstChapter.StartTime != 0.0)
{ {
str1 = " -ss " + chapters.FirstChapterStart.ToString("0.000", CultureInfo.InvariantCulture) + " -t " + (chapters.LastChapterStart - 1.0).ToString("0.000", CultureInfo.InvariantCulture) + " "; str1 = " -ss " + chapters.FirstChapter.StartTime.ToString("0.000", CultureInfo.InvariantCulture) + " -t " + chapters.LastChapter.EndTime.ToString("0.000", CultureInfo.InvariantCulture) + " ";
} }
var ffmpegTags = tags.GenerateFfmpegTags(); var ffmpegTags = tags.GenerateFfmpegTags();

View File

@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace AaxDecrypter
{
public class Chapter
{
public Chapter(double startTime, double endTime, string title)
{
StartTime = startTime;
EndTime = endTime;
Title = title;
}
/// <summary>
/// Chapter start time, in seconds.
/// </summary>
public double StartTime { get; private set; }
/// <summary>
/// Chapter end time, in seconds.
/// </summary>
public double EndTime { get; private set; }
public string Title { get; private set; }
}
}

View File

@ -1,72 +1,37 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Dinah.Core.Diagnostics;
namespace AaxDecrypter namespace AaxDecrypter
{ {
public class Chapters public abstract class Chapters
{ {
private List<double> markers { get; } private List<Chapter> _chapterList = new();
public int Count => _chapterList.Count;
public double FirstChapterStart => markers[0]; public Chapter FirstChapter => _chapterList[0];
public double LastChapterStart => markers[markers.Count - 1]; public Chapter LastChapter => _chapterList[Count - 1];
public IEnumerable<Chapter> ChapterList => _chapterList.AsEnumerable();
public Chapters(string file, double totalTime) public IEnumerable<TimeSpan> GetBeginningTimes() => ChapterList.Select(c => TimeSpan.FromSeconds(c.StartTime));
protected void AddChapter(Chapter chapter)
{ {
markers = getAAXChapters(file); _chapterList.Add(chapter);
// add end time
markers.Add(totalTime);
} }
protected void AddChapters(IEnumerable<Chapter> chapters)
private static List<double> getAAXChapters(string file)
{ {
var info = new ProcessStartInfo _chapterList.AddRange(chapters);
{
FileName = DecryptSupportLibraries.ffprobePath,
Arguments = "-loglevel panic -show_chapters -print_format xml \"" + file + "\""
};
var xml = info.RunHidden().Output;
var xmlDocument = new System.Xml.XmlDocument();
xmlDocument.LoadXml(xml);
var chapters = xmlDocument.SelectNodes("/ffprobe/chapters/chapter")
.Cast<System.Xml.XmlNode>()
.Select(xmlNode => double.Parse(xmlNode.Attributes["start_time"].Value.Replace(",", "."), CultureInfo.InvariantCulture))
.ToList();
return chapters;
} }
// subtract 1 b/c end time marker is a real entry but isn't a real chapter. ie: fencepost
public int Count => markers.Count - 1;
public IEnumerable<TimeSpan> GetBeginningTimes()
{
for (var i = 0; i < Count; i++)
yield return TimeSpan.FromSeconds(markers[i]);
}
public string GenerateFfmpegChapters() public string GenerateFfmpegChapters()
{ {
var stringBuilder = new StringBuilder(); var stringBuilder = new StringBuilder();
for (var i = 0; i < Count; i++) foreach (Chapter c in ChapterList)
{ {
var chapter = i + 1;
var start = markers[i] * 1000.0;
var end = markers[i + 1] * 1000.0;
var chapterName = chapter.ToString("D3");
stringBuilder.Append("[CHAPTER]\n"); stringBuilder.Append("[CHAPTER]\n");
stringBuilder.Append("TIMEBASE=1/1000\n"); stringBuilder.Append("TIMEBASE=1/1000\n");
stringBuilder.Append("START=" + start + "\n"); stringBuilder.Append("START=" + c.StartTime * 1000 + "\n");
stringBuilder.Append("END=" + end + "\n"); stringBuilder.Append("END=" + c.EndTime * 1000 + "\n");
stringBuilder.Append("title=" + chapterName + "\n"); stringBuilder.Append("title=" + c.Title + "\n");
} }
return stringBuilder.ToString(); return stringBuilder.ToString();

View File

@ -14,20 +14,15 @@ namespace AaxDecrypter
stringBuilder.AppendLine(GetFileLine(filePath, "MP3")); stringBuilder.AppendLine(GetFileLine(filePath, "MP3"));
var beginningTimes = chapters.GetBeginningTimes().ToList(); var trackCount = 0;
for (var i = 0; i < beginningTimes.Count; i++) foreach (Chapter c in chapters.ChapterList)
{ {
var chapter = i + 1; trackCount++;
var startTime = TimeSpan.FromSeconds(c.StartTime);
var timeSpan = beginningTimes[i]; stringBuilder.AppendLine($"TRACK {trackCount} AUDIO");
var minutes = Math.Floor(timeSpan.TotalMinutes).ToString(); stringBuilder.AppendLine($" TITLE \"{c.Title}\"");
var seconds = timeSpan.Seconds.ToString("D2"); stringBuilder.AppendLine($" INDEX 01 {(int)startTime.TotalMinutes}:{startTime:ss\\:ff}");
var milliseconds = (timeSpan.Milliseconds / 10).ToString("D2");
var time = minutes + ":" + seconds + ":" + milliseconds;
stringBuilder.AppendLine($"TRACK {chapter} AUDIO");
stringBuilder.AppendLine($" TITLE \"Chapter {chapter:D2}\"");
stringBuilder.AppendLine($" INDEX 01 {time}");
} }
return stringBuilder.ToString(); return stringBuilder.ToString();

View File

@ -56,7 +56,9 @@ namespace FileLiberator
if (AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId)) if (AudibleFileStorage.Audio.Exists(libraryBook.Book.AudibleProductId))
return new StatusHandler { "Cannot find decrypt. Final audio file already exists" }; return new StatusHandler { "Cannot find decrypt. Final audio file already exists" };
var outputAudioFilename = await aaxToM4bConverterDecrypt(aaxFilename, libraryBook); var chapters = await downloadChapterNames(libraryBook);
var outputAudioFilename = await aaxToM4bConverterDecrypt(aaxFilename, libraryBook, chapters);
// decrypt failed // decrypt failed
if (outputAudioFilename == null) if (outputAudioFilename == null)
@ -90,7 +92,7 @@ namespace FileLiberator
} }
} }
private async Task<string> aaxToM4bConverterDecrypt(string aaxFilename, LibraryBook libraryBook) private async Task<string> aaxToM4bConverterDecrypt(string aaxFilename, LibraryBook libraryBook, Chapters chapters = null)
{ {
DecryptBegin?.Invoke(this, $"Begin decrypting {aaxFilename}"); DecryptBegin?.Invoke(this, $"Begin decrypting {aaxFilename}");
@ -102,7 +104,7 @@ namespace FileLiberator
.AccountsSettings .AccountsSettings
.GetAccount(libraryBook.Account, libraryBook.Book.Locale); .GetAccount(libraryBook.Account, libraryBook.Book.Locale);
var converter = await AaxToM4bConverter.CreateAsync(aaxFilename, account.DecryptKey); var converter = await AaxToM4bConverter.CreateAsync(aaxFilename, account.DecryptKey, chapters);
converter.AppName = "Libation"; converter.AppName = "Libation";
TitleDiscovered?.Invoke(this, converter.tags.title); TitleDiscovered?.Invoke(this, converter.tags.title);
@ -132,6 +134,23 @@ namespace FileLiberator
} }
} }
private async Task<Chapters> downloadChapterNames(LibraryBook libraryBook)
{
try
{
var api = await AudibleApiActions.GetApiAsync(libraryBook.Account, libraryBook.Book.Locale);
var contentMetadata = await api.GetLibraryBookMetadataAsync(libraryBook.Book.AudibleProductId);
if (contentMetadata?.ChapterInfo != null)
return new DownloadedChapters(contentMetadata.ChapterInfo);
return null;
}
catch
{
return null;
}
}
private static string moveFilesToBooksDir(Book product, string outputAudioFilename) private static string moveFilesToBooksDir(Book product, string outputAudioFilename)
{ {
// create final directory. move each file into it. MOVE AUDIO FILE LAST // create final directory. move each file into it. MOVE AUDIO FILE LAST

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using AaxDecrypter;
using AudibleApiDTOs;
using Dinah.Core.Diagnostics;
namespace FileLiberator
{
public class DownloadedChapters : Chapters
{
public DownloadedChapters(ChapterInfo chapterInfo)
{
AddChapters(chapterInfo.Chapters
.Select(c => new AaxDecrypter.Chapter(c.StartOffsetMs / 1000d, (c.StartOffsetMs + c.LengthMs) / 1000d, c.Title)));
}
}
}

View File

@ -13,7 +13,7 @@
<!-- <PublishSingleFile>true</PublishSingleFile> --> <!-- <PublishSingleFile>true</PublishSingleFile> -->
<RuntimeIdentifier>win-x64</RuntimeIdentifier> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Version>4.2.4.1</Version> <Version>4.2.4.9</Version>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>