├── iAmDeaf ├── 302526_data_file_icon.ico ├── Interfaces │ ├── IAudiobook.cs │ └── ISelect.cs ├── Codecs │ ├── Select.cs │ ├── M4B.cs │ └── MP3.cs ├── Alert.cs ├── Config.cs ├── Util │ ├── Log.cs │ ├── Other.cs │ └── Files.cs ├── iAmDeaf.csproj ├── Properties │ ├── Resources.Designer.cs │ └── Resources.resx ├── Audible │ ├── Voucher.cs │ ├── Secret.cs │ └── Plus.cs └── Program.cs ├── .editorconfig ├── iAmDeaf.sln ├── README.md ├── .gitattributes └── .gitignore /iAmDeaf/302526_data_file_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VarSell/iAmDeaf/HEAD/iAmDeaf/302526_data_file_icon.ico -------------------------------------------------------------------------------- /iAmDeaf/Interfaces/IAudiobook.cs: -------------------------------------------------------------------------------- 1 | namespace iAmDeaf.Interfaces 2 | { 3 | internal interface IAudiobook 4 | { 5 | bool Open(string aax); 6 | bool SetPathAndFileName(string file); 7 | void SetDecryptionKey(string license_key, string license_iv); 8 | bool Encode(bool split = false); 9 | bool Close(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /iAmDeaf/Codecs/Select.cs: -------------------------------------------------------------------------------- 1 | using iAmDeaf.Interfaces; 2 | using iAmDeaf.Codecs; 3 | 4 | namespace iAmDeaf.Codecs 5 | { 6 | internal class Select 7 | { 8 | public static IAudiobook M4b() 9 | { 10 | return new M4B(); 11 | } 12 | public static IAudiobook Mp3() 13 | { 14 | return new MP3(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # CS8600: Converting null literal or possible null value to non-nullable type. 4 | dotnet_diagnostic.CS8600.severity = none 5 | 6 | # CS8618: Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. 7 | dotnet_diagnostic.CS8618.severity = none 8 | 9 | # CS8604: Possible null reference argument. 10 | dotnet_diagnostic.CS8604.severity = none 11 | 12 | # CS8602: Dereference of a possibly null reference. 13 | dotnet_diagnostic.CS8602.severity = none 14 | 15 | # CA1716: Identifiers should not match keywords 16 | dotnet_diagnostic.CA1716.severity = none 17 | -------------------------------------------------------------------------------- /iAmDeaf/Interfaces/ISelect.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace iAmDeaf.Interfaces 4 | { 5 | internal class ISelect 6 | { 7 | internal static IAudiobook Load(string aax) 8 | { 9 | switch (((string)(dynamic)JObject.Parse(File.ReadAllText("src\\configuration.json"))["codec"]).ToLower()) 10 | { 11 | case "m4b": 12 | { 13 | return new Codecs.M4B(); 14 | } 15 | 16 | case "mp3": 17 | { 18 | return new Codecs.MP3(); 19 | } 20 | 21 | default: 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /iAmDeaf/Alert.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | class Alert 3 | { 4 | public static void Notify(string alert) 5 | { 6 | Console.OutputEncoding = Encoding.UTF8; 7 | Console.Write(" ["); 8 | Console.ForegroundColor = ConsoleColor.DarkCyan; 9 | Console.Write(alert); 10 | Console.ResetColor(); 11 | Console.WriteLine("]"); 12 | Console.OutputEncoding = Encoding.Default; 13 | } 14 | public static void Error(string alert) 15 | { 16 | Console.OutputEncoding = Encoding.UTF8; 17 | Console.Write(" [Error: "); 18 | Console.ForegroundColor = ConsoleColor.Red; 19 | Console.Error.Write(alert); 20 | Console.ResetColor(); 21 | Console.WriteLine("]"); 22 | Console.OutputEncoding = Encoding.Default; 23 | } 24 | public static void Success(string alert) 25 | { 26 | Console.OutputEncoding = Encoding.UTF8; 27 | Console.Write(" ["); 28 | Console.ForegroundColor = ConsoleColor.Green; 29 | Console.Write(alert); 30 | Console.ResetColor(); 31 | Console.WriteLine("]"); 32 | Console.OutputEncoding = Encoding.Default; 33 | } 34 | } -------------------------------------------------------------------------------- /iAmDeaf/Config.cs: -------------------------------------------------------------------------------- 1 | namespace iAmDeaf.Config 2 | { 3 | public class Settings 4 | { 5 | public bool DEFAULT { get; set; } 6 | public Title[] Title { get; set; } 7 | public File[] Files { get; set; } 8 | public Output[] Output { get; set; } 9 | public AAXC[] AAXC { get; set; } 10 | } 11 | 12 | public class Title 13 | { 14 | public string T1 { get; set; } 15 | public string T2 { get; set; } 16 | public string T3 { get; set; } 17 | public string T4 { get; set; } 18 | public string T5 { get; set; } 19 | } 20 | 21 | public class File 22 | { 23 | public bool Cue { get; set; } 24 | public bool NFO { get; set; } 25 | public bool Cover { get; set; } 26 | } 27 | 28 | public class Output 29 | { 30 | public string Codec { get; set; } 31 | public bool Split { get; set; } 32 | } 33 | 34 | public class AAXC 35 | { 36 | public bool NFO { get; set; } 37 | public bool Cue { get; set; } 38 | public bool Cover { get; set; } 39 | public bool Split { get; set; } 40 | public bool Backup { get; set; } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /iAmDeaf.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.32014.148 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "iAmDeaf", "iAmDeaf\iAmDeaf.csproj", "{B52A1C02-B080-4319-A36B-A106D615D613}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{44F4013C-F1C8-4808-80B2-6DF11E2F21B1}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {B52A1C02-B080-4319-A36B-A106D615D613}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {B52A1C02-B080-4319-A36B-A106D615D613}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {B52A1C02-B080-4319-A36B-A106D615D613}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {B52A1C02-B080-4319-A36B-A106D615D613}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {95023987-39FE-48F7-9C00-B07AB6ACE744} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /iAmDeaf/Util/Log.cs: -------------------------------------------------------------------------------- 1 | namespace iAmDeaf.Other 2 | { 3 | internal static class Record 4 | { 5 | public static void Log(Exception ex, System.Diagnostics.StackTrace st) 6 | { 7 | string logFile = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"\rec.log"); 8 | 9 | try 10 | { 11 | string date = DateTime.Now.ToString("MMMM-dd-yyyy HH:mm:ss"); 12 | var x = st.GetFrame(0); 13 | 14 | string classCrash = x.GetMethod().DeclaringType.ToString(); 15 | string methodCrash = x.GetMethod().ToString(); 16 | string lnCrash = x.GetFileLineNumber().ToString(); 17 | string clnCrash = x.GetFileColumnNumber().ToString(); 18 | 19 | string _t = $"\n{new string('*', date.Length+4)}\n* {date} *\n{new string('*', date.Length+4)}\n\nClass : {classCrash}\nMethod : {methodCrash}\nLine : {lnCrash}\nColumn : {clnCrash}\n\nMessage\n{ex.Message}\n\nStackTrace\n{ex.StackTrace}".Trim(); 20 | string msg = Environment.NewLine+_t+Environment.NewLine; 21 | if (!File.Exists(logFile)) 22 | { 23 | File.WriteAllText(logFile, msg); 24 | } 25 | else 26 | { 27 | File.AppendAllText(logFile, msg); 28 | } 29 | Alert.Error(string.Concat(x.GetMethod().Name, ": ", ex.Message)); 30 | } 31 | catch (Exception e) 32 | { 33 | Alert.Error("Log Crash"); 34 | Console.Error.WriteLine(e.ToString()); 35 | Console.ReadKey(); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CLI ripper for AAX - AAXC (Plus Catalogue) files. 2 | #### Uses [Audible-CLI](https://github.com/mkb79/audible-cli) and [AAXClean](https://github.com/Mbucari/AAXClean) as it's backbone. Many thanks to the creators! 3 | 4 | ## Description 5 | ### [Requirements: Win, x64] 6 | Creates a properly tagged .M4B / 7 | MP3, with (optional) a copy of the JGP cover, an NFO and a CUE file. 8 | Created with the purpose of being a "one-click" ripper. 9 | 10 | ## Usage 11 | Should be set as an environment variable for ease of use. 12 | 13 | ```bash 14 | iAmDeaf 15 | ``` 16 | Otherwise can be set as the default program to handle .aax files. 17 | It can then be started by simply double clicking the file, or just entering the path to the encrypted book without the "iAmDeaf" prefix. 18 | 19 | ## Settings 20 | The program's settings can be found in the config.json file. 21 | Options available are: 22 | - Title order of (Author / Title / Year / Narrator / Bitrate / null (for empty)) 23 | - Codec (MP3 / M4B) 24 | - Split by chapters (true / false) 25 | 26 | # AAXC Usage 27 | You must first set up your Audible profile. Run "ProfileSetup.cmd" and follow the given instructions. 28 | 29 | Personal suggestions for ease of setup: 30 | - Encrypt auth file: n 31 | - login with external browser: y 32 | - login with pre.amazon audible account: n 33 | 34 | ## Download + Decryption of Plus Catalogue Titles 35 | ```bash 36 | iAmDeaf -c //Where ASIN is the book ID in your library. Example: iAmDeaf -c B002V5B8P8 37 | ``` 38 | 39 | ## AAXC Settings 40 | - By default generates an nfo, cue, cover. Can be changed in config.json 41 | - Split (true / false) 42 | - Backup (true / false) //Backups the .voucher and .aaxc to "Audiobooks/bak" in case of future offline decryption. Enabled by default. 43 | 44 | The created Audiobooks will be added to the "Audiobooks" on the desktop. 45 | 46 | ## Decryption of offline backups 47 | [There must be only one voucher and aaxc file in the same folder, to prevent mixups] 48 | ```bash 49 | iAmDeaf 50 | ``` 51 | #### The offline decryption only saves an M4B copy of the file in the same directry as the source files. No nfo, cue or image. 52 | -------------------------------------------------------------------------------- /iAmDeaf/iAmDeaf.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0-windows 6 | enable 7 | enable 8 | 302526_data_file_icon.ico 9 | False 10 | OnOutputUpdated 11 | none 12 | False 13 | 302526_data_file_icon.ico 14 | 2.0.3 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ..\..\AAXHash\AAXHash\bin\Release\net6.0\win-x64\AAXHash.dll 36 | 37 | 38 | ..\..\csatomreader\CsAtomReader\bin\Release\netcoreapp2.1\win-x64\CsAtomReader.dll 39 | 40 | 41 | 42 | 43 | 44 | True 45 | True 46 | Resources.resx 47 | 48 | 49 | 50 | 51 | 52 | ResXFileCodeGenerator 53 | Resources.Designer.cs 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /iAmDeaf/Util/Other.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Diagnostics; 3 | using iAmDeaf.Other; 4 | 5 | internal class Other 6 | { 7 | public static string SoftWare(string software, string arguments, bool std) 8 | { 9 | Process SoftWare = new Process(); 10 | SoftWare.StartInfo.FileName = software; 11 | SoftWare.StartInfo.Arguments = arguments; 12 | 13 | if (std == true) 14 | { 15 | SoftWare.StartInfo.RedirectStandardError = true; 16 | SoftWare.StartInfo.StandardErrorEncoding = Encoding.UTF8; 17 | SoftWare.Start(); 18 | 19 | using (StreamReader reader = SoftWare.StandardError) 20 | { 21 | string result = reader.ReadToEnd(); 22 | SoftWare.WaitForExit(); 23 | return result.Trim(); 24 | } 25 | } 26 | else 27 | { 28 | SoftWare.StartInfo.RedirectStandardOutput = true; 29 | SoftWare.StartInfo.StandardOutputEncoding = Encoding.UTF8; 30 | SoftWare.Start(); 31 | 32 | using (StreamReader reader = SoftWare.StandardOutput) 33 | { 34 | string result = reader.ReadToEnd(); 35 | SoftWare.WaitForExit(); 36 | return result.Trim(); 37 | } 38 | } 39 | } 40 | public static string GetSafeFilename(string filename) 41 | { 42 | return string.Join(String.Empty, filename.Split(Path.GetInvalidFileNameChars())).Trim(); 43 | } 44 | public static string GetPreferredFilename(string file) 45 | { 46 | string filename = SoftWare(@"src\tools\mediainfo.exe", $"\"{file}\" --Inform=General;%Album%", false); 47 | try 48 | { 49 | var _filename = Get.AaxInformation(file); 50 | var title = _filename[0]; 51 | _filename[0] = _filename[0].Trim().Replace(":", " -"); 52 | string placeholder = _filename[1].Replace("(Unabridged)", string.Empty); 53 | if (placeholder.Length < 2) 54 | placeholder = string.Concat("0", placeholder); 55 | var _file = $"{_filename[2]} [{placeholder}] {_filename[3]}"; 56 | filename = _file.Trim(); 57 | 58 | } 59 | catch 60 | { 61 | filename = filename.Trim().Replace(":", " -"); 62 | } 63 | return GetSafeFilename(filename); 64 | } 65 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /iAmDeaf/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace iAmDeaf.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("iAmDeaf.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /iAmDeaf/Audible/Voucher.cs: -------------------------------------------------------------------------------- 1 | namespace iAmDeaf.Audible 2 | { 3 | internal class Voucher 4 | { 5 | public class Rootobject 6 | { 7 | public Content_License content_license { get; set; } 8 | public string[] response_groups { get; set; } 9 | } 10 | 11 | public class Content_License 12 | { 13 | public string acr { get; set; } 14 | public string asin { get; set; } 15 | public Content_Metadata content_metadata { get; set; } 16 | public string drm_type { get; set; } 17 | public string license_id { get; set; } 18 | public License_Response license_response { get; set; } 19 | public string message { get; set; } 20 | public string request_id { get; set; } 21 | public bool requires_ad_supported_playback { get; set; } 22 | public string status_code { get; set; } 23 | public string voucher_id { get; set; } 24 | } 25 | 26 | public class Content_Metadata 27 | { 28 | public Chapter_Info chapter_info { get; set; } 29 | public Content_Reference content_reference { get; set; } 30 | public Content_Url content_url { get; set; } 31 | public Last_Position_Heard last_position_heard { get; set; } 32 | } 33 | 34 | public class Chapter_Info 35 | { 36 | public int brandIntroDurationMs { get; set; } 37 | public int brandOutroDurationMs { get; set; } 38 | public Chapter[] chapters { get; set; } 39 | public bool is_accurate { get; set; } 40 | public int runtime_length_ms { get; set; } 41 | public int runtime_length_sec { get; set; } 42 | } 43 | 44 | public class Chapter 45 | { 46 | public int length_ms { get; set; } 47 | public int start_offset_ms { get; set; } 48 | public int start_offset_sec { get; set; } 49 | public string title { get; set; } 50 | } 51 | 52 | public class Content_Reference 53 | { 54 | public string acr { get; set; } 55 | public string asin { get; set; } 56 | public string content_format { get; set; } 57 | public int content_size_in_bytes { get; set; } 58 | public string file_version { get; set; } 59 | public string marketplace { get; set; } 60 | public string sku { get; set; } 61 | public string tempo { get; set; } 62 | public string version { get; set; } 63 | } 64 | 65 | public class Content_Url 66 | { 67 | public string offline_url { get; set; } 68 | } 69 | 70 | public class Last_Position_Heard 71 | { 72 | public string status { get; set; } 73 | } 74 | 75 | public class License_Response 76 | { 77 | public string key { get; set; } 78 | public string iv { get; set; } 79 | public Rule[] rules { get; set; } 80 | } 81 | 82 | public class Rule 83 | { 84 | public Parameter[] parameters { get; set; } 85 | public string name { get; set; } 86 | } 87 | 88 | public class Parameter 89 | { 90 | public DateTime expireDate { get; set; } 91 | public string type { get; set; } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /iAmDeaf/Codecs/M4B.cs: -------------------------------------------------------------------------------- 1 | using AAXClean; 2 | using iAmDeaf.Interfaces; 3 | using CsAtomReader; 4 | using System.Diagnostics; 5 | using iAmDeaf.Other; 6 | 7 | namespace iAmDeaf.Codecs 8 | { 9 | 10 | internal class M4B : IAudiobook 11 | { 12 | internal string sourceFile { get; set; } 13 | internal string outFile { get; set; } 14 | internal AAXClean.AaxFile encryptedFile { get; set; } 15 | internal string secret { get; set; } 16 | public bool Open(string file) 17 | { 18 | try 19 | { 20 | this.sourceFile = file; 21 | this.outFile = Path.GetDirectoryName(sourceFile); 22 | using (FileStream stream = new FileStream(sourceFile, FileMode.Open)) 23 | { 24 | string aaxTitle = new AtomReader(stream) 25 | .GetMetaAtomValue(AtomReader.TitleTypeName) 26 | .Replace(":", " -").Replace("?", ""); 27 | 28 | this.outFile = Path.Combine(outFile, string.Concat(aaxTitle, ".m4b")); 29 | } 30 | 31 | this.encryptedFile = new AAXClean.AaxFile(File.OpenRead(sourceFile)); 32 | if (Path.GetExtension(this.sourceFile) == ".aax") 33 | { 34 | this.secret = Audible.Secret.GetBytesFromFile(sourceFile); 35 | this.encryptedFile.SetDecryptionKey(this.secret); 36 | } 37 | return true; 38 | } 39 | catch (Exception ex) 40 | { 41 | Record.Log(ex, new StackTrace(true)); 42 | return false; 43 | } 44 | } 45 | public bool SetPathAndFileName(string file) 46 | { 47 | try 48 | { 49 | if (string.IsNullOrEmpty(file)) 50 | { 51 | Alert.Error("Path cannot be empty."); 52 | return false; 53 | } 54 | 55 | this.outFile = string.Concat(file, ".", this.GetType().Name.ToLower()); 56 | return true; 57 | } 58 | catch (Exception ex) 59 | { 60 | Record.Log(ex, new StackTrace(true)); 61 | return false; 62 | } 63 | } 64 | public void SetDecryptionKey(string license_key, string license_iv) 65 | { 66 | this.encryptedFile.SetDecryptionKey(license_key, license_iv); 67 | } 68 | public bool Encode(bool split = false) 69 | { 70 | try 71 | { 72 | Stopwatch sw = new Stopwatch(); 73 | if (split) 74 | { 75 | string splitPath = Path.Combine(Path.GetDirectoryName(this.outFile), Path.GetFileNameWithoutExtension(this.outFile)); 76 | if (!Directory.Exists(splitPath)) 77 | { 78 | Directory.CreateDirectory(splitPath); 79 | } 80 | 81 | sw.Start(); 82 | var conversionResult = this.encryptedFile.ConvertToMultiMp4a(this.encryptedFile.GetChapterInfo(), NewSplit); 83 | 84 | void NewSplit(NewSplitCallback newSplitCallback) 85 | { 86 | string dir = splitPath; 87 | string fileName = newSplitCallback.Chapter.Title.Replace(":", "") + ".m4b"; 88 | newSplitCallback.OutputFile = File.OpenWrite(Path.Combine(dir, fileName)); 89 | } 90 | } 91 | else 92 | { 93 | sw.Start(); 94 | this.encryptedFile.ConvertToMp4a(File.Open((this.outFile), FileMode.OpenOrCreate, FileAccess.ReadWrite)); 95 | } 96 | 97 | sw.Stop(); 98 | Alert.Notify(String.Format("Decrypted in {0}ms", sw.ElapsedMilliseconds.ToString())); 99 | return true; 100 | } 101 | catch (Exception ex) 102 | { 103 | Record.Log(ex, new StackTrace(true)); 104 | return false; 105 | } 106 | } 107 | public bool Close() 108 | { 109 | try 110 | { 111 | this.encryptedFile.Close(); 112 | return true; 113 | } 114 | catch (Exception ex) 115 | { 116 | Record.Log(ex, new StackTrace(true)); 117 | return false; 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /iAmDeaf/Audible/Secret.cs: -------------------------------------------------------------------------------- 1 | using Aax.Activation.ApiClient; 2 | using System.Text; 3 | using System.Diagnostics; 4 | using iAmDeaf.Other; 5 | 6 | using System.Data.SQLite; 7 | 8 | namespace iAmDeaf.Audible 9 | { 10 | internal class Secret 11 | { 12 | /*internal static void InsertSqlTest(string data) 13 | { 14 | SQLiteConnection con; 15 | SQLiteCommand cmd; 16 | SQLiteDataReader dr; 17 | if (!File.Exists("MyDatabase.sqlite")) 18 | { 19 | SQLiteConnection.CreateFile("MyDatabase.sqlite"); 20 | 21 | string sql = @"CREATE TABLE keys( 22 | ID INTEGER PRIMARY KEY AUTOINCREMENT , 23 | HASH TEXT NOT NULL, 24 | BYTES TEXT NOT NULL 25 | );"; 26 | con = new SQLiteConnection("Data Source=MyDatabase.sqlite;Version=3;"); 27 | con.Open(); 28 | cmd = new SQLiteCommand(sql, con); 29 | cmd.ExecuteNonQuery(); 30 | con.Close(); 31 | 32 | } 33 | else 34 | { 35 | con = new SQLiteConnection("Data Source=MyDatabase.sqlite;Version=3;"); 36 | } 37 | 38 | int counter = 0; 39 | cmd = new SQLiteCommand("Select *From keys", con); 40 | con.Open(); 41 | dr = cmd.ExecuteReader(); 42 | while (dr.Read()) 43 | { 44 | counter++; 45 | Console.WriteLine(dr[0] + " : " + dr[1]+" "+dr[2]); 46 | 47 | } 48 | con.Close(); 49 | }*/ 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | internal static async Task aaxcAsync(string checksum) 59 | { 60 | return AaxActivationClient.Instance.ResolveActivationBytes(checksum).Result.ToString(); 61 | } 62 | internal static string Hash(string file) 63 | { 64 | try 65 | { 66 | using (var fs = System.IO.File.OpenRead(file)) 67 | using (var br = new BinaryReader(fs)) 68 | { 69 | fs.Position = 0x251 + 56 + 4; 70 | var checksum = br.ReadBytes(20); 71 | return bytes(checksum); 72 | } 73 | } 74 | catch (Exception ex) 75 | { 76 | Alert.Error("Error calculating Hash."); 77 | Record.Log(ex, new StackTrace(true)); 78 | } 79 | return String.Empty; 80 | } 81 | internal static string bytes(byte[] bt) 82 | { 83 | try 84 | { 85 | StringBuilder sb = new StringBuilder(); 86 | foreach (byte b in bt) 87 | sb.Append(b.ToString("X2")); 88 | 89 | string hexString = sb.ToString(); 90 | return hexString; 91 | } 92 | catch (Exception ex) 93 | { 94 | Alert.Error("Converting Hash to Hex."); 95 | Record.Log(ex, new StackTrace(true)); 96 | return String.Empty; 97 | } 98 | } 99 | public static string GetBytesFromFile(string aax) 100 | { 101 | var checksum = Hash(aax); 102 | string[] keys = File.ReadAllLines(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"src\data\KeyHistory\log")); 103 | 104 | for (int i = 0; i < keys.Length; i+=2) 105 | { 106 | if (keys[i] == checksum) 107 | { 108 | return keys[i+1]; 109 | } 110 | } 111 | 112 | string bytes = string.Empty; 113 | try 114 | { 115 | var h = new AAXHash.Data(); 116 | h.Reverse(aax); 117 | bytes = h.bytes; 118 | } 119 | catch 120 | { 121 | try 122 | { 123 | bytes = AaxActivationClient.Instance.ResolveActivationBytes(checksum).Result.ToString(); 124 | } 125 | catch (Exception ex) 126 | { 127 | Alert.Error("Unable to retrieve Secret."); 128 | Record.Log(ex, new StackTrace(true)); 129 | return string.Empty; 130 | } 131 | } 132 | 133 | File.AppendAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"src\data\KeyHistory\log"), $"{checksum}\n{bytes}" + Environment.NewLine); 134 | return bytes; 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /iAmDeaf/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | -------------------------------------------------------------------------------- /iAmDeaf/Audible/Plus.cs: -------------------------------------------------------------------------------- 1 | using AAXClean; 2 | using Newtonsoft.Json; 3 | using static Other; 4 | using System.Diagnostics; 5 | using iAmDeaf.Audible; 6 | using iAmDeaf.Other; 7 | 8 | namespace iAmDeaf.Plus 9 | { 10 | internal static class Catalogue 11 | { 12 | public static void Download(string ASIN) 13 | { 14 | Config.Settings settings = JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"src\config.json"))); 15 | 16 | bool nfo = settings.AAXC[0].NFO; 17 | string codec = settings.Output[0].Codec; 18 | bool cue = settings.AAXC[0].Cue; 19 | bool cover = settings.AAXC[0].Cover; 20 | bool split = settings.AAXC[0].Split; 21 | bool backup = settings.AAXC[0].Backup; 22 | string param = string.Empty; 23 | if (cover) 24 | { 25 | param = "--cover --cover-size 1215"; 26 | } 27 | 28 | Alert.Notify("Downloading"); 29 | SoftWare(@"src\tools\audible.exe", $@"download -a {ASIN} {param} -o {AppDomain.CurrentDomain.BaseDirectory}src\data\dump --aaxc", false); 30 | 31 | Alert.Notify("Parsing Voucher"); 32 | var keys = ParseVoucher(); 33 | 34 | Alert.Notify("Creating Audiobook"); 35 | if (!AAXCDecrypt(keys[0], keys[1], nfo, cue, cover, split, codec)) 36 | { 37 | Alert.Error("AAXC Decryption Failed"); 38 | } 39 | else 40 | { 41 | Cleanup(backup); 42 | Alert.Success("Audiobook Created"); 43 | } 44 | } 45 | 46 | public static bool AAXCDecrypt(string key, string iv, bool nfo, bool cue, bool cover, bool split, string codec) 47 | { 48 | string root = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); 49 | 50 | if (!Directory.Exists(Path.Combine(root, "Audiobooks"))) 51 | { 52 | Directory.CreateDirectory(Path.Combine(root, "Audiobooks")); 53 | } 54 | 55 | var file = Directory.GetFiles($@"{AppDomain.CurrentDomain.BaseDirectory}src\data\dump\", "*.aaxc")[0]; 56 | string filename = GetPreferredFilename(file); 57 | 58 | Alert.Success(filename); 59 | 60 | try 61 | { 62 | string PID = Process.GetCurrentProcess().Id.ToString(); 63 | var fileDir = Path.Combine(root, "Audiobooks", filename); 64 | if (!Directory.Exists(fileDir)) 65 | { 66 | Directory.CreateDirectory(fileDir); 67 | } 68 | 69 | Interfaces.IAudiobook audio; 70 | switch (codec.ToLower()) 71 | { 72 | case "m4b": 73 | audio = Codecs.Select.M4b(); 74 | break; 75 | case "mp3": 76 | audio = Codecs.Select.Mp3(); 77 | break; 78 | default: 79 | audio = Codecs.Select.M4b(); 80 | break; 81 | } 82 | 83 | audio.Open(file); 84 | audio.SetDecryptionKey(key, iv); 85 | audio.SetPathAndFileName(Path.Combine(fileDir, filename)); 86 | audio.Encode(split); 87 | audio.Close(); 88 | 89 | if (cue) 90 | { 91 | Create.Cuesheet(file, Path.Combine(fileDir, filename), codec); 92 | } 93 | 94 | if (cover) 95 | { 96 | Alert.Notify("Moving Cover"); 97 | File.Move(Directory.GetFiles($@"{AppDomain.CurrentDomain.BaseDirectory}src\data\dump\", "*.jpg")[0], $@"{fileDir}\{filename}.jpg"); 98 | } 99 | 100 | if (nfo) 101 | { 102 | Alert.Notify("Generating nfo"); 103 | Create.Nfo(file, $"{Path.Combine(fileDir, filename)}.{codec}"); 104 | /*string nfnm = filename; 105 | if (split) { filename = Path.GetFileNameWithoutExtension(Directory.GetFiles(fileDir, "*.m4b")[0]); }*/ 106 | File.WriteAllText($"{Path.Combine(fileDir, filename)}.nfo", Create.Nfo(file, $"{Path.Combine(fileDir, filename)}.{codec}", split), System.Text.Encoding.UTF8); 107 | } 108 | 109 | return true; 110 | } 111 | catch (Exception ex) 112 | { 113 | Record.Log(ex, new StackTrace(true)); 114 | return false; 115 | } 116 | } 117 | 118 | public static void Cleanup(bool bak) 119 | { 120 | 121 | string aaxc = Directory.GetFiles($@"{AppDomain.CurrentDomain.BaseDirectory}src\data\dump\", "*.aaxc")[0]; 122 | string voucher = Directory.GetFiles($@"{AppDomain.CurrentDomain.BaseDirectory}src\data\dump\", "*.voucher")[0]; 123 | if (!bak) 124 | { 125 | Alert.Success("Wiping temp files"); 126 | File.Delete(aaxc); 127 | File.Delete(voucher); 128 | } 129 | else 130 | { 131 | string dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), @"Audiobooks\bak"); 132 | string bakdir = Path.Combine(dir, Path.GetFileNameWithoutExtension(aaxc)); 133 | if (!Directory.Exists(dir)) 134 | { 135 | Directory.CreateDirectory(dir); 136 | } 137 | if (!Directory.Exists(bakdir)) 138 | { 139 | Directory.CreateDirectory(bakdir); 140 | } 141 | Alert.Success("Backing up files"); 142 | File.Move(aaxc, Path.Combine(bakdir, Path.GetFileName(aaxc))); 143 | File.Move(voucher, Path.Combine(bakdir, Path.GetFileName(voucher))); 144 | } 145 | } 146 | 147 | 148 | public static string[] ParseVoucher() 149 | { 150 | try 151 | { 152 | var Voucher = Directory.GetFiles($@"{AppDomain.CurrentDomain.BaseDirectory}src\data\dump\", "*.voucher")[0]; 153 | 154 | Voucher.Rootobject Settings = JsonConvert.DeserializeObject(File.ReadAllText(Voucher)); 155 | 156 | return new string[] { Settings.content_license.license_response.key, Settings.content_license.license_response.iv }; 157 | } 158 | catch (Exception ex) 159 | { 160 | Record.Log(ex, new StackTrace(true)); 161 | return null; 162 | } 163 | } 164 | } 165 | 166 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd -------------------------------------------------------------------------------- /iAmDeaf/Util/Files.cs: -------------------------------------------------------------------------------- 1 | using AAXClean; 2 | using AAXClean.Codecs; 3 | using System; 4 | using System.Text; 5 | using System.Diagnostics; 6 | using static Other; 7 | using System.IO; 8 | using Mp4Chapters; 9 | using Main; 10 | 11 | namespace iAmDeaf.Util 12 | { 13 | internal static class Create 14 | { 15 | internal static string Root = AppDomain.CurrentDomain.BaseDirectory; 16 | public static string Nfo(string aax, string file, bool split = false) 17 | { 18 | string[] nfoPart = new string[15]; 19 | try 20 | { 21 | aax = string.Concat("\"", aax, "\""); 22 | file = string.Concat("\"", file, "\""); 23 | 24 | string mi = $"{Root}\\src\\tools\\mediainfo.exe"; 25 | nfoPart[0] = SoftWare(mi, $"{aax} --Inform=General;%Album%", false); //Title 26 | nfoPart[1] = SoftWare(mi, $"{aax} --Inform=General;%Performer%", false); //Author 27 | nfoPart[2] = SoftWare(mi, $"{aax} --Inform=General;%nrt%", false); //Narrator 28 | nfoPart[3] = SoftWare(mi, $"{aax} --Inform=General;%Copyright%", false); //Copyright 29 | nfoPart[4] = SoftWare(mi, $"{aax} --Inform=General;%Genre%", false); //Genre 30 | nfoPart[5] = SoftWare(mi, $"{aax} --Inform=General;%pub%", false); //Publisher 31 | nfoPart[6] = SoftWare(mi, $"{aax} --Inform=General;%rldt%", false); //Release Date 32 | nfoPart[7] = SoftWare(mi, $"{aax} --Inform=General;%Duration/String2%", false); //Duration (h, m) 33 | nfoPart[8] = SoftWare(mi, $"{aax} --Inform=\"Menu;%FrameCount%\"", false); //Chapters 34 | nfoPart[9] = SoftWare(mi, $"{aax} --Inform=General;%Format%", false); //general format 35 | nfoPart[10] = SoftWare(mi, $"{aax} --Inform=Audio;%Format%", false); //audio format 36 | nfoPart[11] = SoftWare(mi, $"{aax} --Inform=Audio;%BitRate%", false); //source bitrate 37 | try 38 | { 39 | nfoPart[11] = (int.Parse(nfoPart[11]) / 1024).ToString(); 40 | } 41 | catch 42 | { 43 | nfoPart[11] = "NULL"; 44 | Alert.Error("Failed Getting Source BitRate"); 45 | } 46 | if (Path.GetExtension(file.Replace("\"", "")) == ".m4b") 47 | { 48 | nfoPart[12] = SoftWare(mi, $"{file} --Inform=General;%CodecID%", false); 49 | } 50 | else 51 | { 52 | string mp3enc = string.Empty; 53 | switch (split) 54 | { 55 | case true: 56 | mp3enc = "LAME 3.100"; 57 | break; 58 | case false: 59 | mp3enc = "Lavf59.16.100"; 60 | break; 61 | } 62 | nfoPart[12] = $"{mp3enc} MP3"; 63 | } 64 | nfoPart[13] = TagLib.File.Create(file.Replace("\"", "")).Properties.AudioBitrate.ToString(); 65 | nfoPart[14] = SoftWare(mi, $"{aax} --Inform=General;%Track_More%", false); 66 | } 67 | catch (Exception ex) 68 | { 69 | Alert.Error(ex.Message); 70 | } 71 | 72 | string nfo = @$"General Information 73 | =================== 74 | Title: {nfoPart[0]} 75 | Author: {nfoPart[1]} 76 | Narrator: {nfoPart[2]} 77 | AudioBook Copyright: {nfoPart[3].Replace("©", string.Empty).Replace(";", " ")} 78 | Genre: {nfoPart[4]} 79 | Publisher: {nfoPart[5]} 80 | Published: {nfoPart[6]} 81 | Duration: {nfoPart[7]} 82 | Chapters: {nfoPart[8]} 83 | 84 | Media Information 85 | ================= 86 | Source Format: Audible {nfoPart[9].ToUpper()} ({nfoPart[10]}) 87 | Source Bitrate: {nfoPart[11]} kbps 88 | 89 | Lossless Encode: {Path.GetExtension(file.Replace("\"", "")) == ".m4b"} 90 | Encoded Codec: {nfoPart[12]} 91 | Encoded Bitrate: {nfoPart[13]} kbps 92 | 93 | Ripper: {Program.MARK} {Program.VERSION} 94 | 95 | Publisher's Summary 96 | =================== 97 | {nfoPart[14]} 98 | "; 99 | return nfo; 100 | } 101 | public static void CueSheet(string aax, string file, string codec) 102 | { 103 | string format = "MP4"; 104 | if (codec != "m4b") 105 | { 106 | format = "MP3"; 107 | } 108 | 109 | try 110 | { 111 | string performer = SoftWare($@"{root}\src\tools\mediainfo.exe", $"{aax} --Inform=General;%Performer%", false); 112 | string date = SoftWare($@"{root}\src\tools\mediainfo.exe", $"{aax} --Inform=General;%rldt%", false); 113 | string title = SoftWare($@"{root}\src\tools\mediainfo.exe", $"{aax} --Inform=General;%Album%", false); 114 | 115 | date = DateTime.Parse(date).ToString("yyyy"); 116 | 117 | List cueSheet = new List(); 118 | cueSheet.Add("REM GENRE Audiobook"); 119 | cueSheet.Add($"REM DATE {date}"); 120 | cueSheet.Add($"PERFORMER \"{performer}\""); 121 | cueSheet.Add($"TITLE \"{title}\""); 122 | cueSheet.Add($"FILE \"{Path.GetFileNameWithoutExtension(file)}.{codec}\" {format}"); 123 | 124 | using (var str = File.OpenRead(aax)) 125 | { 126 | int i = 1; 127 | var extractor = new ChapterExtractor(new StreamWrapper(str)); 128 | extractor.Run(); 129 | 130 | foreach (var c in extractor.Chapters) 131 | { 132 | string pos = string.Empty; 133 | if (i<10) 134 | { 135 | pos = new string(String.Concat('0', i)); 136 | } 137 | else 138 | { 139 | pos = (i).ToString(); 140 | } 141 | string time = (c.Time.ToString(@"dd\:hh\:mm\:ss\:fff")); 142 | 143 | string d = (time.Split(':')[0]); 144 | string h = (time.Split(':')[1].Split(':')[0]); 145 | string m = (time.Split(':')[2].Split(':')[0]); 146 | string s = (time.Split(':')[3].Split(':')[0]); 147 | string ms = (time.Split(':')[4].Split(':')[0]); 148 | 149 | string cueFrames = ((int)(Int32.Parse(ms) * 0.075)).ToString(); 150 | if (cueFrames.Length == 1) 151 | cueFrames = String.Concat('0', cueFrames); 152 | 153 | string cueMin = ((((Int32.Parse(d) * 24) + Int32.Parse(h)) * 60) + Int32.Parse(m)).ToString(); 154 | 155 | if (cueMin.ToString().Length == 1) 156 | cueMin = String.Concat('0', cueMin); 157 | 158 | cueSheet.Add($" TRACK {pos} AUDIO"); 159 | cueSheet.Add($" TITLE \"{c.Name}\""); 160 | cueSheet.Add($" INDEX 01 {cueMin}:{s}:{cueFrames}"); 161 | 162 | i++; 163 | } 164 | } 165 | 166 | using (TextWriter tw = new StreamWriter(String.Concat(file, ".cue"))) 167 | { 168 | foreach (String ln in cueSheet) 169 | tw.WriteLine(ln); 170 | } 171 | } 172 | catch (Exception ex) 173 | { 174 | Alert.Error(ex.Message); 175 | } 176 | } 177 | } 178 | public class Get 179 | { 180 | public static string[] AaxInformation(string aax) 181 | { 182 | aax = string.Concat("\"", aax, "\""); 183 | string mi = Path.Combine(Root, "src\\tools\\mediainfo.exe"); 184 | string[] info = new string[5]; 185 | info[0] = SoftWare(mi, $"{aax} --Inform=General;%Album%", false); 186 | info[1] = info[0].Split(",").Last().Trim(); 187 | info[2] = info[0].Replace(info[1], null).Replace(",", null).Split(":").Last().Trim(); 188 | info[3] = info[0].Replace(info[1], null).Replace(info[2], null).Replace(":", null).Replace(",", null).Trim(); 189 | info[4] = SoftWare(mi, $"{aax} --Inform=General;%Track_More%", false); 190 | info[1] = info[1].Replace(" ", null).Replace("Book", null); 191 | 192 | if (info[1].Contains("Volume")) 193 | { 194 | info[1] = info[1].Replace("Volume", "Volume "); 195 | } 196 | 197 | if (info[1].Length < 2) 198 | { 199 | info[1] = string.Concat("0", info[1]); 200 | } 201 | 202 | return info; 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /iAmDeaf/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.RegularExpressions; 3 | using System.Globalization; 4 | using System.Threading; 5 | using Newtonsoft.Json; 6 | using System.Diagnostics; 7 | using static Other; 8 | using iAmDeaf.Audible; 9 | using CsAtomReader; 10 | using iAmDeaf.Interfaces; 11 | using iAmDeaf.Codecs; 12 | using iAmDeaf.Plus; 13 | using iAmDeaf.Other; 14 | namespace Main 15 | { 16 | using Mp4Chapters; 17 | using Newtonsoft.Json.Linq; 18 | 19 | internal class Program 20 | { 21 | internal const string MARK = "iAmDeaf"; 22 | internal const string VERSION = "2.0.3"; 23 | public static string root = AppDomain.CurrentDomain.BaseDirectory; 24 | 25 | internal static string Title 26 | { get; set; } 27 | internal static string DestinationFile 28 | { get; set; } 29 | internal static string Aax 30 | { get; set; } 31 | internal static Boolean CueEnabled 32 | { get; set; } 33 | internal static Boolean NfoEnabled 34 | { get; set; } 35 | internal static Boolean CoverEnabled 36 | { get; set; } 37 | internal static Boolean SplitFile 38 | { get; set; } 39 | internal static string HostDirectory 40 | { get; set; } 41 | internal static string Root = AppDomain.CurrentDomain.BaseDirectory; 42 | 43 | static int Main(string[] args) 44 | { 45 | if (args.Length > 0) 46 | { 47 | if (args[0] == "-c") 48 | { 49 | try 50 | { 51 | Alert.Notify($"AAXC Decryption: {args[1]}"); 52 | Catalogue.Download(args[1]); 53 | } 54 | catch (Exception ex) 55 | { 56 | Record.Log(ex, new StackTrace(true)); 57 | } 58 | return 0; 59 | } 60 | 61 | foreach (Object obj in args) 62 | { 63 | aax = obj.ToString(); 64 | } 65 | 66 | if (!File.Exists(Aax)) 67 | { 68 | Alert.Error("Invalid filename."); 69 | return 0; 70 | } 71 | } 72 | 73 | else 74 | { 75 | Alert.Error("No args provided."); 76 | return 0; 77 | } 78 | 79 | Console.CursorVisible = false; 80 | 81 | 82 | 83 | Alert.Notify("Parsing File"); 84 | dynamic settings = JObject.Parse(File.ReadAllText(Path.Combine(Root, "src\\configuration.json"))); 85 | string preferredFilename = String.Concat((string)settings["Title"][0]["T1"], " ", (string)settings.["Title"][0]["T2"], " ", (string)settings["Title"][0]["T3"], " ", (string)settings["Title"][0]["T4"], " ", (string)settings["Title"][0]["T5"]); 86 | 87 | preferredFilename = Regex.Replace(preferredFilename.Replace("null", null), @"\s+", " ").Trim().Replace (" ", " - "); 88 | 89 | 90 | using (FileStream stream = new FileStream(Aax, FileMode.Open)) 91 | { 92 | Title = new AtomReader(stream) 93 | .GetMetaAtomValue(AtomReader.TitleTypeName).Replace(":", " -"); 94 | } 95 | 96 | try 97 | { 98 | if (Convert.ToBoolean(settings["DEFAULT"])) 99 | { 100 | string aaxAuthor = SoftWare($"{root}src\\tools\\mediainfo.exe", $"\"{Aax}\" --Inform=General;%Performer%", false).Trim(); 101 | string aaxYear = SoftWare($"{root}src\\tools\\mediainfo.exe", $"\"{Aax}\" --Inform=General;%rldt%", false).Trim(); 102 | aaxYear = DateTime.ParseExact(aaxYear, "dd-MMM-yyyy", CultureInfo.InvariantCulture).ToString("yyyy"); 103 | string aaxNarrator = SoftWare($"{root}src\\tools\\mediainfo.exe", $"\"{Aax}\" --Inform=General;%nrt%", false).Trim(); 104 | string aaxBitrate = (Int32.Parse(SoftWare($"{root}src\\tools\\mediainfo.exe", $"\"{Aax}\" --Inform=Audio;%BitRate%", false).Trim()) / 1000).ToString() + "K"; 105 | 106 | DestinationFile = preferredFilename.Replace("Author", aaxAuthor) 107 | .Replace("Title", aaxTitle) 108 | .Replace("Year", aaxYear) 109 | .Replace("Narrator", aaxNarrator) 110 | .Replace("Bitrate", aaxBitrate) 111 | .TrimEnd('.'); 112 | 113 | DestinationFile = GetSafeFilename(DestinationFile); 114 | 115 | Alert.Success(DestinationFile); 116 | 117 | Codec = (string)settings["Output"][0]["Codec"]; 118 | SplitFile = (Boolean)settings["Output"][0]["Split"]; 119 | cueEnabled = settings.Files[0].Cue; 120 | nfoEnabled = settings.Files[0].NFO; 121 | coverEnabled = settings.Files[0].Cover; 122 | 123 | Directory.CreateDirectory(Path.Combine(HostDirectory, DestinationFile)); 124 | title = file; 125 | file = $"{hostDir}\\{file}\\{file.Trim()}"; 126 | } 127 | else 128 | { 129 | file = GetPreferredFilename(aax); 130 | 131 | Alert.Success(file); 132 | 133 | string _f = file; 134 | file = Path.Combine(hostDir, file, file); 135 | Directory.CreateDirectory(Path.Combine(hostDir, _f)); 136 | } 137 | } 138 | catch (Exception ex) 139 | { 140 | Alert.Error("Bad config"); 141 | Record.Log(ex, new StackTrace(true)); 142 | title = aaxTitle; 143 | aaxTitle = GetSafeFilename(aaxTitle.Trim().Replace(":", " -").TrimEnd('.')); 144 | file = GetSafeFilename(aaxTitle); 145 | 146 | Alert.Success(file); 147 | 148 | file = Path.Combine(hostDir, aaxTitle, file); 149 | Directory.CreateDirectory(Path.Combine(hostDir, aaxTitle)); 150 | } 151 | 152 | IAudiobook audio; 153 | 154 | switch (codec.ToLower()) 155 | { 156 | case "m4b": 157 | audio = Select.M4b(); 158 | break; 159 | case "mp3": 160 | audio = Select.Mp3(); 161 | break; 162 | default: audio = Select.M4b(); 163 | break; 164 | } 165 | audio.Open(aax); 166 | audio.SetPathAndFileName(file); 167 | if (Path.GetExtension(aax) == ".aaxc") 168 | { 169 | try 170 | { 171 | string voucher = Path.Combine(Path.GetDirectoryName(aax), string.Concat(Path.GetFileNameWithoutExtension(aax), ".voucher")); 172 | if (!File.Exists(voucher)) 173 | { 174 | Alert.Error("Voucher not found."); 175 | return 0; 176 | } 177 | else 178 | { 179 | Alert.Notify(Path.GetFileName(voucher)); 180 | } 181 | Voucher.Rootobject license = JsonConvert.DeserializeObject(File.ReadAllText(voucher)); 182 | audio.SetDecryptionKey(license.content_license.license_response.key, license.content_license.license_response.iv); 183 | } 184 | catch (Exception ex) 185 | { 186 | Alert.Error($"Unable to parse voucher."); 187 | Record.Log(ex, new StackTrace(true)); 188 | } 189 | } 190 | 191 | Thread cueThr = new Thread(() => 192 | { 193 | try 194 | { 195 | iAmDeaf.Util.Create.CueSheet(Aax, DestinationFile, Codec); 196 | } 197 | catch (Exception ex) 198 | { 199 | Alert.Error(ex.Message); 200 | } 201 | }); 202 | Thread audioThr = new Thread(() => 203 | { 204 | audio.Encode(SplitFile); 205 | audio.Close(); 206 | if (NfoEnabled) 207 | { 208 | string nfo; 209 | if (!SplitFile) 210 | { 211 | Alert.Notify("Generating NFO"); 212 | nfo = iAmDeaf.Util.Create.Nfo(Aax, String.Concat(DestinationFile, Codec)); 213 | } 214 | else 215 | { 216 | string[] extensions = { string.Concat('.', codec) }; 217 | var files = Directory.GetFiles(Path.GetDirectoryName(file), ".") 218 | .Where(f => Array.Exists(extensions, e => f.EndsWith(e))).ToArray(); 219 | Alert.Notify("Generating NFO"); 220 | nfo = Create.Nfo(aax, files[0], split); 221 | } 222 | File.WriteAllText($"{file}.nfo", nfo, Encoding.UTF8); 223 | } 224 | }); 225 | 226 | Stopwatch sw = new Stopwatch(); 227 | sw.Start(); 228 | 229 | audioThr.Start(); 230 | 231 | if (cueEnabled) 232 | { 233 | Alert.Notify("Generating cuesheet"); 234 | cueThr.Start(); 235 | if (coverEnabled) 236 | { 237 | Alert.Notify("Extracting JPG"); 238 | SoftWare($"\"{root}src\\tools\\ffmpeg.exe\"", $"-i \"{aax}\" -map 0:v -map -0:V -c copy \"{file}.jpg\" -y", true); 239 | } 240 | } 241 | 242 | if (cueThr.IsAlive) 243 | { 244 | cueThr.Join(); 245 | } 246 | 247 | audioThr.Join(); 248 | 249 | sw.Stop(); 250 | Alert.Notify($"Execution: {(sw.ElapsedMilliseconds / 1000).ToString()}s"); 251 | 252 | Console.CursorVisible = true; 253 | return 0; 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /iAmDeaf/Codecs/MP3.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using AAXClean; 3 | using AAXClean.Codecs; 4 | using iAmDeaf.Interfaces; 5 | using CsAtomReader; 6 | using NAudio.MediaFoundation; 7 | using NAudio.Wave; 8 | using NAudio.Lame; 9 | using NAudio; 10 | using static Other; 11 | using iAmDeaf.Other; 12 | 13 | namespace iAmDeaf.Codecs 14 | { 15 | internal class MP3 : IAudiobook 16 | { 17 | internal string sourceFile { get; set; } 18 | internal string outFile { get; set; } 19 | internal AAXClean.AaxFile encryptedFile { get; set; } 20 | internal string secret { get; set; } 21 | public bool Open(string file) 22 | { 23 | try 24 | { 25 | this.sourceFile = file; 26 | this.outFile = Path.GetDirectoryName(sourceFile); 27 | 28 | using (FileStream stream = new FileStream(sourceFile, FileMode.Open)) 29 | { 30 | string aaxTitle = new AtomReader(stream) 31 | .GetMetaAtomValue(AtomReader.TitleTypeName) 32 | .Replace(":", " -").Replace("?", ""); 33 | 34 | this.outFile = Path.Combine(outFile, string.Concat(aaxTitle, ".mp3")); 35 | } 36 | 37 | this.encryptedFile = new AAXClean.AaxFile(File.OpenRead(sourceFile)); 38 | if (Path.GetExtension(this.sourceFile) == ".aax") 39 | { 40 | this.secret = Audible.Secret.GetBytesFromFile(sourceFile); 41 | this.encryptedFile.SetDecryptionKey(this.secret); 42 | } 43 | return true; 44 | } 45 | catch (Exception ex) 46 | { 47 | Record.Log(ex, new StackTrace(true)); 48 | return false; 49 | } 50 | } 51 | public bool SetPathAndFileName(string file) 52 | { 53 | try 54 | { 55 | if (string.IsNullOrEmpty(file)) 56 | { 57 | Alert.Error("Path cannot be empty."); 58 | return false; 59 | } 60 | 61 | this.outFile = file; 62 | return true; 63 | } 64 | catch (Exception ex) 65 | { 66 | Record.Log(ex, new StackTrace(true)); 67 | return false; 68 | } 69 | } 70 | public void SetDecryptionKey(string license_key, string license_iv) 71 | { 72 | this.encryptedFile.SetDecryptionKey(license_key, license_iv); 73 | } 74 | public bool Encode(bool split = false) 75 | { 76 | try 77 | { 78 | Stopwatch sw = new Stopwatch(); 79 | if (split) 80 | { 81 | string splitPath = Path.Combine(Path.GetDirectoryName(this.outFile), Path.GetFileNameWithoutExtension(this.outFile)); 82 | if (!Directory.Exists(splitPath)) 83 | { 84 | Directory.CreateDirectory(splitPath); 85 | } 86 | 87 | sw.Start(); 88 | var conversionResult = this.encryptedFile.ConvertToMultiMp3(this.encryptedFile.GetChapterInfo(), NewSplit); 89 | 90 | void NewSplit(NewSplitCallback newSplitCallback) 91 | { 92 | string dir = splitPath; 93 | string fileName = newSplitCallback.Chapter.Title.Replace(":", "") + ".mp3"; 94 | newSplitCallback.OutputFile = File.OpenWrite(Path.Combine(dir, fileName)); 95 | } 96 | sw.Stop(); 97 | } 98 | else 99 | { 100 | string PID = Process.GetCurrentProcess().Id.ToString(); 101 | try 102 | { 103 | sw.Start(); 104 | string _f = Path.Combine(root, "src\\data\\dump", $"{PID}.mp4"); 105 | this.encryptedFile.ConvertToMp4a(File.Open($"{_f}", FileMode.OpenOrCreate, FileAccess.ReadWrite)); 106 | this.encryptedFile.Close(); 107 | 108 | TagLib.File mp4 = TagLib.File.Create(_f); 109 | int br = Int32.Parse(string.Concat(mp4.Properties.AudioBitrate.ToString(), "000")); 110 | 111 | string nrt = SoftWare($@"{root}\\src\tools\mediainfo.exe", $"\"{_f}\" --Inform=General;%nrt%", false); 112 | string comment = SoftWare($@"{root}\\src\tools\mediainfo.exe", $"\"{_f}\" --Inform=General;%Track_More%", false); 113 | 114 | Alert.Notify($"Lavf59.16.100 - {br.ToString()[..3]}_CBR"); 115 | 116 | LoadLameDLL(); 117 | MediaFoundationApi.Startup(); 118 | 119 | var aacFilePath = $@"{root}\\src\data\dump\{PID}.mp3"; 120 | Monitor(aacFilePath, _f); 121 | using (var reader = new MediaFoundationReader(_f)) 122 | { 123 | MediaFoundationEncoder.EncodeToMp3(reader, aacFilePath, br); 124 | } 125 | Console.WriteLine(); 126 | Alert.Notify("Tagging File"); 127 | 128 | SoftWare($@"{root}\src\tools\ffmpeg.exe", $"-i \"{_f}\" -i \"{root}\\src\\data\\dump\\{PID}.mp3\" -map 1 -metadata Narrator=\"{nrt}\" -metadata Comment=\"{comment.Replace("\"", string.Empty)}\" -c copy \"{this.outFile}.mp3\" -y", true); 129 | SoftWare($@"{root}\src\tools\ffmpeg.exe", $"-i \"{_f}\" -map 0:v -map -0:V -c copy \"{root}\\src\\data\\dump\\{PID}.jpg\" -y", true); 130 | sw.Stop(); 131 | } 132 | catch (Exception ex) 133 | { 134 | Record.Log(ex, new StackTrace(true)); 135 | return false; 136 | } 137 | if (!(EmbedCoverArt(string.Concat(this.outFile, ".mp3"), $@"{root}src\data\dump\{PID}.jpg"))) 138 | { 139 | Alert.Notify("Unable to set cover art"); 140 | } 141 | 142 | File.Delete($@"{root}\\src\\data\\dump\\{PID}.mp4"); 143 | File.Delete($@"{root}\\src\\data\\dump\\{PID}.mp3"); 144 | File.Delete($@"{root}\\src\\data\\dump\\{PID}.jpg"); 145 | } 146 | 147 | Alert.Notify(String.Format("Decrypted in {0}ms", sw.ElapsedMilliseconds.ToString())); 148 | return true; 149 | } 150 | catch (Exception ex) 151 | { 152 | Record.Log(ex, new StackTrace(true)); 153 | return false; 154 | } 155 | } 156 | public bool Close() 157 | { 158 | try 159 | { 160 | this.encryptedFile.Close(); 161 | return true; 162 | } 163 | catch (Exception ex) 164 | { 165 | Record.Log(ex, new StackTrace(true)); 166 | return false; 167 | } 168 | } 169 | internal static bool EmbedCoverArt(string audioFilePath, string coverFilePath) 170 | { 171 | if (!(File.Exists(audioFilePath) || !File.Exists(coverFilePath))) 172 | { 173 | Alert.Error("Audio and/or Cover file(s) do not exist."); 174 | return false; 175 | } 176 | 177 | try 178 | { 179 | // Формируем картинку в кадр Id3v2 180 | TagLib.Id3v2.Tag.DefaultVersion = 3; 181 | TagLib.Id3v2.Tag.ForceDefaultVersion = true; 182 | 183 | TagLib.File TagLibFile = TagLib.File.Create(audioFilePath); 184 | TagLib.Picture picture = new TagLib.Picture(coverFilePath); 185 | TagLib.Id3v2.AttachmentFrame albumCoverPictFrame = new TagLib.Id3v2.AttachmentFrame(picture); 186 | albumCoverPictFrame.MimeType = System.Net.Mime.MediaTypeNames.Image.Jpeg; 187 | albumCoverPictFrame.Type = TagLib.PictureType.FrontCover; 188 | TagLib.IPicture[] pictFrames = new TagLib.IPicture[1]; 189 | pictFrames[0] = (TagLib.IPicture)albumCoverPictFrame; 190 | TagLibFile.Tag.Pictures = pictFrames; 191 | TagLibFile.Save(); 192 | 193 | return true; 194 | } 195 | catch (Exception ex) 196 | { 197 | Alert.Error($"Unable to set cover art."); 198 | Record.Log(ex, new StackTrace(true)); 199 | return false; 200 | } 201 | } 202 | internal static void LoadLameDLL() 203 | { 204 | LameDLL.LoadNativeDLL(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"src\data")); 205 | } 206 | internal static NAudio.Lame.LAMEPreset Preset(string file) 207 | { 208 | int bitrate = (Int32.Parse(SoftWare(@"src\tools\mediainfo.exe", $"\"{file}\" --Inform=Audio;%BitRate%", false)) / 1024); 209 | 210 | switch (bitrate) 211 | { 212 | case >= 124: 213 | Alert.Notify($"LAME 3.100 - V1"); return LAMEPreset.V1; 214 | case >= 95: 215 | Alert.Notify($"LAME 3.100 - ABR_96"); return LAMEPreset.ABR_96; 216 | case >= 60: 217 | Alert.Notify($"LAME 3.100 - ABR_64"); return LAMEPreset.ABR_64; 218 | case >= 31: 219 | Alert.Notify($"LAME 3.100 - ABR_32"); return LAMEPreset.ABR_32; 220 | default: 221 | Alert.Notify($"LAME 3.100 - ABR_32"); return LAMEPreset.ABR_32; 222 | } 223 | } 224 | private static async void Monitor(string file, string oFile) 225 | { 226 | await Task.Run(() => 227 | { 228 | decimal buffer; 229 | decimal oSize = Decimal.Round((decimal)new FileInfo(oFile).Length / 1048576, 2); 230 | while (!File.Exists(file)) 231 | { 232 | Thread.Sleep(700); 233 | } 234 | while (true) 235 | { 236 | buffer = (decimal)new FileInfo(file).Length; 237 | Console.Write($" S: {oSize} O: {Decimal.Round(buffer / 1048576, 2)} MiB"); 238 | Console.Write("\r"); 239 | Thread.Sleep(270); 240 | if (buffer == (new System.IO.FileInfo(file).Length)) 241 | { 242 | Alert.Success($"File size of {Decimal.Round(buffer / 1048576, 2)} MiB"); 243 | break; 244 | } 245 | } 246 | }); 247 | } 248 | } 249 | } 250 | --------------------------------------------------------------------------------