using FNZCM.Core; using Microsoft.VisualBasic.FileIO; 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 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 // disc // Tracks(FLAC / AAC_*) // Meta(title(artist) / duration) // FSI ( size ) // TODO: Generate thumbnail of BKS private static readonly ConcurrentDictionary Libraries = new(); private static readonly ConcurrentDictionary PathMapping = new(); private static readonly ConcurrentDictionary 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) { Console.WriteLine("Still scanning, SKIP"); return; } _isLoading = true; try { ConfigFile.Reload(); Console.WriteLine("Scanning libraries..."); MediaTags.Clear(); PathMapping.Clear(); Libraries.Clear(); foreach (var kvpLib in ConfigFile.Instance.Libraries) { if (_isRunning == false) throw new OperationCanceledException(); Console.WriteLine($"Library {kvpLib.Key} - {kvpLib.Value}"); var libPath = kvpLib.Key.ToLower(); var lib = Libraries[libPath] = new Library2(kvpLib.Key); var discDirArray = Directory.GetDirectories(kvpLib.Value); foreach (var discDir in discDirArray) { if (_isRunning == false) throw new OperationCanceledException(); Console.WriteLine($" Disc {discDir}"); var discName = Path.GetFileName(discDir); var discPath = discName.ToLower(); try { var disc = new Disc(discName); var bkDir = Path.Combine(discDir, "bk"); var mainTrackFiles = FileSystem.GetFiles(discDir, SearchOption.SearchTopLevelOnly, ConfigFile.Instance.MediaFilePattern); foreach (var mainTrackFile in mainTrackFiles) { var trackName = Path.GetFileName(mainTrackFile); var trackPath = trackName.ToLower(); disc.MainTracks[trackPath] = trackName; PathMapping[$"/media/{libPath}/{discPath}/{trackPath}"] = mainTrackFile; } 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(); disc.Bks[bkPath] = bkName; PathMapping[$"/bk/{libPath}/{discPath}/{bkPath}"] = file; } } var aacTrackDirArray = Directory.GetDirectories(discDir, "AAC_Q*"); foreach (var aacTrackDir in aacTrackDirArray) { var aacTrackSetName = Path.GetFileName(aacTrackDir); var aacTrackSetPath = aacTrackSetName.ToLower(); var aacTrackSet = disc.SubTracks[aacTrackSetPath] = new TrackSet(aacTrackSetName); foreach (var file in Directory.GetFiles(aacTrackDir, "*.m4a")) { var aacTrackName = Path.GetFileName(file); var aacTrackPath = aacTrackName.ToLower(); aacTrackSet.Tracks[aacTrackPath] = aacTrackName; PathMapping[$"/media/{libPath}/{discPath}/{aacTrackSetPath}/{aacTrackPath}"] = file; } } var coverFilePath = Path.Combine(discDir, "cover.jpg"); if (File.Exists(coverFilePath)) PathMapping[$"/cover/{libPath}/{discPath}/cover.jpg"] = coverFilePath; lib.Discs[discPath] = disc; } catch (Exception ex) { Console.WriteLine(ex); } } } Console.WriteLine("Looking tags..."); Parallel.ForEach(PathMapping.Keys.Where(p => p.StartsWith("/media/")), k => GetTag(k)); Console.WriteLine("Looking tags...Done"); } 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=disc // GET /list/foo/ show all disc 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 /lib_list/foo/playlist.m3u8 auto gen // GET /lib_list/foo/aac_q1.00/playlist.m3u8 auto gen // 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 image/ // GET /media/foo/bar/01.%20foobar.flac audio/ // GET /media/foo/bar/aac_q1.00/01.%20foobar.m4a audio/ // GET /bk/foo/bar/foobar.jpg image/ try { // ReSharper disable once PossibleNullReferenceException var requestPath = request.Url.LocalPath.ToLower(); var pathParts = (IReadOnlyList)requestPath.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); if (requestPath == "/admin/" && false == request.QueryString.AllKeys.Contains("action")) { var sb = new StringBuilder(); sb.Append(""); sb.Append($" Libraries - {ConfigFile.Instance.Title} "); sb.Append(""); sb.Append($"

Admin

