diff --git a/AaxDecrypter/AaxDecrypter.csproj b/AaxDecrypter/AaxDecrypter.csproj
index a3eeee41..a7bf3b44 100644
--- a/AaxDecrypter/AaxDecrypter.csproj
+++ b/AaxDecrypter/AaxDecrypter.csproj
@@ -4,6 +4,10 @@
net5.0
+
+
+
+
lib\taglib-sharp.dll
@@ -42,9 +46,6 @@
Always
-
- Always
-
diff --git a/AaxDecrypter/AaxcDownloadConverter.cs b/AaxDecrypter/AaxcDownloadConverter.cs
index f853cbee..e8ee0a0f 100644
--- a/AaxDecrypter/AaxcDownloadConverter.cs
+++ b/AaxDecrypter/AaxcDownloadConverter.cs
@@ -41,12 +41,12 @@ namespace AaxDecrypter
public string outDir { get; private set; }
public string outputFileName { get; private set; }
public ChapterInfo chapters { get; private set; }
- public string Title => aaxcTagLib.Tag.Title?.Replace(" (Unabridged)", "") ?? "[unknown]";
- public string Author => aaxcTagLib.Tag.FirstPerformer ?? "[unknown]";
- public string Narrator => aaxcTagLib.GetTag(TagLib.TagTypes.Apple).Narrator ?? "[unknown]";
+ public string Title => aaxcTagLib.TitleSansUnabridged ?? "[unknown]";
+ public string Author => aaxcTagLib.FirstAuthor ?? "[unknown]";
+ public string Narrator => aaxcTagLib.Narrator ?? "[unknown]";
public byte[] CoverArt => aaxcTagLib.Tag.Pictures.Length > 0 ? aaxcTagLib.Tag.Pictures[0].Data.Data : default;
- private TagLib.Mpeg4.File aaxcTagLib { get; set; }
+ private AaxcTagLibFile aaxcTagLib { get; set; }
private StepSequence steps { get; }
private DownloadLicense downloadLicense { get; set; }
private FFMpegAaxcProcesser aaxcProcesser;
@@ -90,7 +90,7 @@ namespace AaxDecrypter
var networkFile = await NetworkFileAbstraction.CreateAsync(client, new Uri(downloadLicense.DownloadUrl));
- aaxcTagLib = await Task.Run(() => TagLib.File.Create(networkFile, "audio/mp4", TagLib.ReadStyle.Average) as TagLib.Mpeg4.File);
+ aaxcTagLib = await Task.Run(() => new AaxcTagLibFile(networkFile));
var defaultFilename = Path.Combine(
outDir,
@@ -239,21 +239,8 @@ namespace AaxDecrypter
///
public bool Step3_RestoreMetadata()
{
- var outFile = new TagLib.Mpeg4.File(outputFileName, TagLib.ReadStyle.Average);
-
- var destTags = outFile.GetTag(TagLib.TagTypes.Apple) as TagLib.Mpeg4.AppleTag;
- destTags.Clear();
-
- var sourceTag = aaxcTagLib.GetTag(TagLib.TagTypes.Apple) as TagLib.Mpeg4.AppleTag;
-
- //copy all metadata fields in the source file, even those that TagLib doesn't
- //recognize, to the output file.
- //NOTE: Chapters aren't stored in MPEG-4 metadata. They are encoded as a Timed
- //Text Stream (MPEG-4 Part 17), so taglib doesn't read or write them.
- foreach (var stag in sourceTag)
- {
- destTags.SetData(stag.BoxType, stag.Children.Cast().ToArray());
- }
+ var outFile = new AaxcTagLibFile(outputFileName);
+ outFile.CopyTagsFrom(aaxcTagLib);
outFile.Save();
return true;
diff --git a/AaxDecrypter/DecryptLib/taglib-sharp.dll b/AaxDecrypter/DecryptLib/taglib-sharp.dll
deleted file mode 100644
index 87ec4bf1..00000000
Binary files a/AaxDecrypter/DecryptLib/taglib-sharp.dll and /dev/null differ
diff --git a/AaxDecrypter/NFO.cs b/AaxDecrypter/NFO.cs
index ab652f45..eca864ff 100644
--- a/AaxDecrypter/NFO.cs
+++ b/AaxDecrypter/NFO.cs
@@ -3,32 +3,31 @@ namespace AaxDecrypter
{
public static class NFO
{
- public static string CreateContents(string ripper, TagLib.File aaxcTagLib, ChapterInfo chapters)
+ public static string CreateContents(string ripper, AaxcTagLibFile aaxcTagLib, ChapterInfo chapters)
{
- var tag = aaxcTagLib.GetTag(TagLib.TagTypes.Apple);
-
- string narator = string.IsNullOrWhiteSpace(aaxcTagLib.Tag.FirstComposer) ? tag.Narrator : aaxcTagLib.Tag.FirstComposer;
+ var tag = aaxcTagLib.AppleTags;
var _hours = (int)aaxcTagLib.Properties.Duration.TotalHours;
var myDuration
- = (_hours > 0 ? _hours + " hours, " : "")
+ = (_hours > 0 ? _hours + " hours, " : string.Empty)
+ aaxcTagLib.Properties.Duration.Minutes + " minutes, "
+ aaxcTagLib.Properties.Duration.Seconds + " seconds";
var header
= "General Information\r\n"
+ "===================\r\n"
- + $" Title: {aaxcTagLib.Tag.Title.Replace(" (Unabridged)", "")}\r\n"
- + $" Author: {aaxcTagLib.Tag.FirstPerformer ?? "[unknown]"}\r\n"
- + $" Read By: {aaxcTagLib.GetTag(TagLib.TagTypes.Apple).Narrator??"[unknown]"}\r\n"
- + $" Copyright: {aaxcTagLib.Tag.Year}\r\n"
- + $" Audiobook Copyright: {aaxcTagLib.Tag.Year}\r\n";
- if (!string.IsNullOrEmpty(aaxcTagLib.Tag.FirstGenre))
- header += $" Genre: {aaxcTagLib.Tag.FirstGenre}\r\n";
+ + $" Title: {aaxcTagLib.TitleSansUnabridged ?? "[unknown]"}\r\n"
+ + $" Author: {aaxcTagLib.FirstAuthor ?? "[unknown]"}\r\n"
+ + $" Read By: {aaxcTagLib.Narrator ?? "[unknown]"}\r\n"
+ + $" Release Date: {aaxcTagLib.ReleaseDate ?? "[unknown]"}\r\n"
+ + $" Book Copyright: {aaxcTagLib.BookCopyright ?? "[unknown]"}\r\n"
+ + $" Recording Copyright: {aaxcTagLib.RecordingCopyright ?? "[unknown]"}\r\n";
+ if (!string.IsNullOrEmpty(tag.FirstGenre))
+ header += $" Genre: {aaxcTagLib.AppleTags.FirstGenre}\r\n";
var s
= header
- + $" Publisher: {tag.Publisher ?? ""}\r\n"
+ + $" Publisher: {aaxcTagLib.Publisher ?? string.Empty}\r\n"
+ $" Duration: {myDuration}\r\n"
+ $" Chapters: {chapters.Count}\r\n"
+ "\r\n"
@@ -38,20 +37,20 @@ namespace AaxDecrypter
+ " Source Format: Audible AAX\r\n"
+ $" Source Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n"
+ $" Source Channels: {aaxcTagLib.Properties.AudioChannels}\r\n"
- + $" Source Bitrate: {aaxcTagLib.Properties.AudioBitrate} kbits\r\n"
+ + $" Source Bitrate: {aaxcTagLib.Properties.AudioBitrate} Kbps\r\n"
+ "\r\n"
+ " Lossless Encode: Yes\r\n"
+ " Encoded Codec: AAC / M4B\r\n"
+ $" Encoded Sample Rate: {aaxcTagLib.Properties.AudioSampleRate} Hz\r\n"
+ $" Encoded Channels: {aaxcTagLib.Properties.AudioChannels}\r\n"
- + $" Encoded Bitrate: {aaxcTagLib.Properties.AudioBitrate} kbits\r\n"
+ + $" Encoded Bitrate: {aaxcTagLib.Properties.AudioBitrate} Kbps\r\n"
+ "\r\n"
+ $" Ripper: {ripper}\r\n"
+ "\r\n"
+ "\r\n"
+ "Book Description\r\n"
+ "================\r\n"
- + (!string.IsNullOrWhiteSpace(tag.LongDescription) ? tag.LongDescription : tag.Description);
+ + (!string.IsNullOrWhiteSpace(aaxcTagLib.LongDescription) ? aaxcTagLib.LongDescription : aaxcTagLib.Tag.Comment);
return s;
}
diff --git a/AaxDecrypter/TagLibMpeg4Ex.cs b/AaxDecrypter/TagLibMpeg4Ex.cs
new file mode 100644
index 00000000..f3ffd3c7
--- /dev/null
+++ b/AaxDecrypter/TagLibMpeg4Ex.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using TagLib;
+using TagLib.Mpeg4;
+
+namespace AaxDecrypter
+{
+ public class AaxcTagLibFile : TagLib.Mpeg4.File
+ {
+ public AppleTag AppleTags => GetTag(TagTypes.Apple) as AppleTag;
+
+ private static ReadOnlyByteVector naratorType = new ReadOnlyByteVector(0xa9, (byte)'n', (byte)'r', (byte)'t');
+ private static ReadOnlyByteVector descriptionType = new ReadOnlyByteVector(0xa9, (byte)'d', (byte)'e', (byte)'s');
+ private static ReadOnlyByteVector publisherType = new ReadOnlyByteVector(0xa9, (byte)'p', (byte)'u', (byte)'b');
+ public string Narrator { get; }
+ public string LongDescription { get; }
+ public string ReleaseDate { get; }
+ public string Publisher { get; }
+ //TagLib uses @ART, which is the Artist tag
+ public string[] Authors => AppleTags.Performers;
+ public string FirstAuthor { get; }
+ public string TitleSansUnabridged { get; }
+ public string BookCopyright { get; }
+ public string RecordingCopyright { get; }
+
+ private string[] _copyright;
+ public AaxcTagLibFile(IFileAbstraction abstraction)
+ : base(abstraction, ReadStyle.Average)
+ {
+ _copyright = AppleTags.Copyright?.Replace("©", string.Empty).Replace("(P)", string.Empty)?.Split(';');
+
+ BookCopyright = _copyright is not null && _copyright.Length > 0 ? _copyright[0] : default;
+
+ RecordingCopyright = _copyright is not null && _copyright.Length > 1 ? _copyright[1] : default;
+
+ TitleSansUnabridged = AppleTags.Title?.Replace(" (Unabridged)", "");
+
+ FirstAuthor = Authors?.Length > 0 ? unicodeToAscii(Authors[0]) : default;
+
+ string[] text = AppleTags.GetText(publisherType);
+ Publisher = text.Length == 0 ? null : text[0];
+
+ text = AppleTags.GetText("rldt");
+ ReleaseDate = text.Length == 0 ? null : text[0];
+
+ text = AppleTags.GetText(descriptionType);
+ LongDescription = text.Length == 0 ? null : unicodeToAscii(text[0]);
+
+ text = AppleTags.GetText(naratorType);
+ Narrator = text.Length == 0 ? null : unicodeToAscii(text[0]);
+
+ }
+
+ public AaxcTagLibFile(string path) : this(new LocalFileAbstraction(path))
+ {
+ }
+ public void CopyTagsFrom(AaxcTagLibFile sourceFile)
+ {
+ AppleTags.Clear();
+
+ //copy all metadata fields in the source file, even those that TagLib doesn't
+ //recognize, to the output file.
+ //NOTE: Chapters aren't stored in MPEG-4 metadata. They are encoded as a Timed
+ //Text Stream (MPEG-4 Part 17), so taglib doesn't read or write them.
+ foreach (var stag in sourceFile.AppleTags)
+ {
+ AppleTags.SetData(stag.BoxType, stag.Children.Cast().ToArray());
+ }
+ }
+
+
+ ///
+ /// Attempts to convert unicode characters to an approximately equal ASCII character.
+ ///
+ private string unicodeToAscii(string unicodeStr)
+ {
+ //Accents
+ unicodeStr = Regex.Replace(unicodeStr, "[éèëêð]", "e");
+ unicodeStr = Regex.Replace(unicodeStr, "[ÉÈËÊ]", "E");
+ unicodeStr = Regex.Replace(unicodeStr, "[àâä]", "a");
+ unicodeStr = Regex.Replace(unicodeStr, "[ÀÁÂÃÄÅ]", "A");
+ unicodeStr = Regex.Replace(unicodeStr, "[àáâãäå]", "a");
+ unicodeStr = Regex.Replace(unicodeStr, "[ÙÚÛÜ]", "U");
+ unicodeStr = Regex.Replace(unicodeStr, "[ùúûüµ]", "u");
+ unicodeStr = Regex.Replace(unicodeStr, "[òóôõöø]", "o");
+ unicodeStr = Regex.Replace(unicodeStr, "[ÒÓÔÕÖØ]", "O");
+ unicodeStr = Regex.Replace(unicodeStr, "[ìíîï]", "i");
+ unicodeStr = Regex.Replace(unicodeStr, "[ÌÍÎÏ]", "I");
+ unicodeStr = Regex.Replace(unicodeStr, "[š]", "s");
+ unicodeStr = Regex.Replace(unicodeStr, "[Š]", "S");
+ unicodeStr = Regex.Replace(unicodeStr, "[ñ]", "n");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ñ]", "N");
+ unicodeStr = Regex.Replace(unicodeStr, "[ç]", "c");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ç]", "C");
+ unicodeStr = Regex.Replace(unicodeStr, "[ÿ]", "y");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ÿ]", "Y");
+ unicodeStr = Regex.Replace(unicodeStr, "[ž]", "z");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ž]", "Z");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ð]", "D");
+
+ //Ligatures
+ unicodeStr = Regex.Replace(unicodeStr, "[œ]", "oe");
+ unicodeStr = Regex.Replace(unicodeStr, "[Œ]", "Oe");
+ unicodeStr = Regex.Replace(unicodeStr, "[ꜳ]", "aa");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ꜳ]", "AA");
+ unicodeStr = Regex.Replace(unicodeStr, "[æ]", "ae");
+ unicodeStr = Regex.Replace(unicodeStr, "[Æ]", "AE");
+ unicodeStr = Regex.Replace(unicodeStr, "[ꜵ]", "ao");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ꜵ]", "AO");
+ unicodeStr = Regex.Replace(unicodeStr, "[ꜷ]", "au");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ꜷ]", "AU");
+ unicodeStr = Regex.Replace(unicodeStr, "[«»ꜹꜻ]", "av");
+ unicodeStr = Regex.Replace(unicodeStr, "[«»ꜸꜺ]", "AV");
+ unicodeStr = Regex.Replace(unicodeStr, "[🙰]", "et");
+ unicodeStr = Regex.Replace(unicodeStr, "[ff]", "ff");
+ unicodeStr = Regex.Replace(unicodeStr, "[ffi]", "ffi");
+ unicodeStr = Regex.Replace(unicodeStr, "[ffl]", "ffl");
+ unicodeStr = Regex.Replace(unicodeStr, "[fi]", "fi");
+ unicodeStr = Regex.Replace(unicodeStr, "[fl]", "fl");
+ unicodeStr = Regex.Replace(unicodeStr, "[ƕ]", "hv");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ƕ]", "Hv");
+ unicodeStr = Regex.Replace(unicodeStr, "[℔]", "lb");
+ unicodeStr = Regex.Replace(unicodeStr, "[ꝏ]", "oo");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ꝏ]", "OO");
+ unicodeStr = Regex.Replace(unicodeStr, "[st]", "st");
+ unicodeStr = Regex.Replace(unicodeStr, "[ꜩ]", "tz");
+ unicodeStr = Regex.Replace(unicodeStr, "[Ꜩ]", "TZ");
+ unicodeStr = Regex.Replace(unicodeStr, "[ᵫ]", "ue");
+ unicodeStr = Regex.Replace(unicodeStr, "[ꭣ]", "uo");
+
+ //Punctuation
+ unicodeStr = Regex.Replace(unicodeStr, "[«»\u2018\u2019\u201A\u201B\u2032\u2035]", "\'");
+ unicodeStr = Regex.Replace(unicodeStr, "[«»\u201C\u201D\u201E\u201F\u2033\u2036]", "\"");
+ unicodeStr = Regex.Replace(unicodeStr, "[\u2026]", "...");
+ unicodeStr = Regex.Replace(unicodeStr, "[\u1680]", "-");
+
+ //Spaces
+ unicodeStr = Regex.Replace(unicodeStr, "[«»\u00A0\u2000\u2002\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200F\u205F\u3000]", " ");
+ unicodeStr = Regex.Replace(unicodeStr, "[«»\u2001\u2003]", " ");
+ unicodeStr = Regex.Replace(unicodeStr, "[«»\u180E\u200B\uFEFF]", "");
+
+ return unicodeStr;
+ }
+ }
+}