HostProgram.cs 58 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202
  1. using FNZCM.Core;
  2. using FNZCM.Shared;
  3. using FNZCM.Shared.MediaModels;
  4. using FNZCM.Shared.MetadataModels;
  5. using Microsoft.VisualBasic.FileIO;
  6. using Newtonsoft.Json;
  7. using System;
  8. using System.Collections.Concurrent;
  9. using System.Collections.Generic;
  10. using System.IO;
  11. using System.Linq;
  12. using System.Net;
  13. using System.Text;
  14. using System.Threading;
  15. using System.Threading.Tasks;
  16. using SearchOption = Microsoft.VisualBasic.FileIO.SearchOption;
  17. namespace FNZCM.ConHost
  18. {
  19. internal static class HostProgram
  20. {
  21. //0. start http server
  22. //1. scan libraries and fill data struct
  23. // libs
  24. // disc
  25. // Tracks(FLAC / AAC_*)
  26. // Meta(title(artist) / duration)
  27. // FSI ( size )
  28. // TODO: Generate thumbnail of BKS
  29. private static readonly ConcurrentDictionary<string, Library> Libraries = new();
  30. private static readonly ConcurrentDictionary<string, string> PathMapping = new();
  31. private static readonly ConcurrentDictionary<string, MediaTag> MediaTags = new();
  32. private static readonly ConcurrentDictionary<string, LoadedModule> Modules = new();
  33. private static LoadedModule _defaultModule;
  34. private static bool _isRunning;
  35. private static bool _isLoading;
  36. private static int TotalTrackCount => PathMapping.Count(p => p.Key.StartsWith("/media/"));
  37. private static string LoadingText => $"Loading... {MediaTags.Count} / {TotalTrackCount}";
  38. private static DateTime _lastRequestAccepted;
  39. private static void Main()
  40. {
  41. Console.WriteLine("Starting...");
  42. var tWorker = new Thread(Working);
  43. _isRunning = true;
  44. tWorker.Start();
  45. Task.Run(ReloadConfig);
  46. Console.WriteLine("Press ENTER to Stop.");
  47. Console.ReadLine();
  48. Console.WriteLine("Shutting down...");
  49. _isRunning = false;
  50. tWorker.Join();
  51. Console.WriteLine("Stopped.");
  52. Console.WriteLine();
  53. Console.Write("Press ENTER to Exit.");
  54. Console.ReadLine();
  55. }
  56. private static void ReloadConfig()
  57. {
  58. if (_isLoading)
  59. {
  60. Console.WriteLine("Still loading, SKIP");
  61. return;
  62. }
  63. _isLoading = true;
  64. try
  65. {
  66. ConfigFile.Reload();
  67. Modules.Clear();
  68. _defaultModule = null;
  69. if (ConfigFile.Instance.Modules?.Any() == true)
  70. {
  71. foreach (var modEnt in ConfigFile.Instance.Modules)
  72. {
  73. Console.WriteLine($"Loading module `{modEnt.Value.DisplayText}'...");
  74. var module = new LoadedModule
  75. {
  76. VirtualPath = modEnt.Key,
  77. DisplayText = modEnt.Value.DisplayText,
  78. DefaultDocument = modEnt.Value.DefaultDocument,
  79. Files = new Dictionary<string, byte[]>()
  80. };
  81. if (Directory.Exists(modEnt.Value.Path))
  82. {
  83. //load by fs
  84. var files = Directory.GetFiles(modEnt.Value.Path, "*", System.IO.SearchOption.AllDirectories);
  85. foreach (var item in files)
  86. {
  87. var k = item.Substring(modEnt.Value.Path.Length + 1).Replace("\\", "/").ToLower();
  88. module.Files[k] = File.ReadAllBytes(item);
  89. }
  90. }
  91. else if (File.Exists(modEnt.Value.Path))
  92. {
  93. //load by package
  94. using var arc = SharpCompress.Archives.ArchiveFactory.Open(modEnt.Value.Path);
  95. foreach (var ent in arc.Entries.Where(p => p.IsDirectory == false))
  96. {
  97. var buf = new byte[ent.Size];
  98. using var s = ent.OpenEntryStream();
  99. var r = s.Read(buf, 0, buf.Length);
  100. module.Files[ent.Key.ToLower()] = buf;
  101. }
  102. }
  103. else
  104. {
  105. Console.WriteLine("WARN: resource not found");
  106. continue;
  107. }
  108. if (modEnt.Value.IsDefault && _defaultModule == null) _defaultModule = module;
  109. Modules[modEnt.Key] = module;
  110. }
  111. }
  112. Console.WriteLine("Scanning libraries...");
  113. MediaTags.Clear();
  114. PathMapping.Clear();
  115. Libraries.Clear();
  116. foreach (var kvpLib in ConfigFile.Instance.Libraries)
  117. {
  118. if (_isRunning == false) throw new OperationCanceledException();
  119. Console.WriteLine($"Library {kvpLib.Key} - {kvpLib.Value}");
  120. var libPath = kvpLib.Key.ToLower();
  121. var lib = Libraries[libPath] = new Library(kvpLib.Key);
  122. var discDirArray = Directory.GetDirectories(kvpLib.Value);
  123. foreach (var discDir in discDirArray)
  124. {
  125. if (_isRunning == false) throw new OperationCanceledException();
  126. Console.WriteLine($" Disc {discDir}");
  127. var discName = Path.GetFileName(discDir);
  128. var discPath = discName.ToLower();
  129. try
  130. {
  131. var disc = new Disc(discName);
  132. var bkDir = Path.Combine(discDir, "bk");
  133. var mainTrackFiles = FileSystem.GetFiles(discDir, SearchOption.SearchTopLevelOnly, ConfigFile.Instance.MediaFilePattern);
  134. foreach (var mainTrackFile in mainTrackFiles)
  135. {
  136. var trackName = Path.GetFileName(mainTrackFile);
  137. var trackPath = trackName.ToLower();
  138. disc.MainTracks[trackPath] = trackName;
  139. PathMapping[$"/media/{libPath}/{discPath}/{trackPath}"] = mainTrackFile;
  140. }
  141. if (Directory.Exists(bkDir))
  142. {
  143. var bkFiles = FileSystem.GetFiles(bkDir, SearchOption.SearchTopLevelOnly, ConfigFile.Instance.BkFilePattern);
  144. foreach (var file in bkFiles)
  145. {
  146. var bkName = Path.GetFileName(file);
  147. var bkPath = bkName.ToLower();
  148. disc.Bks[bkPath] = bkName;
  149. PathMapping[$"/bk/{libPath}/{discPath}/{bkPath}"] = file;
  150. }
  151. }
  152. var aacTrackDirArray = Directory.GetDirectories(discDir, "AAC_Q*");
  153. foreach (var aacTrackDir in aacTrackDirArray)
  154. {
  155. var aacTrackSetName = Path.GetFileName(aacTrackDir);
  156. var aacTrackSetPath = aacTrackSetName.ToLower();
  157. var aacTrackSet = disc.SubTracks[aacTrackSetPath] = new TrackSet(aacTrackSetName);
  158. foreach (var file in FileSystem.GetFiles(aacTrackDir, SearchOption.SearchTopLevelOnly, "*.m4a", "*.mp4"))
  159. {
  160. var aacTrackName = Path.GetFileName(file);
  161. var aacTrackPath = aacTrackName.ToLower();
  162. aacTrackSet.Tracks[aacTrackPath] = aacTrackName;
  163. PathMapping[$"/media/{libPath}/{discPath}/{aacTrackSetPath}/{aacTrackPath}"] = file;
  164. }
  165. }
  166. var coverFilePath = Path.Combine(discDir, "cover.jpg");
  167. if (File.Exists(coverFilePath)) PathMapping[$"/cover/{libPath}/{discPath}/cover.jpg"] = coverFilePath;
  168. lib.Discs[discPath] = disc;
  169. }
  170. catch (Exception ex)
  171. {
  172. Console.WriteLine(ex);
  173. }
  174. }
  175. }
  176. Console.WriteLine("Looking tags...");
  177. foreach (var k in PathMapping.Keys.Where(p => p.StartsWith("/media/")))
  178. {
  179. GetTag(k);
  180. }
  181. Console.WriteLine("Looking tags...Done");
  182. }
  183. catch (Exception e)
  184. {
  185. Console.WriteLine($"Load error: {e}");
  186. }
  187. _isLoading = false;
  188. }
  189. private static void ReloadModules()
  190. {
  191. }
  192. private static void Working()
  193. {
  194. var listener = new HttpListener();
  195. listener.Prefixes.Add(ConfigFile.Instance.ListenPrefix);
  196. if (ConfigFile.Instance.AliasPrefix?.Any() == true)
  197. foreach (var prefix in ConfigFile.Instance.AliasPrefix)
  198. listener.Prefixes.Add(prefix);
  199. listener.Start();
  200. var upTime = DateTime.Now;
  201. Console.WriteLine($"HTTP Server started, listening on {ConfigFile.Instance.ListenPrefix}");
  202. listener.BeginGetContext(ContextGet, listener);
  203. _lastRequestAccepted = DateTime.Now;
  204. while (_isRunning)
  205. {
  206. var timeSpan = DateTime.Now - _lastRequestAccepted;
  207. var up = DateTime.Now - upTime;
  208. Console.Title =
  209. "FNZCM"
  210. + $" UP {up.Days:00}D {up.Hours:00}H {up.Minutes:00}M {up.Seconds:00}S {up.Milliseconds:000}"
  211. + $" / "
  212. + $" LA {timeSpan.Days:00}D {timeSpan.Hours:00}H {timeSpan.Minutes:00}M {timeSpan.Seconds:00}S {timeSpan.Milliseconds:000}"
  213. ;
  214. Thread.Sleep(1000);
  215. }
  216. listener.Close();
  217. Thread.Sleep(1000);
  218. }
  219. private static void ContextGet(IAsyncResult ar)
  220. {
  221. var listener = (HttpListener)ar.AsyncState;
  222. HttpListenerContext context;
  223. try
  224. {
  225. // ReSharper disable once PossibleNullReferenceException
  226. context = listener.EndGetContext(ar);
  227. }
  228. catch (Exception e)
  229. {
  230. Console.WriteLine(e);
  231. return;
  232. }
  233. if (_isRunning) listener.BeginGetContext(ContextGet, listener);
  234. ProcessRequest(context);
  235. }
  236. private static void ProcessRequest(HttpListenerContext context)
  237. {
  238. _lastRequestAccepted = DateTime.Now;
  239. var request = context.Request;
  240. Console.WriteLine($"Request from {request.RemoteEndPoint} {request.HttpMethod} {request.RawUrl}");
  241. if (false == string.IsNullOrEmpty(request.Headers["Origin"]))
  242. {
  243. if (ConfigFile.Instance.AccessControlAllowOrigin?.Any(p => request.Headers["Origin"].StartsWith(p)) == true)
  244. context.Response.Headers["Access-Control-Allow-Origin"] = "*";
  245. }
  246. // GET / show all libraries
  247. // foo=library bar=disc
  248. // GET /list/foo/ show all disc and cover with name, provide m3u path
  249. // GET /list/foo/bar/bk/ list all picture as grid
  250. // GET /list/foo/bar/tracks/ list all tracks as text list
  251. // GET /lib_list/foo/playlist.m3u8 auto gen
  252. // GET /lib_list/foo/aac_q1.00/playlist.m3u8 auto gen
  253. // GET /list/foo/bar/playlist.m3u8 auto gen
  254. // GET /list/foo/bar/aac_q1.00/playlist.m3u8 auto gen
  255. // media streaming HTTP Partial RANGE SUPPORT
  256. // GET /cover/foo/bar/cover.jpg image/<ext>
  257. // GET /media/foo/bar/01.%20foobar.flac audio/<ext>
  258. // GET /media/foo/bar/aac_q1.00/01.%20foobar.m4a audio/<ext>
  259. // GET /bk/foo/bar/foobar.jpg image/<ext>
  260. // metadata catalog
  261. // GET /metadata/progress.json
  262. // GET /metadata/file-set.json
  263. // GET /metadata/tag-dict.json
  264. // loadable module
  265. // GET /module/<module-key>/<in-module-path>/
  266. try
  267. {
  268. // ReSharper disable once PossibleNullReferenceException
  269. var requestPath = request.Url.LocalPath.ToLower();
  270. var pathParts = (IReadOnlyList<string>)requestPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
  271. if (requestPath == "/admin/" && false == request.QueryString.AllKeys.Contains("action"))
  272. {
  273. var sb = new StringBuilder();
  274. sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
  275. sb.Append($"<title> Libraries - {ConfigFile.Instance.Title} </title>");
  276. sb.Append("<body bgColor=skyBlue>");
  277. sb.Append($"<h3>Admin</h3>");
  278. sb.Append("<div><a href=/>Back to home</a></div>");
  279. sb.Append($"<form method=GET>");
  280. sb.Append($"Password: <input type=password name=pass />");
  281. sb.Append($"<br/>");
  282. sb.Append($"Operation: <input type=submit name=action value=Reload />");
  283. sb.Append($"</form>");
  284. context.WriteTextUtf8(sb.ToString());
  285. }
  286. else if (requestPath == "/admin/" && request.QueryString["action"] == "Reload" && request.QueryString["pass"] == ConfigFile.Instance.AdminPassword)
  287. {
  288. Task.Run(ReloadConfig);
  289. context.Response.Redirect("/");
  290. }
  291. else if (requestPath == "/admin/")
  292. {
  293. context.Response.Redirect("/");
  294. }
  295. else if (requestPath.StartsWith("/module/") && pathParts.Count > 1)
  296. {
  297. var moduleKey = pathParts[1];
  298. if (Modules.TryGetValue(moduleKey, out var module))
  299. {
  300. var entPath = string.Join("/", pathParts.Skip(2));
  301. if (module.Files.TryGetValue(entPath, out var bin))
  302. {
  303. if (entPath.EndsWith(".js")) context.Response.ContentType = "application/javascript";
  304. context.Response.OutputStream.Write(bin, 0, bin.Length);
  305. }
  306. else context.Response.StatusCode = 404;
  307. }
  308. else context.Response.StatusCode = 404;
  309. }
  310. else if (requestPath == "/" && _defaultModule != null)
  311. {
  312. context.Response.Redirect($"/module/{_defaultModule.VirtualPath}/{_defaultModule.DefaultDocument}");
  313. }
  314. else if (requestPath == "/" || requestPath == "/classic-index")
  315. {
  316. var sb = new StringBuilder();
  317. sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
  318. sb.Append($"<title> Libraries - {ConfigFile.Instance.Title} </title>");
  319. sb.Append("<body bgColor=skyBlue style=font-size:3vh>");
  320. if (_isLoading) sb.Append($"<h4 style=position:fixed;right:0px;top:0px;margin:0>{LoadingText}</h4>");
  321. sb.Append($"<h2>{ConfigFile.Instance.Title}</h2>");
  322. if (Modules?.Any() == true)
  323. {
  324. sb.Append($"<h3>Modules</h3>");
  325. sb.Append($"<h4>(Total number of modules: {Modules.Count})</h4>");
  326. sb.Append("<ul>");
  327. foreach (var module in Modules)
  328. {
  329. sb.Append("<li>");
  330. sb.Append($"<a href='/module/{module.Key.UrlEscape()}/{module.Value.DefaultDocument}'>{module.Value.DisplayText}</a>");
  331. sb.Append("</li>");
  332. }
  333. sb.Append("</ul>");
  334. }
  335. sb.Append($"<h3>Libraries</h3>");
  336. sb.Append($"<h4>(Total number of disc: {Libraries.Sum(p => p.Value.Discs.Count)})</h4>");
  337. sb.Append("<ul>");
  338. foreach (var library in Libraries.OrderBy(p => p.Key))
  339. {
  340. sb.Append("<li>");
  341. sb.Append($"<a href='/list/{library.Key.UrlEscape()}/'>{library.Value.Name}</a>");
  342. sb.Append($"<br/>&nbsp;&nbsp;&nbsp; Number of disc: {library.Value.Discs.Count}");
  343. sb.Append("</li>");
  344. }
  345. sb.Append("</ul>");
  346. sb.Append($"<hr/>");
  347. sb.Append($"<div>Your IP: {context?.Request?.RemoteEndPoint?.Address.ToString() ?? "Unknown"}</div>");
  348. sb.Append($"<div><a href=/admin/>Admin</a></div>");
  349. sb.Append($"<div>-</div>");
  350. sb.Append($"<div>Author: Coder (V)</div>");
  351. sb.Append($"<div>Blog: <a target=_blank href=https://topcl.net/myapps/private-colud-music.html>https://topcl.net/myapps/private-colud-music.html</a></div>");
  352. sb.Append($"<div>Source Repo: <a target=_blank href=https://topcl.net/gogs/coder/CloudMusic/>https://topcl.net/gogs/coder/CloudMusic/</a></div>");
  353. context.WriteTextUtf8(sb.ToString());
  354. }
  355. else if (pathParts.Count == 2 && pathParts[0] == "list")
  356. {
  357. var libName = pathParts[1];
  358. if (Libraries.TryGetValue(libName, out var lib))
  359. {
  360. var sb = new StringBuilder();
  361. sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
  362. sb.Append($"<title> Discs of {lib.Name} - {ConfigFile.Instance.Title} </title>");
  363. sb.Append(
  364. "<style>" +
  365. "a:link{ text-decoration: none; }" +
  366. "div.item{" +
  367. " vertical-align:top;" +
  368. " margin-bottom:1vh;" +
  369. " padding:0.5vh;" +
  370. " border:solid 1px;" +
  371. " border-radius:0.5vh;" +
  372. " font-size:2.2vh;" +
  373. "}" +
  374. "div.item::-webkit-scrollbar{" +
  375. " display: none;" +
  376. "}" +
  377. "img.cover{" +
  378. " float:left;" +
  379. " width:45vw;" +
  380. "}" +
  381. "div.disc_name{" +
  382. "}" +
  383. "div.links{" +
  384. " clear:both;" +
  385. "}" +
  386. "a.button{" +
  387. " margin-left:4vw;" +
  388. "}" +
  389. "</style>");
  390. sb.Append("<script>" +
  391. "function listPage(a){" +
  392. "window.open(a.href+'.html','CNZCM_LIST_PAGE',1);" +
  393. "return false;" +
  394. "}" +
  395. "</script>");
  396. sb.Append($"<body bgColor=skyBlue>");
  397. if (_isLoading) sb.Append($"<h4 style=position:fixed;right:0px;top:0px;margin:0>{LoadingText}</h4>");
  398. sb.Append($"<h1>Discs of {lib.Name}</h1>");
  399. sb.Append("<div><a href=/>Back to home</a></div>");
  400. if (!_isLoading)
  401. {
  402. sb.Append("<div style=margin-top:1vh;margin-bottom:1vh;>");
  403. //big m3u8
  404. var trackKeys = lib.Discs.SelectMany(p => p.Value.MainTracks.Keys.Select(q => new { DiscName = p.Key, TrackName = q })).ToArray();
  405. var totalDur = trackKeys.Sum(p => GetTag($"/media/{libName}/{p.DiscName}/{p.TrackName}", true)?.Duration ?? 0);
  406. var totalLen = trackKeys.Sum(p => GetTag($"/media/{libName}/{p.DiscName}/{p.TrackName}", true)?.Length ?? 0);
  407. sb.Append($"Number of track: {trackKeys.Length}");
  408. sb.Append($"<br/>{totalDur.FormatDuration()} {totalLen.FormatFileSize()} <a onclick=\"return listPage(this)\" href=\"/lib_list/{libName}/playlist.m3u8\">ALL_M3U8_MAIN</a>");
  409. var subTrackSetNames = lib.Discs.SelectMany(p => p.Value.SubTracks.Keys).Distinct().ToArray();
  410. foreach (var setName in subTrackSetNames)
  411. {
  412. trackKeys = lib.Discs.SelectMany(p =>
  413. {
  414. if (p.Value.SubTracks.TryGetValue(setName, out var tSet))
  415. {
  416. return tSet.Tracks.Select(q => new { DiscName = p.Key, TrackName = q.Key });
  417. }
  418. return new string[0].Select(q => new { DiscName = p.Key, TrackName = q });
  419. }).ToArray();
  420. totalDur = trackKeys.Sum(p => GetTag($"/media/{libName}/{p.DiscName}/{setName}/{p.TrackName}", true)?.Duration ?? 0);
  421. totalLen = trackKeys.Sum(p => GetTag($"/media/{libName}/{p.DiscName}/{setName}/{p.TrackName}", true)?.Length ?? 0);
  422. sb.Append($"<br/>{totalDur.FormatDuration()} {totalLen.FormatFileSize()} <a onclick=\"return listPage(this)\" href=\"/lib_list/{libName}/{setName}/playlist.m3u8\">ALL_M3U8_{setName.ToUpper()}</a>");
  423. }
  424. sb.Append("</div>");
  425. }
  426. //Cover list
  427. foreach (var disc in lib.Discs.OrderByDescending(p => p.Key))
  428. {
  429. sb.Append("<div class=item>");
  430. sb.Append($"<div>");
  431. sb.Append($"<img class=cover src=\"/cover/{libName}/{disc.Key.UrlEscape()}/cover.jpg\" />");
  432. sb.Append($"<div class=disc_name>{disc.Value.Name}</div>");
  433. sb.Append($"</div>");
  434. sb.Append("<div class=links>");
  435. sb.Append("<div>");
  436. sb.Append($"Number of track: {disc.Value.MainTracks.Count} <a href=\"/list/{libName}/{disc.Key.UrlEscape()}/tracks/\">[TRACKERS]</a>");
  437. if (disc.Value.Bks?.Count > 0) sb.Append($"<a class=button href=\"/list/{libName}/{disc.Key.UrlEscape()}/bk/\">[BK]</a>");
  438. sb.Append("</div>");
  439. var totalDur = disc.Value.MainTracks.Sum(p => GetTag($"/media/{libName}/{disc.Key}/{p.Key}", true)?.Duration ?? 0);
  440. var totalLen = disc.Value.MainTracks.Sum(p => GetTag($"/media/{libName}/{disc.Key}/{p.Key}", true)?.Length ?? 0);
  441. sb.Append($"{totalDur.FormatDuration()} {totalLen.FormatFileSize()} <a onclick=\"return listPage(this)\" href=\"/list/{libName}/{disc.Key.UrlEscape()}/playlist.m3u8\">M3U8_MAIN</a>");
  442. if (disc.Value.SubTracks.Count > 0)
  443. {
  444. foreach (var subTrack in disc.Value.SubTracks)
  445. {
  446. totalDur = subTrack.Value.Tracks.Sum(p => GetTag($"/media/{libName}/{disc.Key}/{subTrack.Key}/{p.Key}", true)?.Duration ?? 0);
  447. totalLen = subTrack.Value.Tracks.Sum(p => GetTag($"/media/{libName}/{disc.Key}/{subTrack.Key}/{p.Key}", true)?.Length ?? 0);
  448. sb.Append($"<br/>{totalDur.FormatDuration()} {totalLen.FormatFileSize()} <a onclick=\"return listPage(this)\" href=\"/list/{libName}/{disc.Key.UrlEscape()}/{subTrack.Key.UrlEscape()}/playlist.m3u8\">{subTrack.Value.Name}</a>");
  449. }
  450. }
  451. sb.Append("</div>");
  452. sb.Append("</div>");
  453. }
  454. context.WriteTextUtf8(sb.ToString());
  455. }
  456. else
  457. {
  458. context.Response.StatusCode = 404;
  459. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  460. }
  461. }
  462. else if (pathParts.Count == 4 && pathParts[0] == "list" && pathParts[3] == "tracks")
  463. {
  464. var libName = pathParts[1];
  465. var discPath = pathParts[2];
  466. if (Libraries.TryGetValue(libName, out var l) && l.Discs.TryGetValue(discPath, out var disc))
  467. {
  468. var sb = new StringBuilder();
  469. sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
  470. sb.Append($"<body bgColor=skyBlue style=font-size:2vh>");
  471. if (_isLoading) sb.Append($"<h4 style=position:fixed;right:0px;top:0px;margin:0>{LoadingText}</h4>");
  472. sb.Append($"<h2>Tracks of</h2><h1>{disc.Name}</h1>");
  473. sb.Append($"<div><a href='/list/{libName.UrlEscape()}/'>Back to library</a></div>");
  474. sb.Append($"<img style=float:left;max-width:50vw src=\"/cover/{libName}/{discPath}/cover.jpg\" />");
  475. var durTotal = 0;
  476. var sizeTotal = 0L;
  477. var sbm = new StringBuilder();
  478. foreach (var kvpTrack in disc.MainTracks.OrderBy(p => p.Key))
  479. {
  480. sbm.Append($"<li>");
  481. sbm.Append($"<a href=\"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{kvpTrack.Key.UrlEscape()}\" >{kvpTrack.Value}</a>");
  482. var tag = GetTag($"/media/{libName}/{discPath}/{kvpTrack.Key}");
  483. durTotal += tag.Duration;
  484. sizeTotal += tag.Length;
  485. sbm.Append($"<br> &nbsp; &nbsp; &nbsp; {tag.Duration.FormatDuration()} {tag.Length.FormatFileSize()}");
  486. sbm.Append($"</li>");
  487. }
  488. sb.Append($"<h2>Main ({durTotal.FormatDuration()}) {sizeTotal.FormatFileSize()}</h2>");
  489. sb.Append(sbm);
  490. foreach (var kvpSubSet in disc.SubTracks.OrderBy(p => p.Key))
  491. {
  492. durTotal = 0;
  493. sizeTotal = 0L;
  494. sbm.Clear();
  495. foreach (var kvpTrack in kvpSubSet.Value.Tracks.OrderBy(p => p.Key))
  496. {
  497. sbm.Append($"<li>");
  498. sbm.Append($"<a href=\"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{kvpSubSet.Key.UrlEscape()}/{kvpTrack.Key.UrlEscape()}\" >{kvpTrack.Value}</a>");
  499. var tag = GetTag($"/media/{libName}/{discPath}/{kvpSubSet.Key}/{kvpTrack.Key}");
  500. durTotal += tag.Duration;
  501. sizeTotal += tag.Length;
  502. sbm.Append($"<br/> &nbsp; &nbsp; &nbsp; {tag.Duration.FormatDuration()} {tag.Length.FormatFileSize()}");
  503. sbm.Append($"</li>");
  504. }
  505. sb.Append($"<h2>{kvpSubSet.Value.Name} ({durTotal.FormatDuration()}) {sizeTotal.FormatFileSize()}</h2>");
  506. sb.Append(sbm);
  507. }
  508. context.WriteTextUtf8(sb.ToString());
  509. }
  510. else
  511. {
  512. context.Response.StatusCode = 404;
  513. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  514. }
  515. }
  516. else if (pathParts.Count == 4 && pathParts[0] == "list" && pathParts[3] == "bk")
  517. {
  518. var libName = pathParts[1];
  519. var discPath = pathParts[2];
  520. if (Libraries.TryGetValue(libName, out var lib) && lib.Discs.TryGetValue(discPath, out var disc))
  521. {
  522. var sb = new StringBuilder();
  523. sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
  524. sb.Append($"<body bgColor=skyBlue style=font-size:2vh>");
  525. if (_isLoading) sb.Append($"<h4 style=position:fixed;right:0px;top:0px;margin:0>{LoadingText}</h4>");
  526. sb.Append($"<h2>BK of </h2><h1>{disc.Name}</h1>");
  527. sb.Append($"<div><a href='/list/{libName.UrlEscape()}/'>Back to library</a></div>");
  528. foreach (var discBk in disc.Bks.OrderBy(p => p.Key))
  529. {
  530. //TODO: auto gen thumbnail 512x512 jpg 80
  531. sb.Append($"<img src='/bk/{libName.UrlEscape()}/{discPath.UrlEscape()}/{discBk.Key.UrlEscape()}' style=max-width:24vw;max-height:24vw;margin-right:1vw;margin-bottom:1vh; />");
  532. }
  533. context.WriteTextUtf8(sb.ToString());
  534. }
  535. else
  536. {
  537. context.Response.StatusCode = 404;
  538. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  539. }
  540. }
  541. else if (pathParts.Count == 3 && pathParts[0] == "lib_list" && pathParts[2] == "playlist.m3u8.html")
  542. {
  543. var libName = pathParts[1];
  544. if (Libraries.TryGetValue(libName, out var lib))
  545. {
  546. var sb = new StringBuilder();
  547. //var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
  548. foreach (var discKvp in lib.Discs.OrderByDescending(p => p.Key))
  549. {
  550. var disc = discKvp.Value;
  551. var discPath = discKvp.Key;
  552. //var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/cover.jpg";
  553. var tracks = disc.MainTracks;
  554. foreach (var track in tracks.OrderBy(p => p.Key))
  555. {
  556. var mediaTag = GetTag($"/media/{libName}/{discPath}/{track.Key}");
  557. var mediaPath = $"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{track.Key.UrlEscape()}";
  558. //<li><span class="simp-source" data-src="http://">Title</span><span class="simp-desc">Artist</span></li>
  559. if (mediaTag != null && false == string.IsNullOrWhiteSpace(mediaTag.Title))
  560. {
  561. sb.AppendLine($"<li>" +
  562. $"<span class=\"simp-source\" data-src=\"{mediaPath}\">{mediaTag.Title}</span>" +
  563. $"<span class=\"simp-desc\">{mediaTag.Artist}</span>" +
  564. $"<span style=\"float:right\">{mediaTag.Duration.SecondsToHumanRead()}</span>" +
  565. $"</li>");
  566. }
  567. else
  568. {
  569. sb.AppendLine($"<li><span class=\"simp-source\" data-src=\"{mediaPath}\">{track.Value}</span></li>");
  570. }
  571. }
  572. }
  573. var sbPage = new StringBuilder(Resource.listPage);
  574. sbPage.Replace("<!-- list place here -->", sb.ToString());
  575. context.WriteTextUtf8(sbPage.ToString());
  576. }
  577. else
  578. {
  579. context.Response.StatusCode = 404;
  580. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  581. }
  582. }
  583. else if (pathParts.Count == 3 && pathParts[0] == "lib_list" && pathParts[2] == "playlist.m3u8")
  584. {
  585. var libName = pathParts[1];
  586. if (Libraries.TryGetValue(libName, out var lib))
  587. {
  588. var sb = new StringBuilder();
  589. sb.WriteM3U8Header();
  590. var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
  591. foreach (var discKvp in lib.Discs.OrderByDescending(p => p.Key))
  592. {
  593. var disc = discKvp.Value;
  594. var discPath = discKvp.Key;
  595. var tracks = disc.MainTracks;
  596. foreach (var track in tracks.OrderBy(p => p.Key))
  597. {
  598. var coverPath = $"/cover/{libName.UrlEscape()}/{discPath.UrlEscape()}/cover.jpg";
  599. var mediaTag = GetTag($"/media/{libName}/{discPath}/{track.Key}");
  600. if (mediaTag != null && false == string.IsNullOrWhiteSpace(mediaTag.Title))
  601. {
  602. sb.AppendLine($"#EXTINF:{mediaTag.Duration} tvg-logo=\"{prefix + coverPath}\",{mediaTag.Artist} - {mediaTag.Title}");
  603. }
  604. else
  605. {
  606. sb.AppendLine($"#EXTINF:0 logo=\"{prefix + coverPath}\",{Path.GetFileNameWithoutExtension(track.Key)}");
  607. }
  608. var mediaPath = $"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{track.Key.UrlEscape()}";
  609. sb.AppendLine(prefix + mediaPath);
  610. }
  611. }
  612. context.WriteTextUtf8(sb.ToString(), Const.ContentTypeM3U);
  613. }
  614. else
  615. {
  616. context.Response.StatusCode = 404;
  617. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  618. }
  619. }
  620. else if (pathParts.Count == 4 && pathParts[0] == "lib_list" && pathParts[3] == "playlist.m3u8.html")
  621. {
  622. var libName = pathParts[1];
  623. var trackSetName = pathParts[2];
  624. if (Libraries.TryGetValue(libName, out var lib))
  625. {
  626. var sb = new StringBuilder();
  627. //var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
  628. foreach (var discKvp in lib.Discs.OrderByDescending(p => p.Key))
  629. {
  630. var disc = discKvp.Value;
  631. var discPath = discKvp.Key;
  632. //var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/cover.jpg";
  633. if (disc.SubTracks.TryGetValue(trackSetName, out var tracksSet))
  634. {
  635. var tracks = tracksSet.Tracks;
  636. foreach (var track in tracks.OrderBy(p => p.Key))
  637. {
  638. var mediaTag = GetTag($"/media/{libName}/{discPath}/{trackSetName}/{track.Key}");
  639. var mediaPath = $"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{trackSetName.UrlEscape()}/{track.Key.UrlEscape()}";
  640. if (mediaTag != null && false == string.IsNullOrWhiteSpace(mediaTag.Title))
  641. {
  642. sb.AppendLine($"<li>" +
  643. $"<span class=\"simp-source\" data-src=\"{mediaPath}\">{mediaTag.Title}</span>" +
  644. $"<span class=\"simp-desc\">{mediaTag.Artist}</span>" +
  645. $"<span style=\"float:right\">{mediaTag.Duration.SecondsToHumanRead()}</span>" +
  646. $"</li>");
  647. }
  648. else
  649. {
  650. sb.AppendLine($"<li><span class=\"simp-source\" data-src=\"{mediaPath}\">{track.Value}</span></li>");
  651. }
  652. }
  653. }
  654. }
  655. var sbPage = new StringBuilder(Resource.listPage);
  656. sbPage.Replace("<!-- list place here -->", sb.ToString());
  657. context.WriteTextUtf8(sbPage.ToString());
  658. }
  659. else
  660. {
  661. context.Response.StatusCode = 404;
  662. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  663. }
  664. }
  665. else if (pathParts.Count == 4 && pathParts[0] == "lib_list" && pathParts[3] == "playlist.m3u8")
  666. {
  667. var libName = pathParts[1];
  668. var trackSetName = pathParts[2];
  669. if (Libraries.TryGetValue(libName, out var lib))
  670. {
  671. var sb = new StringBuilder();
  672. sb.WriteM3U8Header();
  673. var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
  674. foreach (var discKvp in lib.Discs.OrderByDescending(p => p.Key))
  675. {
  676. var disc = discKvp.Value;
  677. var discPath = discKvp.Key;
  678. if (disc.SubTracks.TryGetValue(trackSetName, out var tracksSet))
  679. {
  680. var tracks = tracksSet.Tracks;
  681. foreach (var track in tracks.OrderBy(p => p.Key))
  682. {
  683. var mediaTag = GetTag($"/media/{libName}/{discPath}/{trackSetName}/{track.Key}");
  684. var coverPath = $"/cover/{libName.UrlEscape()}/{discPath.UrlEscape()}/cover.jpg";
  685. var mediaPath = $"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{trackSetName.UrlEscape()}/{track.Key.UrlEscape()}";
  686. if (mediaTag != null && false == string.IsNullOrWhiteSpace(mediaTag.Title))
  687. {
  688. sb.AppendLine($"#EXTINF:{mediaTag.Duration} logo=\"{prefix + coverPath}\",{mediaTag.Artist} - {mediaTag.Title}");
  689. }
  690. else
  691. {
  692. sb.AppendLine($"#EXTINF:0 logo=\"{prefix + coverPath}\",{Path.GetFileNameWithoutExtension(track.Key)}");
  693. }
  694. sb.AppendLine(prefix + mediaPath);
  695. }
  696. }
  697. }
  698. context.WriteTextUtf8(sb.ToString(), Const.ContentTypeM3U);
  699. }
  700. else
  701. {
  702. context.Response.StatusCode = 404;
  703. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  704. }
  705. }
  706. else if (pathParts.Count == 4 && pathParts[0] == "list" && pathParts[3] == "playlist.m3u8.html")
  707. {
  708. var libName = pathParts[1];
  709. var discPath = pathParts[2];
  710. if (Libraries.TryGetValue(libName, out var lib) && lib.Discs.TryGetValue(discPath, out var disc))
  711. {
  712. // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
  713. //var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
  714. var sb = new StringBuilder();
  715. //var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/cover.jpg";
  716. foreach (var track in disc.MainTracks.OrderBy(p => p.Key))
  717. {
  718. var mediaTag = GetTag($"/media/{libName}/{discPath}/{track.Key}");
  719. var mediaPath = $"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{track.Key.UrlEscape()}";
  720. if (mediaTag != null && false == string.IsNullOrWhiteSpace(mediaTag.Title))
  721. {
  722. sb.AppendLine($"<li>" +
  723. $"<span class=\"simp-source\" data-src=\"{mediaPath}\">{mediaTag.Title}</span>" +
  724. $"<span class=\"simp-desc\">{mediaTag.Artist}</span>" +
  725. $"<span style=\"float:right\">{mediaTag.Duration.SecondsToHumanRead()}</span>" +
  726. $"</li>");
  727. }
  728. else
  729. {
  730. sb.AppendLine($"<li><span class=\"simp-source\" data-src=\"{mediaPath}\">{track.Value}</span></li>");
  731. }
  732. }
  733. var sbPage = new StringBuilder(Resource.listPage);
  734. sbPage.Replace("<!-- list place here -->", sb.ToString());
  735. context.WriteTextUtf8(sbPage.ToString());
  736. }
  737. else
  738. {
  739. context.Response.StatusCode = 404;
  740. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  741. }
  742. }
  743. else if (pathParts.Count == 4 && pathParts[0] == "list" && pathParts[3] == "playlist.m3u8")
  744. {
  745. var libName = pathParts[1];
  746. var discPath = pathParts[2];
  747. if (Libraries.TryGetValue(libName, out var lib) && lib.Discs.TryGetValue(discPath, out var disc))
  748. {
  749. // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
  750. var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
  751. var sb = new StringBuilder();
  752. sb.WriteM3U8Header();
  753. sb.AppendLine($"#EXTALB: {discPath}");
  754. var coverPath = $"/cover/{libName.UrlEscape()}/{discPath.UrlEscape()}/cover.jpg";
  755. foreach (var track in disc.MainTracks.OrderBy(p => p.Key))
  756. {
  757. var mediaTag = GetTag($"/media/{libName}/{discPath}/{track.Key}");
  758. if (mediaTag != null && false == string.IsNullOrWhiteSpace(mediaTag.Title))
  759. {
  760. sb.AppendLine($"#EXTINF:{mediaTag.Duration} logo=\"{prefix + coverPath}\",{mediaTag.Artist} - {mediaTag.Title}");
  761. }
  762. else
  763. {
  764. sb.AppendLine($"#EXTINF:0 logo=\"{prefix + coverPath}\",{Path.GetFileNameWithoutExtension(track.Key)}");
  765. }
  766. var mediaPath = $"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{track.Key.UrlEscape()}";
  767. sb.AppendLine(prefix + mediaPath);
  768. }
  769. context.WriteTextUtf8(sb.ToString(), Const.ContentTypeM3U);
  770. }
  771. else
  772. {
  773. context.Response.StatusCode = 404;
  774. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  775. }
  776. }
  777. else if (pathParts.Count == 5 && pathParts[0] == "list" && pathParts[4] == "playlist.m3u8.html")
  778. {
  779. var libName = pathParts[1];
  780. var discPath = pathParts[2];
  781. var subSetPath = pathParts[3];
  782. //var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/cover.jpg";
  783. if (Libraries.TryGetValue(libName, out var lib) && lib.Discs.TryGetValue(discPath, out var disc))
  784. {
  785. // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
  786. var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
  787. if (false == disc.SubTracks.TryGetValue(subSetPath, out var trackSet))
  788. {
  789. context.Response.StatusCode = 404;
  790. }
  791. else
  792. {
  793. var sb = new StringBuilder();
  794. foreach (var track in trackSet.Tracks.OrderBy(p => p.Key))
  795. {
  796. var mediaTag = GetTag($"/media/{libName}/{discPath}/{subSetPath}/{track.Key}");
  797. var mediaPath = $"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{subSetPath.UrlEscape()}/{track.Key.UrlEscape()}";
  798. if (mediaTag != null && false == string.IsNullOrWhiteSpace(mediaTag.Title))
  799. {
  800. sb.AppendLine($"<li>" +
  801. $"<span class=\"simp-source\" data-src=\"{mediaPath}\">{mediaTag.Title}</span>" +
  802. $"<span class=\"simp-desc\">{mediaTag.Artist}</span>" +
  803. $"<span style=\"float:right\">{mediaTag.Duration.SecondsToHumanRead()}</span>" +
  804. $"</li>");
  805. }
  806. else
  807. {
  808. sb.AppendLine($"<li><span class=\"simp-source\" data-src=\"{mediaPath}\">{track.Value}</span></li>");
  809. }
  810. }
  811. var sbPage = new StringBuilder(Resource.listPage);
  812. sbPage.Replace("<!-- list place here -->", sb.ToString());
  813. context.WriteTextUtf8(sbPage.ToString());
  814. }
  815. }
  816. else
  817. {
  818. context.Response.StatusCode = 404;
  819. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  820. }
  821. }
  822. else if (pathParts.Count == 5 && pathParts[0] == "list" && pathParts[4] == "playlist.m3u8")
  823. {
  824. var libName = pathParts[1];
  825. var discPath = pathParts[2];
  826. var subSetPath = pathParts[3];
  827. if (Libraries.TryGetValue(libName, out var lib) && lib.Discs.TryGetValue(discPath, out var disc))
  828. {
  829. // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
  830. var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
  831. if (false == disc.SubTracks.TryGetValue(subSetPath, out var trackSet))
  832. {
  833. context.Response.StatusCode = 404;
  834. }
  835. else
  836. {
  837. var sb = new StringBuilder();
  838. sb.WriteM3U8Header();
  839. foreach (var track in trackSet.Tracks.OrderBy(p => p.Key))
  840. {
  841. var mediaTag = GetTag($"/media/{libName}/{discPath}/{subSetPath}/{track.Key}");
  842. var coverPath = $"/cover/{libName.UrlEscape()}/{discPath.UrlEscape()}/cover.jpg";
  843. if (mediaTag != null && false == string.IsNullOrWhiteSpace(mediaTag.Title))
  844. {
  845. sb.AppendLine($"#EXTINF:{mediaTag.Duration} logo=\"{prefix + coverPath}\",{mediaTag.Artist} - {mediaTag.Title}");
  846. }
  847. else
  848. {
  849. sb.AppendLine($"#EXTINF:0 logo=\"{prefix + coverPath}\",{Path.GetFileNameWithoutExtension(track.Key)}");
  850. }
  851. var mediaPath = $"/media/{libName.UrlEscape()}/{discPath.UrlEscape()}/{subSetPath.UrlEscape()}/{track.Key.UrlEscape()}";
  852. sb.AppendLine(prefix + mediaPath);
  853. }
  854. context.WriteTextUtf8(sb.ToString(), Const.ContentTypeM3U);
  855. }
  856. }
  857. else
  858. {
  859. context.Response.StatusCode = 404;
  860. //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
  861. }
  862. }
  863. else if (requestPath == "/metadata/progress.json")
  864. {
  865. var dic = new Dictionary<string, object>()
  866. {
  867. {"IsLoading",_isLoading},
  868. {nameof(TotalTrackCount),TotalTrackCount},
  869. {nameof(MediaTags),MediaTags.Count},
  870. };
  871. context.WriteTextUtf8(JsonConvert.SerializeObject(new LoadingProgress
  872. {
  873. IsLoading = _isLoading,
  874. TotalTrackCount= TotalTrackCount,
  875. LoadedTags= MediaTags.Count
  876. }), Const.ApplicationJson);
  877. }
  878. else if (requestPath == "/metadata/file-set.json")
  879. {
  880. context.WriteTextUtf8(JsonConvert.SerializeObject(Libraries), Const.ApplicationJson);
  881. }
  882. else if (requestPath == "/metadata/tag-dict.json")
  883. {
  884. context.WriteTextUtf8(JsonConvert.SerializeObject(MediaTags), Const.ApplicationJson);
  885. }
  886. else if (PathMapping.TryGetValue(requestPath, out var realPath))
  887. {
  888. var ext = requestPath.Split('.').LastOrDefault()?.ToLower();
  889. switch (ext)
  890. {
  891. case "flac": context.Response.ContentType = "audio/flac"; break;
  892. case "m4a": context.Response.ContentType = "audio/mp4"; break;
  893. case "mp3": context.Response.ContentType = "audio/mpeg"; break;
  894. case "aac": context.Response.ContentType = "audio/aac"; break;
  895. case "mp4": context.Response.ContentType = "video/mp4"; break;
  896. case "mkv": context.Response.ContentType = "video/webm"; break;
  897. case "jpg":
  898. case "jpeg": context.Response.ContentType = "image/jpeg"; break;
  899. case "png": context.Response.ContentType = "image/png"; break;
  900. case "bmp": context.Response.ContentType = "image/bmp"; break;
  901. default:
  902. var firstParts = requestPath.Split('/').FirstOrDefault();
  903. switch (firstParts)
  904. {
  905. case "media": context.Response.ContentType = "audio/" + ext; break;
  906. case "bk":
  907. case "cover": context.Response.ContentType = "image/" + ext; break;
  908. }
  909. break;
  910. }
  911. var range = request.Headers.GetValues("Range");
  912. FileStream fs = null;
  913. try
  914. {
  915. fs = File.OpenRead(realPath);
  916. context.Response.Headers.Add("Accept-Ranges", "bytes");
  917. if (range is { Length: > 0 })
  918. {
  919. var rngParts = range[0].Split(new[] { "bytes=", "-" }, StringSplitOptions.RemoveEmptyEntries);
  920. if (rngParts.Length == 1 && long.TryParse(rngParts[0], out var start))
  921. {
  922. fs.Position = start;
  923. context.Response.StatusCode = 206;
  924. context.Response.Headers.Add("Content-Range", $"bytes {start}-{fs.Length - 1}/{fs.Length}");
  925. context.Response.ContentLength64 = fs.Length - start;
  926. fs.CopyTo(context.Response.OutputStream);
  927. }
  928. else if (rngParts.Length == 2 && long.TryParse(rngParts[0], out var start2) && long.TryParse(rngParts[1], out var end))
  929. {
  930. fs.Position = start2;
  931. var realLen = end - start2 + 1;
  932. context.Response.StatusCode = 206;
  933. context.Response.Headers.Add("Content-Range", $"bytes {start2}-{end}/{fs.Length}");
  934. context.Response.ContentLength64 = realLen;
  935. if (realLen < 4096)
  936. {
  937. var buf = new byte[realLen];
  938. fs.Read(buf, 0, buf.Length);
  939. context.Response.OutputStream.Write(buf, 0, buf.Length);
  940. }
  941. else
  942. {
  943. fs.CopyTo(context.Response.OutputStream);
  944. }
  945. }
  946. }
  947. else
  948. {
  949. context.Response.ContentLength64 = fs.Length;
  950. fs.CopyTo(context.Response.OutputStream);
  951. }
  952. }
  953. catch (Exception e)
  954. {
  955. Console.WriteLine(e);
  956. }
  957. finally
  958. {
  959. fs?.Close();
  960. }
  961. }
  962. else
  963. {
  964. context.Response.StatusCode = 404;
  965. }
  966. }
  967. catch (Exception e)
  968. {
  969. Console.WriteLine(e);
  970. try
  971. {
  972. context.Response.StatusCode = 500;
  973. }
  974. catch (Exception exception)
  975. {
  976. Console.WriteLine(exception);
  977. }
  978. }
  979. finally
  980. {
  981. try
  982. {
  983. context.Response.Close();
  984. }
  985. catch (Exception e)
  986. {
  987. Console.WriteLine(e);
  988. }
  989. }
  990. }
  991. private static MediaTag GetTag(string internalPath, bool peekMem = false)
  992. {
  993. if (peekMem)
  994. {
  995. return MediaTags.TryGetValue(internalPath, out var peek) ? peek : null;
  996. }
  997. if (MediaTags.TryGetValue(internalPath, out var mediaTag) || !PathMapping.TryGetValue(internalPath, out var mediaFilePath)) return mediaTag;
  998. try
  999. {
  1000. var fi = new FileInfo(mediaFilePath);
  1001. using var tagLib = TagLib.File.Create(mediaFilePath);
  1002. mediaTag = MediaTags[internalPath] = new MediaTag(
  1003. $"{tagLib.Tag.Title}",
  1004. (int)tagLib.Properties.Duration.TotalSeconds,
  1005. fi.Length,
  1006. string.Join(";", tagLib.Tag.Performers)
  1007. );
  1008. }
  1009. catch (Exception e)
  1010. {
  1011. Console.WriteLine($"ERROR on get tags: {mediaFilePath}{Environment.NewLine} {e.Message}");
  1012. return null;
  1013. }
  1014. return mediaTag;
  1015. }
  1016. }
  1017. }