"); sb.Append(""); sb.Append($"
"); sb.Append($"Password: "); sb.Append($"
"); sb.Append($"Operation: "); sb.Append($"
"); context.Response.WriteText(sb.ToString()); } else if (requestPath == "/admin/" && request.QueryString["action"] == "Scan" && request.QueryString["pass"] == ConfigFile.Instance.AdminPassword) { Task.Run(ScanLibrary); context.Response.Redirect("/"); } else if (requestPath == "/admin/") { context.Response.Redirect("/"); } else if (requestPath == "/") { var sb = new StringBuilder(); sb.Append(""); sb.Append($" Libraries - {ConfigFile.Instance.Title} "); sb.Append(""); if (_isLoading) sb.Append("

Still Loading...

"); sb.Append($"

{ConfigFile.Instance.Title}

"); sb.Append($"

Libraries

"); sb.Append($"

(Total number of disc: {Libraries.Sum(p => p.Value.Discs.Count)})

"); sb.Append("
    "); foreach (var library in Libraries.OrderBy(p => p.Key)) { sb.Append("
  • "); sb.Append($"{library.Value.Name}"); sb.Append($"
        Number of disc: {library.Value.Discs.Count}"); sb.Append("
  • "); } sb.Append("
"); sb.Append($"
"); sb.Append($"
Your IP: {context?.Request?.RemoteEndPoint?.Address.ToString() ?? "Unknown"}
"); sb.Append($""); sb.Append($"
-
"); sb.Append($"
Author: Coder (V)
"); sb.Append($""); sb.Append($""); context.Response.WriteText(sb.ToString()); } else if (pathParts.Count == 2 && pathParts[0] == "list") { var libName = pathParts[1]; if (Libraries.TryGetValue(libName, out var lib)) { var sb = new StringBuilder(); sb.Append(""); sb.Append($" Discs of {lib.Name} - {ConfigFile.Instance.Title} "); sb.Append( ""); sb.Append($""); if (_isLoading) sb.Append("

Still Loading...

"); sb.Append($"

Discs of {lib.Name}

"); sb.Append(""); if (!_isLoading) { sb.Append("
"); //big m3u8 var trackKeys = lib.Discs.SelectMany(p => p.Value.MainTracks.Keys.Select(q => new { DiscName = p.Key, TrackName = q })).ToArray(); var totalDur = trackKeys.Sum(p => GetTag($"/media/{libName}/{p.DiscName}/{p.TrackName}", true)?.Duration ?? 0); var totalLen = trackKeys.Sum(p => GetTag($"/media/{libName}/{p.DiscName}/{p.TrackName}", true)?.Length ?? 0); sb.Append($"Number of track: {trackKeys.Length}"); sb.Append($"
{totalDur.FormatDuration()} {totalLen.FormatFileSize()} ALL_M3U8_MAIN"); var subTrackSetNames = lib.Discs.SelectMany(p => p.Value.SubTracks.Keys).Distinct().ToArray(); foreach (var setName in subTrackSetNames) { trackKeys = lib.Discs.SelectMany(p => { if (p.Value.SubTracks.TryGetValue(setName, out var tSet)) { return tSet.Tracks.Select(q => new { DiscName = p.Key, TrackName = q.Key }); } return new string[0].Select(q => new { DiscName = p.Key, TrackName = q }); }).ToArray(); totalDur = trackKeys.Sum(p => GetTag($"/media/{libName}/{p.DiscName}/{setName}/{p.TrackName}", true)?.Duration ?? 0); totalLen = trackKeys.Sum(p => GetTag($"/media/{libName}/{p.DiscName}/{setName}/{p.TrackName}", true)?.Length ?? 0); sb.Append($"
{totalDur.FormatDuration()} {totalLen.FormatFileSize()} ALL_M3U8_{setName.ToUpper()}"); } sb.Append("
"); } //Cover list foreach (var disc in lib.Discs.OrderByDescending(p => p.Key)) { sb.Append("
"); sb.Append($"
"); sb.Append($""); sb.Append($"
{disc.Value.Name}
"); sb.Append($"
"); sb.Append(""); sb.Append("
"); } 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 discPath = pathParts[2]; if (Libraries.TryGetValue(libName, out var l) && l.Discs.TryGetValue(discPath, out var disc)) { var sb = new StringBuilder(); sb.Append(""); sb.Append($""); if (_isLoading) sb.Append("

Still Loading...

"); sb.Append($"

Tracks of

{disc.Name}

