using System.Drawing; using System.Globalization; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Text; using System.Text.Json; using System.Text.RegularExpressions; using System.Threading; using MpvNet.ExtensionMethod; using MpvNet.Help; using MpvNet.Native; using static MpvNet.Native.LibMpv; namespace MpvNet; public class MainPlayer : MpvClient { public string ConfPath { get => ConfigFolder + "mpv.conf"; } public string GPUAPI { get; set; } = "auto"; public string Path { get; set; } = ""; public string VO { get; set; } = "gpu"; public string UsedInputConfContent { get; set; } = ""; public string VID { get; set; } = ""; public string AID { get; set; } = ""; public string SID { get; set; } = ""; public bool Border { get; set; } = true; public bool FileEnded { get; set; } public bool Fullscreen { get; set; } public bool IsQuitNeeded { set; get; } = true; public bool KeepaspectWindow { get; set; } public bool Paused { get; set; } public bool SnapWindow { get; set; } public bool TaskbarProgress { get; set; } = true; public bool TitleBar { get; set; } = true; public bool WasInitialSizeSet; public bool WindowMaximized { get; set; } public bool WindowMinimized { get; set; } public int Edition { get; set; } public int PlaylistPos { get; set; } = -1; public int Screen { get; set; } = -1; public int VideoRotate { get; set; } public float Autofit { get; set; } = 0.6f; public float AutofitSmaller { get; set; } = 0.3f; public float AutofitLarger { get; set; } = 0.8f; public AutoResetEvent ShutdownAutoResetEvent { get; } = new AutoResetEvent(false); public nint MainHandle { get; set; } public List MediaTracks { get; set; } = new List(); public List BluRayTitles { get; } = new List(); public object MediaTracksLock { get; } = new object(); public Size VideoSize { get; set; } public TimeSpan Duration; public List Clients { get; } = new List(); List? _audioDevices; public event Action? Initialized; public event Action? Pause; public event Action? PlaylistPosChanged; public event Action? VideoSizeChanged; public void Init(IntPtr formHandle, bool processCommandLine) { App.ApplyShowMenuFix(); MainHandle = mpv_create(); Handle = MainHandle; var events = Enum.GetValues().Cast(); foreach (mpv_event_id i in events) { mpv_request_event(MainHandle, i, 0); } mpv_request_log_messages(MainHandle, "no"); if (formHandle != IntPtr.Zero) TaskHelp.Run(MainEventLoop); if (MainHandle == IntPtr.Zero) throw new Exception("error mpv_create"); if (App.IsTerminalAttached) { SetPropertyString("terminal", "yes"); SetPropertyString("input-terminal", "yes"); } if (formHandle != IntPtr.Zero) { SetPropertyString("force-window", "yes"); SetPropertyLong("wid", formHandle.ToInt64()); } SetPropertyInt("osd-duration", 2000); SetPropertyBool("input-default-bindings", true); SetPropertyBool("input-builtin-bindings", false); SetPropertyBool("input-media-keys", true); SetPropertyString("autocreate-playlist", "filter"); SetPropertyString("media-controls", "yes"); SetPropertyString("idle", "yes"); SetPropertyString("screenshot-directory", "~~desktop/"); SetPropertyString("osd-playing-msg", "${media-title}"); SetPropertyString("osc", "yes"); SetPropertyString("config-dir", ConfigFolder); SetPropertyString("config", "yes"); UsedInputConfContent = App.InputConf.GetContent(); if (!string.IsNullOrEmpty(UsedInputConfContent)) SetPropertyString("input-conf", @"memory://" + UsedInputConfContent); if (processCommandLine) CommandLine.ProcessCommandLineArgsPreInit(); if (CommandLine.Contains("config-dir")) { string configDir = CommandLine.GetValue("config-dir"); string fullPath = System.IO.Path.GetFullPath(configDir); App.InputConf.Path = fullPath.AddSep() + "input.conf"; string content = App.InputConf.GetContent(); if (!string.IsNullOrEmpty(content)) SetPropertyString("input-conf", @"memory://" + content); } Environment.SetEnvironmentVariable("MPVNET_VERSION", AppInfo.Version.ToString()); // deprecated mpv_error err = mpv_initialize(MainHandle); if (err < 0) throw new Exception("mpv_initialize error" + BR2 + GetError(err) + BR); string idle = GetPropertyString("idle"); App.Exit = idle == "no" || idle == "once"; Handle = mpv_create_client(MainHandle, "mpvnet"); if (Handle == IntPtr.Zero) throw new Exception("mpv_create_client error"); mpv_request_log_messages(Handle, "info"); if (formHandle != IntPtr.Zero) TaskHelp.Run(EventLoop); // otherwise shutdown is raised before media files are loaded, // this means Lua scripts that use idle might not work correctly SetPropertyString("idle", "yes"); SetPropertyString("user-data/frontend/name", "mpv.net"); SetPropertyString("user-data/frontend/version", AppInfo.Version.ToString()); SetPropertyString("user-data/frontend/process-path", Environment.ProcessPath!); ObservePropertyBool("pause", value => { Paused = value; Pause?.Invoke(); }); VideoRotate = GetPropertyInt("video-rotate"); ObservePropertyInt("video-rotate", value => { if (VideoRotate != value) { VideoRotate = value; UpdateVideoSize("dwidth", "dheight"); } }); ObservePropertyInt("playlist-pos", value => { PlaylistPos = value; PlaylistPosChanged?.Invoke(value); if (FileEnded && value == -1) if (GetPropertyString("keep-open") == "no" && App.Exit) CommandV("quit"); }); Initialized?.Invoke(); } public void Destroy() { mpv_destroy(MainHandle); mpv_destroy(Handle); foreach (var client in Clients) { mpv_destroy(client.Handle); } } public void ProcessProperty(string? name, string? value) { switch (name) { case "autofit": { if (int.TryParse(value?.Trim('%'), out int result)) Autofit = result / 100f; } break; case "autofit-smaller": { if (int.TryParse(value?.Trim('%'), out int result)) AutofitSmaller = result / 100f; } break; case "autofit-larger": { if (int.TryParse(value?.Trim('%'), out int result)) AutofitLarger = result / 100f; } break; case "border": Border = value == "yes"; break; case "fs": case "fullscreen": Fullscreen = value == "yes"; break; case "gpu-api": GPUAPI = value!; break; case "keepaspect-window": KeepaspectWindow = value == "yes"; break; case "screen": Screen = Convert.ToInt32(value); break; case "snap-window": SnapWindow = value == "yes"; break; case "taskbar-progress": TaskbarProgress = value == "yes"; break; case "vo": VO = value!; break; case "window-maximized": WindowMaximized = value == "yes"; break; case "window-minimized": WindowMinimized = value == "yes"; break; case "title-bar": TitleBar = value == "yes"; break; } if (AutofitLarger > 1) AutofitLarger = 1; } string? _configFolder; public string ConfigFolder { get { if (_configFolder == null) { string? mpvnet_home = Environment.GetEnvironmentVariable("MPVNET_HOME"); if (Directory.Exists(mpvnet_home)) return _configFolder = mpvnet_home.AddSep(); _configFolder = Folder.Startup + "portable_config"; if (!Directory.Exists(_configFolder)) _configFolder = Folder.AppData + "mpv.net"; if (!Directory.Exists(_configFolder)) Directory.CreateDirectory(_configFolder); _configFolder = _configFolder.AddSep(); } return _configFolder; } } private readonly Regex ConfRegex = new Regex("^[\\w-]+$", RegexOptions.Compiled); Dictionary? _Conf; public Dictionary Conf { get { if (_Conf != null) return _Conf; App.ApplyInputDefaultBindingsFix(); _Conf = []; if (File.Exists(ConfPath)) { foreach (string? it in File.ReadAllLines(ConfPath)) { string line = it.TrimStart(' ', '-').TrimEnd(); if (line.StartsWith('#')) continue; if (!line.Contains('=')) { if (ConfRegex.Match(line).Success) line += "=yes"; else continue; } string key = line[..line.IndexOf("=")].Trim(); string value = line[(line.IndexOf("=") + 1)..].Trim(); if (value.Contains('#') && !value.StartsWith("#") && !value.StartsWith("'#") && !value.StartsWith("\"#")) value = value[..value.IndexOf("#")].Trim(); _Conf[key] = value; } } foreach (var i in _Conf) { ProcessProperty(i.Key, i.Value); } return _Conf; } } void UpdateVideoSize(string w, string h) { if (string.IsNullOrEmpty(Path)) return; Size size = new Size(GetPropertyInt(w), GetPropertyInt(h)); if (VideoRotate == 90 || VideoRotate == 270) size = new Size(size.Height, size.Width); if (size != VideoSize && size != Size.Empty) { VideoSize = size; VideoSizeChanged?.Invoke(size); } } public void MainEventLoop() { while (true) { mpv_wait_event(MainHandle, -1); } } protected override void OnShutdown() { IsQuitNeeded = false; base.OnShutdown(); ShutdownAutoResetEvent.Set(); } protected override void OnLogMessage(mpv_event_log_message data) { if (data.log_level == mpv_log_level.MPV_LOG_LEVEL_INFO) { string prefix = ConvertFromUtf8(data.prefix); if (prefix == "bd") ProcessBluRayLogMessage(ConvertFromUtf8(data.text)); } base.OnLogMessage(data); } protected override void OnEndFile(mpv_event_end_file data) { base.OnEndFile(data); FileEnded = true; } protected override void OnVideoReconfig() { UpdateVideoSize("dwidth", "dheight"); base.OnVideoReconfig(); } // executed before OnFileLoaded protected override void OnStartFile() { Path = GetPropertyString("path"); base.OnStartFile(); TaskHelp.Run(LoadFolder); } // executed after OnStartFile protected override void OnFileLoaded() { Duration = TimeSpan.FromSeconds(GetPropertyDouble("duration")); if (App.StartSize == "video") WasInitialSizeSet = false; TaskHelp.Run(UpdateTracks); base.OnFileLoaded(); } void ProcessBluRayLogMessage(string msg) { lock (BluRayTitles) { if (msg.Contains(" 0 duration: ")) BluRayTitles.Clear(); if (msg.Contains(" duration: ")) { int start = msg.IndexOf(" duration: ") + 11; BluRayTitles.Add(new TimeSpan( msg.Substring(start, 2).ToInt(), msg.Substring(start + 3, 2).ToInt(), msg.Substring(start + 6, 2).ToInt())); } } } public void SetBluRayTitle(int id) => LoadFiles(new[] { @"bd://" + id }, false, false); public DateTime LastLoad; public void LoadFiles(string[]? files, bool loadFolder, bool append) { if (files == null || files.Length == 0) return; if ((DateTime.Now - LastLoad).TotalMilliseconds < 1000) append = true; LastLoad = DateTime.Now; for (int i = 0; i < files.Length; i++) { string file = files[i]; if (string.IsNullOrEmpty(file)) continue; if (file.Contains('|')) file = file[..file.IndexOf("|")]; file = ConvertFilePath(file); string ext = file.Ext(); if (OperatingSystem.IsWindows()) { switch (ext) { case "avs": LoadAviSynth(); break; case "lnk": file = GetShortcutTarget(file); break; } } if (ext == "iso") LoadISO(file); else if(FileTypes.Subtitle.Contains(ext)) CommandV("sub-add", file); else { if (i == 0 && !append) CommandV("loadfile", file); else CommandV("loadfile", file, "append"); } } if (string.IsNullOrEmpty(GetPropertyString("path"))) SetPropertyInt("playlist-pos", 0); } public static string ConvertFilePath(string path) { if ((path.Contains(":/") && !path.Contains("://")) || (path.Contains(":\\") && path.Contains('/'))) path = path.Replace("/", "\\"); if (!path.Contains(':') && !path.StartsWith("\\\\") && File.Exists(path)) path = System.IO.Path.GetFullPath(path); return path; } public void LoadISO(string path) { using var mi = new MediaInfo(path); if (mi.GetGeneral("Format") == "ISO 9660 / DVD Video") { Command("stop"); Thread.Sleep(500); SetPropertyString("dvd-device", path); LoadFiles([@"dvd://"], false, false); } else { Command("stop"); Thread.Sleep(500); SetPropertyString("bluray-device", path); LoadFiles([@"bd://"], false, false); } } public void LoadDiskFolder(string path) { Command("stop"); Thread.Sleep(500); if (Directory.Exists(path + "\\BDMV")) { SetPropertyString("bluray-device", path); LoadFiles([@"bd://"], false, false); } else { SetPropertyString("dvd-device", path); LoadFiles([@"dvd://"], false, false); } } static readonly object LoadFolderLockObject = new object(); public void LoadFolder() { if (!App.AutoLoadFolder) return; Thread.Sleep(1000); lock (LoadFolderLockObject) { string path = GetPropertyString("path"); if (!File.Exists(path) || GetPropertyInt("playlist-count") != 1) return; string dir = Environment.CurrentDirectory; if (path.Contains(":/") && !path.Contains("://")) path = path.Replace("/", "\\"); if (path.Contains('\\')) dir = System.IO.Path.GetDirectoryName(path)!; List files = FileTypes.GetMediaFiles(Directory.GetFiles(dir)).ToList(); if (OperatingSystem.IsWindows()) files.Sort(new StringLogicalComparer()); int index = files.IndexOf(path); files.Remove(path); foreach (string file in files) CommandV("loadfile", file, "append"); if (index > 0) CommandV("playlist-move", "0", (index + 1).ToString()); } } bool _wasAviSynthLoaded; [SupportedOSPlatform("windows")] void LoadAviSynth() { if (!_wasAviSynthLoaded) { string? dll = Environment.GetEnvironmentVariable("AviSynthDLL"); // StaxRip sets it in portable mode LoadLibrary(File.Exists(dll) ? dll : "AviSynth.dll"); _wasAviSynthLoaded = true; } } [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] static extern IntPtr LoadLibrary(string path); [SupportedOSPlatform("windows")] public static string GetShortcutTarget(string path) { Type? t = Type.GetTypeFromProgID("WScript.Shell"); dynamic? sh = Activator.CreateInstance(t!); return sh?.CreateShortcut(path).TargetPath!; } static string GetLanguage(string id) { foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.NeutralCultures)) if (ci.ThreeLetterISOLanguageName == id || Convert(ci.ThreeLetterISOLanguageName) == id) return ci.EnglishName; return id; static string Convert(string id2) => id2 switch { "bng" => "ben", "ces" => "cze", "deu" => "ger", "ell" => "gre", "eus" => "baq", "fra" => "fre", "hye" => "arm", "isl" => "ice", "kat" => "geo", "mya" => "bur", "nld" => "dut", "sqi" => "alb", "zho" => "chi", _ => id2, }; } static string GetNativeLanguage(string name) { foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.NeutralCultures)) { if (ci.EnglishName == name) return ci.NativeName; } return name; } public void UpdateTracks() { string path = GetPropertyString("path"); if (!path.ToLowerEx().StartsWithEx("bd://")) lock (BluRayTitles) BluRayTitles.Clear(); lock (MediaTracksLock) { if (App.MediaInfo && !path.Contains("://") && !path.Contains(@"\\.\pipe\") && File.Exists(path)) MediaTracks = GetMediaInfoTracks(path); else MediaTracks = GetTracks(); } } public List AudioDevices { get { if (_audioDevices != null) return _audioDevices; _audioDevices = []; string json = GetPropertyString("audio-device-list"); var enumerator = JsonDocument.Parse(json).RootElement.EnumerateArray(); foreach (var element in enumerator) { string name = element.GetProperty("name").GetString()!; string description = element.GetProperty("description").GetString()!; _audioDevices.Add(new StringPair(name, description)); } return _audioDevices; } } public List GetChapters() { List chapters = new List(); int count = GetPropertyInt("chapter-list/count"); for (int x = 0; x < count; x++) { string title = GetPropertyString($"chapter-list/{x}/title"); double time = GetPropertyDouble($"chapter-list/{x}/time"); if (string.IsNullOrEmpty(title) || (title.Length == 12 && title.Contains(':') && title.Contains('.'))) title = "Chapter " + (x + 1); chapters.Add(new Chapter() { Title = title, Time = time }); } return chapters; } public void UpdateExternalTracks() { int trackListTrackCount = GetPropertyInt("track-list/count"); int editionCount = GetPropertyInt("edition-list/count"); int count = MediaTracks.Where(i => i.Type != "g").Count(); lock (MediaTracksLock) { if (count != (trackListTrackCount + editionCount)) { MediaTracks = MediaTracks.Where(i => !i.External).ToList(); MediaTracks.AddRange(GetTracks(false)); } } } private readonly Regex TitleRegex = new Regex(@"^[\._\-]", RegexOptions.Compiled); public List GetTracks(bool includeInternal = true, bool includeExternal = true) { List tracks = new List(); int trackCount = GetPropertyInt("track-list/count"); for (int i = 0; i < trackCount; i++) { bool external = GetPropertyBool($"track-list/{i}/external"); if ((external && !includeExternal) || (!external && !includeInternal)) continue; string type = GetPropertyString($"track-list/{i}/type"); string filename = GetPropertyString($"filename/no-ext"); string title = GetPropertyString($"track-list/{i}/title").Replace(filename, ""); title = TitleRegex.Replace(title, ""); if (type == "video") { string codec = GetPropertyString($"track-list/{i}/codec").ToUpperEx(); if (codec == "MPEG2VIDEO") codec = "MPEG2"; else if (codec == "DVVIDEO") codec = "DV"; MediaTrack track = new MediaTrack(); Add(track, codec); Add(track, GetPropertyString($"track-list/{i}/demux-w") + "x" + GetPropertyString($"track-list/{i}/demux-h")); Add(track, GetPropertyString($"track-list/{i}/demux-fps").Replace(".000000", "") + " FPS"); Add(track, GetPropertyBool($"track-list/{i}/default") ? "Default" : null); track.Text = "V: " + track.Text.Trim(' ', ','); track.Type = "v"; track.ID = GetPropertyInt($"track-list/{i}/id"); tracks.Add(track); } else if (type == "audio") { string codec = GetPropertyString($"track-list/{i}/codec").ToUpperEx(); if (codec.Contains("PCM")) codec = "PCM"; MediaTrack track = new MediaTrack(); Add(track, GetLanguage(GetPropertyString($"track-list/{i}/lang"))); Add(track, codec); Add(track, GetPropertyInt($"track-list/{i}/audio-channels") + " ch"); Add(track, GetPropertyInt($"track-list/{i}/demux-samplerate") / 1000 + " kHz"); Add(track, GetPropertyBool($"track-list/{i}/forced") ? "Forced" : null); Add(track, GetPropertyBool($"track-list/{i}/default") ? "Default" : null); Add(track, GetPropertyBool($"track-list/{i}/external") ? "External" : null); Add(track, title); track.Text = "A: " + track.Text.Trim(' ', ','); track.Type = "a"; track.ID = GetPropertyInt($"track-list/{i}/id"); track.External = external; tracks.Add(track); } else if (type == "sub") { string codec = GetPropertyString($"track-list/{i}/codec").ToUpperEx(); if (codec.Contains("PGS")) codec = "PGS"; else if (codec == "SUBRIP") codec = "SRT"; else if (codec == "WEBVTT") codec = "VTT"; else if (codec == "DVB_SUBTITLE") codec = "DVB"; else if (codec == "DVD_SUBTITLE") codec = "VOB"; MediaTrack track = new MediaTrack(); Add(track, GetLanguage(GetPropertyString($"track-list/{i}/lang"))); Add(track, codec); Add(track, GetPropertyBool($"track-list/{i}/forced") ? "Forced" : null); Add(track, GetPropertyBool($"track-list/{i}/default") ? "Default" : null); Add(track, GetPropertyBool($"track-list/{i}/external") ? "External" : null); Add(track, title); track.Text = "S: " + track.Text.Trim(' ', ','); track.Type = "s"; track.ID = GetPropertyInt($"track-list/{i}/id"); track.External = external; tracks.Add(track); } } if (includeInternal) { int editionCount = GetPropertyInt("edition-list/count"); for (int i = 0; i < editionCount; i++) { string title = GetPropertyString($"edition-list/{i}/title"); if (string.IsNullOrEmpty(title)) title = "Edition " + i; MediaTrack track = new MediaTrack { Text = "E: " + title, Type = "e", ID = i }; tracks.Add(track); } } return tracks; static void Add(MediaTrack track, object? value) { string str = (value + "").Trim(); if (str != "" && !track.Text.Contains(str)) track.Text += " " + str + ","; } } public List GetMediaInfoTracks(string path) { List tracks = new List(); using (MediaInfo mi = new MediaInfo(path)) { MediaTrack track = new MediaTrack(); Add(track, mi.GetGeneral("Format")); Add(track, mi.GetGeneral("FileSize/String")); Add(track, mi.GetGeneral("Duration/String")); Add(track, mi.GetGeneral("OverallBitRate/String")); track.Text = "G: " + track.Text.Trim(' ', ','); track.Type = "g"; tracks.Add(track); int videoCount = mi.GetCount(MediaInfoStreamKind.Video); for (int i = 0; i < videoCount; i++) { string fps = mi.GetVideo(i, "FrameRate"); if (float.TryParse(fps, NumberStyles.Float, CultureInfo.InvariantCulture, out float result)) fps = result.ToString(CultureInfo.InvariantCulture); track = new MediaTrack(); Add(track, mi.GetVideo(i, "Format")); Add(track, mi.GetVideo(i, "Format_Profile")); Add(track, mi.GetVideo(i, "Width") + "x" + mi.GetVideo(i, "Height")); Add(track, mi.GetVideo(i, "BitRate/String")); Add(track, fps + " FPS"); Add(track, (videoCount > 1 && mi.GetVideo(i, "Default") == "Yes") ? "Default" : ""); track.Text = "V: " + track.Text.Trim(' ', ','); track.Type = "v"; track.ID = i + 1; tracks.Add(track); } int audioCount = mi.GetCount(MediaInfoStreamKind.Audio); for (int i = 0; i < audioCount; i++) { string lang = mi.GetAudio(i, "Language/String"); string nativeLang = GetNativeLanguage(lang); string? title = mi.GetAudio(i, "Title"); string format = mi.GetAudio(i, "Format"); if (!string.IsNullOrEmpty(title)) { if (title.ContainsEx("DTS-HD MA")) format = "DTS-MA"; if (title.ContainsEx("DTS-HD MA")) title = title.Replace("DTS-HD MA", ""); if (title.ContainsEx("Blu-ray")) title = title.Replace("Blu-ray", ""); if (title.ContainsEx("UHD ")) title = title.Replace("UHD ", ""); if (title.ContainsEx("EAC")) title = title.Replace("EAC", "E-AC"); if (title.ContainsEx("AC3")) title = title.Replace("AC3", "AC-3"); if (title.ContainsEx(lang)) title = title.Replace(lang, "").Trim(); if (title.ContainsEx(nativeLang)) title = title.Replace(nativeLang, "").Trim(); if (title.ContainsEx("Surround")) title = title.Replace("Surround", ""); if (title.ContainsEx("Dolby Digital")) title = title.Replace("Dolby Digital", ""); if (title.ContainsEx("Stereo")) title = title.Replace("Stereo", ""); if (title.StartsWithEx(format + " ")) title = title.Replace(format + " ", ""); foreach (string i2 in new[] { "2.0", "5.1", "6.1", "7.1" }) if (title.ContainsEx(i2)) title = title.Replace(i2, "").Trim(); if (title.ContainsEx("@ ")) title = title.Replace("@ ", ""); if (title.ContainsEx(" @")) title = title.Replace(" @", ""); if (title.ContainsEx("()")) title = title.Replace("()", ""); if (title.ContainsEx("[]")) title = title.Replace("[]", ""); if (title.TrimEx() == format) title = null; if (!string.IsNullOrEmpty(title)) title = title.Trim(" _-".ToCharArray()); } track = new MediaTrack(); Add(track, lang); Add(track, format); Add(track, mi.GetAudio(i, "Format_Profile")); Add(track, mi.GetAudio(i, "BitRate/String")); Add(track, mi.GetAudio(i, "Channel(s)") + " ch"); Add(track, mi.GetAudio(i, "SamplingRate/String")); Add(track, mi.GetAudio(i, "Forced") == "Yes" ? "Forced" : ""); Add(track, (audioCount > 1 && mi.GetAudio(i, "Default") == "Yes") ? "Default" : ""); Add(track, title); if (track.Text.Contains("MPEG Audio, Layer 2")) track.Text = track.Text.Replace("MPEG Audio, Layer 2", "MP2"); if (track.Text.Contains("MPEG Audio, Layer 3")) track.Text = track.Text.Replace("MPEG Audio, Layer 2", "MP3"); track.Text = "A: " + track.Text.Trim(' ', ','); track.Type = "a"; track.ID = i + 1; tracks.Add(track); } int subCount = mi.GetCount(MediaInfoStreamKind.Text); for (int i = 0; i < subCount; i++) { string codec = mi.GetText(i, "Format").ToUpperEx(); if (codec == "UTF-8") codec = "SRT"; else if (codec == "WEBVTT") codec = "VTT"; else if (codec == "VOBSUB") codec = "VOB"; string lang = mi.GetText(i, "Language/String"); string nativeLang = GetNativeLanguage(lang); string title = mi.GetText(i, "Title"); bool forced = mi.GetText(i, "Forced") == "Yes"; if (!string.IsNullOrEmpty(title)) { if (title.ContainsEx("VobSub")) title = title.Replace("VobSub", "VOB"); if (title.ContainsEx(codec)) title = title.Replace(codec, ""); if (title.ContainsEx(lang.ToLowerEx())) title = title.Replace(lang.ToLowerEx(), lang); if (title.ContainsEx(nativeLang.ToLowerEx())) title = title.Replace(nativeLang.ToLowerEx(), nativeLang).Trim(); if (title.ContainsEx(lang)) title = title.Replace(lang, ""); if (title.ContainsEx(nativeLang)) title = title.Replace(nativeLang, "").Trim(); if (title.ContainsEx("full")) title = title.Replace("full", "").Trim(); if (title.ContainsEx("Full")) title = title.Replace("Full", "").Trim(); if (title.ContainsEx("Subtitles")) title = title.Replace("Subtitles", "").Trim(); if (title.ContainsEx("forced")) title = title.Replace("forced", "Forced").Trim(); if (forced && title.ContainsEx("Forced")) title = title.Replace("Forced", "").Trim(); if (title.ContainsEx("()")) title = title.Replace("()", ""); if (title.ContainsEx("[]")) title = title.Replace("[]", ""); if (!string.IsNullOrEmpty(title)) title = title.Trim(" _-".ToCharArray()); } track = new MediaTrack(); Add(track, lang); Add(track, codec); Add(track, mi.GetText(i, "Format_Profile")); Add(track, forced ? "Forced" : ""); Add(track, (subCount > 1 && mi.GetText(i, "Default") == "Yes") ? "Default" : ""); Add(track, title); track.Text = "S: " + track.Text.Trim(' ', ','); track.Type = "s"; track.ID = i + 1; tracks.Add(track); } } int editionCount = GetPropertyInt("edition-list/count"); for (int i = 0; i < editionCount; i++) { string title = GetPropertyString($"edition-list/{i}/title"); if (string.IsNullOrEmpty(title)) title = "Edition " + i; MediaTrack track = new MediaTrack { Text = "E: " + title, Type = "e", ID = i }; tracks.Add(track); } return tracks; static void Add(MediaTrack track, object? value) { string str = value?.ToStringEx().Trim() ?? ""; if (str != "" && !(track.Text != null && track.Text.Contains(str))) track.Text += " " + str + ","; } } string[]? _profileNames; public string[] ProfileNames { get { if (_profileNames != null) return _profileNames; string[] ignore = ["builtin-pseudo-gui", "encoding", "libmpv", "pseudo-gui", "default"]; string json = GetPropertyString("profile-list"); return _profileNames = JsonDocument.Parse(json).RootElement.EnumerateArray() .Select(it => it.GetProperty("name").GetString()) .Where(it => !ignore.Contains(it)).ToArray()!; } } public string GetProfiles() { string json = GetPropertyString("profile-list"); StringBuilder sb = new StringBuilder(); foreach (var profile in JsonDocument.Parse(json).RootElement.EnumerateArray()) { sb.Append(profile.GetProperty("name").GetString() + BR2); foreach (var it in profile.GetProperty("options").EnumerateArray()) sb.AppendLine($" {it.GetProperty("key").GetString()} = {it.GetProperty("value").GetString()}"); sb.Append(BR); } return sb.ToString(); } public string GetDecoders() { var list = JsonDocument.Parse(GetPropertyString("decoder-list")).RootElement.EnumerateArray() .Select(it => $"{it.GetProperty("codec").GetString()} - {it.GetProperty("description").GetString()}") .OrderBy(it => it); return string.Join(BR, list); } public string GetProtocols() => string.Join(BR, GetPropertyString("protocol-list").Split(',').OrderBy(i => i)); public string GetDemuxers() => string.Join(BR, GetPropertyString("demuxer-lavf-list").Split(',').OrderBy(i => i)); public MpvClient CreateNewPlayer(string name) { var client = new MpvClient { Handle = mpv_create_client(MainHandle, name) }; if (client.Handle == IntPtr.Zero) throw new Exception("Error CreateNewPlayer"); TaskHelp.Run(client.EventLoop); Clients.Add(client); return client; } }