using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; using static libmpv; using static mpvnet.Global; using System.Text.RegularExpressions; namespace mpvnet { public class CorePlayer { public static string[] VideoTypes { get; set; } = "264 265 asf avc avi avs dav flv h264 h265 hevc m2t m2ts m2v m4v mkv mov mp4 mpeg mpg mpv mts ts vob vpy webm wmv y4m".Split(' '); public static string[] AudioTypes { get; set; } = "aac ac3 dts dtshd dtshr dtsma eac3 flac m4a mka mp2 mp3 mpa mpc ogg opus thd w64 wav".Split(' '); public static string[] ImageTypes { get; set; } = { "jpg", "bmp", "png", "gif", "webp" }; public static string[] SubtitleTypes { get; } = { "srt", "ass", "idx", "sub", "sup", "ttxt", "txt", "ssa", "smi", "mks" }; public event Action LogMessageAsync; // log-message MPV_EVENT_LOG_MESSAGE public event Action EndFileAsync; // end-file MPV_EVENT_END_FILE public event Action ClientMessageAsync; // client-message MPV_EVENT_CLIENT_MESSAGE public event Action GetPropertyReplyAsync; // get-property-reply MPV_EVENT_GET_PROPERTY_REPLY public event Action SetPropertyReplyAsync; // set-property-reply MPV_EVENT_SET_PROPERTY_REPLY public event Action CommandReplyAsync; // command-reply MPV_EVENT_COMMAND_REPLY public event Action StartFileAsync; // start-file MPV_EVENT_START_FILE public event Action FileLoadedAsync; // file-loaded MPV_EVENT_FILE_LOADED public event Action VideoReconfigAsync; // video-reconfig MPV_EVENT_VIDEO_RECONFIG public event Action AudioReconfigAsync; // audio-reconfig MPV_EVENT_AUDIO_RECONFIG public event Action SeekAsync; // seek MPV_EVENT_SEEK public event Action PlaybackRestartAsync; // playback-restart MPV_EVENT_PLAYBACK_RESTART public event ActionLogMessage; // log-message MPV_EVENT_LOG_MESSAGE public event Action EndFile; // end-file MPV_EVENT_END_FILE public event Action ClientMessage; // client-message MPV_EVENT_CLIENT_MESSAGE public event Action Shutdown; // shutdown MPV_EVENT_SHUTDOWN public event Action GetPropertyReply; // get-property-reply MPV_EVENT_GET_PROPERTY_REPLY public event Action SetPropertyReply; // set-property-reply MPV_EVENT_SET_PROPERTY_REPLY public event Action CommandReply; // command-reply MPV_EVENT_COMMAND_REPLY public event Action StartFile; // start-file MPV_EVENT_START_FILE public event Action FileLoaded; // file-loaded MPV_EVENT_FILE_LOADED public event Action VideoReconfig; // video-reconfig MPV_EVENT_VIDEO_RECONFIG public event Action AudioReconfig; // audio-reconfig MPV_EVENT_AUDIO_RECONFIG public event Action Seek; // seek MPV_EVENT_SEEK public event Action PlaybackRestart; // playback-restart MPV_EVENT_PLAYBACK_RESTART public event Action Initialized; public event Action InitializedAsync; public event Action Pause; public event Action ShowMenu; public event Action WindowScaleMpv; public event Action ScaleWindow; public event Action WindowScaleNET; public event Action PlaylistPosChanged; public event Action PlaylistPosChangedAsync; public event Action VideoSizeChanged; public event Action VideoSizeChangedAsync; public Dictionary> PropChangeActions { get; set; } = new Dictionary>(); public Dictionary>> IntPropChangeActions { get; set; } = new Dictionary>>(); public Dictionary>> BoolPropChangeActions { get; set; } = new Dictionary>>(); public Dictionary>> DoublePropChangeActions { get; set; } = new Dictionary>>(); public Dictionary>> StringPropChangeActions { get; set; } = new Dictionary>>(); public AutoResetEvent ShutdownAutoResetEvent { get; } = new AutoResetEvent(false); public AutoResetEvent VideoSizeAutoResetEvent { get; } = new AutoResetEvent(false); public IntPtr Handle { get; set; } public IntPtr NamedHandle { 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 string ConfPath { get => ConfigFolder + "mpv.conf"; } public string GPUAPI { get; set; } = "auto"; public string InputConfPath => ConfigFolder + "input.conf"; public string Path { get; set; } = ""; public string VO { get; set; } = "gpu"; 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 Shown { get; set; } public bool SnapWindow { get; set; } public bool TaskbarProgress { 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 void Init(IntPtr handle) { ApplyShowMenuFix(); Handle = mpv_create(); mpv_request_log_messages(Handle, "no"); App.RunTask(() => MainEventLoop()); if (Handle == IntPtr.Zero) throw new Exception("error mpv_create"); if (App.IsTerminalAttached) { SetPropertyString("terminal", "yes"); SetPropertyString("input-terminal", "yes"); } SetPropertyInt("osd-duration", 2000); SetPropertyLong("wid", handle.ToInt64()); SetPropertyBool("input-default-bindings", true); SetPropertyBool("input-builtin-bindings", false); SetPropertyString("watch-later-options", "mute"); SetPropertyString("screenshot-directory", "~~desktop/"); SetPropertyString("osd-playing-msg", "${media-title}"); SetPropertyString("osc", "yes"); SetPropertyString("force-window", "yes"); SetPropertyString("config-dir", ConfigFolder); SetPropertyString("config", "yes"); ProcessCommandLine(true); Environment.SetEnvironmentVariable("MPVNET_VERSION", Application.ProductVersion); mpv_error err = mpv_initialize(Handle); if (err < 0) throw new Exception("mpv_initialize error" + BR2 + GetError(err) + BR); string idle = GetPropertyString("idle"); App.Exit = idle == "no" || idle == "once"; NamedHandle = mpv_create_client(Handle, "mpvnet"); if (NamedHandle == IntPtr.Zero) throw new Exception("mpv_create_client error"); mpv_request_log_messages(NamedHandle, "terminal-default"); App.RunTask(() => EventLoop()); // otherwise shutdown is raised before media files are loaded, // this means Lua scripts that use idle might not work correctly SetPropertyString("idle", "yes"); ObservePropertyDouble("window-scale", value => WindowScaleMpv(value)); ObservePropertyString("path", value => Path = value); ObservePropertyBool("pause", value => { Paused = value; Pause(); }); ObservePropertyInt("video-rotate", value => { VideoRotate = value; UpdateVideoSize("dwidth", "dheight"); }); ObservePropertyInt("playlist-pos", value => { PlaylistPos = value; InvokeEvent(PlaylistPosChanged, PlaylistPosChangedAsync, value); if (value == -1 && Core.Shown) ShowLogo(); if (value != -1) HideLogo(); if (FileEnded && value == -1) { if (GetPropertyString("keep-open") == "no" && App.Exit) Core.CommandV("quit"); } }); if (!GetPropertyBool("osd-scale-by-window")) App.StartThreshold = 0; Initialized?.Invoke(); InvokeAsync(InitializedAsync); } void ApplyShowMenuFix() { if (App.Settings.ShowMenuFixApplied) return; if (File.Exists(InputConfPath)) { string content = File.ReadAllText(InputConfPath); if (!content.Contains("script-message mpv.net show-menu") && !content.Contains("script-message-to mpvnet show-menu")) File.WriteAllText(InputConfPath, BR + content.Trim() + BR + "MBTN_Right script-message-to mpvnet show-menu" + BR); } App.Settings.ShowMenuFixApplied = true; } void ApplyInputDefaultBindingsFix() { if (App.Settings.InputDefaultBindingsFixApplied) return; if (File.Exists(ConfPath)) { string content = File.ReadAllText(ConfPath); if (content.Contains("input-default-bindings = no")) File.WriteAllText(ConfPath, content.Replace("input-default-bindings = no", "")); if (content.Contains("input-default-bindings=no")) File.WriteAllText(ConfPath, content.Replace("input-default-bindings=no", "")); } App.Settings.InputDefaultBindingsFixApplied = true; } 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; } if (AutofitLarger > 1) AutofitLarger = 1; } bool? _UseNewMsgModel; public bool UseNewMsgModel { get { if (!_UseNewMsgModel.HasValue) _UseNewMsgModel = InputConfContent.Contains("script-message-to mpvnet"); return _UseNewMsgModel.Value; } } string _InputConfContent; public string InputConfContent { get { if (_InputConfContent == null) _InputConfContent = File.ReadAllText(Core.InputConfPath); return _InputConfContent; } } string _ConfigFolder; public string ConfigFolder { get { if (_ConfigFolder == null) { _ConfigFolder = Folder.Startup + "portable_config"; if (!Directory.Exists(_ConfigFolder)) _ConfigFolder = Folder.AppData + "mpv.net"; if (!Directory.Exists(_ConfigFolder)) { try { using (Process proc = new Process()) { proc.StartInfo.UseShellExecute = false; proc.StartInfo.CreateNoWindow = true; proc.StartInfo.FileName = "powershell.exe"; proc.StartInfo.Arguments = $@"-Command New-Item -Path '{_ConfigFolder}' -ItemType Directory"; proc.Start(); proc.WaitForExit(); } } catch (Exception) {} if (!Directory.Exists(_ConfigFolder)) Directory.CreateDirectory(_ConfigFolder); } _ConfigFolder = _ConfigFolder.AddSep(); if (!File.Exists(_ConfigFolder + "input.conf")) { File.WriteAllText(_ConfigFolder + "input.conf", Properties.Resources.input_conf); string scriptOptsPath = _ConfigFolder + "script-opts" + System.IO.Path.DirectorySeparatorChar; if (!Directory.Exists(scriptOptsPath)) { Directory.CreateDirectory(scriptOptsPath); File.WriteAllText(scriptOptsPath + "console.conf", BR + "scale=1.5" + BR); string content = BR + "scalewindowed=1.5" + BR + "hidetimeout=2000" + BR + "idlescreen=no" + BR; File.WriteAllText(scriptOptsPath + "osc.conf", content); } } } return _ConfigFolder; } } Dictionary _Conf; public Dictionary Conf { get { if (_Conf == null) { ApplyInputDefaultBindingsFix(); _Conf = new Dictionary(); if (File.Exists(ConfPath)) foreach (var i in File.ReadAllLines(ConfPath)) if (i.Contains("=") && !i.TrimStart().StartsWith("#")) { string key = i.Substring(0, i.IndexOf("=")).Trim(); string value = i.Substring(i.IndexOf("=") + 1).Trim(); if (value.Contains("#") && !value.StartsWith("#") && !value.StartsWith("'#") && !value.StartsWith("\"#")) value = value.Substring(0, value.IndexOf("#")).Trim(); _Conf[key] = value; } foreach (var i in _Conf) ProcessProperty(i.Key, i.Value); } return _Conf; } } public void LoadScripts() { if (Directory.Exists(ConfigFolder + "scripts-ps")) foreach (string file in Directory.GetFiles(ConfigFolder + "scripts-ps", "*.ps1")) App.RunTask(() => InvokePowerShellScript(file)); } public void InvokePowerShellScript(string file) { PowerShell ps = new PowerShell(); ps.Variables.Add(new KeyValuePair("core", Core)); ps.Variables.Add(new KeyValuePair("window", MainForm.Instance)); ps.Scripts.Add("Using namespace mpvnet; [Reflection.Assembly]::LoadWithPartialName('mpvnet')" + BR); string eventCode = @" $eventJob = Register-ObjectEvent -InputObject $mp -EventName Event -Action { foreach ($pair in $mp.EventHandlers) { if ($pair.Key -eq $args[0]) { if ($args.Length -gt 1) { $args2 = $args[1] } Invoke-Command -ScriptBlock $pair.Value -ArgumentList $args2 } } } $mp.RedirectStreams($eventJob) "; string propertyChangedCode = @" $propertyChangedJob = Register-ObjectEvent -InputObject $mp -EventName PropertyChanged -Action { foreach ($pair in $mp.PropChangedHandlers) { if ($pair.Key -eq $args[0]) { if ($args.Length -gt 1) { $args2 = $args[1] } Invoke-Command -ScriptBlock $pair.Value -ArgumentList $args2 } } } $mp.RedirectStreams($propertyChangedJob) "; ps.Scripts.Add(eventCode); ps.Scripts.Add(propertyChangedCode); ps.Scripts.Add(File.ReadAllText(file)); ps.Module = System.IO.Path.GetFileName(file); ps.Print = true; lock (PowerShell.References) PowerShell.References.Add(ps); ps.Invoke(); } void UpdateVideoSize(string w, string h) { Size size = new Size(GetPropertyInt(w), GetPropertyInt(h)); if (size.Width == 0 || size.Height == 0) return; if (VideoRotate == 90 || VideoRotate == 270) size = new Size(size.Height, size.Width); if (VideoSize != size) { VideoSize = size; InvokeEvent(VideoSizeChanged, VideoSizeChangedAsync, size); VideoSizeAutoResetEvent.Set(); } } public void MainEventLoop() { while (true) mpv_wait_event(Handle, -1); } public void EventLoop() { while (true) { IntPtr ptr = mpv_wait_event(NamedHandle, -1); mpv_event evt = (mpv_event)Marshal.PtrToStructure(ptr, typeof(mpv_event)); try { switch (evt.event_id) { case mpv_event_id.MPV_EVENT_SHUTDOWN: IsQuitNeeded = false; Shutdown?.Invoke(); WriteHistory(null); ShutdownAutoResetEvent.Set(); return; case mpv_event_id.MPV_EVENT_LOG_MESSAGE: { var data = (mpv_event_log_message)Marshal.PtrToStructure(evt.data, typeof(mpv_event_log_message)); if (data.log_level == mpv_log_level.MPV_LOG_LEVEL_INFO) { string prefix = ConvertFromUtf8(data.prefix); if (prefix == "bd") ProcessBluRayLogMessage(ConvertFromUtf8(data.text)); } if (LogMessage != null || LogMessageAsync != null) { string msg = $"[{ConvertFromUtf8(data.prefix)}] {ConvertFromUtf8(data.text)}"; InvokeAsync(LogMessageAsync, data.log_level, msg); LogMessage?.Invoke(data.log_level, msg); } } break; case mpv_event_id.MPV_EVENT_CLIENT_MESSAGE: { var data = (mpv_event_client_message)Marshal.PtrToStructure(evt.data, typeof(mpv_event_client_message)); string[] args = ConvertFromUtf8Strings(data.args, data.num_args); if (UseNewMsgModel && args[0] != "mpv.net") App.RunTask(() => Commands.Execute(args[0], args.Skip(1).ToArray())); else if (args.Length > 1 && args[0] == "mpv.net") App.RunTask(() => Commands.Execute(args[1], args.Skip(2).ToArray())); if (args.Length > 1 && args[0] == "osc-idlescreen") { if (args[1] == "no") HideLogo(); else if (args[1] == "yes" && PlaylistPos == -1) ShowLogo(); } InvokeAsync(ClientMessageAsync, args); ClientMessage?.Invoke(args); } break; case mpv_event_id.MPV_EVENT_VIDEO_RECONFIG: UpdateVideoSize("dwidth", "dheight"); InvokeEvent(VideoReconfig, VideoReconfigAsync); break; case mpv_event_id.MPV_EVENT_END_FILE: { var data = (mpv_event_end_file)Marshal.PtrToStructure(evt.data, typeof(mpv_event_end_file)); var reason = (mpv_end_file_reason)data.reason; InvokeAsync(EndFileAsync, reason); EndFile?.Invoke(reason); FileEnded = true; } break; case mpv_event_id.MPV_EVENT_FILE_LOADED: { if (App.AutoPlay && Paused) SetPropertyBool("pause", false); App.QuickBookmark = 0; HideLogo(); Duration = TimeSpan.FromSeconds(GetPropertyDouble("duration")); if (App.StartSize == "video") WasInitialSizeSet = false; string path = GetPropertyString("path"); if (!VideoTypes.Contains(path.Ext()) || AudioTypes.Contains(path.Ext())) { UpdateVideoSize("width", "height"); VideoSizeAutoResetEvent.Set(); } App.RunTask(new Action(() => UpdateTracks())); App.RunTask(new Action(() => { if (path.Contains("://")) path = GetPropertyString("media-title"); WriteHistory(path); })); InvokeEvent(FileLoaded, FileLoadedAsync); } break; case mpv_event_id.MPV_EVENT_PROPERTY_CHANGE: { var data = (mpv_event_property)Marshal.PtrToStructure(evt.data, typeof(mpv_event_property)); if (data.format == mpv_format.MPV_FORMAT_FLAG) { lock (BoolPropChangeActions) foreach (var pair in BoolPropChangeActions) if (pair.Key == data.name) { bool value = Marshal.PtrToStructure(data.data) == 1; foreach (var action in pair.Value) action.Invoke(value); } } else if (data.format == mpv_format.MPV_FORMAT_STRING) { lock (StringPropChangeActions) foreach (var pair in StringPropChangeActions) if (pair.Key == data.name) { string value = ConvertFromUtf8(Marshal.PtrToStructure(data.data)); foreach (var action in pair.Value) action.Invoke(value); } } else if (data.format == mpv_format.MPV_FORMAT_INT64) { lock (IntPropChangeActions) foreach (var pair in IntPropChangeActions) if (pair.Key == data.name) { int value = Marshal.PtrToStructure(data.data); foreach (var action in pair.Value) action.Invoke(value); } } else if (data.format == mpv_format.MPV_FORMAT_NONE) { lock (PropChangeActions) foreach (var pair in PropChangeActions) if (pair.Key == data.name) foreach (var action in pair.Value) action.Invoke(); } else if (data.format == mpv_format.MPV_FORMAT_DOUBLE) { lock (DoublePropChangeActions) foreach (var pair in DoublePropChangeActions) if (pair.Key == data.name) { double value = Marshal.PtrToStructure(data.data); foreach (var action in pair.Value) action.Invoke(value); } } } break; case mpv_event_id.MPV_EVENT_GET_PROPERTY_REPLY: InvokeEvent(GetPropertyReply, GetPropertyReplyAsync); break; case mpv_event_id.MPV_EVENT_SET_PROPERTY_REPLY: InvokeEvent(SetPropertyReply, SetPropertyReplyAsync); break; case mpv_event_id.MPV_EVENT_COMMAND_REPLY: InvokeEvent(CommandReply, CommandReplyAsync); break; case mpv_event_id.MPV_EVENT_START_FILE: InvokeEvent(StartFile, StartFileAsync); App.RunTask(() => LoadFolder()); break; case mpv_event_id.MPV_EVENT_AUDIO_RECONFIG: InvokeEvent(AudioReconfig, AudioReconfigAsync); break; case mpv_event_id.MPV_EVENT_SEEK: InvokeEvent(Seek, SeekAsync); break; case mpv_event_id.MPV_EVENT_PLAYBACK_RESTART: InvokeEvent(PlaybackRestart, PlaybackRestartAsync); break; } } catch (Exception ex) { App.ShowException(ex); } } } 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); } void InvokeEvent(Action action, Action asyncAction) { InvokeAsync(asyncAction); action?.Invoke(); } void InvokeEvent(Action action, Action asyncAction, T t) { InvokeAsync(asyncAction, t); action?.Invoke(t); } void InvokeAsync(Action action) { if (action != null) { foreach (Action a in action.GetInvocationList()) { var a2 = a; App.RunTask(a2); } } } void InvokeAsync(Action action, T t) { if (action != null) { foreach (Action a in action.GetInvocationList()) { var a2 = a; App.RunTask(() => a2.Invoke(t)); } } } void InvokeAsync(Action action, T1 t1, T2 t2) { if (action != null) { foreach (Action a in action.GetInvocationList()) { var a2 = a; App.RunTask(() => a2.Invoke(t1, t2)); } } } public void Command(string command) { mpv_error err = mpv_command_string(Handle, command); if (err < 0) HandleError(err, "error executing command: " + command); } public void CommandV(params string[] args) { int count = args.Length + 1; IntPtr[] pointers = new IntPtr[count]; IntPtr rootPtr = Marshal.AllocHGlobal(IntPtr.Size * count); for (int index = 0; index < args.Length; index++) { var bytes = GetUtf8Bytes(args[index]); IntPtr ptr = Marshal.AllocHGlobal(bytes.Length); Marshal.Copy(bytes, 0, ptr, bytes.Length); pointers[index] = ptr; } Marshal.Copy(pointers, 0, rootPtr, count); mpv_error err = mpv_command(Handle, rootPtr); foreach (IntPtr ptr in pointers) Marshal.FreeHGlobal(ptr); Marshal.FreeHGlobal(rootPtr); if (err < 0) HandleError(err, "error executing command: " + string.Join("\n", args)); } public string Expand(string value) { if (value == null) return ""; if (!value.Contains("${")) return value; string[] args = { "expand-text", value }; int count = args.Length + 1; IntPtr[] pointers = new IntPtr[count]; IntPtr rootPtr = Marshal.AllocHGlobal(IntPtr.Size * count); for (int index = 0; index < args.Length; index++) { var bytes = GetUtf8Bytes(args[index]); IntPtr ptr = Marshal.AllocHGlobal(bytes.Length); Marshal.Copy(bytes, 0, ptr, bytes.Length); pointers[index] = ptr; } Marshal.Copy(pointers, 0, rootPtr, count); IntPtr resultNodePtr = Marshal.AllocHGlobal(16); mpv_error err = mpv_command_ret(Handle, rootPtr, resultNodePtr); foreach (IntPtr ptr in pointers) Marshal.FreeHGlobal(ptr); Marshal.FreeHGlobal(rootPtr); if (err < 0) { HandleError(err, "error executing command: " + string.Join("\n", args)); Marshal.FreeHGlobal(resultNodePtr); return "property expansion error"; } mpv_node resultNode = Marshal.PtrToStructure(resultNodePtr); string ret = ConvertFromUtf8(resultNode.str); mpv_free_node_contents(resultNodePtr); Marshal.FreeHGlobal(resultNodePtr); return ret; } public bool GetPropertyBool(string name) { mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_FLAG, out IntPtr lpBuffer); if (err < 0) HandleError(err, "error getting property: " + name); return lpBuffer.ToInt32() != 0; } public void SetPropertyBool(string name, bool value) { long val = value ? 1 : 0; mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_FLAG, ref val); if (err < 0) HandleError(err, $"error setting property: {name} = {value}"); } public int GetPropertyInt(string name) { mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, out IntPtr lpBuffer); if (err < 0 && App.DebugMode) HandleError(err, "error getting property: " + name); return lpBuffer.ToInt32(); } public void SetPropertyInt(string name, int value) { long val = value; mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, ref val); if (err < 0) HandleError(err, $"error setting property: {name} = {value}"); } public void SetPropertyLong(string name, long value) { mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, ref value); if (err < 0) HandleError(err, $"error setting property: {name} = {value}"); } public long GetPropertyLong(string name) { mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, out IntPtr lpBuffer); if (err < 0) HandleError(err, "error getting property: " + name); return lpBuffer.ToInt64(); } public double GetPropertyDouble(string name, bool handleError = true) { mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_DOUBLE, out double value); if (err < 0 && handleError && App.DebugMode) HandleError(err, "error getting property: " + name); return value; } public void SetPropertyDouble(string name, double value) { double val = value; mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_DOUBLE, ref val); if (err < 0) HandleError(err, $"error setting property: {name} = {value}"); } public string GetPropertyString(string name) { mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, out IntPtr lpBuffer); if (err == 0) { string ret = ConvertFromUtf8(lpBuffer); mpv_free(lpBuffer); return ret; } if (err < 0 && App.DebugMode) HandleError(err, "error getting property: " + name); return ""; } public void SetPropertyString(string name, string value) { byte[] bytes = GetUtf8Bytes(value); mpv_error err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, ref bytes); if (err < 0) HandleError(err, $"error setting property: {name} = {value}"); } public string GetPropertyOsdString(string name) { mpv_error err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_OSD_STRING, out IntPtr lpBuffer); if (err == 0) { string ret = ConvertFromUtf8(lpBuffer); mpv_free(lpBuffer); return ret; } if (err < 0) HandleError(err, "error getting property: " + name); return ""; } public string GetScriptOption(string name, string defaultValue = "") { string value = GetPropertyString("script-opts"); if (string.IsNullOrEmpty(value)) return defaultValue; string[] values = value.Split(','); foreach (string item in values) { if (item.Contains("=")) { string optionName = item.Substring(0, item.IndexOf("=")); if (optionName == name) return item.Substring(item.IndexOf("=") + 1); } } return defaultValue; } public void ObservePropertyInt(string name, Action action) { lock (IntPropChangeActions) { if (!IntPropChangeActions.ContainsKey(name)) { mpv_error err = mpv_observe_property(NamedHandle, 0, name, mpv_format.MPV_FORMAT_INT64); if (err < 0) HandleError(err, "error observing property: " + name); else IntPropChangeActions[name] = new List>(); } if (IntPropChangeActions.ContainsKey(name)) IntPropChangeActions[name].Add(action); } } public void ObservePropertyDouble(string name, Action action) { lock (DoublePropChangeActions) { if (!DoublePropChangeActions.ContainsKey(name)) { mpv_error err = mpv_observe_property(NamedHandle, 0, name, mpv_format.MPV_FORMAT_DOUBLE); if (err < 0) HandleError(err, "error observing property: " + name); else DoublePropChangeActions[name] = new List>(); } if (DoublePropChangeActions.ContainsKey(name)) DoublePropChangeActions[name].Add(action); } } public void ObservePropertyBool(string name, Action action) { lock (BoolPropChangeActions) { if (!BoolPropChangeActions.ContainsKey(name)) { mpv_error err = mpv_observe_property(NamedHandle, 0, name, mpv_format.MPV_FORMAT_FLAG); if (err < 0) HandleError(err, "error observing property: " + name); else BoolPropChangeActions[name] = new List>(); } if (BoolPropChangeActions.ContainsKey(name)) BoolPropChangeActions[name].Add(action); } } public void ObservePropertyString(string name, Action action) { lock (StringPropChangeActions) { if (!StringPropChangeActions.ContainsKey(name)) { mpv_error err = mpv_observe_property(NamedHandle, 0, name, mpv_format.MPV_FORMAT_STRING); if (err < 0) HandleError(err, "error observing property: " + name); else StringPropChangeActions[name] = new List>(); } if (StringPropChangeActions.ContainsKey(name)) StringPropChangeActions[name].Add(action); } } public void ObserveProperty(string name, Action action) { lock (PropChangeActions) { if (!PropChangeActions.ContainsKey(name)) { mpv_error err = mpv_observe_property(NamedHandle, 0, name, mpv_format.MPV_FORMAT_NONE); if (err < 0) HandleError(err, "error observing property: " + name); else PropChangeActions[name] = new List(); } if (PropChangeActions.ContainsKey(name)) PropChangeActions[name].Add(action); } } public void HandleError(mpv_error err, string msg) { Terminal.WriteError(msg); Terminal.WriteError(GetError(err)); } public void ProcessCommandLine(bool preInit) { bool shuffle = false; var args = Environment.GetCommandLineArgs().Skip(1); string[] preInitProperties = { "input-terminal", "terminal", "input-file", "config", "config-dir", "input-conf", "load-scripts", "scripts", "player-operation-mode", "idle", "log-file", "msg-color", "dump-stats", "msg-level", "really-quiet" }; foreach (string i in args) { string arg = i; if (arg.StartsWith("-") && arg.Length > 1) { if (!preInit) { if (arg == "--profile=help") { Console.WriteLine(mpvHelp.GetProfiles()); continue; } else if (arg == "--vd=help" || arg == "--ad=help") { Console.WriteLine(mpvHelp.GetDecoders()); continue; } else if (arg == "--audio-device=help") { Console.WriteLine(GetPropertyOsdString("audio-device-list")); continue; } else if (arg == "--version") { Console.WriteLine(App.Version); continue; } else if (arg == "--input-keylist") { Console.WriteLine(GetPropertyString("input-key-list").Replace(",", BR)); continue; } else if (arg.StartsWith("--command=")) { Command(arg.Substring(10)); continue; } } if (!arg.StartsWith("--")) arg = "-" + arg; if (!arg.Contains("=")) { if (arg.Contains("--no-")) { arg = arg.Replace("--no-", "--"); arg += "=no"; } else arg += "=yes"; } string left = arg.Substring(2, arg.IndexOf("=") - 2); string right = arg.Substring(left.Length + 3); if (left == "script") left = "scripts"; if (left == "external-file") left = "external-files"; if (preInit && preInitProperties.Contains(left)) { ProcessProperty(left, right); if (!App.ProcessProperty(left, right)) SetPropertyString(left, right); } else if (!preInit && !preInitProperties.Contains(left)) { ProcessProperty(left, right); if (!App.ProcessProperty(left, right)) { SetPropertyString(left, right); if (left == "shuffle" && right == "yes") shuffle = true; } } } } if (!preInit) { List files = new List(); foreach (string i in args) if (!i.StartsWith("--") && (i == "-" || i.Contains("://") || i.Contains(":\\") || i.StartsWith("\\\\") || File.Exists(i))) files.Add(i); LoadFiles(files.ToArray(), !App.Queue, Control.ModifierKeys.HasFlag(Keys.Control) || App.Queue); if (shuffle) { Command("playlist-shuffle"); SetPropertyInt("playlist-pos", 0); } if (files.Count == 0 || files[0].Contains("://")) { VideoSizeChanged?.Invoke(VideoSize); VideoSizeAutoResetEvent.Set(); } } } public DateTime LastLoad; public void LoadFiles(string[] files, bool loadFolder, bool append) { if (files is 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.Substring(0, file.IndexOf("|")); string ext = file.Ext(); switch (ext) { case "avs": LoadAviSynth(); break; case "lnk": file = GetShortcutTarget(file); break; } if (ext == "iso") LoadISO(file); else if(SubtitleTypes.Contains(ext)) CommandV("sub-add", file); else if (ext == "" && !file.Contains("://") && Directory.Exists(file)) { if (File.Exists(System.IO.Path.Combine(file, "BDMV\\index.bdmv"))) { Command("stop"); Thread.Sleep(500); SetPropertyString("bluray-device", file); CommandV("loadfile", @"bd://"); } else { files = GetMediaFiles(Directory.GetFiles(file, "*.*", SearchOption.AllDirectories)).ToArray(); LoadFiles(files, loadFolder, append); return; } } else { if (i == 0 && !append) CommandV("loadfile", file); else CommandV("loadfile", file, "append"); } } if (string.IsNullOrEmpty(GetPropertyString("path"))) SetPropertyInt("playlist-pos", 0); } public void LoadISO(string path) { long gb = new FileInfo(path).Length / 1024 / 1024 / 1024; if (gb < 10) { System.Windows.MessageBoxResult result = Msg.ShowQuestion("Click Yes for Blu-ray and No for DVD.", System.Windows.MessageBoxButton.YesNoCancel); switch (result) { case System.Windows.MessageBoxResult.Yes: Command("stop"); Thread.Sleep(500); SetPropertyString("bluray-device", path); LoadFiles(new[] { @"bd://" }, false, false); break; case System.Windows.MessageBoxResult.No: Command("stop"); Thread.Sleep(500); SetPropertyString("dvd-device", path); LoadFiles(new[] { @"dvd://" }, false, false); break; } } else { Command("stop"); Thread.Sleep(500); SetPropertyString("bluray-device", path); LoadFiles(new[] { @"bd://" }, false, false); } } public void LoadDiskFolder(string path) { Core.Command("stop"); Thread.Sleep(500); if (Directory.Exists(path + "\\BDMV")) { Core.SetPropertyString("bluray-device", path); Core.LoadFiles(new[] { @"bd://" }, false, false); } else { Core.SetPropertyString("dvd-device", path); Core.LoadFiles(new[] { @"dvd://" }, false, false); } } static object LoadFolderLockObject = new object(); public void LoadFolder() { if (!App.AutoLoadFolder || Control.ModifierKeys.HasFlag(Keys.Shift)) 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 = GetMediaFiles(Directory.GetFiles(dir)).ToList(); files.Sort(new StringLogicalComparer()); int index = files.IndexOf(path); files.Remove(path); foreach (string i in files) CommandV("loadfile", i, "append"); if (index > 0) CommandV("playlist-move", "0", (index + 1).ToString()); } } IEnumerable GetMediaFiles(IEnumerable files) { return files.Where(i => VideoTypes.Contains(i.Ext()) || AudioTypes.Contains(i.Ext()) || ImageTypes.Contains(i.Ext())); } bool WasAviSynthLoaded; void LoadAviSynth() { if (!WasAviSynthLoaded) { string dll = Environment.GetEnvironmentVariable("AviSynthDLL"); if (File.Exists(dll)) Native.LoadLibrary(dll); else Native.LoadLibrary("AviSynth.dll"); WasAviSynthLoaded = true; } } string LastHistoryPath; DateTime LastHistoryStartDateTime; void WriteHistory(string path) { if (!File.Exists(ConfigFolder + "history.txt")) return; double totalMinutes = (DateTime.Now - LastHistoryStartDateTime).TotalMinutes; if (LastHistoryPath != null && totalMinutes > 1 && !HistoryDiscard()) File.AppendAllText(ConfigFolder + "history.txt", DateTime.Now.ToString().Substring(0, 16) + " " + Convert.ToInt32(totalMinutes).ToString().PadLeft(3) + " " + LastHistoryPath + "\r\n"); LastHistoryPath = path; LastHistoryStartDateTime = DateTime.Now; } string HistoryDiscardOption; bool HistoryDiscard() { if (HistoryDiscardOption == null) HistoryDiscardOption = GetScriptOption("history-discard"); if (string.IsNullOrEmpty(HistoryDiscardOption)) return false; foreach (string i in HistoryDiscardOption.Split(';')) if (LastHistoryPath.Contains(i)) return true; return false; } public void ShowLogo() { if (!App.ShowLogo || MainForm.Instance == null || Core.Handle == IntPtr.Zero) return; bool december = DateTime.Now.Month == 12; Rectangle cr = MainForm.Instance.ClientRectangle; int len = Convert.ToInt32(cr.Height / (december ? 4.5 : 5)); if (len < 16 || cr.Height < 16) return; using (Bitmap bmp = new Bitmap(len, len)) { using (Graphics gx = Graphics.FromImage(bmp)) { gx.InterpolationMode = InterpolationMode.HighQualityBicubic; gx.Clear(Color.Black); Rectangle rect = new Rectangle(0, 0, len, len); Bitmap bmp2 = (december && App.ShowSantaLogo) ? Properties.Resources.mpvnet_santa : Properties.Resources.mpvnet; gx.DrawImage(bmp2, rect); BitmapData bd = bmp.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb); int x = Convert.ToInt32((cr.Width - len) / (december ? 1.95 : 2)); int y = Convert.ToInt32((cr.Height - len) / 2.0 * (december ? 0.85 : 0.9)); CommandV("overlay-add", "0", $"{x}", $"{y}", "&" + bd.Scan0.ToInt64().ToString(), "0", "bgra", bd.Width.ToString(), bd.Height.ToString(), bd.Stride.ToString()); bmp.UnlockBits(bd); } } } void HideLogo() => Command("overlay-remove 0"); public bool IsImage => ImageTypes.Contains(Path.Ext()); public bool IsAudio => AudioTypes.Contains(Path.Ext()); 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; string Convert(string id2) { switch (id2) { case "bng": return "ben"; case "ces": return "cze"; case "deu": return "ger"; case "ell": return "gre"; case "eus": return "baq"; case "fra": return "fre"; case "hye": return "arm"; case "isl": return "ice"; case "kat": return "geo"; case "mya": return "bur"; case "nld": return "dut"; case "sqi": return "alb"; case "zho": return "chi"; default: return id2; } } } string GetNativeLanguage(string name) { foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.NeutralCultures)) if (ci.EnglishName == name) return ci.NativeName; return name; } public static string GetShortcutTarget(string path) { Type t = Type.GetTypeFromProgID("WScript.Shell"); dynamic sh = Activator.CreateInstance(t); return sh.CreateShortcut(path).TargetPath; } public void RaiseScaleWindow(float value) => ScaleWindow(value); public void RaiseWindowScaleNET(float value) => WindowScaleNET(value); public void RaiseShowMenu() => ShowMenu(); 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 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 trackCount = GetPropertyInt("track-list/count"); int editionCount = GetPropertyInt("edition-list/count"); lock (MediaTracksLock) { if (MediaTracks.Count != (trackCount + editionCount)) { MediaTracks = MediaTracks.Where(i => !i.External).ToList(); MediaTracks.AddRange(GetTracks(false)); } } } 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 = Regex.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(); track.Text = "E: " + title; track.Type = "e"; track.ID = i; tracks.Add(track); } } return tracks; } 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" : ""); Add(track, mi.GetVideo(i, "Title")); 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.StartsWith(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, ""); if (title.ContainsEx("()")) title = title.Replace("()", ""); if (title.ContainsEx("[]")) title = title.Replace("[]", ""); if (title.TrimEx() == format) title = null; } 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"); 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)) 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("forced")) title = title.Replace("forced", "Forced").Trim(); if (title.ContainsEx("()")) title = title.Replace("()", ""); if (title.ContainsEx("[]")) title = title.Replace("[]", ""); } track = new MediaTrack(); Add(track, lang); Add(track, codec); Add(track, mi.GetText(i, "Format_Profile")); Add(track, mi.GetText(i, "Forced") == "Yes" ? "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(); track.Text = "E: " + title; track.Type = "e"; track.ID = i; tracks.Add(track); } return tracks; } void Add(MediaTrack track, object value) { string str = value.ToStringEx().Trim(); if (str != "" && !(track.Text != null && track.Text.Contains(str))) track.Text += " " + str + ","; } private string[] _ProfileNames; public string[] ProfileNames { get { if (_ProfileNames == null) { string[] ignore = { "builtin-pseudo-gui", "encoding", "libmpv", "pseudo-gui", "default" }; string profileList = Core.GetPropertyString("profile-list"); var json = profileList.FromJson>>(); _ProfileNames = json.Select(i => i["name"].ToString()) .Where(i => !ignore.Contains(i)).ToArray(); } return _ProfileNames; } } } }