|
@@ -0,0 +1,526 @@
|
|
|
|
+using System;
|
|
|
|
+using System.Collections.Concurrent;
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
+using System.IO;
|
|
|
|
+using System.Linq;
|
|
|
|
+using System.Net;
|
|
|
|
+using System.Text;
|
|
|
|
+using System.Threading;
|
|
|
|
+using System.Threading.Tasks;
|
|
|
|
+using Microsoft.VisualBasic.FileIO;
|
|
|
|
+using SearchOption = Microsoft.VisualBasic.FileIO.SearchOption;
|
|
|
|
+
|
|
|
|
+namespace FNZCM.ConHost.Ver2
|
|
|
|
+{
|
|
|
|
+ internal static class Program2
|
|
|
|
+ {
|
|
|
|
+ //0. start http server
|
|
|
|
+ //1. scan libraries and fill data struct
|
|
|
|
+ // libs
|
|
|
|
+ // albums
|
|
|
|
+ // Tracks(FLAC / AAC_*)
|
|
|
|
+ // Meta(title / artist / duration)
|
|
|
|
+
|
|
|
|
+ // TODO: Generate thumbnail of BKS
|
|
|
|
+
|
|
|
|
+ private static readonly ConcurrentDictionary<string, Library2> _library = new();
|
|
|
|
+ private static readonly ConcurrentDictionary<string, string> _pathMapping = new();
|
|
|
|
+ private static readonly ConcurrentDictionary<string, MediaTag2> _mediaTags = new();
|
|
|
|
+
|
|
|
|
+ private static bool _isRunning;
|
|
|
|
+ private static bool _isLoading;
|
|
|
|
+ private static DateTime _lastRequestAccepted;
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ private static void Main()
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine("Starting...");
|
|
|
|
+
|
|
|
|
+ var tWorker = new Thread(Working);
|
|
|
|
+ _isRunning = true;
|
|
|
|
+ tWorker.Start();
|
|
|
|
+
|
|
|
|
+ Task.Run(ScanLibrary);
|
|
|
|
+
|
|
|
|
+ Console.WriteLine("Press ENTER to Stop.");
|
|
|
|
+ Console.ReadLine();
|
|
|
|
+
|
|
|
|
+ Console.WriteLine("Shutting down...");
|
|
|
|
+ _isRunning = false;
|
|
|
|
+ tWorker.Join();
|
|
|
|
+
|
|
|
|
+ Console.WriteLine("Stopped.");
|
|
|
|
+
|
|
|
|
+ Console.WriteLine();
|
|
|
|
+ Console.Write("Press ENTER to Exit.");
|
|
|
|
+ Console.ReadLine();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static void ScanLibrary()
|
|
|
|
+ {
|
|
|
|
+ if (_isLoading) return;
|
|
|
|
+ _isLoading = true;
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine("Scanning libraries...");
|
|
|
|
+
|
|
|
|
+ foreach (var kvpLib in ConfigFile.Instance.Libraries)
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine($"Library {kvpLib.Key} - {kvpLib.Value}");
|
|
|
|
+
|
|
|
|
+ var libPath = kvpLib.Key.ToLower();
|
|
|
|
+ var lib = _library[libPath] = new Library2(kvpLib.Key);
|
|
|
|
+ var albDirArray = Directory.GetDirectories(kvpLib.Value);
|
|
|
|
+
|
|
|
|
+ foreach (var albDir in albDirArray)
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine($" Album {albDir}");
|
|
|
|
+
|
|
|
|
+ var albName = Path.GetFileName(albDir);
|
|
|
|
+ var albPath = albName.ToLower();
|
|
|
|
+ var alb = lib.Albums[albPath] = new Album2(albName);
|
|
|
|
+
|
|
|
|
+ var coverFilePath = Path.Combine(albDir, "cover.jpg");
|
|
|
|
+ if (File.Exists(coverFilePath)) _pathMapping[$"/cover/{libPath}/{albPath}/cover.jpg"] = coverFilePath;
|
|
|
|
+
|
|
|
|
+ var bkDir = Path.Combine(albDir, "bk");
|
|
|
|
+ if (Directory.Exists(bkDir))
|
|
|
|
+ {
|
|
|
|
+ var bkFiles = FileSystem.GetFiles(bkDir, SearchOption.SearchTopLevelOnly, ConfigFile.Instance.BkFilePattern);
|
|
|
|
+ foreach (var file in bkFiles)
|
|
|
|
+ {
|
|
|
|
+ var bkName = Path.GetFileName(file);
|
|
|
|
+ var bkPath = bkName.ToLower();
|
|
|
|
+ alb.Bks[bkPath] = bkName;
|
|
|
|
+
|
|
|
|
+ _pathMapping[$"/bk/{libPath}/{albPath}/{bkPath}"] = file;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var mainTrackFiles = FileSystem.GetFiles(albDir, SearchOption.SearchTopLevelOnly, ConfigFile.Instance.MediaFilePattern);
|
|
|
|
+
|
|
|
|
+ foreach (var mainTrackFile in mainTrackFiles)
|
|
|
|
+ {
|
|
|
|
+ var trackName = Path.GetFileName(mainTrackFile);
|
|
|
|
+ var trackPath = trackName.ToLower();
|
|
|
|
+ alb.MainTracks[trackPath] = trackName;
|
|
|
|
+
|
|
|
|
+ _pathMapping[$"/media/{libPath}/{albPath}/{trackPath}"] = mainTrackFile;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var aacTrackDirArray = Directory.GetDirectories(albDir, "AAC_Q*");
|
|
|
|
+ foreach (var aacTrackDir in aacTrackDirArray)
|
|
|
|
+ {
|
|
|
|
+ var aacTrackSetName = Path.GetFileName(aacTrackDir);
|
|
|
|
+ var aacTrackSetPath = aacTrackSetName.ToLower();
|
|
|
|
+ var aacTrackSet = alb.SubTracks[aacTrackSetPath] = new TrackSet(aacTrackSetName);
|
|
|
|
+
|
|
|
|
+ foreach (var file in Directory.GetFiles(aacTrackDir))
|
|
|
|
+ {
|
|
|
|
+ var aacTrackName = Path.GetFileName(file);
|
|
|
|
+ var aacTrackPath = aacTrackName.ToLower();
|
|
|
|
+ aacTrackSet.Tracks[aacTrackPath] = aacTrackName;
|
|
|
|
+
|
|
|
|
+ _pathMapping[$"/media/{libPath}/{albPath}/{aacTrackSetPath}/{aacTrackPath}"] = file;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine($"Load error: {e}");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ _isLoading = false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static void Working()
|
|
|
|
+ {
|
|
|
|
+ var listener = new HttpListener();
|
|
|
|
+ listener.Prefixes.Add(ConfigFile.Instance.ListenPrefix);
|
|
|
|
+ listener.Start();
|
|
|
|
+
|
|
|
|
+ var upTime = DateTime.Now;
|
|
|
|
+
|
|
|
|
+ Console.WriteLine($"HTTP Server started, listening on {ConfigFile.Instance.ListenPrefix}");
|
|
|
|
+
|
|
|
|
+ listener.BeginGetContext(ContextGet, listener);
|
|
|
|
+
|
|
|
|
+ _lastRequestAccepted = DateTime.Now;
|
|
|
|
+ while (_isRunning)
|
|
|
|
+ {
|
|
|
|
+ var timeSpan = DateTime.Now - _lastRequestAccepted;
|
|
|
|
+ var up = DateTime.Now - upTime;
|
|
|
|
+ Console.Title =
|
|
|
|
+ "FNZCM"
|
|
|
|
+ + $" UP {up.Days:00}D {up.Hours:00}H {up.Minutes:00}M {up.Seconds:00}S {up.Milliseconds:000}"
|
|
|
|
+ + $" / "
|
|
|
|
+ + $" LA {timeSpan.Days:00}D {timeSpan.Hours:00}H {timeSpan.Minutes:00}M {timeSpan.Seconds:00}S {timeSpan.Milliseconds:000}"
|
|
|
|
+ ;
|
|
|
|
+
|
|
|
|
+ Thread.Sleep(1000);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ listener.Close();
|
|
|
|
+
|
|
|
|
+ Thread.Sleep(1000);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static void ContextGet(IAsyncResult ar)
|
|
|
|
+ {
|
|
|
|
+ var listener = (HttpListener)ar.AsyncState;
|
|
|
|
+ HttpListenerContext context;
|
|
|
|
+
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ // ReSharper disable once PossibleNullReferenceException
|
|
|
|
+ context = listener.EndGetContext(ar);
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine(e);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (_isRunning) listener.BeginGetContext(ContextGet, listener);
|
|
|
|
+ ProcessRequest(context);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static void ProcessRequest(HttpListenerContext context)
|
|
|
|
+ {
|
|
|
|
+ _lastRequestAccepted = DateTime.Now;
|
|
|
|
+
|
|
|
|
+ var request = context.Request;
|
|
|
|
+ Console.WriteLine($"Request from {request.RemoteEndPoint} {request.HttpMethod} {request.RawUrl}");
|
|
|
|
+
|
|
|
|
+ // GET / show all libraries
|
|
|
|
+
|
|
|
|
+ // foo=library bar=album
|
|
|
|
+ // GET /list/foo/ show all album and cover with name, provide m3u path
|
|
|
|
+ // GET /list/foo/bar/bk/ list all picture as grid
|
|
|
|
+ // GET /list/foo/bar/tracks/ list all tracks as text list
|
|
|
|
+ // GET /list/foo/bar/playlist.m3u8 auto gen
|
|
|
|
+
|
|
|
|
+ // GET /list/foo/bar/aac_q1.00/playlist.m3u8 auto gen
|
|
|
|
+
|
|
|
|
+ // media streaming HTTP Partial RANGE SUPPORT
|
|
|
|
+ // GET /cover/foo/bar/cover.jpg
|
|
|
|
+ // GET /media/foo/bar/01.%20foobar.flac
|
|
|
|
+ // GET /bk/foo/bar/foobar.jpg
|
|
|
|
+
|
|
|
|
+ // GET /media/foo/aac_q1.00/01.%20foobar.m4a
|
|
|
|
+
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ // ReSharper disable once PossibleNullReferenceException
|
|
|
|
+ var requestPath = request.Url.LocalPath.ToLower();
|
|
|
|
+ var pathParts = (IReadOnlyList<string>)requestPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
+
|
|
|
|
+ if (requestPath == "/scan/")
|
|
|
|
+ {
|
|
|
|
+ Task.Run(ScanLibrary);
|
|
|
|
+ context.Response.Redirect("/");
|
|
|
|
+ }
|
|
|
|
+ else if (requestPath == "/")
|
|
|
|
+ {
|
|
|
|
+ var sb = new StringBuilder();
|
|
|
|
+ sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
|
|
|
|
+ sb.Append($"<title> Libraries - {ConfigFile.Instance.Title} </title>");
|
|
|
|
+ sb.Append("<body bgColor=skyBlue style=font-size:3vh>");
|
|
|
|
+
|
|
|
|
+ if (_isLoading) sb.Append("<h2 style=position:fixed;right:0px;top:0px;margin:0>Still Loading...</h2>");
|
|
|
|
+
|
|
|
|
+ sb.Append($"<h1>{ConfigFile.Instance.Title}</h1>");
|
|
|
|
+ sb.Append("<h1>Libraries</h1>");
|
|
|
|
+
|
|
|
|
+ sb.Append("<ul>");
|
|
|
|
+ foreach (var library in _library.OrderBy(p => p.Key))
|
|
|
|
+ {
|
|
|
|
+ sb.Append("<li>");
|
|
|
|
+ sb.Append($"<a href='/list/{library.Key.FuckVlcAndEscape()}/'>{library.Value.Name}</a>");
|
|
|
|
+ sb.Append("</li>");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ sb.Append("</ul>");
|
|
|
|
+
|
|
|
|
+ sb.Append("<a href=/scan/> Scan Libraries</a>");
|
|
|
|
+
|
|
|
|
+ context.Response.WriteText(sb.ToString());
|
|
|
|
+ }
|
|
|
|
+ else if (pathParts.Count == 2 && pathParts[0] == "list")
|
|
|
|
+ {
|
|
|
|
+ var libName = pathParts[1];
|
|
|
|
+ if (_library.TryGetValue(libName, out var l))
|
|
|
|
+ {
|
|
|
|
+ var sb = new StringBuilder();
|
|
|
|
+ sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
|
|
|
|
+ sb.Append($"<title> Albums of {l.Name} - {ConfigFile.Instance.Title} </title>");
|
|
|
|
+ sb.Append(
|
|
|
|
+ "<style>" +
|
|
|
|
+ "a:link{ text-decoration: none; }" +
|
|
|
|
+ "div.item{" +
|
|
|
|
+ " vertical-align:top;" +
|
|
|
|
+ " height:20vh;" +
|
|
|
|
+ " margin-bottom:1vh;" +
|
|
|
|
+ " padding:0.5vh;" +
|
|
|
|
+ " border:solid 1px;" +
|
|
|
|
+ " border-radius:0.5vh;" +
|
|
|
|
+ " font-size:2.5vh;" +
|
|
|
|
+ " overflow:scroll;" +
|
|
|
|
+ "}" +
|
|
|
|
+ "div.item::-webkit-scrollbar{" +
|
|
|
|
+ " display: none;" +
|
|
|
|
+ "}" +
|
|
|
|
+ "img.cover{" +
|
|
|
|
+ " float:left;" +
|
|
|
|
+ " background-size:cover;" +
|
|
|
|
+ " max-width:25vw;" +
|
|
|
|
+ " max-height:20vh" +
|
|
|
|
+ "}" +
|
|
|
|
+ "div.buttons{" +
|
|
|
|
+ "}" +
|
|
|
|
+ "a.button{" +
|
|
|
|
+ " margin-left:4vw" +
|
|
|
|
+ "}" +
|
|
|
|
+ "</style>");
|
|
|
|
+
|
|
|
|
+ sb.Append($"<body bgColor=skyBlue>");
|
|
|
|
+
|
|
|
|
+ if (_isLoading) sb.Append("<h2 style=position:fixed;right:0px;top:0px;margin:0>Still Loading...</h2>");
|
|
|
|
+
|
|
|
|
+ sb.Append($"<h1>Albums of {l.Name}</h1>");
|
|
|
|
+ sb.Append("<div><a href=/>Back to home</a></div>");
|
|
|
|
+
|
|
|
|
+ //Cover list
|
|
|
|
+ foreach (var a in l.Albums.OrderBy(p => p.Key))
|
|
|
|
+ {
|
|
|
|
+ sb.Append("<div class=item>");
|
|
|
|
+ sb.Append($"<img class=cover src=\"/cover/{libName}/{a.Key}/cover.jpg\" />");
|
|
|
|
+ sb.Append("<div class=buttons>");
|
|
|
|
+ sb.Append($"<a class=button href=\"/list/{libName}/{a.Key}/tracks/\">[TRACKERS]</a>");
|
|
|
|
+ if (a.Value.Bks?.Count > 0) sb.Append($"<a class=button href=\"/list/{libName}/{a.Key}/bk/\">[BK]</a>");
|
|
|
|
+ sb.Append($"<a class=button href=\"/list/{libName}/{a.Key.FuckVlcAndEscape()}/playlist.m3u8\">[M3U8]</a>");
|
|
|
|
+ //TODO: AAC M3U8
|
|
|
|
+ sb.Append("</div>");
|
|
|
|
+ sb.Append($"<span>{a.Value.Name}<span>");
|
|
|
|
+ sb.Append("</div>");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ context.Response.ContentType = "text/html";
|
|
|
|
+ context.Response.ContentEncoding = Encoding.UTF8;
|
|
|
|
+ context.Response.WriteText(sb.ToString());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ context.Response.StatusCode = 404;
|
|
|
|
+ //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (pathParts.Count == 4 && pathParts[0] == "list" && pathParts[3] == "tracks")
|
|
|
|
+ {
|
|
|
|
+ var libName = pathParts[1];
|
|
|
|
+ var albPath = pathParts[2];
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ if (_library.TryGetValue(libName, out var l) && l.Albums.TryGetValue(albPath, out var alb))
|
|
|
|
+ {
|
|
|
|
+ var sb = new StringBuilder();
|
|
|
|
+ sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
|
|
|
|
+ sb.Append($"<body bgColor=skyBlue style=font-size:2vh>");
|
|
|
|
+
|
|
|
|
+ if (_isLoading) sb.Append("<h2 style=position:fixed;right:0px;top:0px;margin:0>Still Loading...</h2>");
|
|
|
|
+
|
|
|
|
+ sb.Append($"<h2>Tracks of</h2><h1>{alb.Name}</h1>");
|
|
|
|
+ sb.Append($"<div><a href=/list/{libName}/>Back to library</a></div>");
|
|
|
|
+
|
|
|
|
+ if (alb.SubTracks.Count > 0) sb.Append($"<div>Main</div>");
|
|
|
|
+
|
|
|
|
+ foreach (var kvpTrack in alb.MainTracks.OrderBy(p => p.Key))
|
|
|
|
+ {
|
|
|
|
+ sb.Append($"<li><a href=\"/media/{libName.FuckVlcAndEscape()}/{albPath.FuckVlcAndEscape()}/{kvpTrack.Key.FuckVlcAndEscape()}\" >{kvpTrack.Value}</a></li>");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ foreach (var kvpAacSet in alb.SubTracks.OrderBy(p => p.Key))
|
|
|
|
+ {
|
|
|
|
+ sb.Append($"<div>{kvpAacSet.Value.Name}</div>");
|
|
|
|
+
|
|
|
|
+ foreach (var kvpTrack in kvpAacSet.Value.Tracks.OrderBy(p => p.Key))
|
|
|
|
+ {
|
|
|
|
+ sb.Append($"<li><a href=\"/media/{libName.FuckVlcAndEscape()}/{albPath.FuckVlcAndEscape()}/{kvpAacSet.Key.FuckVlcAndEscape()}/{kvpTrack.Key.FuckVlcAndEscape()}\" >{kvpTrack.Value}</a></li>");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ context.Response.ContentType = "text/html";
|
|
|
|
+ context.Response.ContentEncoding = Encoding.UTF8;
|
|
|
|
+ context.Response.WriteText(sb.ToString());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ context.Response.StatusCode = 404;
|
|
|
|
+ //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (pathParts.Count == 4 && pathParts[0] == "list" && pathParts[3] == "bk")
|
|
|
|
+ {
|
|
|
|
+ var libName = pathParts[1];
|
|
|
|
+ var albPath = pathParts[2];
|
|
|
|
+
|
|
|
|
+ if (_library.TryGetValue(libName, out var lib) && lib.Albums.TryGetValue(albPath, out var alb))
|
|
|
|
+ {
|
|
|
|
+ var sb = new StringBuilder();
|
|
|
|
+ sb.Append("<!DOCTYPE html><html lang=\"zh-cn\"><meta charset=\"UTF-8\">");
|
|
|
|
+ sb.Append($"<body bgColor=skyBlue style=font-size:2vh>");
|
|
|
|
+
|
|
|
|
+ if (_isLoading) sb.Append("<h2 style=position:fixed;right:0px;top:0px;margin:0>Still Loading...</h2>");
|
|
|
|
+
|
|
|
|
+ sb.Append($"<h2>BK of </h2><h1>{alb.Name}</h1>");
|
|
|
|
+ sb.Append($"<div><a href=/list/{libName}/>Back to library</a></div>");
|
|
|
|
+
|
|
|
|
+ foreach (var albBk in alb.Bks.OrderBy(p => p.Key))
|
|
|
|
+ {
|
|
|
|
+ //TODO: auto gen thumbnail 512x512 jpg 80
|
|
|
|
+ sb.Append($"<img src='/bk/{libName.FuckVlcAndEscape()}/{albPath.FuckVlcAndEscape()}/{albBk.Key.FuckVlcAndEscape()}' style=max-width:24vw;max-height:24vw;margin-right:1vw;margin-bottom:1vh; />");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ context.Response.ContentType = "text/html";
|
|
|
|
+ context.Response.ContentEncoding = Encoding.UTF8;
|
|
|
|
+ context.Response.WriteText(sb.ToString());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ context.Response.StatusCode = 404;
|
|
|
|
+ //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (pathParts.Count == 4 && pathParts[0] == "list" && pathParts[3] == "playlist.m3u8")
|
|
|
|
+ {
|
|
|
|
+ //TODO: AAC M3U8
|
|
|
|
+
|
|
|
|
+ var libName = pathParts[1];
|
|
|
|
+ var albPath = pathParts[2];
|
|
|
|
+
|
|
|
|
+ if (_library.TryGetValue(libName, out var lib) && lib.Albums.TryGetValue(albPath, out var alb))
|
|
|
|
+ {
|
|
|
|
+ // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags
|
|
|
|
+ var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}";
|
|
|
|
+
|
|
|
|
+ var sb = new StringBuilder();
|
|
|
|
+
|
|
|
|
+ sb.AppendLine("#EXTM3U");
|
|
|
|
+ foreach (var track in alb.MainTracks.OrderBy(p => p.Key))
|
|
|
|
+ {
|
|
|
|
+ var mediaInternalPath = $"/media/{libName}/{albPath}/{track.Key}";
|
|
|
|
+ if (false == _mediaTags.TryGetValue(mediaInternalPath, out var mediaTag) && _pathMapping.TryGetValue(mediaInternalPath, out var mediaFilePath))
|
|
|
|
+ {
|
|
|
|
+ using var tagLib = TagLib.File.Create(mediaFilePath);
|
|
|
|
+ mediaTag = _mediaTags[mediaInternalPath] = new MediaTag2($"{string.Join(";", tagLib.Tag.Performers)} - {tagLib.Tag.Title}", (int)tagLib.Properties.Duration.TotalSeconds);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (mediaTag != null)
|
|
|
|
+ {
|
|
|
|
+ var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{albPath.FuckVlcAndEscape()}/cover.jpg";
|
|
|
|
+ sb.AppendLine($"#EXTINF:{mediaTag.Duration} tvg-logo=\"{prefix + coverPath}\",{mediaTag.Title}");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var mediaPath = $"/media/{libName.FuckVlcAndEscape()}/{albPath.FuckVlcAndEscape()}/{track.Key.FuckVlcAndEscape()}";
|
|
|
|
+ sb.AppendLine(prefix + mediaPath);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ context.Response.ContentType = "audio/mpegurl";
|
|
|
|
+ context.Response.ContentEncoding = Encoding.UTF8;
|
|
|
|
+ context.Response.WriteText(sb.ToString());
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ context.Response.StatusCode = 404;
|
|
|
|
+ //context.Response.Redirect("https://www.youtube.com/watch?v=dQw4w9WgXcQ");
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else if (_pathMapping.TryGetValue(requestPath, out var realPath))
|
|
|
|
+ {
|
|
|
|
+ var range = request.Headers.GetValues("Range");
|
|
|
|
+
|
|
|
|
+ FileStream fs = null;
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ fs = File.OpenRead(realPath);
|
|
|
|
+
|
|
|
|
+ if (range is { Length: > 0 })
|
|
|
|
+ {
|
|
|
|
+ var rngParts = range[0].Split(new[] { "bytes=", "-" }, StringSplitOptions.RemoveEmptyEntries);
|
|
|
|
+ if (rngParts.Length >= 1 && long.TryParse(rngParts[0], out var start))
|
|
|
|
+ {
|
|
|
|
+ fs.Position = start;
|
|
|
|
+ context.Response.StatusCode = 206;
|
|
|
|
+ context.Response.Headers.Add("Accept-Ranges", "bytes");
|
|
|
|
+ context.Response.Headers.Add("Content-Range", $"bytes {start}-{fs.Length - 1}/{fs.Length}");
|
|
|
|
+ context.Response.ContentLength64 = fs.Length - start;
|
|
|
|
+ context.Response.ContentType = "video/mp4";
|
|
|
|
+ fs.CopyTo(context.Response.OutputStream);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ context.Response.ContentType = "video/mp4";
|
|
|
|
+ context.Response.ContentLength64 = fs.Length;
|
|
|
|
+ fs.CopyTo(context.Response.OutputStream);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine(e);
|
|
|
|
+ }
|
|
|
|
+ finally
|
|
|
|
+ {
|
|
|
|
+ fs?.Close();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ else
|
|
|
|
+ {
|
|
|
|
+ context.Response.StatusCode = 404;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine(e);
|
|
|
|
+
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ context.Response.StatusCode = 500;
|
|
|
|
+ }
|
|
|
|
+ catch (Exception exception)
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine(exception);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ finally
|
|
|
|
+ {
|
|
|
|
+ try
|
|
|
|
+ {
|
|
|
|
+ context.Response.Close();
|
|
|
|
+ }
|
|
|
|
+ catch (Exception e)
|
|
|
|
+ {
|
|
|
|
+ Console.WriteLine(e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static void WriteText(this HttpListenerResponse response, string content)
|
|
|
|
+ {
|
|
|
|
+ var bytes = Encoding.UTF8.GetBytes(content);
|
|
|
|
+ response.OutputStream.Write(bytes);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static string FuckVlcAndEscape(this string input)
|
|
|
|
+ {
|
|
|
|
+ if (input == null) return null;
|
|
|
|
+ return input
|
|
|
|
+ .Replace("[", "%5B")
|
|
|
|
+ .Replace("]", "%5D")
|
|
|
|
+ .Replace("'", "%27")
|
|
|
|
+ ;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|