Program.cs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using Bsm.Core;
  9. using Bsm.Core.Arcache;
  10. namespace Bsm.Arcache.Runner
  11. {
  12. internal class Program
  13. {
  14. private static void Main(string[] args)
  15. {
  16. #if DEBUG
  17. var dir = @"Y:\BeatSaverAll";
  18. var arcBase = @"Z:\bsa";
  19. var lenArc = @"Z:\bslp";
  20. #else
  21. var dir = AppDomain.CurrentDomain.BaseDirectory;
  22. var arcBase = Path.Combine(dir, "bsa");
  23. var lenArc = Path.Combine(dir, "bslp");
  24. #endif
  25. //TODO: speed up, only once lookup, extract egg and dat at once
  26. CreateJsonArchive(dir, arcBase);
  27. CreateLenArchiveDirectly(dir, lenArc);
  28. Console.WriteLine();
  29. Console.Write("Press ENTER to exit...");
  30. Console.ReadLine();
  31. }
  32. internal static void CreateLenArchiveDirectly(string dir, string lenArc)
  33. {
  34. var lst = new List<string>();
  35. Console.Write("Looking up ogg files...");
  36. IoUtil.LookupFilesRecursively(dir, (p, f) =>
  37. {
  38. var ext = Path.GetExtension(p.cFileName)?.ToLower();
  39. if (ext != ".egg") return;
  40. var archivePath = f.TrimStart('/') + "/" + p.cFileName;
  41. lst.Add(archivePath.ToLower());
  42. });
  43. Console.WriteLine($"{lst.Count}");
  44. var processed = 0;
  45. var ms = new MemoryStream();
  46. var writer = new BinaryWriter(ms, Encoding.UTF8, true);
  47. void AppendEntry(string item)
  48. {
  49. var fullPath = Path.Combine(dir, item);
  50. float len;
  51. Exception ex = null;
  52. try
  53. {
  54. len = GetLengthFromOgg(fullPath);
  55. }
  56. catch (Exception e)
  57. {
  58. len = -1;
  59. ex = e;
  60. }
  61. lock (writer)
  62. {
  63. writer.Write(item);
  64. writer.Write(len);
  65. }
  66. Console.WriteLine($"[{Interlocked.Increment(ref processed)}/{lst.Count}]Added:{fullPath}:{len}:{ex?.Message}");
  67. }
  68. Parallel.ForEach(lst, AppendEntry);
  69. writer.Flush();
  70. writer.Close();
  71. var bufRaw = ms.ToArray();
  72. var bufCompress = Lz4ArchiveBase.CompressPickle(bufRaw);
  73. File.WriteAllBytes(lenArc, bufCompress);
  74. }
  75. internal static void CreateJsonArchive(string dir, string arcBase)
  76. {
  77. Console.WriteLine("Creating archive file...");
  78. var arc = new Lz4ArchiveCreator(arcBase);
  79. var lst = new List<string>();
  80. Console.Write("Looking up json files...");
  81. IoUtil.LookupFilesRecursively(dir, (p, f) =>
  82. {
  83. var ext = Path.GetExtension(p.cFileName)?.ToLower();
  84. if (ext != ".dat") return;
  85. var archivePath = f.TrimStart('/') + "/" + p.cFileName;
  86. lst.Add(archivePath.ToLower());
  87. });
  88. Console.WriteLine($"{lst.Count}");
  89. var processed = 0;
  90. void AddToArchive(string archivePath)
  91. {
  92. var fullPath = Path.Combine(dir, archivePath);
  93. arc.AppendFile(fullPath, archivePath.ToLower());
  94. Console.WriteLine($"[{Interlocked.Increment(ref processed)}/{lst.Count}]Added:{fullPath}");
  95. }
  96. //foreach (var item in lst) AddToArchive(item);
  97. Parallel.ForEach(lst, AddToArchive);
  98. arc.Close();
  99. }
  100. internal static void CreateLenArchive(string dir, string lenArc)
  101. {
  102. var lst = new List<string>();
  103. Console.Write("Looking up len files...");
  104. IoUtil.LookupFilesRecursively(dir, (p, f) =>
  105. {
  106. var ext = Path.GetExtension(p.cFileName)?.ToLower();
  107. if (ext != ".len") return;
  108. var archivePath = f.TrimStart('/') + "/" + p.cFileName;
  109. lst.Add(archivePath);
  110. });
  111. Console.WriteLine($"{lst.Count}");
  112. var ms = new MemoryStream();
  113. var writer = new BinaryWriter(ms, Encoding.UTF8, true);
  114. for (var i = 0; i < lst.Count; i++)
  115. {
  116. var fullPath = Path.Combine(dir, lst[i]);
  117. writer.Write(lst[i]);
  118. writer.Write(File.ReadAllBytes(fullPath));
  119. Console.WriteLine($"[{i + 1}/{lst.Count}]Added:{fullPath}");
  120. }
  121. writer.Flush();
  122. writer.Close();
  123. var bufRaw = ms.ToArray();
  124. var bufCompress = Lz4ArchiveBase.CompressPickle(bufRaw);
  125. File.WriteAllBytes(lenArc, bufCompress);
  126. }
  127. internal static void CreateOggLengthFile(string dir)
  128. {
  129. var dic = new ConcurrentDictionary<string, float>();
  130. Console.Write("Looking up ogg files...");
  131. IoUtil.LookupFilesRecursively(dir, (p, f) =>
  132. {
  133. if (Path.GetExtension(p.cFileName)?.ToLower() != ".egg") return;
  134. var archivePath = f.TrimStart('/') + "/" + p.cFileName;
  135. dic.TryAdd(archivePath, float.NaN);
  136. });
  137. Console.WriteLine($"{dic.Count}");
  138. var processed = 0;
  139. void Process(KeyValuePair<string, float> keyValuePair)
  140. {
  141. var fullPath = Path.Combine(dir, keyValuePair.Key);
  142. var lenPath = fullPath + ".len";
  143. if (File.Exists(lenPath))
  144. {
  145. Console.WriteLine($"[{Interlocked.Increment(ref processed)}/{dic.Count}]{keyValuePair.Key}:Skip");
  146. return;
  147. }
  148. try
  149. {
  150. var len = GetLengthFromOgg(fullPath);
  151. File.WriteAllBytes(lenPath, BitConverter.GetBytes(len));
  152. Console.WriteLine($"[{Interlocked.Increment(ref processed)}/{dic.Count}]{keyValuePair.Key}:{len}");
  153. dic[keyValuePair.Key] = len;
  154. }
  155. catch (Exception e)
  156. {
  157. dic[keyValuePair.Key] = -1;
  158. Console.WriteLine($"[{Interlocked.Increment(ref processed)}/{dic.Count}]{keyValuePair.Key}:{e.Message}");
  159. }
  160. }
  161. Parallel.ForEach(dic, Process);
  162. //foreach (var item in dic)
  163. //{
  164. // Process(item);
  165. //}
  166. }
  167. private static byte[] oggBytes = new byte[] { 0x4F, 0x67, 0x67, 0x53, 0x00, 0x04 };
  168. public static float GetLengthFromOgg(string oggFile)
  169. {
  170. using (var fs = File.OpenRead(oggFile))
  171. using (var br = new BinaryReader(fs, Encoding.ASCII))
  172. {
  173. /*
  174. * Tries to find the array of bytes from the stream
  175. */
  176. bool FindBytes(byte[] bytes, int searchLength)
  177. {
  178. for (var i = 0; i < searchLength; i++)
  179. {
  180. var b = br.ReadByte();
  181. if (b != bytes[0]) continue;
  182. var by = br.ReadBytes(bytes.Length - 1);
  183. // hardcoded 6 bytes compare, is fine because all inputs used are 6 bytes
  184. // bitwise AND the last byte to read only the flag bit for lastSample searching
  185. // shouldn't cause issues finding rate, hopefully
  186. if (by[0] == bytes[1] && by[1] == bytes[2] && by[2] == bytes[3] && by[3] == bytes[4] && (by[4] & bytes[5]) == bytes[5]) return true;
  187. var index = Array.IndexOf(@by, bytes[0]);
  188. if (index != -1)
  189. {
  190. fs.Position += index - (bytes.Length - 1);
  191. i += index;
  192. }
  193. else
  194. i += (bytes.Length - 1);
  195. }
  196. return false;
  197. }
  198. var rate = -1;
  199. long lastSample = -1;
  200. //Skip Capture Pattern
  201. fs.Position = 24;
  202. //{0x76, 0x6F, 0x72, 0x62, 0x69, 0x73} = "vorbis" in byte values
  203. var foundVorbis = FindBytes(new byte[] { 0x76, 0x6F, 0x72, 0x62, 0x69, 0x73 }, 256);
  204. if (foundVorbis)
  205. {
  206. fs.Position += 5;
  207. rate = br.ReadInt32();
  208. }
  209. else
  210. {
  211. return -1;
  212. }
  213. /*
  214. * this finds the last occurrence of OggS in the file by checking for a bit flag (0x04)
  215. * reads in blocks determined by seekBlockSize
  216. * 6144 does not add significant overhead and speeds up the search significantly
  217. */
  218. const int seekBlockSize = 6144;
  219. const int seekTries = 10; // 60 KiB should be enough for any sane ogg file
  220. for (var i = 0; i < seekTries; i++)
  221. {
  222. var seekPos = (i + 1) * seekBlockSize * -1;
  223. var overshoot = Math.Max((int)(-seekPos - fs.Length), 0);
  224. if (overshoot >= seekBlockSize)
  225. {
  226. break;
  227. }
  228. fs.Seek(seekPos + overshoot, SeekOrigin.End);
  229. var foundOggS = FindBytes(oggBytes, seekBlockSize - overshoot);
  230. if (foundOggS)
  231. {
  232. lastSample = br.ReadInt64();
  233. break;
  234. }
  235. }
  236. if (lastSample == -1)
  237. {
  238. return -1;
  239. }
  240. var length = lastSample / (float)rate;
  241. return length;
  242. }
  243. }
  244. }
  245. }