static void Main(string[] args) { bool showLicense = false; bool showHelp = false; eDumpFormat dumpFormat = eDumpFormat.FormatText; bool useVbr = false; string mode = ""; string outputLocation = ""; string prefixLocation = null; string audioId = ""; string jsonFile = "tonies.json"; int bitRate = 96; var p = new OptionSet { { "m|mode=", "Operating mode: info, decode, encode", (string n) => mode = n }, { "o|output=", "Location where to write the file(s) to", (string r) => outputLocation = r }, { "p|prefix=", "Location where to find prefix files", (string r) => prefixLocation = r }, { "i|id=", "Set AudioID for encoding (default: current time)", (string r) => audioId = r }, { "b|bitrate=", "Set opus bit rate (default: " + bitRate + " kbps)", (int r) => bitRate = r }, { "vbr", "Use VBR encoding", r => useVbr = true }, { "j|json=", "Set JSON file/URL with details about tonies", (string r) => jsonFile = r }, { "f|format=", "Output details as: csv, json or text", v => { switch (v) { case "csv": dumpFormat = eDumpFormat.FormatCSV; break; case "json": dumpFormat = eDumpFormat.FormatJSON; break; case "text": dumpFormat = eDumpFormat.FormatText; break; } } }, { "v", "increase debug message verbosity", v => { if (v != null) { ++Verbosity; } } }, { "h|help", "show this message and exit", h => showHelp = true }, { "license", "show licenses and disclaimer", h => showLicense = true }, }; List <string> extra; try { extra = p.Parse(args); } catch (OptionException e) { Console.Write("Teddy.exe: "); Console.WriteLine(e.Message); Console.WriteLine("Try `Teddy.exe --help' for more information."); return; } if (showLicense) { ShowLicense(p); return; } if (showHelp) { ShowHelp(p); return; } LoadJson(jsonFile); switch (mode) { default: ShowHelp(p); return; case "info": { if (extra.Count < 1 || (!Directory.Exists(extra[0]) && !File.Exists(extra[0]))) { Console.WriteLine("Error: You must specify a file"); return; } switch (dumpFormat) { case eDumpFormat.FormatCSV: if (extra.Count > 1 || Directory.Exists(extra[0])) { Console.WriteLine("UID;AudioID;AudioDate;HeaderLength;HeaderOK;Padding;AudioLength;AudioLengthCheck;AudioHash;AudioHashCheck;Chapters;Segments;MinSegmentsPerPage;MaxSegmentsPerPage;SegLengthSum;HighestGranule;Time;MinGranules;MaxGranules;MinTime;MaxTime;"); } break; case eDumpFormat.FormatJSON: Console.WriteLine("["); break; case eDumpFormat.FormatText: Console.WriteLine("[Mode: dump information]"); break; } List <string> files = new List <string>(); foreach (string file in extra) { if (Directory.Exists(file)) { FindTonieFiles(files, file); } else { if (!File.Exists(file)) { Console.WriteLine("Error: file '" + file + "' does not exist"); return; } files.Add(file); } } bool first = true; foreach (string file in files.ToArray()) { try { TonieAudio dumpFile = TonieAudio.FromFile(file); dumpFile.CalculateStatistics(out long segCount, out long segLength, out int minSegs, out int maxSegs, out ulong minGranule, out ulong maxGranule, out ulong highestGranule); string uidrev = new FileInfo(file).Directory.Name + new FileInfo(file).Name; List <string> groups = (from Match m in Regex.Matches(uidrev, @"[A-F0-9]{2}") select m.Value).ToList(); groups.Reverse(); string uid = string.Join("", groups.ToArray()); var date = DateTimeOffset.FromUnixTimeSeconds(dumpFile.Header.AudioId); switch (dumpFormat) { case eDumpFormat.FormatCSV: { Console.Write(uid + ";"); Console.Write(dumpFile.Header.AudioId.ToString("X8") + ";"); Console.Write(date.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + ";"); Console.Write(dumpFile.HeaderLength + ";"); Console.Write(((dumpFile.HeaderLength != 0xFFC) ? "[WARNING: EXTRA DATA]" : "[OK]") + ";"); Console.Write(dumpFile.Header.Padding.Length + ";"); Console.Write(dumpFile.Header.AudioLength + ";"); Console.Write((dumpFile.Header.AudioLength == dumpFile.Audio.Length ? "[OK]" : "[INCORRECT]") + ";"); Console.Write(BitConverter.ToString(dumpFile.Header.Hash).Replace("-", "") + ";"); Console.Write((dumpFile.HashCorrect ? "[OK]" : "[INCORRECT]") + ";"); foreach (var offset in dumpFile.Header.AudioChapters) { Console.Write(offset + " "); } Console.Write(";"); Console.Write(segCount + ";"); Console.Write(minSegs + ";"); Console.Write(maxSegs + ";"); Console.Write(segLength + ";"); Console.Write(highestGranule + ";"); Console.Write(TonieAudio.FormatGranule(highestGranule) + ";"); Console.Write(minGranule + ";"); Console.Write(maxGranule + ";"); Console.Write((1000 * minGranule / 48000.0f) + ";"); Console.Write((1000 * maxGranule / 48000.0f) + ";"); Console.WriteLine(); break; } case eDumpFormat.FormatText: { Console.WriteLine("Dump of " + dumpFile.Filename + " (UID " + uid + "):"); Console.WriteLine(" Header: AudioID 0x" + dumpFile.Header.AudioId.ToString("X8") + " (" + date.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + ")"); string[] titles = null; var found = TonieInfos.Where(t => t.AudioIds.Contains(dumpFile.Header.AudioId)); if (found.Count() > 0) { var info = found.First(); titles = info.Tracks; Console.WriteLine(" Header: JSON Name '" + info.Title + "'"); } Console.WriteLine(" Header: Length 0x" + dumpFile.HeaderLength.ToString("X8") + " " + ((dumpFile.HeaderLength != 0xFFC) ? " [WARNING: EXTRA DATA]" : "[OK]")); Console.WriteLine(" Header: Padding 0x" + dumpFile.Header.Padding.Length.ToString("X8")); Console.WriteLine(" Header: AudioLen 0x" + dumpFile.Header.AudioLength.ToString("X8") + " " + (dumpFile.Header.AudioLength == dumpFile.Audio.Length ? "[OK]" : "[INCORRECT]")); Console.WriteLine(" Header: Checksum " + BitConverter.ToString(dumpFile.Header.Hash).Replace("-", "") + " " + (dumpFile.HashCorrect ? "[OK]" : "[INCORRECT]")); Console.WriteLine(" Header: Chapters "); TimeSpan prevTime = new TimeSpan(); for (int track = 1; track <= dumpFile.Header.AudioChapters.Length; track++) { uint off = dumpFile.GetHighestPage(); if (track < dumpFile.Header.AudioChapters.Length) { off = dumpFile.Header.AudioChapters[track]; } ulong granule = dumpFile.GetGranuleByPage(off); string lengthString = "@" + off; if (granule != ulong.MaxValue) { TimeSpan trackOffset = TimeSpan.FromSeconds(granule / 48000.0f); lengthString = (trackOffset - prevTime).ToString(@"mm\:ss\.ff"); prevTime = trackOffset; } string title = ""; if (titles != null && track - 1 < titles.Length) { title = titles[track - 1]; } Console.WriteLine(" Track #" + track.ToString("00") + " " + lengthString + " " + title); } Console.WriteLine(" Ogg: Segments " + segCount + " (min: " + minSegs + " max: " + maxSegs + " per OggPage)"); Console.WriteLine(" Ogg: net payload " + segLength + " byte"); Console.WriteLine(" Ogg: granules total: " + highestGranule + " (" + TonieAudio.FormatGranule(highestGranule) + " hh:mm:ss.ff)"); Console.WriteLine(" Ogg: granules/page min: " + minGranule + " max: " + maxGranule + " (" + (1000 * minGranule / 48000.0f) + "ms - " + (1000 * maxGranule / 48000.0f) + "ms)"); Console.WriteLine(); break; } case eDumpFormat.FormatJSON: { if (!first) { Console.WriteLine(","); } Console.WriteLine(" {"); Console.WriteLine(" \"uid\": \"" + uid + "\","); Console.WriteLine(" \"audio_id\": \"" + dumpFile.Header.AudioId.ToString("X8") + "\","); Console.WriteLine(" \"audio_date\": \"" + date.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + "\","); Console.WriteLine(" \"header_length\": " + dumpFile.HeaderLength + ","); Console.WriteLine(" \"header_ok\": \"" + ((dumpFile.HeaderLength != 0xFFC) ? "FALSE" : "TRUE") + "\","); Console.WriteLine(" \"padding\": " + dumpFile.Header.Padding.Length + ","); Console.WriteLine(" \"audio_length\": " + dumpFile.Header.AudioLength + ","); Console.WriteLine(" \"audio_length_check\": \"" + (dumpFile.Header.AudioLength == dumpFile.Audio.Length ? "TRUE" : "FALSE") + "\","); Console.WriteLine(" \"audio_hash\": \"" + BitConverter.ToString(dumpFile.Header.Hash).Replace("-", "") + "\","); Console.WriteLine(" \"audio_hash_check\": \"" + (dumpFile.HashCorrect ? "TRUE" : "FALSE") + "\","); Console.Write(" \"chapters\": [ 0"); foreach (var offset in dumpFile.Header.AudioChapters) { Console.Write(", " + offset); } Console.WriteLine(" ],"); Console.WriteLine(" \"segments\": " + segCount + ","); Console.WriteLine(" \"min_segments_per_page\": " + minSegs + ","); Console.WriteLine(" \"max_segments_per_page\": " + maxSegs + ","); Console.WriteLine(" \"segment_length_sum\": " + segLength + ","); Console.WriteLine(" \"highest_granule\": " + highestGranule + ","); Console.WriteLine(" \"time\": \"" + TonieAudio.FormatGranule(highestGranule) + "\","); Console.WriteLine(" \"min_granules\": " + minGranule + ","); Console.WriteLine(" \"max_granules\": " + maxGranule + ","); Console.WriteLine(" \"min_time\": " + (1000 * minGranule / 48000.0f) + ","); Console.WriteLine(" \"max_time\": " + (1000 * maxGranule / 48000.0f) + ""); Console.WriteLine(" }"); break; } } } catch (FileNotFoundException ex) { Console.WriteLine("File not found: " + file); } catch (InvalidDataException ex) { Console.WriteLine("File corrupt: " + file); } catch (Exception e) { Console.WriteLine(); Console.WriteLine("[ERROR] Failed to process '" + file + "'"); Console.WriteLine(" Exception: " + e.GetType()); Console.WriteLine(" Message: " + e.Message); Console.WriteLine(" Stacktrace: " + e.StackTrace); } if (first) { first = false; } } switch (dumpFormat) { case eDumpFormat.FormatCSV: break; case eDumpFormat.FormatJSON: Console.WriteLine("]"); break; case eDumpFormat.FormatText: break; } break; } case "decode": { if (extra.Count < 1 || (!Directory.Exists(extra[0]) && !File.Exists(extra[0]))) { Console.WriteLine("Error: You must specify a file"); return; } List <string> files = new List <string>(); foreach (string file in extra) { if (Directory.Exists(file)) { FindTonieFiles(files, file); } else { if (!File.Exists(file)) { Console.WriteLine("Error: file '" + file + "' does not exist"); return; } files.Add(file); } } Console.WriteLine("[Mode: decode file]"); foreach (string file in files.ToArray()) { try { Console.WriteLine("Dumping '" + file + "'"); TonieAudio dump2 = TonieAudio.FromFile(file); string inFile = new FileInfo(file).Name; string inDir = new FileInfo(file).DirectoryName; string outDirectory = !string.IsNullOrEmpty(outputLocation) ? outputLocation : inDir; if (!Directory.Exists(outDirectory)) { Console.WriteLine("Error: Output directory '" + outDirectory + "' does not exist"); return; } string outFile = outDirectory + Path.DirectorySeparatorChar + inFile; try { File.WriteAllBytes(outFile + ".ogg", dump2.Audio); File.WriteAllText(outFile + ".cue", BuildCueSheet(dump2), Encoding.UTF8); } catch (Exception ex) { Console.WriteLine("[ERROR] Failed to write file '" + outFile + ".occ/.cue'"); Console.WriteLine(" Message: " + ex.Message); } Console.WriteLine("Written content to " + outFile + ".ogg/.cue"); } catch (FileNotFoundException ex) { Console.WriteLine("File not found: " + file); } catch (InvalidDataException ex) { Console.WriteLine("File corrupt: " + file); } catch (Exception e) { Console.WriteLine(); Console.WriteLine("[ERROR] Failed to process '" + file + "'"); Console.WriteLine(" Exception: " + e.GetType()); Console.WriteLine(" Message: " + e.Message); Console.WriteLine(" Stacktrace: " + e.StackTrace); } } break; } case "encode": Console.WriteLine("[Mode: encode, " + bitRate + " kbps, " + (useVbr ? "VBR" : "CBR") + "]"); if (extra.Count < 1) { Console.WriteLine("Error: You must specify a directory or files to encode"); return; } /* * if ((bitRate % 24) != 0) * { * Console.WriteLine("Error: You must specify a multiple of 24 kbps, else block alignment in output file would produce incompatible files"); * return; * }*/ uint id = (uint)DateTimeOffset.Now.ToUnixTimeSeconds(); if (audioId != "") { if (audioId.Trim().StartsWith("0x")) { if (!uint.TryParse(audioId.Replace("0x", ""), System.Globalization.NumberStyles.HexNumber, null, out id)) { Console.WriteLine("Error: You must specify the AudioID as hex value like 0x5E034216 or as decimal number like 1577271830"); return; } } else { if (!uint.TryParse(audioId, System.Globalization.NumberStyles.Integer, null, out id)) { Console.WriteLine("Error: You must specify the AudioID as hex value like 0x5E034216 or as decimal number like 1577271830"); return; } } } try { string outLocationEncode = ((!string.IsNullOrEmpty(outputLocation)) ? outputLocation : "."); string outFile; if (!Directory.Exists(outLocationEncode)) { string baseDirOutFile = new FileInfo(outLocationEncode).DirectoryName; if (!Directory.Exists(baseDirOutFile)) { Console.WriteLine("Error: Specified output directory '" + outLocationEncode + "' does not exist and file '" + baseDirOutFile + "' not reachable."); return; } outFile = outLocationEncode; } else { outFile = outLocationEncode + Path.DirectorySeparatorChar + "500304E0"; } TonieAudio generated = new TonieAudio(extra.ToArray(), id, bitRate * 1000, useVbr, prefixLocation); try { File.WriteAllBytes(outFile, generated.FileContent); } catch (Exception ex) { Console.WriteLine("[ERROR] Failed to write file '" + outFile + "'"); Console.WriteLine(" Message: " + ex.Message); } Console.WriteLine(""); Console.WriteLine("Written content to " + outFile); } catch (FileNotFoundException ex) { Console.WriteLine("[ERROR] Failed to process due to a missing file"); Console.WriteLine(" Message: " + ex.Message); } catch (InvalidDataException ex) { Console.WriteLine("[ERROR] Failed to process due to an invalid file"); Console.WriteLine(" Message: " + ex.Message); } catch (TonieAudio.EncodingException ex) { Console.WriteLine("[ERROR] Failed to encode audio"); Console.WriteLine(" Message: " + ex.Message); } catch (Exception e) { Console.WriteLine(); Console.WriteLine("[ERROR] Failed to process"); Console.WriteLine(" Exception: " + e.GetType()); Console.WriteLine(" Message: " + e.Message); Console.WriteLine(" Stacktrace: " + e.StackTrace); } break; } }
public static bool DumpInfo(StringBuilder message, eDumpFormat dumpFormat, string file, TonieData[] tonieInfos, string customName = null) { TonieAudio dumpFile = TonieAudio.FromFile(file); dumpFile.CalculateStatistics(out long segCount, out long segLength, out int minSegs, out int maxSegs, out ulong minGranule, out ulong maxGranule, out ulong highestGranule); string uidrev = new FileInfo(file).Directory.Name + new FileInfo(file).Name; List <string> groups = (from Match m in Regex.Matches(uidrev, @"[A-F0-9]{2}") select m.Value).ToList(); groups.Reverse(); string uid = string.Join("", groups.ToArray()); string dateExtra = ""; int id = dumpFile.Header.AudioId; if (id < 0x50000000) { dateExtra = "custom file, real date "; id += 0x50000000; } var date = DateTimeOffset.FromUnixTimeSeconds(id); bool first = false; switch (dumpFormat) { case eDumpFormat.FormatCSV: { message.Append(uid + ";"); message.Append(dumpFile.Header.AudioId.ToString("X8") + ";"); message.Append(date.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + ";"); message.Append(dumpFile.HeaderLength + ";"); message.Append(((dumpFile.HeaderLength != 0xFFC) ? "[WARNING: EXTRA DATA]" : "[OK]") + ";"); message.Append(dumpFile.Header.Padding.Length + ";"); message.Append(dumpFile.Header.AudioLength + ";"); message.Append((dumpFile.Header.AudioLength == dumpFile.Audio.Length ? "[OK]" : "[INCORRECT]") + ";"); message.Append(BitConverter.ToString(dumpFile.Header.Hash).Replace("-", "") + ";"); message.Append((dumpFile.HashCorrect ? "[OK]" : "[INCORRECT]") + ";"); foreach (var offset in dumpFile.Header.AudioChapters) { message.Append(offset + " "); } message.Append(";"); message.Append(segCount + ";"); message.Append(minSegs + ";"); message.Append(maxSegs + ";"); message.Append(segLength + ";"); message.Append(highestGranule + ";"); message.Append(TonieAudio.FormatGranule(highestGranule) + ";"); message.Append(minGranule + ";"); message.Append(maxGranule + ";"); message.Append((1000 * minGranule / 48000.0f) + ";"); message.Append((1000 * maxGranule / 48000.0f) + ";"); message.AppendLine(); break; } case eDumpFormat.FormatText: { message.AppendLine("Dump of " + dumpFile.Filename + " (UID " + uid + "):"); message.AppendLine(" Header: AudioID 0x" + dumpFile.Header.AudioId.ToString("X8") + " (" + dateExtra + date.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + ")"); string[] titles = null; string hashString = BitConverter.ToString(dumpFile.Header.Hash).Replace("-", ""); var found = tonieInfos.Where(t => t.Hash.Contains(hashString)); TonieData info = null; string infoHashString = null; int infoIndex = 0; if (found.Count() > 0) { info = found.First(); titles = info.Tracks; infoIndex = Array.IndexOf(info.AudioIds, dumpFile.Header.AudioId); Array.Resize(ref info.Hash, info.AudioIds.Length); infoHashString = info.Hash[infoIndex]; message.AppendLine(" Header: JSON Name '" + info.Title + "'"); } if (!string.IsNullOrEmpty(customName)) { message.AppendLine(" Header: Custom Name '" + customName + "'"); } message.AppendLine(" Header: Length 0x" + dumpFile.HeaderLength.ToString("X8") + " " + ((dumpFile.HeaderLength != 0xFFC) ? " [WARNING: EXTRA DATA]" : "[OK]")); message.AppendLine(" Header: Padding 0x" + dumpFile.Header.Padding.Length.ToString("X8")); message.AppendLine(" Header: AudioLen 0x" + dumpFile.Header.AudioLength.ToString("X8") + " " + (dumpFile.Header.AudioLength == dumpFile.Audio.Length ? "[OK]" : "[INCORRECT]")); message.AppendLine(" Header: Checksum " + hashString + " " + (dumpFile.HashCorrect ? "[OK]" : "[INCORRECT]") + " " + (infoHashString != null ? (infoHashString == hashString ? "[JSON MATCH]" : "[JSON MISMATCH]") : "[NO JSON INFO]")); message.AppendLine(" Header: Chapters "); if (info != null && infoHashString == null) { info.Hash[infoIndex] = hashString; } TimeSpan prevTime = new TimeSpan(); for (int track = 1; track <= dumpFile.Header.AudioChapters.Length; track++) { uint off = dumpFile.GetHighestPage(); if (track < dumpFile.Header.AudioChapters.Length) { off = dumpFile.Header.AudioChapters[track]; } ulong granule = dumpFile.GetGranuleByPage(off); string lengthString = "@" + off; if (granule != ulong.MaxValue) { TimeSpan trackOffset = TimeSpan.FromSeconds(granule / 48000.0f); lengthString = (trackOffset - prevTime).ToString(@"mm\:ss\.ff"); prevTime = trackOffset; } string title = ""; if (titles != null && track - 1 < titles.Length) { title = titles[track - 1]; } message.AppendLine(" Track #" + track.ToString("00") + " " + lengthString + " " + title); } message.AppendLine(" Ogg: Segments " + segCount + " (min: " + minSegs + " max: " + maxSegs + " per OggPage)"); message.AppendLine(" Ogg: net payload " + segLength + " byte"); message.AppendLine(" Ogg: granules total: " + highestGranule + " (" + TonieAudio.FormatGranule(highestGranule) + " hh:mm:ss.ff)"); message.AppendLine(" Ogg: granules/page min: " + minGranule + " max: " + maxGranule + " (" + (1000 * minGranule / 48000.0f) + "ms - " + (1000 * maxGranule / 48000.0f) + "ms)"); message.AppendLine(); break; } case eDumpFormat.FormatJSON: { if (!first) { message.AppendLine(","); } message.AppendLine(" {"); message.AppendLine(" \"uid\": \"" + uid + "\","); message.AppendLine(" \"audio_id\": \"" + dumpFile.Header.AudioId.ToString("X8") + "\","); message.AppendLine(" \"audio_date\": \"" + date.ToString("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + "\","); message.AppendLine(" \"header_length\": " + dumpFile.HeaderLength + ","); message.AppendLine(" \"header_ok\": \"" + ((dumpFile.HeaderLength != 0xFFC) ? "FALSE" : "TRUE") + "\","); message.AppendLine(" \"padding\": " + dumpFile.Header.Padding.Length + ","); message.AppendLine(" \"audio_length\": " + dumpFile.Header.AudioLength + ","); message.AppendLine(" \"audio_length_check\": \"" + (dumpFile.Header.AudioLength == dumpFile.Audio.Length ? "TRUE" : "FALSE") + "\","); message.AppendLine(" \"audio_hash\": \"" + BitConverter.ToString(dumpFile.Header.Hash).Replace("-", "") + "\","); message.AppendLine(" \"audio_hash_check\": \"" + (dumpFile.HashCorrect ? "TRUE" : "FALSE") + "\","); message.Append(" \"chapters\": [ 0"); foreach (var offset in dumpFile.Header.AudioChapters) { message.Append(", " + offset); } message.AppendLine(" ],"); message.AppendLine(" \"segments\": " + segCount + ","); message.AppendLine(" \"min_segments_per_page\": " + minSegs + ","); message.AppendLine(" \"max_segments_per_page\": " + maxSegs + ","); message.AppendLine(" \"segment_length_sum\": " + segLength + ","); message.AppendLine(" \"highest_granule\": " + highestGranule + ","); message.AppendLine(" \"time\": \"" + TonieAudio.FormatGranule(highestGranule) + "\","); message.AppendLine(" \"min_granules\": " + minGranule + ","); message.AppendLine(" \"max_granules\": " + maxGranule + ","); message.AppendLine(" \"min_time\": " + (1000 * minGranule / 48000.0f) + ","); message.AppendLine(" \"max_time\": " + (1000 * maxGranule / 48000.0f) + ""); message.AppendLine(" }"); break; } } return(true); }