"); sb.Append($""); sb.Append($""); var durTotal = 0; var sizeTotal = 0L; var sbm = new StringBuilder(); foreach (var kvpTrack in disc.MainTracks.OrderBy(p => p.Key)) { sbm.Append($"
  • "); sbm.Append($"{kvpTrack.Value}"); var tag = GetTag($"/media/{libName}/{discPath}/{kvpTrack.Key}"); durTotal += tag.Duration; sizeTotal += tag.Length; sbm.Append($"
          {tag.Duration.FormatDuration()} {tag.Length.FormatFileSize()}"); sbm.Append($"
  • "); } sb.Append($"

    Main ({durTotal.FormatDuration()}) {sizeTotal.FormatFileSize()}

    "); sb.Append(sbm); foreach (var kvpSubSet in disc.SubTracks.OrderBy(p => p.Key)) { durTotal = 0; sizeTotal = 0L; sbm.Clear(); foreach (var kvpTrack in kvpSubSet.Value.Tracks.OrderBy(p => p.Key)) { sbm.Append($"
  • "); sbm.Append($"{kvpTrack.Value}"); var tag = GetTag($"/media/{libName}/{discPath}/{kvpSubSet.Key}/{kvpTrack.Key}"); durTotal += tag.Duration; sizeTotal += tag.Length; sbm.Append($"
          {tag.Duration.FormatDuration()} {tag.Length.FormatFileSize()}"); sbm.Append($"
  • "); } sb.Append($"

    {kvpSubSet.Value.Name} ({durTotal.FormatDuration()}) {sizeTotal.FormatFileSize()}

    "); sb.Append(sbm); } 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 discPath = pathParts[2]; if (Libraries.TryGetValue(libName, out var lib) && lib.Discs.TryGetValue(discPath, out var disc)) { var sb = new StringBuilder(); sb.Append(""); sb.Append($""); if (_isLoading) sb.Append("

    Still Loading...

    "); sb.Append($"

    BK of

    {disc.Name}

    "); sb.Append($""); foreach (var discBk in disc.Bks.OrderBy(p => p.Key)) { //TODO: auto gen thumbnail 512x512 jpg 80 sb.Append($""); } 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 == 3 && pathParts[0] == "lib_list" && pathParts[2] == "playlist.m3u8") { var libName = pathParts[1]; if (Libraries.TryGetValue(libName, out var lib)) { var sb = new StringBuilder(); sb.AppendLine("#EXTM3U"); var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}"; foreach (var discKvp in lib.Discs.OrderByDescending(p => p.Key)) { var disc = discKvp.Value; var discPath = discKvp.Key; var tracks = disc.MainTracks; foreach (var track in tracks.OrderBy(p => p.Key)) { var mediaTag = GetTag($"/media/{libName}/{discPath}/{track.Key}"); if (mediaTag != null) { var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/cover.jpg"; sb.AppendLine($"#EXTINF:{mediaTag.Duration} tvg-logo=\"{prefix + coverPath}\",{mediaTag.Title}"); } var mediaPath = $"/media/{libName.FuckVlcAndEscape()}/{discPath.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 (pathParts.Count == 4 && pathParts[0] == "lib_list" && pathParts[3] == "playlist.m3u8") { var libName = pathParts[1]; var trackSetName = pathParts[2]; if (Libraries.TryGetValue(libName, out var lib)) { var sb = new StringBuilder(); sb.AppendLine("#EXTM3U"); var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}"; foreach (var discKvp in lib.Discs.OrderByDescending(p => p.Key)) { var disc = discKvp.Value; var discPath = discKvp.Key; if (disc.SubTracks.TryGetValue(trackSetName, out var tracksSet)) { var tracks = tracksSet.Tracks; foreach (var track in tracks.OrderBy(p => p.Key)) { var mediaTag = GetTag($"/media/{libName}/{discPath}/{trackSetName}/{track.Key}"); if (mediaTag != null) { var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/cover.jpg"; sb.AppendLine($"#EXTINF:{mediaTag.Duration} tvg-logo=\"{prefix + coverPath}\",{mediaTag.Title}"); } var mediaPath = $"/media/{libName.FuckVlcAndEscape()}/{discPath.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 (pathParts.Count == 4 && pathParts[0] == "list" && pathParts[3] == "playlist.m3u8") { var libName = pathParts[1]; var discPath = pathParts[2]; if (Libraries.TryGetValue(libName, out var lib) && lib.Discs.TryGetValue(discPath, out var disc)) { // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}"; var sb = new StringBuilder(); sb.AppendLine("#EXTM3U"); foreach (var track in disc.MainTracks.OrderBy(p => p.Key)) { var mediaTag = GetTag($"/media/{libName}/{discPath}/{track.Key}"); if (mediaTag != null) { var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/cover.jpg"; sb.AppendLine($"#EXTINF:{mediaTag.Duration} tvg-logo=\"{prefix + coverPath}\",{mediaTag.Title}"); } var mediaPath = $"/media/{libName.FuckVlcAndEscape()}/{discPath.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 (pathParts.Count == 5 && pathParts[0] == "list" && pathParts[4] == "playlist.m3u8") { var libName = pathParts[1]; var discPath = pathParts[2]; var subSetPath = pathParts[3]; if (Libraries.TryGetValue(libName, out var lib) && lib.Discs.TryGetValue(discPath, out var disc)) { // ReSharper disable once BitwiseOperatorOnEnumWithoutFlags var prefix = $"{request.Url.GetLeftPart(UriPartial.Scheme | UriPartial.Authority)}"; if (false == disc.SubTracks.TryGetValue(subSetPath, out var trackSet)) { context.Response.StatusCode = 404; } else { var sb = new StringBuilder(); sb.AppendLine("#EXTM3U"); foreach (var track in trackSet.Tracks.OrderBy(p => p.Key)) { var mediaTag = GetTag($"/media/{libName}/{discPath}/{subSetPath}/{track.Key}"); if (mediaTag != null) { var coverPath = $"/cover/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/cover.jpg"; sb.AppendLine($"#EXTINF:{mediaTag.Duration} tvg-logo=\"{prefix + coverPath}\",{mediaTag.Title}"); } var mediaPath = $"/media/{libName.FuckVlcAndEscape()}/{discPath.FuckVlcAndEscape()}/{subSetPath.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 ext = requestPath.Split('.').LastOrDefault()?.ToLower(); switch (ext) { case "flac": context.Response.ContentType = "audio/flac"; break; case "m4a": context.Response.ContentType = "audio/mp4"; break; case "mp3": context.Response.ContentType = "audio/mpeg"; break; case "aac": context.Response.ContentType = "audio/aac"; break; case "mp4": context.Response.ContentType = "video/mp4"; break; case "mkv": context.Response.ContentType = $"video/webm"; break; case "jpg": case "jpeg": context.Response.ContentType = $"image/jpeg"; break; case "png": context.Response.ContentType = $"image/png"; break; case "bmp": context.Response.ContentType = $"image/bmp"; break; default: var firstParts = requestPath.Split('/').FirstOrDefault(); switch (firstParts) { case "media": context.Response.ContentType = "audio/" + ext; break; case "bk": case "cover": context.Response.ContentType = "image/" + ext; break; } break; } 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; fs.CopyTo(context.Response.OutputStream); } } else { 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 string FormatDuration(this int second) { var sbd = new StringBuilder(); var ts = TimeSpan.FromSeconds(second); if (ts.TotalHours > 1) sbd.Append($"{ts.TotalHours:00}:"); sbd.Append($"{ts.Minutes:00}:{ts.Seconds:00}"); return sbd.ToString(); } private static string FormatFileSize(this long length) { string[] sizes = { "B", "KB", "MB", "GB", "TB" }; double len = length; int order = 0; while (len >= 1024 && order < sizes.Length - 1) { order++; len = len / 1024; } // Adjust the format string to your preferences. For example "{0:0.#}{1}" would // show a single decimal place, and no space. string result = $"{len:000.00} {sizes[order]}"; return result; } 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") ; } private static MediaTag2 GetTag(string internalPath, bool peek = false) { if (peek) { if (MediaTags.TryGetValue(internalPath, out var mediaTag)) { return mediaTag; } return null; } else { if (false == MediaTags.TryGetValue(internalPath, out var mediaTag) && PathMapping.TryGetValue(internalPath, out var mediaFilePath)) { try { var fi = new FileInfo(mediaFilePath); using var tagLib = TagLib.File.Create(mediaFilePath); mediaTag = MediaTags[internalPath] = new MediaTag2( $"{string.Join(";", tagLib.Tag.Performers)} - {tagLib.Tag.Title}", (int)tagLib.Properties.Duration.TotalSeconds, fi.Length ); } catch (Exception e) { Console.WriteLine($"ERROR on lookup tags: {mediaFilePath}{Environment.NewLine} {e.Message}"); return null; } } return mediaTag; } } } }