Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fc0ebf9d5 | ||
|
|
a0e30ff982 | ||
|
|
c2a0d95851 | ||
|
|
34d5d2fff5 | ||
|
|
baa669fe1e | ||
|
|
67ecebbf6b | ||
|
|
e9a8b1962a | ||
|
|
3ded1573f4 | ||
|
|
d38adcfc70 | ||
|
|
5f1f5b3811 | ||
|
|
c18c70c2af | ||
|
|
9cdd76e1ac | ||
|
|
7db8a1e534 |
@@ -11,7 +11,7 @@ Public Class CSScriptAddon
|
|||||||
Implements IAddon
|
Implements IAddon
|
||||||
|
|
||||||
Sub New()
|
Sub New()
|
||||||
Dim scriptDir = mp.mpvConfFolderPath + "scripts"
|
Dim scriptDir = mp.MpvConfFolderPath + "scripts"
|
||||||
If Not Directory.Exists(scriptDir) Then Return
|
If Not Directory.Exists(scriptDir) Then Return
|
||||||
Dim csFiles = Directory.GetFiles(scriptDir, "*.cs").ToList
|
Dim csFiles = Directory.GetFiles(scriptDir, "*.cs").ToList
|
||||||
csFiles.AddRange(Directory.GetFiles(Application.StartupPath + "\\Scripts", "*.cs"))
|
csFiles.AddRange(Directory.GetFiles(Application.StartupPath + "\\Scripts", "*.cs"))
|
||||||
|
|||||||
89
README.md
@@ -22,7 +22,8 @@ Table of contents
|
|||||||
- Customizable context menu defined in the same file as the key bindings
|
- Customizable context menu defined in the same file as the key bindings
|
||||||
- Searchable options dialog with modern UI as mpv compatible standalone application
|
- Searchable options dialog with modern UI as mpv compatible standalone application
|
||||||
- Searchable input (key/mouse) binding editor with modern UI as mpv compatible standalone application
|
- Searchable input (key/mouse) binding editor with modern UI as mpv compatible standalone application
|
||||||
- Rich addon API for .NET languages
|
- Modern UI using the OS theme color and dark mode
|
||||||
|
- Rich addon/extension API for .NET languages, over 700 available mpv properties
|
||||||
- Rich scripting API for Python, C#, Lua, JavaScript and PowerShell
|
- Rich scripting API for Python, C#, Lua, JavaScript and PowerShell
|
||||||
- mpv's OSC (on screen controller (play control bar)), IPC, conf files
|
- mpv's OSC (on screen controller (play control bar)), IPC, conf files
|
||||||
|
|
||||||
@@ -30,6 +31,8 @@ Table of contents
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||

|

|
||||||
|
|
||||||

|

|
||||||
@@ -56,65 +59,53 @@ if it's missing mpv.net generates it with the following defaults:
|
|||||||
|
|
||||||
### Scripting
|
### Scripting
|
||||||
|
|
||||||
Scripting is supported for Python, C#, Lua, JavaScript and PowerShell
|
Scripting is supported via Python, C#, Lua, JavaScript and PowerShell
|
||||||
|
|
||||||
https://github.com/stax76/mpv.net/wiki/Scripting-(CSharp,-Python,-JavaScript,-Lua,-PowerShell)
|
[Scripting wiki page](https://github.com/stax76/mpv.net/wiki/Scripting-(CSharp,-Python,-JavaScript,-Lua,-PowerShell))
|
||||||
|
|
||||||
### Support
|
### Support
|
||||||
|
|
||||||
<https://forum.doom9.org/showthread.php?t=174841>
|
[Support thread in Doom9 forum](https://forum.doom9.org/showthread.php?t=174841)
|
||||||
|
|
||||||
<https://forum.videohelp.com/threads/392514-mpv-net-a-extendable-media-player-for-windows>
|
[Support thread in VideoHelp forum](https://forum.videohelp.com/threads/392514-mpv-net-a-extendable-media-player-for-windows)
|
||||||
|
|
||||||
<https://github.com/stax76/mpv.net/issues>
|
[Issue tracker to report bugs and request features](https://github.com/stax76/mpv.net/issues)
|
||||||
|
|
||||||
### Changelog
|
### Changelog
|
||||||
|
|
||||||
### 2.8 (2019-04-12)
|
### 3.0 (2019-04-20)
|
||||||
|
|
||||||
- Win 7 dark-mode render issue fix
|
- the history feature logs now only files that were opened longer than 90 seconds
|
||||||
|
- the default input command for cycling the audio tracks was replaced with an
|
||||||
### 2.7 (2019-04-12)
|
mpv.net command that shows detailed track info and has no 'no audio' track
|
||||||
|
- new web site for mpv.net <https://mpv-net.github.io/mpv.net-web-site/>
|
||||||
- the autofit mpv property was added to the conf editor
|
- the Tracks menu supports now MKV edition selection. [Default binding](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/input.conf.txt#L106).
|
||||||
- the routine that writes the mpv.conf file in the conf editor was completely rewritten
|
- the Navigate menu supports now chapter selection. [Default binding](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/input.conf.txt#L57).
|
||||||
- the conf editor has a dedicated page for mpv.net specific settings,
|
- opening the context menu was crashing if the default binding for Tracks was missing
|
||||||
these settings are saved in the same folder as mpv.conf using mpvnet.conf as filename,
|
|
||||||
the first setting there is dark-mode
|
|
||||||
- new optional dark theme
|
|
||||||
|
|
||||||
[go to download page](https://github.com/stax76/mpv.net/releases)
|
[go to download page](https://github.com/stax76/mpv.net/releases)
|
||||||
|
|
||||||
### 2.6 (2019-04-09)
|
### 2.9 (2019-04-16)
|
||||||
|
|
||||||
- on Win 7 controls in the conf editor were using a difficult too read too light color
|
- clicking the right top corner in full screen mode
|
||||||
- context menu renderer changed to look like Win 10 design, except colors are still system theme colors
|
closes the player but it did not work on all displays
|
||||||
|
- the info display was changed to display the filename on top
|
||||||
### 2.5 (2019-04-08)
|
so it's not displayed in the middle of the screen
|
||||||
|
- on start up of the conf editor all text is now selected in the
|
||||||
- in case the input conf don't contain a menu definition mpv.net creates the default menu instead no menu like before
|
search text box so it's ready for a new search to be typed
|
||||||
- all message boxes were migrated to use the TaskDialog API
|
- the conf editor was changed to write the settings to disk
|
||||||
- an improvement in the previous release unfortunately introduced a bug
|
only if the settings were actually modified, also the message
|
||||||
causing the conf editor not to save settings
|
that says that the settings will be available on next start
|
||||||
|
is now only shown if the settings were actually modified.
|
||||||
### 2.4 (2019-04-06)
|
- there was an instance in the context menu where the sub menu
|
||||||
|
arrow was overlapping with the text
|
||||||
- new options added to the conf GUI editor: gpu-context, gpu-api, scale, cscale,
|
- in the input editor when only one character is entered in the
|
||||||
dscale, dither-depth, correct-downscaling, sigmoid-upscaling, deband
|
search text box the search is performed only in the input and
|
||||||
- the conf edit GUI has a 'Apply' feature added to write the conf to mpv.conf
|
not in the command or menu
|
||||||
without the need to close the conf edit GUI
|
- in the input editor the routine that generates the input string
|
||||||
- the input edit GUI shows a message box when a duplicate is detected and it has
|
was completely rewritten because it was adding Shift where it
|
||||||
a new feature to reduce the filter scope to eather of input, menu or command and
|
wasn't necessary (it took a huge amount of time to implement)
|
||||||
the editor writes always the same help on top of input.conf as it is found in the defaults
|
- the context menu has a new track menu where the active track
|
||||||
- the conf edit GUI was often starting out of working area bounds and is now starting with center screen
|
can be seen and selected, it shows video, audio and subtitle
|
||||||
- the startup size was reduced and a issue was fixed that when the screen property
|
tracks with various metadata. [Menu default definition](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/input.conf.txt#L104).
|
||||||
was defined for a screen that isn't connected the startup size wasn't applied
|
The screenshots were updated showing the [new track menu](https://github.com/stax76/mpv.net#screenshots).
|
||||||
- added feature to load external audio and subtitle files in the menu under:
|
|
||||||
Open > Load external audio|subtitle files (default binding at:
|
|
||||||
[input.conf](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/input.conf.txt))
|
|
||||||
- previously the conf edit GUI removed settings from the conf file if the setting
|
|
||||||
was set to the default, the new behavior is not to remove anything
|
|
||||||
- the autofit mpv property was partly implemented, you can use 'autofit = 50%' in mpv.conf or
|
|
||||||
'--autofit=50%' on the command line, WxH isn't implemented and only percent values are accepted.
|
|
||||||
There is a new wiki page explaining the mpv.net limitations compared to the original mpv:
|
|
||||||
[Limitations](https://github.com/stax76/mpv.net/wiki/Limitations)
|
|
||||||
@@ -10,7 +10,7 @@ namespace RatingAddon
|
|||||||
[Export(typeof(IAddon))]
|
[Export(typeof(IAddon))]
|
||||||
public class RatingAddon : IAddon
|
public class RatingAddon : IAddon
|
||||||
{
|
{
|
||||||
private Dictionary<string, int> Dic = new Dictionary<string, int>();
|
Dictionary<string, int> Dic = new Dictionary<string, int>();
|
||||||
|
|
||||||
public RatingAddon()
|
public RatingAddon()
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@ namespace RatingAddon
|
|||||||
mp.Shutdown += mpv_Shutdown;
|
mp.Shutdown += mpv_Shutdown;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mpv_Shutdown()
|
void mpv_Shutdown()
|
||||||
{
|
{
|
||||||
foreach (var i in Dic)
|
foreach (var i in Dic)
|
||||||
{
|
{
|
||||||
@@ -44,7 +44,7 @@ namespace RatingAddon
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mpv_ClientMessage(string[] args)
|
void mpv_ClientMessage(string[] args)
|
||||||
{
|
{
|
||||||
int rating;
|
int rating;
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ Public Class Msg
|
|||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Private Shared ShownMessages As String
|
Shared ShownMessages As String
|
||||||
|
|
||||||
Public Shared Sub ShowWarning(mainInstruction As String,
|
Public Shared Sub ShowWarning(mainInstruction As String,
|
||||||
Optional content As String = Nothing,
|
Optional content As String = Nothing,
|
||||||
@@ -421,7 +421,7 @@ Public Class TaskDialog(Of T)
|
|||||||
|
|
||||||
Private ExitTickCount As Integer
|
Private ExitTickCount As Integer
|
||||||
|
|
||||||
Private Function DialogProc(hwnd As IntPtr,
|
Function DialogProc(hwnd As IntPtr,
|
||||||
msg As UInteger,
|
msg As UInteger,
|
||||||
wParam As IntPtr,
|
wParam As IntPtr,
|
||||||
lParam As IntPtr,
|
lParam As IntPtr,
|
||||||
@@ -464,7 +464,7 @@ Public Class TaskDialog(Of T)
|
|||||||
End Select
|
End Select
|
||||||
End Function
|
End Function
|
||||||
|
|
||||||
Private Sub MarshalDialogControlStructs()
|
Sub MarshalDialogControlStructs()
|
||||||
If Not Buttons Is Nothing AndAlso Buttons.Count > 0 Then
|
If Not Buttons Is Nothing AndAlso Buttons.Count > 0 Then
|
||||||
ButtonArray = AllocateAndMarshalButtons(Buttons)
|
ButtonArray = AllocateAndMarshalButtons(Buttons)
|
||||||
Config.pButtons = ButtonArray
|
Config.pButtons = ButtonArray
|
||||||
@@ -478,7 +478,7 @@ Public Class TaskDialog(Of T)
|
|||||||
End If
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
|
|
||||||
Private Shared Function AllocateAndMarshalButtons(structs As List(Of TASKDIALOG_BUTTON)) As IntPtr
|
Shared Function AllocateAndMarshalButtons(structs As List(Of TASKDIALOG_BUTTON)) As IntPtr
|
||||||
Dim initialPtr = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(TASKDIALOG_BUTTON)) * structs.Count)
|
Dim initialPtr = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(TASKDIALOG_BUTTON)) * structs.Count)
|
||||||
Dim currentPtr = initialPtr
|
Dim currentPtr = initialPtr
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace mpvnet
|
|||||||
[ImportMany]
|
[ImportMany]
|
||||||
public IEnumerable<IAddon> Addons = null;
|
public IEnumerable<IAddon> Addons = null;
|
||||||
|
|
||||||
private readonly CompositionContainer CompositionContainer;
|
readonly CompositionContainer CompositionContainer;
|
||||||
|
|
||||||
public Addon()
|
public Addon()
|
||||||
{
|
{
|
||||||
@@ -28,7 +28,7 @@ namespace mpvnet
|
|||||||
foreach (string i in Directory.GetDirectories(dir))
|
foreach (string i in Directory.GetDirectories(dir))
|
||||||
catalog.Catalogs.Add(new DirectoryCatalog(i, "*Addon.dll"));
|
catalog.Catalogs.Add(new DirectoryCatalog(i, "*Addon.dll"));
|
||||||
|
|
||||||
dir = mp.mpvConfFolderPath + "\\Addons";
|
dir = mp.MpvConfFolderPath + "\\Addons";
|
||||||
|
|
||||||
if (Directory.Exists(dir))
|
if (Directory.Exists(dir))
|
||||||
foreach (string i in Directory.GetDirectories(dir))
|
foreach (string i in Directory.GetDirectories(dir))
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using VBNET;
|
using VBNET;
|
||||||
@@ -15,7 +15,7 @@ namespace mpvnet
|
|||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public Action<string[]> Action { get; set; }
|
public Action<string[]> Action { get; set; }
|
||||||
|
|
||||||
private static List<Command> commands;
|
static List<Command> commands;
|
||||||
|
|
||||||
public static List<Command> Commands
|
public static List<Command> Commands
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,7 @@ namespace mpvnet
|
|||||||
|
|
||||||
public static void open_conf_folder(string[] args)
|
public static void open_conf_folder(string[] args)
|
||||||
{
|
{
|
||||||
Process.Start(mp.mpvConfFolderPath);
|
Process.Start(mp.MpvConfFolderPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void show_input_editor(string[] args)
|
public static void show_input_editor(string[] args)
|
||||||
@@ -73,7 +73,7 @@ namespace mpvnet
|
|||||||
|
|
||||||
public static void show_history(string[] args)
|
public static void show_history(string[] args)
|
||||||
{
|
{
|
||||||
var fp = mp.mpvConfFolderPath + "history.txt";
|
var fp = mp.MpvConfFolderPath + "history.txt";
|
||||||
|
|
||||||
if (File.Exists(fp))
|
if (File.Exists(fp))
|
||||||
Process.Start(fp);
|
Process.Start(fp);
|
||||||
@@ -89,9 +89,9 @@ namespace mpvnet
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var fileInfo = new FileInfo(mp.get_property_string("path"));
|
FileInfo fileInfo = new FileInfo(mp.get_property_string("path"));
|
||||||
|
|
||||||
using (var mediaInfo = new MediaInfo(fileInfo.FullName))
|
using (MediaInfo mediaInfo = new MediaInfo(fileInfo.FullName))
|
||||||
{
|
{
|
||||||
string width = mediaInfo.GetInfo(MediaInfoStreamKind.Video, "Width");
|
string width = mediaInfo.GetInfo(MediaInfoStreamKind.Video, "Width");
|
||||||
|
|
||||||
@@ -125,17 +125,19 @@ namespace mpvnet
|
|||||||
if (bitrate == "")
|
if (bitrate == "")
|
||||||
bitrate = "0";
|
bitrate = "0";
|
||||||
|
|
||||||
var bitrate2 = Convert.ToDouble(bitrate) / 1000.0 / 1000.0;
|
double bitrate2 = Convert.ToDouble(bitrate) / 1000.0 / 1000.0;
|
||||||
var videoCodec = mp.get_property_string("video-format").ToUpper();
|
string videoCodec = mp.get_property_string("video-format").ToUpper();
|
||||||
var filename = fileInfo.Name;
|
string filename = fileInfo.Name;
|
||||||
|
|
||||||
var text =
|
string text = filename + "\n" +
|
||||||
FormatTime(position.TotalMinutes) + ":" +
|
FormatTime(position.TotalMinutes) + ":" +
|
||||||
FormatTime(position.Seconds) + " / " +
|
FormatTime(position.Seconds) + " / " +
|
||||||
FormatTime(duration.TotalMinutes) + ":" +
|
FormatTime(duration.TotalMinutes) + ":" +
|
||||||
FormatTime(duration.Seconds) + "\n" +
|
FormatTime(duration.Seconds) + "\n" +
|
||||||
Convert.ToInt32(fileInfo.Length / 1024 / 1024).ToString() +
|
$"{width} x {height}\n" +
|
||||||
$" MB - {width} x {height}\n{videoCodec} - {bitrate2.ToString("f1")} Mb/s" + "\n" + filename;
|
$"{bitrate2.ToString("f1")} Mb/s\n" +
|
||||||
|
Convert.ToInt32(fileInfo.Length / 1024 / 1024).ToString() + " MB\n" +
|
||||||
|
$"{videoCodec}\n";
|
||||||
|
|
||||||
mp.commandv("show-text", text, "5000");
|
mp.commandv("show-text", text, "5000");
|
||||||
}
|
}
|
||||||
@@ -195,5 +197,22 @@ namespace mpvnet
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void cycle_audio(string[] args)
|
||||||
|
{
|
||||||
|
string filePath = mp.get_property_string("path", false);
|
||||||
|
if (!File.Exists(filePath)) return;
|
||||||
|
|
||||||
|
using (MediaInfo mi = new MediaInfo(filePath))
|
||||||
|
{
|
||||||
|
MediaTrack[] audTracks = mp.MediaTracks.Where(track => track.Type == "a").ToArray();
|
||||||
|
if (audTracks.Length < 2) return;
|
||||||
|
int aid = mp.get_property_int("aid");
|
||||||
|
aid += 1;
|
||||||
|
if (aid > audTracks.Length) aid = 1;
|
||||||
|
mp.commandv("set", "aid", aid.ToString());
|
||||||
|
mp.commandv("show-text", audTracks[aid - 1].Text.Substring(3), "5000");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,25 +6,35 @@ using System.Threading;
|
|||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
|
||||||
using VBNET;
|
using VBNET;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
namespace mpvnet
|
namespace mpvnet
|
||||||
{
|
{
|
||||||
public partial class MainForm : Form
|
public partial class MainForm : Form
|
||||||
{
|
{
|
||||||
public static MainForm Instance { get; set; }
|
public static MainForm Instance { get; set; }
|
||||||
public static IntPtr Hwnd;
|
public static IntPtr Hwnd { get; set; }
|
||||||
|
|
||||||
private Point LastCursorPosChanged;
|
public new ContextMenuStripEx ContextMenu;
|
||||||
private int LastCursorChangedTickCount;
|
|
||||||
private bool IgnoreDpiChanged = true;
|
|
||||||
private float MpvAutofit = 0.50f;
|
|
||||||
private bool MpvFullscreen;
|
|
||||||
private int MpvScreen = -1;
|
|
||||||
private string MpvNetDarkMode = "system";
|
|
||||||
|
|
||||||
public ContextMenuStripEx CMS;
|
MenuItemEx TracksMenu;
|
||||||
|
MenuItemEx ChaptersMenu;
|
||||||
|
|
||||||
|
Point LastCursorPosChanged;
|
||||||
|
int LastCursorChangedTickCount;
|
||||||
|
bool IgnoreDpiChanged = true;
|
||||||
|
|
||||||
|
float MpvAutofit = 0.50f;
|
||||||
|
bool MpvFullscreen;
|
||||||
|
int MpvScreen = -1;
|
||||||
|
string MpvNetDarkMode = "system";
|
||||||
|
string MpvSid = "";
|
||||||
|
string MpvAid = "";
|
||||||
|
string MpvVid = "";
|
||||||
|
int MpvEdition;
|
||||||
|
|
||||||
public MainForm()
|
public MainForm()
|
||||||
{
|
{
|
||||||
@@ -60,6 +70,77 @@ namespace mpvnet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContextMenu_Opening(object sender, CancelEventArgs e)
|
||||||
|
{
|
||||||
|
lock (mp.MediaTracks)
|
||||||
|
{
|
||||||
|
if (TracksMenu != null)
|
||||||
|
TracksMenu.DropDownItems.Clear();
|
||||||
|
|
||||||
|
MediaTrack[] audTracks = mp.MediaTracks.Where(track => track.Type == "a").ToArray();
|
||||||
|
MediaTrack[] subTracks = mp.MediaTracks.Where(track => track.Type == "s").ToArray();
|
||||||
|
MediaTrack[] vidTracks = mp.MediaTracks.Where(track => track.Type == "v").ToArray();
|
||||||
|
MediaTrack[] ediTracks = mp.MediaTracks.Where(track => track.Type == "e").ToArray();
|
||||||
|
|
||||||
|
foreach (MediaTrack track in vidTracks)
|
||||||
|
{
|
||||||
|
MenuItemEx mi = ContextMenu.Add("Track > " + track.Text);
|
||||||
|
mi.Action = () => { mp.commandv("set", "vid", track.ID.ToString()); };
|
||||||
|
mi.Checked = MpvVid == track.ID.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vidTracks.Length > 0)
|
||||||
|
ContextMenu.Add("Track > -");
|
||||||
|
|
||||||
|
foreach (MediaTrack track in audTracks)
|
||||||
|
{
|
||||||
|
MenuItemEx mi = ContextMenu.Add("Track > " + track.Text);
|
||||||
|
mi.Action = () => { mp.commandv("set", "aid", track.ID.ToString()); };
|
||||||
|
mi.Checked = MpvAid == track.ID.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subTracks.Length > 0)
|
||||||
|
ContextMenu.Add("Track > -");
|
||||||
|
|
||||||
|
foreach (MediaTrack track in subTracks)
|
||||||
|
{
|
||||||
|
MenuItemEx mi = ContextMenu.Add("Track > " + track.Text);
|
||||||
|
mi.Action = () => { mp.commandv("set", "sid", track.ID.ToString()); };
|
||||||
|
mi.Checked = MpvSid == track.ID.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subTracks.Length > 0)
|
||||||
|
{
|
||||||
|
MenuItemEx mi = ContextMenu.Add("Track > S: No subtitles");
|
||||||
|
mi.Action = () => { mp.commandv("set", "sid", "no"); };
|
||||||
|
mi.Checked = MpvSid == "no";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ediTracks.Length > 0)
|
||||||
|
ContextMenu.Add("Track > -");
|
||||||
|
|
||||||
|
foreach (MediaTrack track in ediTracks)
|
||||||
|
{
|
||||||
|
MenuItemEx mi = ContextMenu.Add("Track > " + track.Text);
|
||||||
|
mi.Action = () => { mp.commandv("set", "edition", track.ID.ToString()); };
|
||||||
|
mi.Checked = MpvEdition == track.ID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (mp.Chapters)
|
||||||
|
{
|
||||||
|
if (ChaptersMenu != null)
|
||||||
|
ChaptersMenu.DropDownItems.Clear();
|
||||||
|
|
||||||
|
foreach (var i in mp.Chapters)
|
||||||
|
{
|
||||||
|
MenuItemEx mi = ContextMenu.Add("Navigate > Chapters > " + i.Key);
|
||||||
|
mi.ShortcutKeyDisplayString = TimeSpan.FromSeconds(i.Value).ToString().Substring(0, 8) + " ";
|
||||||
|
mi.Action = () => { mp.commandv("seek", i.Value.ToString(CultureInfo.InvariantCulture), "absolute"); };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void SetScreen(int targetIndex)
|
protected void SetScreen(int targetIndex)
|
||||||
{
|
{
|
||||||
Screen[] screens = Screen.AllScreens;
|
Screen[] screens = Screen.AllScreens;
|
||||||
@@ -210,7 +291,7 @@ namespace mpvnet
|
|||||||
else
|
else
|
||||||
input = "";
|
input = "";
|
||||||
|
|
||||||
var menuItem = CMS.Add(path, () => {
|
MenuItemEx menuItem = ContextMenu.Add(path, () => {
|
||||||
try {
|
try {
|
||||||
mp.command_string(command);
|
mp.command_string(command);
|
||||||
}
|
}
|
||||||
@@ -220,51 +301,46 @@ namespace mpvnet
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (menuItem != null)
|
if (menuItem != null)
|
||||||
menuItem.ShortcutKeyDisplayString = input.Replace("_","") + " ";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CMS_Opened(object sender, EventArgs e) => CursorHelp.Show();
|
|
||||||
|
|
||||||
private string LastHistory;
|
|
||||||
|
|
||||||
private void mp_PlaybackRestart()
|
|
||||||
{
|
{
|
||||||
var filename = mp.get_property_string("filename");
|
menuItem.ShortcutKeyDisplayString = input.Replace("_", "") + " ";
|
||||||
BeginInvoke(new Action(() => { Text = filename + " - mpv.net " + Application.ProductVersion; }));
|
|
||||||
var historyFilepath = mp.mpvConfFolderPath + "history.txt";
|
|
||||||
|
|
||||||
if (LastHistory != filename && File.Exists(historyFilepath))
|
if (TracksMenu == null && menuItem.Text.StartsWith("Track ") &&
|
||||||
{
|
menuItem.Text.Trim() == "Track")
|
||||||
File.AppendAllText(historyFilepath, DateTime.Now.ToString() + " " +
|
|
||||||
Path.GetFileNameWithoutExtension(filename) + "\r\n");
|
TracksMenu = menuItem;
|
||||||
LastHistory = filename;
|
|
||||||
|
if (ChaptersMenu == null && menuItem.Text.StartsWith("Chapters ") &&
|
||||||
|
menuItem.Text.Trim() == "Chapters")
|
||||||
|
|
||||||
|
ChaptersMenu = menuItem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Mp_Idle()
|
void ContextMenu_Opened(object sender, EventArgs e) => CursorHelp.Show();
|
||||||
{
|
|
||||||
BeginInvoke(new Action(() => { Text = "mpv.net " + Application.ProductVersion; }));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CM_Popup(object sender, EventArgs e) => CursorHelp.Show();
|
void mp_PlaybackRestart() => BeginInvoke(new Action(() => { Text = Path.GetFileName(mp.get_property_string("path")) + " - mpv.net " + Application.ProductVersion; }));
|
||||||
|
|
||||||
private void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
|
void Mp_Idle() => BeginInvoke(new Action(() => { Text = "mpv.net " + Application.ProductVersion; }));
|
||||||
|
|
||||||
|
void CM_Popup(object sender, EventArgs e) => CursorHelp.Show();
|
||||||
|
|
||||||
|
void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
Msg.ShowException(e.Exception);
|
Msg.ShowException(e.Exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
Msg.ShowError(e.ExceptionObject.ToString());
|
Msg.ShowError(e.ExceptionObject.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mp_VideoSizeChanged()
|
void mp_VideoSizeChanged()
|
||||||
{
|
{
|
||||||
BeginInvoke(new Action(() => SetFormPositionAndSizeKeepHeight()));
|
BeginInvoke(new Action(() => SetFormPositionAndSizeKeepHeight()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void mp_Shutdown()
|
void mp_Shutdown()
|
||||||
{
|
{
|
||||||
BeginInvoke(new Action(() => Close()));
|
BeginInvoke(new Action(() => Close()));
|
||||||
}
|
}
|
||||||
@@ -378,11 +454,7 @@ namespace mpvnet
|
|||||||
Native.PostMessage(Handle, 0xA1 /* WM_NCLBUTTONDOWN */, HTCAPTION, IntPtr.Zero);
|
Native.PostMessage(Handle, 0xA1 /* WM_NCLBUTTONDOWN */, HTCAPTION, IntPtr.Zero);
|
||||||
}
|
}
|
||||||
|
|
||||||
var sb = Screen.FromControl(this).Bounds;
|
if (Width - e.Location.X < 10 && e.Location.Y < 10)
|
||||||
var p1 = new Point(sb.Width, 0);
|
|
||||||
var p2 = PointToScreen(e.Location);
|
|
||||||
|
|
||||||
if (Math.Abs(p1.X - p2.X) < 10 && Math.Abs(p1.Y - p2.Y) < 10)
|
|
||||||
mp.commandv("quit");
|
mp.commandv("quit");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,12 +467,9 @@ namespace mpvnet
|
|||||||
CursorHelp.Show();
|
CursorHelp.Show();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsMouseInOSC()
|
bool IsMouseInOSC() => PointToClient(Control.MousePosition).Y > ClientSize.Height * 0.9;
|
||||||
{
|
|
||||||
return PointToClient(Control.MousePosition).Y > ClientSize.Height * 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Timer_Tick(object sender, EventArgs e)
|
void Timer_Tick(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
if (CursorHelp.IsPosDifferent(LastCursorPosChanged))
|
if (CursorHelp.IsPosDifferent(LastCursorPosChanged))
|
||||||
{
|
{
|
||||||
@@ -409,7 +478,7 @@ namespace mpvnet
|
|||||||
}
|
}
|
||||||
else if (Environment.TickCount - LastCursorChangedTickCount > 1500 &&
|
else if (Environment.TickCount - LastCursorChangedTickCount > 1500 &&
|
||||||
!IsMouseInOSC() && ClientRectangle.Contains(PointToClient(MousePosition)) &&
|
!IsMouseInOSC() && ClientRectangle.Contains(PointToClient(MousePosition)) &&
|
||||||
Form.ActiveForm == this && !CMS.Visible)
|
Form.ActiveForm == this && !ContextMenu.Visible)
|
||||||
{
|
{
|
||||||
CursorHelp.Hide();
|
CursorHelp.Hide();
|
||||||
}
|
}
|
||||||
@@ -429,6 +498,10 @@ namespace mpvnet
|
|||||||
mp.Init();
|
mp.Init();
|
||||||
mp.observe_property_bool("fullscreen", mpPropChangeFullscreen);
|
mp.observe_property_bool("fullscreen", mpPropChangeFullscreen);
|
||||||
mp.observe_property_bool("ontop", mpPropChangeOnTop);
|
mp.observe_property_bool("ontop", mpPropChangeOnTop);
|
||||||
|
mp.observe_property_string("sid", mpPropChangeSid);
|
||||||
|
mp.observe_property_string("aid", mpPropChangeAid);
|
||||||
|
mp.observe_property_string("vid", mpPropChangeVid);
|
||||||
|
mp.observe_property_int("edition", mpPropChangeEdition);
|
||||||
mp.Shutdown += mp_Shutdown;
|
mp.Shutdown += mp_Shutdown;
|
||||||
mp.VideoSizeChanged += mp_VideoSizeChanged;
|
mp.VideoSizeChanged += mp_VideoSizeChanged;
|
||||||
mp.PlaybackRestart += mp_PlaybackRestart;
|
mp.PlaybackRestart += mp_PlaybackRestart;
|
||||||
@@ -437,15 +510,24 @@ namespace mpvnet
|
|||||||
|
|
||||||
void mpPropChangeOnTop(bool value) => BeginInvoke(new Action(() => TopMost = value));
|
void mpPropChangeOnTop(bool value) => BeginInvoke(new Action(() => TopMost = value));
|
||||||
|
|
||||||
|
void mpPropChangeAid(string value) => MpvAid = value;
|
||||||
|
|
||||||
|
void mpPropChangeSid(string value) => MpvSid = value;
|
||||||
|
|
||||||
|
void mpPropChangeVid(string value) => MpvVid = value;
|
||||||
|
|
||||||
|
void mpPropChangeEdition(int value) => MpvEdition = value;
|
||||||
|
|
||||||
protected override void OnShown(EventArgs e)
|
protected override void OnShown(EventArgs e)
|
||||||
{
|
{
|
||||||
base.OnShown(e);
|
base.OnShown(e);
|
||||||
if ((MpvNetDarkMode == "system" && Misc.IsDarkTheme) || MpvNetDarkMode == "always")
|
if ((MpvNetDarkMode == "system" && Misc.IsDarkTheme) || MpvNetDarkMode == "always")
|
||||||
ToolStripRendererEx.ColorTheme = Color.Black;
|
ToolStripRendererEx.ColorTheme = Color.Black;
|
||||||
CMS = new ContextMenuStripEx(components);
|
ContextMenu = new ContextMenuStripEx(components);
|
||||||
CMS.Opened += CMS_Opened;
|
ContextMenu.Opened += ContextMenu_Opened;
|
||||||
ContextMenuStrip = CMS;
|
ContextMenu.Opening += ContextMenu_Opening;
|
||||||
BuildMenu();
|
BuildMenu();
|
||||||
|
ContextMenuStrip = ContextMenu;
|
||||||
IgnoreDpiChanged = false;
|
IgnoreDpiChanged = false;
|
||||||
CheckYouTube();
|
CheckYouTube();
|
||||||
}
|
}
|
||||||
@@ -469,14 +551,14 @@ namespace mpvnet
|
|||||||
CheckYouTube();
|
CheckYouTube();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string LastURL;
|
|
||||||
|
|
||||||
void CheckYouTube()
|
void CheckYouTube()
|
||||||
{
|
{
|
||||||
string clipboard = Clipboard.GetText();
|
string clipboard = Clipboard.GetText();
|
||||||
if (clipboard.StartsWith("https://www.youtube.com/watch?") && LastURL != clipboard && Visible)
|
|
||||||
|
if (clipboard.StartsWith("https://www.youtube.com/watch?") && RegistryHelp.GetValue("HKCU\\Software\\" + Application.ProductName, "LastYouTubeURL") != clipboard && Visible)
|
||||||
{
|
{
|
||||||
LastURL = clipboard;
|
RegistryHelp.SetValue("HKCU\\Software\\" + Application.ProductName, "LastYouTubeURL", clipboard);
|
||||||
|
|
||||||
if (Msg.ShowQuestion("Play YouTube URL?") == MsgResult.OK)
|
if (Msg.ShowQuestion("Play YouTube URL?") == MsgResult.OK)
|
||||||
mp.LoadURL(clipboard);
|
mp.LoadURL(clipboard);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using System.Runtime.InteropServices;
|
|||||||
|
|
||||||
public class MediaInfo : IDisposable
|
public class MediaInfo : IDisposable
|
||||||
{
|
{
|
||||||
private IntPtr Handle;
|
IntPtr Handle;
|
||||||
private static bool Loaded;
|
static bool Loaded;
|
||||||
|
|
||||||
public MediaInfo(string sourcepath)
|
public MediaInfo(string sourcepath)
|
||||||
{
|
{
|
||||||
@@ -25,7 +25,24 @@ public class MediaInfo : IDisposable
|
|||||||
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, streamKind, 0, parameter, MediaInfoInfoKind.Text, MediaInfoInfoKind.Name));
|
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, streamKind, 0, parameter, MediaInfoInfoKind.Text, MediaInfoInfoKind.Name));
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool Disposed;
|
public int GetCount(MediaInfoStreamKind streamKind) => MediaInfo_Count_Get(Handle, streamKind, -1);
|
||||||
|
|
||||||
|
public string GetVideo(int streamNumber, string parameter)
|
||||||
|
{
|
||||||
|
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, MediaInfoStreamKind.Video, streamNumber, parameter, MediaInfoInfoKind.Text, MediaInfoInfoKind.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAudio(int streamNumber, string parameter)
|
||||||
|
{
|
||||||
|
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, MediaInfoStreamKind.Audio, streamNumber, parameter, MediaInfoInfoKind.Text, MediaInfoInfoKind.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetText(int streamNumber, string parameter)
|
||||||
|
{
|
||||||
|
return Marshal.PtrToStringUni(MediaInfo_Get(Handle, MediaInfoStreamKind.Text, streamNumber, parameter, MediaInfoInfoKind.Text, MediaInfoInfoKind.Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Disposed;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
@@ -37,28 +54,35 @@ public class MediaInfo : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
~MediaInfo()
|
~MediaInfo() { Dispose(); }
|
||||||
{
|
|
||||||
Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
|
||||||
private static extern IntPtr LoadLibrary(string path);
|
static extern IntPtr LoadLibrary(string path);
|
||||||
|
|
||||||
[DllImport("MediaInfo.dll")]
|
[DllImport("MediaInfo.dll")]
|
||||||
private static extern IntPtr MediaInfo_New();
|
static extern IntPtr MediaInfo_New();
|
||||||
|
|
||||||
[DllImport("MediaInfo.dll")]
|
[DllImport("MediaInfo.dll")]
|
||||||
private static extern void MediaInfo_Delete(IntPtr Handle);
|
static extern void MediaInfo_Delete(IntPtr handle);
|
||||||
|
|
||||||
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
|
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
|
||||||
private static extern int MediaInfo_Open(IntPtr Handle, string FileName);
|
static extern int MediaInfo_Open(IntPtr handle, string fileName);
|
||||||
|
|
||||||
[DllImport("MediaInfo.dll")]
|
[DllImport("MediaInfo.dll")]
|
||||||
private static extern int MediaInfo_Close(IntPtr Handle);
|
static extern int MediaInfo_Close(IntPtr handle);
|
||||||
|
|
||||||
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
|
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
|
||||||
private static extern IntPtr MediaInfo_Get(IntPtr Handle, MediaInfoStreamKind StreamKind, int StreamNumber, string Parameter, MediaInfoInfoKind KindOfInfo, MediaInfoInfoKind KindOfSearch);
|
static extern IntPtr MediaInfo_Get(IntPtr handle,
|
||||||
|
MediaInfoStreamKind streamKind,
|
||||||
|
int streamNumber,
|
||||||
|
string parameter,
|
||||||
|
MediaInfoInfoKind kindOfInfo,
|
||||||
|
MediaInfoInfoKind kindOfSearch);
|
||||||
|
|
||||||
|
[DllImport("MediaInfo.dll", CharSet = CharSet.Unicode)]
|
||||||
|
static extern int MediaInfo_Count_Get(IntPtr handle,
|
||||||
|
MediaInfoStreamKind streamKind,
|
||||||
|
int streamNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MediaInfoStreamKind
|
public enum MediaInfoStreamKind
|
||||||
@@ -67,8 +91,10 @@ public enum MediaInfoStreamKind
|
|||||||
Video,
|
Video,
|
||||||
Audio,
|
Audio,
|
||||||
Text,
|
Text,
|
||||||
Chapters,
|
Other,
|
||||||
Image
|
Image,
|
||||||
|
Menu,
|
||||||
|
Max,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum MediaInfoInfoKind
|
public enum MediaInfoInfoKind
|
||||||
|
|||||||
298
mpv.net/Menu.cs
@@ -6,7 +6,6 @@ using System.Drawing.Text;
|
|||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
public class ContextMenuStripEx : ContextMenuStrip
|
public class ContextMenuStripEx : ContextMenuStrip
|
||||||
{
|
{
|
||||||
@@ -24,110 +23,23 @@ public class ContextMenuStripEx : ContextMenuStrip
|
|||||||
Renderer = new ToolStripRendererEx();
|
Renderer = new ToolStripRendererEx();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionMenuItem Add(string path)
|
public MenuItemEx Add(string path)
|
||||||
{
|
{
|
||||||
return Add(path, null);
|
return Add(path, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionMenuItem Add(string path, Action action)
|
public MenuItemEx Add(string path, Action action, bool enabled = true)
|
||||||
{
|
{
|
||||||
return Add(path, action, true);
|
MenuItemEx ret = MenuItemEx.Add(Items, path, action);
|
||||||
}
|
if (ret == null) return null;
|
||||||
|
|
||||||
public ActionMenuItem Add(string path, Action action, bool enabled)
|
|
||||||
{
|
|
||||||
var ret = ActionMenuItem.Add(Items, path, action);
|
|
||||||
if (ret == null)
|
|
||||||
return null;
|
|
||||||
ret.Enabled = enabled;
|
ret.Enabled = enabled;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ActionMenuItem Add(string path, Action action, Func<bool> enabledFunc)
|
|
||||||
{
|
|
||||||
var ret = ActionMenuItem.Add(Items, path, action);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ActionMenuItem : MenuItemEx
|
|
||||||
{
|
|
||||||
private Action Action;
|
|
||||||
|
|
||||||
public ActionMenuItem()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public ActionMenuItem(string text, Action action)
|
|
||||||
{
|
|
||||||
this.Text = text;
|
|
||||||
this.Action = action;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void OnClick(EventArgs e)
|
|
||||||
{
|
|
||||||
Application.DoEvents();
|
|
||||||
if (Action != null)
|
|
||||||
Action();
|
|
||||||
base.OnClick(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ActionMenuItem Add<T>(ToolStripItemCollection items, string path, Action<T> action, T value)
|
|
||||||
{
|
|
||||||
return Add(items, path, () => action(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ActionMenuItem Add(ToolStripItemCollection items, string path, Action action)
|
|
||||||
{
|
|
||||||
var a = path.Split(new[] { " > ", " | " }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
var l = items;
|
|
||||||
|
|
||||||
for (var x = 0; x <= a.Length - 1; x++)
|
|
||||||
{
|
|
||||||
var found = false;
|
|
||||||
|
|
||||||
foreach (var i in l.OfType<ToolStripMenuItem>())
|
|
||||||
{
|
|
||||||
if (x < a.Length - 1)
|
|
||||||
{
|
|
||||||
if (i.Text == a[x] + " ")
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
l = i.DropDownItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found)
|
|
||||||
{
|
|
||||||
if (x == a.Length - 1)
|
|
||||||
{
|
|
||||||
if (a[x] == "-")
|
|
||||||
l.Add(new ToolStripSeparator());
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ActionMenuItem item = new ActionMenuItem(a[x] + " ", action);
|
|
||||||
l.Add(item);
|
|
||||||
l = item.DropDownItems;
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ActionMenuItem item = new ActionMenuItem();
|
|
||||||
item.Text = a[x] + " ";
|
|
||||||
l.Add(item);
|
|
||||||
l = item.DropDownItems;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MenuItemEx : ToolStripMenuItem
|
public class MenuItemEx : ToolStripMenuItem
|
||||||
{
|
{
|
||||||
public static bool UseTooltips { get; set; }
|
public Action Action { get; set; }
|
||||||
|
|
||||||
public MenuItemEx()
|
public MenuItemEx()
|
||||||
{
|
{
|
||||||
@@ -137,20 +49,81 @@ public class MenuItemEx : ToolStripMenuItem
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MenuItemEx(string text, Action action) : base(text)
|
||||||
|
{
|
||||||
|
Action = action;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClick(EventArgs e)
|
||||||
|
{
|
||||||
|
Application.DoEvents();
|
||||||
|
Action?.Invoke();
|
||||||
|
base.OnClick(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MenuItemEx Add<T>(ToolStripItemCollection items, string path, Action<T> action, T value)
|
||||||
|
{
|
||||||
|
return Add(items, path, () => action(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MenuItemEx Add(ToolStripItemCollection items, string path, Action action)
|
||||||
|
{
|
||||||
|
string[] a = path.Split(new[] { " > ", " | " }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
var itemsCollection = items;
|
||||||
|
|
||||||
|
for (int x = 0; x < a.Length; x++)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
foreach (var i in itemsCollection.OfType<ToolStripMenuItem>())
|
||||||
|
{
|
||||||
|
if (x < a.Length - 1)
|
||||||
|
{
|
||||||
|
if (i.Text == a[x] + " ")
|
||||||
|
{
|
||||||
|
found = true;
|
||||||
|
itemsCollection = i.DropDownItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found)
|
||||||
|
{
|
||||||
|
if (x == a.Length - 1)
|
||||||
|
{
|
||||||
|
if (a[x] == "-")
|
||||||
|
itemsCollection.Add(new ToolStripSeparator());
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MenuItemEx item = new MenuItemEx(a[x] + " ", action);
|
||||||
|
itemsCollection.Add(item);
|
||||||
|
itemsCollection = item.DropDownItems;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MenuItemEx item = new MenuItemEx();
|
||||||
|
item.Text = a[x] + " ";
|
||||||
|
itemsCollection.Add(item);
|
||||||
|
itemsCollection = item.DropDownItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public override Size GetPreferredSize(Size constrainingSize)
|
public override Size GetPreferredSize(Size constrainingSize)
|
||||||
{
|
{
|
||||||
var ret = base.GetPreferredSize(constrainingSize);
|
Size size = base.GetPreferredSize(constrainingSize);
|
||||||
ret.Height = Convert.ToInt32(Font.Height * 1.4);
|
size.Height = Convert.ToInt32(Font.Height * 1.4);
|
||||||
return ret;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CloseAll(object item)
|
public void CloseAll(object item)
|
||||||
{
|
{
|
||||||
if (item is ToolStripItem)
|
if (item is ToolStripItem)
|
||||||
{
|
CloseAll(((ToolStripItem)item).Owner);
|
||||||
var d = (ToolStripItem)item;
|
|
||||||
CloseAll(d.Owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item is ToolStripDropDown)
|
if (item is ToolStripDropDown)
|
||||||
{
|
{
|
||||||
@@ -159,12 +132,6 @@ public class MenuItemEx : ToolStripMenuItem
|
|||||||
CloseAll(d.OwnerItem);
|
CloseAll(d.OwnerItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnClick(EventArgs e)
|
|
||||||
{
|
|
||||||
Application.DoEvents();
|
|
||||||
base.OnClick(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ToolStripRendererEx : ToolStripSystemRenderer
|
public class ToolStripRendererEx : ToolStripSystemRenderer
|
||||||
@@ -182,11 +149,12 @@ public class ToolStripRendererEx : ToolStripSystemRenderer
|
|||||||
public static Color ColorToolStrip3 { get; set; }
|
public static Color ColorToolStrip3 { get; set; }
|
||||||
public static Color ColorToolStrip4 { get; set; }
|
public static Color ColorToolStrip4 { get; set; }
|
||||||
|
|
||||||
private int TextOffset;
|
int TextOffset;
|
||||||
|
|
||||||
public ToolStripRendererEx()
|
public ToolStripRendererEx()
|
||||||
{
|
{
|
||||||
var argb = Convert.ToInt32(Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM", "ColorizationColor", 0));
|
var argb = Convert.ToInt32(Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM", "ColorizationColor", 0));
|
||||||
|
|
||||||
if (argb == 0)
|
if (argb == 0)
|
||||||
argb = Color.LightBlue.ToArgb();
|
argb = Color.LightBlue.ToArgb();
|
||||||
if (ColorTheme == Color.Empty)
|
if (ColorTheme == Color.Empty)
|
||||||
@@ -198,7 +166,7 @@ public class ToolStripRendererEx : ToolStripSystemRenderer
|
|||||||
public static void InitColors(Color c)
|
public static void InitColors(Color c)
|
||||||
{
|
{
|
||||||
ColorBorder = HSLColor.Convert(c).ToColorSetLuminosity(100);
|
ColorBorder = HSLColor.Convert(c).ToColorSetLuminosity(100);
|
||||||
ColorChecked = HSLColor.Convert(c).ToColorSetLuminosity(200);
|
ColorChecked = HSLColor.Convert(c).ToColorSetLuminosity(160);
|
||||||
ColorSelection = HSLColor.Convert(c).ToColorSetLuminosity(180);
|
ColorSelection = HSLColor.Convert(c).ToColorSetLuminosity(180);
|
||||||
ColorBackground = HSLColor.Convert(c).ToColorSetLuminosity(210);
|
ColorBackground = HSLColor.Convert(c).ToColorSetLuminosity(210);
|
||||||
ColorTop = HSLColor.Convert(c).ToColorSetLuminosity(240);
|
ColorTop = HSLColor.Convert(c).ToColorSetLuminosity(240);
|
||||||
@@ -209,6 +177,7 @@ public class ToolStripRendererEx : ToolStripSystemRenderer
|
|||||||
ColorBackground = Color.FromArgb(50, 50, 50);
|
ColorBackground = Color.FromArgb(50, 50, 50);
|
||||||
ColorSelection = Color.FromArgb(80, 80, 80);
|
ColorSelection = Color.FromArgb(80, 80, 80);
|
||||||
ColorForeground = Color.White;
|
ColorForeground = Color.White;
|
||||||
|
ColorChecked = Color.FromArgb(90, 90, 90);
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorToolStrip1 = ControlPaint.LightLight(ControlPaint.LightLight(ControlPaint.Light(ColorBorder, 1)));
|
ColorToolStrip1 = ControlPaint.LightLight(ControlPaint.LightLight(ControlPaint.Light(ColorBorder, 1)));
|
||||||
@@ -259,20 +228,18 @@ public class ToolStripRendererEx : ToolStripSystemRenderer
|
|||||||
|
|
||||||
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
|
protected override void OnRenderMenuItemBackground(ToolStripItemRenderEventArgs e)
|
||||||
{
|
{
|
||||||
e.Item.ForeColor = Color.Black;
|
Rectangle rect = new Rectangle(Point.Empty, e.Item.Size);
|
||||||
var r = new Rectangle(Point.Empty, e.Item.Size);
|
|
||||||
var g = e.Graphics;
|
|
||||||
|
|
||||||
if (!(e.Item.Owner is MenuStrip))
|
if (!(e.Item.Owner is MenuStrip))
|
||||||
g.Clear(ColorBackground);
|
e.Graphics.Clear(ColorBackground);
|
||||||
|
|
||||||
if (e.Item.Selected && e.Item.Enabled)
|
if (e.Item.Selected && e.Item.Enabled)
|
||||||
{
|
{
|
||||||
g.SmoothingMode = SmoothingMode.AntiAlias;
|
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
var r2 = new Rectangle(r.X + 2, r.Y, r.Width - 4, r.Height - 1);
|
rect = new Rectangle(rect.X + 2, rect.Y, rect.Width - 4, rect.Height - 1);
|
||||||
r2.Inflate(-1, -1);
|
rect.Inflate(-1, -1);
|
||||||
using (SolidBrush b = new SolidBrush(ColorSelection))
|
using (SolidBrush b = new SolidBrush(ColorSelection))
|
||||||
g.FillRectangle(b, r2);
|
e.Graphics.FillRectangle(b, rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,8 +266,32 @@ public class ToolStripRendererEx : ToolStripSystemRenderer
|
|||||||
|
|
||||||
protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
|
protected override void OnRenderItemCheck(ToolStripItemImageRenderEventArgs e)
|
||||||
{
|
{
|
||||||
int x = Convert.ToInt32(e.ImageRectangle.Height * 0.2);
|
if (e.Item.GetType() != typeof(MenuItemEx))
|
||||||
e.Graphics.DrawImage(e.Image, new Point(x, x));
|
return;
|
||||||
|
|
||||||
|
MenuItemEx item = e.Item as MenuItemEx;
|
||||||
|
|
||||||
|
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
|
||||||
|
|
||||||
|
if (!item.Checked)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Rectangle rect = new Rectangle(Point.Empty, e.Item.Size);
|
||||||
|
rect = new Rectangle(rect.X + 2, rect.Y, rect.Height - 1, rect.Height - 1);
|
||||||
|
rect.Inflate(-1, -1);
|
||||||
|
|
||||||
|
using (Brush brush = new SolidBrush(ColorChecked))
|
||||||
|
e.Graphics.FillRectangle(brush, rect);
|
||||||
|
|
||||||
|
float ellipseWidth = rect.Height / 3f;
|
||||||
|
|
||||||
|
RectangleF rectF = new RectangleF(rect.X + rect.Height / 2f - ellipseWidth / 2f,
|
||||||
|
rect.Y + rect.Height / 2f - ellipseWidth / 2f,
|
||||||
|
ellipseWidth,
|
||||||
|
ellipseWidth);
|
||||||
|
|
||||||
|
using (Brush brush = new SolidBrush(ColorForeground))
|
||||||
|
e.Graphics.FillEllipse(brush, rectF);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e)
|
protected override void OnRenderSeparator(ToolStripSeparatorRenderEventArgs e)
|
||||||
@@ -330,31 +321,28 @@ public struct HSLColor
|
|||||||
Luminosity = l;
|
Luminosity = l;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double hue;
|
double _Hue;
|
||||||
|
|
||||||
public int Hue
|
public int Hue {
|
||||||
{
|
get => System.Convert.ToInt32(_Hue * 240);
|
||||||
get => System.Convert.ToInt32(hue * 240);
|
set => _Hue = CheckRange(value / 240.0);
|
||||||
set => hue = CheckRange(value / 240.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double saturation;
|
double _Saturation;
|
||||||
|
|
||||||
public int Saturation
|
public int Saturation {
|
||||||
{
|
get => System.Convert.ToInt32(_Saturation * 240);
|
||||||
get => System.Convert.ToInt32(saturation * 240);
|
set => _Saturation = CheckRange(value / 240.0);
|
||||||
set => saturation = CheckRange(value / 240.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double luminosity;
|
double _Luminosity;
|
||||||
|
|
||||||
public int Luminosity
|
public int Luminosity {
|
||||||
{
|
get => System.Convert.ToInt32(_Luminosity * 240);
|
||||||
get => System.Convert.ToInt32(luminosity * 240);
|
set => _Luminosity = CheckRange(value / 240.0);
|
||||||
set => luminosity = CheckRange(value / 240.0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private double CheckRange(double value)
|
double CheckRange(double value)
|
||||||
{
|
{
|
||||||
if (value < 0)
|
if (value < 0)
|
||||||
value = 0;
|
value = 0;
|
||||||
@@ -379,21 +367,21 @@ public struct HSLColor
|
|||||||
{
|
{
|
||||||
double r = 0, g = 0, b = 0;
|
double r = 0, g = 0, b = 0;
|
||||||
|
|
||||||
if (luminosity != 0)
|
if (_Luminosity != 0)
|
||||||
{
|
{
|
||||||
if (saturation == 0)
|
if (_Saturation == 0)
|
||||||
{
|
{
|
||||||
b = luminosity;
|
b = _Luminosity;
|
||||||
g = luminosity;
|
g = _Luminosity;
|
||||||
r = luminosity;
|
r = _Luminosity;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
double temp2 = GetTemp2(this);
|
double temp2 = GetTemp2(this);
|
||||||
double temp1 = 2.0 * luminosity - temp2;
|
double temp1 = 2.0 * _Luminosity - temp2;
|
||||||
r = GetColorComponent(temp1, temp2, hue + 1.0 / 3.0);
|
r = GetColorComponent(temp1, temp2, _Hue + 1.0 / 3.0);
|
||||||
g = GetColorComponent(temp1, temp2, hue);
|
g = GetColorComponent(temp1, temp2, _Hue);
|
||||||
b = GetColorComponent(temp1, temp2, hue - 1.0 / 3.0);
|
b = GetColorComponent(temp1, temp2, _Hue - 1.0 / 3.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,7 +391,7 @@ public struct HSLColor
|
|||||||
System.Convert.ToInt32(255 * b));
|
System.Convert.ToInt32(255 * b));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double GetColorComponent(double temp1, double temp2, double temp3)
|
static double GetColorComponent(double temp1, double temp2, double temp3)
|
||||||
{
|
{
|
||||||
temp3 = MoveIntoRange(temp3);
|
temp3 = MoveIntoRange(temp3);
|
||||||
|
|
||||||
@@ -417,7 +405,7 @@ public struct HSLColor
|
|||||||
return temp1;
|
return temp1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double MoveIntoRange(double temp3)
|
static double MoveIntoRange(double temp3)
|
||||||
{
|
{
|
||||||
if (temp3 < 0)
|
if (temp3 < 0)
|
||||||
temp3 += 1;
|
temp3 += 1;
|
||||||
@@ -426,14 +414,14 @@ public struct HSLColor
|
|||||||
return temp3;
|
return temp3;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double GetTemp2(HSLColor hslColor)
|
static double GetTemp2(HSLColor hslColor)
|
||||||
{
|
{
|
||||||
double temp2;
|
double temp2;
|
||||||
|
|
||||||
if (hslColor.luminosity < 0.5)
|
if (hslColor._Luminosity < 0.5)
|
||||||
temp2 = hslColor.luminosity * (1.0 + hslColor.saturation);
|
temp2 = hslColor._Luminosity * (1.0 + hslColor._Saturation);
|
||||||
else
|
else
|
||||||
temp2 = hslColor.luminosity + hslColor.saturation - (hslColor.luminosity * hslColor.saturation);
|
temp2 = hslColor._Luminosity + hslColor._Saturation - (hslColor._Luminosity * hslColor._Saturation);
|
||||||
|
|
||||||
return temp2;
|
return temp2;
|
||||||
}
|
}
|
||||||
@@ -441,17 +429,17 @@ public struct HSLColor
|
|||||||
public static HSLColor Convert(Color c)
|
public static HSLColor Convert(Color c)
|
||||||
{
|
{
|
||||||
HSLColor r = new HSLColor();
|
HSLColor r = new HSLColor();
|
||||||
r.hue = c.GetHue() / 360.0;
|
r._Hue = c.GetHue() / 360.0;
|
||||||
r.luminosity = c.GetBrightness();
|
r._Luminosity = c.GetBrightness();
|
||||||
r.saturation = c.GetSaturation();
|
r._Saturation = c.GetSaturation();
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRGB(int red, int green, int blue)
|
public void SetRGB(int red, int green, int blue)
|
||||||
{
|
{
|
||||||
HSLColor hc = HSLColor.Convert(Color.FromArgb(red, green, blue));
|
HSLColor hc = HSLColor.Convert(Color.FromArgb(red, green, blue));
|
||||||
hue = hc.hue;
|
_Hue = hc._Hue;
|
||||||
saturation = hc.saturation;
|
_Saturation = hc._Saturation;
|
||||||
luminosity = hc.luminosity;
|
_Luminosity = hc._Luminosity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,6 +99,15 @@ namespace mpvnet
|
|||||||
rk.SetValue(name, value);
|
rk.SetValue(name, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string GetValue(string path, string name)
|
||||||
|
{
|
||||||
|
using (RegistryKey rk = GetRootKey(path).OpenSubKey(path.Substring(5)))
|
||||||
|
if (rk != null)
|
||||||
|
return rk.GetValue(name, "").ToString();
|
||||||
|
else
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
public static void RemoveKey(string path)
|
public static void RemoveKey(string path)
|
||||||
{
|
{
|
||||||
GetRootKey(path).DeleteSubKeyTree(path.Substring(5), false);
|
GetRootKey(path).DeleteSubKeyTree(path.Substring(5), false);
|
||||||
@@ -122,4 +131,11 @@ namespace mpvnet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class MediaTrack
|
||||||
|
{
|
||||||
|
public string Text { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
public int ID { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("2.8.0.0")]
|
[assembly: AssemblyVersion("3.0.0.0")]
|
||||||
[assembly: AssemblyFileVersion("2.8.0.0")]
|
[assembly: AssemblyFileVersion("3.0.0.0")]
|
||||||
|
|||||||
@@ -41,18 +41,20 @@
|
|||||||
_ ignore #menu: Navigate > -
|
_ ignore #menu: Navigate > -
|
||||||
PGUP add chapter 1 #menu: Navigate > Next Chapter
|
PGUP add chapter 1 #menu: Navigate > Next Chapter
|
||||||
PGDWN add chapter -1 #menu: Navigate > Previous Chapter
|
PGDWN add chapter -1 #menu: Navigate > Previous Chapter
|
||||||
|
_ ignore #menu: Navigate > -
|
||||||
. frame-step #menu: Seek > Next Frame
|
. frame-step #menu: Navigate > Jump Next Frame
|
||||||
, frame-back-step #menu: Seek > Previous Frame
|
, frame-back-step #menu: Navigate > Jump Previous Frame
|
||||||
_ ignore #menu: Seek > -
|
_ ignore #menu: Navigate > -
|
||||||
Right no-osd seek 7 #menu: Seek > 7 sec forward
|
Right no-osd seek 7 #menu: Navigate > Jump 7 sec forward
|
||||||
Left no-osd seek -7 #menu: Seek > 7 sec backward
|
Left no-osd seek -7 #menu: Navigate > Jump 7 sec backward
|
||||||
_ ignore #menu: Seek > -
|
_ ignore #menu: Navigate > -
|
||||||
Up no-osd seek 40 #menu: Seek > 40 sec forward
|
Up no-osd seek 40 #menu: Navigate > Jump 40 sec forward
|
||||||
Down no-osd seek -40 #menu: Seek > 40 sec backward
|
Down no-osd seek -40 #menu: Navigate > Jump 40 sec backward
|
||||||
_ ignore #menu: Seek > -
|
_ ignore #menu: Navigate > -
|
||||||
Ctrl+Right no-osd seek 300 #menu: Seek > 5 min forward
|
Ctrl+Right no-osd seek 300 #menu: Navigate > Jump 5 min forward
|
||||||
Ctrl+Left no-osd seek -300 #menu: Seek > 5 min backward
|
Ctrl+Left no-osd seek -300 #menu: Navigate > Jump 5 min backward
|
||||||
|
_ ignore #menu: Navigate > -
|
||||||
|
_ ignore #menu: Navigate > Chapters
|
||||||
|
|
||||||
Ctrl++ add video-zoom 0.1 #menu: Pan & Scan > Increase Size
|
Ctrl++ add video-zoom 0.1 #menu: Pan & Scan > Increase Size
|
||||||
Ctrl+- add video-zoom -0.1 #menu: Pan & Scan > Decrease Size
|
Ctrl+- add video-zoom -0.1 #menu: Pan & Scan > Decrease Size
|
||||||
@@ -84,7 +86,7 @@
|
|||||||
d cycle deinterlace #menu: Video > Toggle Deinterlace
|
d cycle deinterlace #menu: Video > Toggle Deinterlace
|
||||||
a cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1" #menu: Video > Cycle Aspect Ratio
|
a cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1" #menu: Video > Cycle Aspect Ratio
|
||||||
|
|
||||||
KP7 cycle audio #menu: Audio > Cycle/Next
|
KP7 script-message mpv.net cycle-audio #menu: Audio > Cycle/Next
|
||||||
_ ignore #menu: Audio > -
|
_ ignore #menu: Audio > -
|
||||||
KP6 add audio-delay 0.100 #menu: Audio > Delay +0.1
|
KP6 add audio-delay 0.100 #menu: Audio > Delay +0.1
|
||||||
KP9 add audio-delay -0.100 #menu: Audio > Delay -0.1
|
KP9 add audio-delay -0.100 #menu: Audio > Delay -0.1
|
||||||
@@ -101,6 +103,8 @@
|
|||||||
_ add sub-scale -0.1 #menu: Subtitle > Decrease Subtitle Font Size
|
_ add sub-scale -0.1 #menu: Subtitle > Decrease Subtitle Font Size
|
||||||
_ add sub-scale 0.1 #menu: Subtitle > Increase Subtitle Font Size
|
_ add sub-scale 0.1 #menu: Subtitle > Increase Subtitle Font Size
|
||||||
|
|
||||||
|
_ ignore #menu: Track
|
||||||
|
|
||||||
+ add volume 10 #menu: Volume > Up
|
+ add volume 10 #menu: Volume > Up
|
||||||
- add volume -10 #menu: Volume > Down
|
- add volume -10 #menu: Volume > Down
|
||||||
_ ignore #menu: Volume > -
|
_ ignore #menu: Volume > -
|
||||||
@@ -114,12 +118,12 @@
|
|||||||
_ ignore #menu: Speed > -
|
_ ignore #menu: Speed > -
|
||||||
BS set speed 1 #menu: Speed > Reset
|
BS set speed 1 #menu: Speed > Reset
|
||||||
|
|
||||||
KP0 script-message rate-file 0 #menu: Addons > Rating > 0stars
|
KP0 script-message rate-file 0 #menu: Extensions > Rating > 0stars
|
||||||
KP1 script-message rate-file 1 #menu: Addons > Rating > 1stars
|
KP1 script-message rate-file 1 #menu: Extensions > Rating > 1stars
|
||||||
KP2 script-message rate-file 2 #menu: Addons > Rating > 2stars
|
KP2 script-message rate-file 2 #menu: Extensions > Rating > 2stars
|
||||||
KP3 script-message rate-file 3 #menu: Addons > Rating > 3stars
|
KP3 script-message rate-file 3 #menu: Extensions > Rating > 3stars
|
||||||
KP4 script-message rate-file 4 #menu: Addons > Rating > 4stars
|
KP4 script-message rate-file 4 #menu: Extensions > Rating > 4stars
|
||||||
KP5 script-message rate-file 5 #menu: Addons > Rating > 5stars
|
KP5 script-message rate-file 5 #menu: Extensions > Rating > 5stars
|
||||||
|
|
||||||
Ctrl+t set ontop yes #menu: View > On Top > Enable
|
Ctrl+t set ontop yes #menu: View > On Top > Enable
|
||||||
Ctrl+T set ontop no #menu: View > On Top > Disable
|
Ctrl+T set ontop no #menu: View > On Top > Disable
|
||||||
|
|||||||
225
mpv.net/mp.cs
@@ -53,19 +53,23 @@ namespace mpvnet
|
|||||||
public static event Action QueueOverflow; // MPV_EVENT_QUEUE_OVERFLOW
|
public static event Action QueueOverflow; // MPV_EVENT_QUEUE_OVERFLOW
|
||||||
public static event Action Hook; // MPV_EVENT_HOOK
|
public static event Action Hook; // MPV_EVENT_HOOK
|
||||||
|
|
||||||
public static IntPtr MpvHandle;
|
public static IntPtr MpvHandle { get; set; }
|
||||||
public static IntPtr MpvWindowHandle;
|
public static IntPtr MpvWindowHandle { get; set; }
|
||||||
public static Addon Addon;
|
public static Addon Addon { get; set; }
|
||||||
public static List<KeyValuePair<string, Action<bool>>> BoolPropChangeActions = new List<KeyValuePair<string, Action<bool>>>();
|
public static List<KeyValuePair<string, Action<bool>>> BoolPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<bool>>>();
|
||||||
public static Size VideoSize = new Size(1920, 1080);
|
public static List<KeyValuePair<string, Action<int>>> IntPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<int>>>();
|
||||||
public static string mpvConfFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\";
|
public static List<KeyValuePair<string, Action<string>>> StringPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<string>>>();
|
||||||
public static string InputConfPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\input.conf";
|
public static Size VideoSize { get; set; } = new Size(1920, 1080);
|
||||||
public static string mpvConfPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\mpv.conf";
|
public static string MpvConfFolderPath { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\";
|
||||||
public static string mpvNetConfPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\mpvnet.conf";
|
public static string InputConfPath { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\input.conf";
|
||||||
public static List<PythonScript> PythonScripts => new List<PythonScript>();
|
public static string MpvConfPath { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\mpv.conf";
|
||||||
public static AutoResetEvent AutoResetEvent = new AutoResetEvent(false);
|
public static string MpvNetConfPath { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\mpvnet.conf";
|
||||||
|
public static List<PythonScript> PythonScripts { get; set; } = new List<PythonScript>();
|
||||||
|
public static AutoResetEvent AutoResetEvent { get; set; } = new AutoResetEvent(false);
|
||||||
|
public static List<MediaTrack> MediaTracks { get; set; } = new List<MediaTrack>();
|
||||||
|
public static List<KeyValuePair<string, double>> Chapters { get; set; } = new List<KeyValuePair<string, double>>();
|
||||||
|
|
||||||
private static Dictionary<string, string> _mpvConf;
|
static Dictionary<string, string> _mpvConf;
|
||||||
|
|
||||||
public static Dictionary<string, string> mpvConf {
|
public static Dictionary<string, string> mpvConf {
|
||||||
get {
|
get {
|
||||||
@@ -73,8 +77,8 @@ namespace mpvnet
|
|||||||
{
|
{
|
||||||
_mpvConf = new Dictionary<string, string>();
|
_mpvConf = new Dictionary<string, string>();
|
||||||
|
|
||||||
if (File.Exists(mpvConfPath))
|
if (File.Exists(MpvConfPath))
|
||||||
foreach (var i in File.ReadAllLines(mpvConfPath))
|
foreach (var i in File.ReadAllLines(MpvConfPath))
|
||||||
if (i.Contains("=") && ! i.StartsWith("#"))
|
if (i.Contains("=") && ! i.StartsWith("#"))
|
||||||
_mpvConf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
|
_mpvConf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
|
||||||
}
|
}
|
||||||
@@ -82,7 +86,7 @@ namespace mpvnet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Dictionary<string, string> _mpvNetConf;
|
static Dictionary<string, string> _mpvNetConf;
|
||||||
|
|
||||||
public static Dictionary<string, string> mpvNetConf {
|
public static Dictionary<string, string> mpvNetConf {
|
||||||
get {
|
get {
|
||||||
@@ -90,8 +94,8 @@ namespace mpvnet
|
|||||||
{
|
{
|
||||||
_mpvNetConf = new Dictionary<string, string>();
|
_mpvNetConf = new Dictionary<string, string>();
|
||||||
|
|
||||||
if (File.Exists(mpvNetConfPath))
|
if (File.Exists(MpvNetConfPath))
|
||||||
foreach (string i in File.ReadAllLines(mpvNetConfPath))
|
foreach (string i in File.ReadAllLines(MpvNetConfPath))
|
||||||
if (i.Contains("=") && !i.StartsWith("#"))
|
if (i.Contains("=") && !i.StartsWith("#"))
|
||||||
_mpvNetConf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
|
_mpvNetConf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
|
||||||
}
|
}
|
||||||
@@ -101,11 +105,11 @@ namespace mpvnet
|
|||||||
|
|
||||||
public static void Init()
|
public static void Init()
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(mp.mpvConfFolderPath))
|
if (!Directory.Exists(mp.MpvConfFolderPath))
|
||||||
Directory.CreateDirectory(mp.mpvConfFolderPath);
|
Directory.CreateDirectory(mp.MpvConfFolderPath);
|
||||||
|
|
||||||
if (!File.Exists(mp.mpvConfPath))
|
if (!File.Exists(mp.MpvConfPath))
|
||||||
File.WriteAllText(mp.mpvConfPath, Properties.Resources.mpv_conf);
|
File.WriteAllText(mp.MpvConfPath, Properties.Resources.mpv_conf);
|
||||||
|
|
||||||
if (!File.Exists(mp.InputConfPath))
|
if (!File.Exists(mp.InputConfPath))
|
||||||
File.WriteAllText(mp.InputConfPath, Properties.Resources.input_conf);
|
File.WriteAllText(mp.InputConfPath, Properties.Resources.input_conf);
|
||||||
@@ -142,7 +146,7 @@ namespace mpvnet
|
|||||||
if (Path.GetExtension(scriptPath) == ".ps1")
|
if (Path.GetExtension(scriptPath) == ".ps1")
|
||||||
PowerShellScript.Init(scriptPath);
|
PowerShellScript.Init(scriptPath);
|
||||||
|
|
||||||
foreach (var scriptPath in Directory.GetFiles(mp.mpvConfFolderPath + "Scripts"))
|
foreach (var scriptPath in Directory.GetFiles(mp.MpvConfFolderPath + "Scripts"))
|
||||||
if (Path.GetExtension(scriptPath) == ".py")
|
if (Path.GetExtension(scriptPath) == ".py")
|
||||||
PythonScripts.Add(new PythonScript(File.ReadAllText(scriptPath)));
|
PythonScripts.Add(new PythonScript(File.ReadAllText(scriptPath)));
|
||||||
else if (Path.GetExtension(scriptPath) == ".ps1")
|
else if (Path.GetExtension(scriptPath) == ".ps1")
|
||||||
@@ -167,6 +171,7 @@ namespace mpvnet
|
|||||||
{
|
{
|
||||||
case mpv_event_id.MPV_EVENT_SHUTDOWN:
|
case mpv_event_id.MPV_EVENT_SHUTDOWN:
|
||||||
Shutdown?.Invoke();
|
Shutdown?.Invoke();
|
||||||
|
WriteHistory(null);
|
||||||
AutoResetEvent.Set();
|
AutoResetEvent.Set();
|
||||||
return;
|
return;
|
||||||
case mpv_event_id.MPV_EVENT_LOG_MESSAGE:
|
case mpv_event_id.MPV_EVENT_LOG_MESSAGE:
|
||||||
@@ -254,12 +259,22 @@ namespace mpvnet
|
|||||||
Seek?.Invoke();
|
Seek?.Invoke();
|
||||||
break;
|
break;
|
||||||
case mpv_event_id.MPV_EVENT_PROPERTY_CHANGE:
|
case mpv_event_id.MPV_EVENT_PROPERTY_CHANGE:
|
||||||
var event_propertyData = (mpv_event_property)Marshal.PtrToStructure(evt.data, typeof(mpv_event_property));
|
var propData = (mpv_event_property)Marshal.PtrToStructure(evt.data, typeof(mpv_event_property));
|
||||||
|
|
||||||
if (event_propertyData.format == mpv_format.MPV_FORMAT_FLAG)
|
if (propData.format == mpv_format.MPV_FORMAT_FLAG)
|
||||||
foreach (var i in BoolPropChangeActions)
|
foreach (var i in BoolPropChangeActions)
|
||||||
if (i.Key== event_propertyData.name)
|
if (i.Key== propData.name)
|
||||||
i.Value.Invoke(Marshal.PtrToStructure<int>(event_propertyData.data) == 1);
|
i.Value.Invoke(Marshal.PtrToStructure<int>(propData.data) == 1);
|
||||||
|
|
||||||
|
if (propData.format == mpv_format.MPV_FORMAT_STRING)
|
||||||
|
foreach (var i in StringPropChangeActions)
|
||||||
|
if (i.Key == propData.name)
|
||||||
|
i.Value.Invoke(StringFromNativeUtf8(Marshal.PtrToStructure<IntPtr>(propData.data)));
|
||||||
|
|
||||||
|
if (propData.format == mpv_format.MPV_FORMAT_INT64)
|
||||||
|
foreach (var i in IntPropChangeActions)
|
||||||
|
if (i.Key == propData.name)
|
||||||
|
i.Value.Invoke(Marshal.PtrToStructure<int>(propData.data));
|
||||||
break;
|
break;
|
||||||
case mpv_event_id.MPV_EVENT_PLAYBACK_RESTART:
|
case mpv_event_id.MPV_EVENT_PLAYBACK_RESTART:
|
||||||
PlaybackRestart?.Invoke();
|
PlaybackRestart?.Invoke();
|
||||||
@@ -270,6 +285,11 @@ namespace mpvnet
|
|||||||
VideoSize = s;
|
VideoSize = s;
|
||||||
VideoSizeChanged?.Invoke();
|
VideoSizeChanged?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task.Run(new Action(() => {
|
||||||
|
WriteHistory(mp.get_property_string("path"));
|
||||||
|
ReadMetaData();
|
||||||
|
}));
|
||||||
break;
|
break;
|
||||||
case mpv_event_id.MPV_EVENT_CHAPTER_CHANGE:
|
case mpv_event_id.MPV_EVENT_CHAPTER_CHANGE:
|
||||||
ChapterChange?.Invoke();
|
ChapterChange?.Invoke();
|
||||||
@@ -289,7 +309,7 @@ namespace mpvnet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<PythonEventObject> PythonEventObjects = new List<PythonEventObject>();
|
static List<PythonEventObject> PythonEventObjects = new List<PythonEventObject>();
|
||||||
|
|
||||||
public static void register_event(string name, PyRT.PythonFunction pyFunc)
|
public static void register_event(string name, PyRT.PythonFunction pyFunc)
|
||||||
{
|
{
|
||||||
@@ -379,7 +399,7 @@ namespace mpvnet
|
|||||||
if (err < 0 && throwOnException)
|
if (err < 0 && throwOnException)
|
||||||
throw new Exception($"{name}: {(mpv_error)err}");
|
throw new Exception($"{name}: {(mpv_error)err}");
|
||||||
|
|
||||||
var ret = StringFromNativeUtf8(lpBuffer);
|
string ret = StringFromNativeUtf8(lpBuffer);
|
||||||
mpv_free(lpBuffer);
|
mpv_free(lpBuffer);
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
@@ -431,6 +451,16 @@ namespace mpvnet
|
|||||||
throw new Exception($"{name}: {(mpv_error)err}");
|
throw new Exception($"{name}: {(mpv_error)err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void observe_property_int(string name, Action<int> action)
|
||||||
|
{
|
||||||
|
int err = mpv_observe_property(MpvHandle, (ulong)action.GetHashCode(), name, mpv_format.MPV_FORMAT_INT64);
|
||||||
|
|
||||||
|
if (err < 0)
|
||||||
|
throw new Exception($"{name}: {(mpv_error)err}");
|
||||||
|
else
|
||||||
|
IntPropChangeActions.Add(new KeyValuePair<string, Action<int>>(name, action));
|
||||||
|
}
|
||||||
|
|
||||||
public static void observe_property_bool(string name, Action<bool> action)
|
public static void observe_property_bool(string name, Action<bool> action)
|
||||||
{
|
{
|
||||||
int err = mpv_observe_property(MpvHandle, (ulong)action.GetHashCode(), name, mpv_format.MPV_FORMAT_FLAG);
|
int err = mpv_observe_property(MpvHandle, (ulong)action.GetHashCode(), name, mpv_format.MPV_FORMAT_FLAG);
|
||||||
@@ -441,16 +471,14 @@ namespace mpvnet
|
|||||||
BoolPropChangeActions.Add(new KeyValuePair<string, Action<bool>>(name, action));
|
BoolPropChangeActions.Add(new KeyValuePair<string, Action<bool>>(name, action));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void unobserve_property_bool(string name, Action<bool> action)
|
public static void observe_property_string(string name, Action<string> action)
|
||||||
{
|
{
|
||||||
foreach (var i in BoolPropChangeActions.ToArray())
|
int err = mpv_observe_property(MpvHandle, (ulong)action.GetHashCode(), name, mpv_format.MPV_FORMAT_STRING);
|
||||||
if (i.Value == action)
|
|
||||||
BoolPropChangeActions.Remove(i);
|
|
||||||
|
|
||||||
int err = mpv_unobserve_property(MpvHandle, (ulong)action.GetHashCode());
|
|
||||||
|
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
throw new Exception($"{name}: {(mpv_error)err}");
|
throw new Exception($"{name}: {(mpv_error)err}");
|
||||||
|
else
|
||||||
|
StringPropChangeActions.Add(new KeyValuePair<string, Action<string>>(name, action));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static void ProcessCommandLine()
|
protected static void ProcessCommandLine()
|
||||||
@@ -505,9 +533,9 @@ namespace mpvnet
|
|||||||
mp.LoadFolder();
|
mp.LoadFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool WasFolderLoaded;
|
static bool WasFolderLoaded;
|
||||||
|
|
||||||
public static void LoadFolder()
|
static void LoadFolder()
|
||||||
{
|
{
|
||||||
if (WasFolderLoaded)
|
if (WasFolderLoaded)
|
||||||
return;
|
return;
|
||||||
@@ -533,7 +561,7 @@ namespace mpvnet
|
|||||||
WasFolderLoaded = true;
|
WasFolderLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntPtr AllocateUtf8IntPtrArrayWithSentinel(string[] arr, out IntPtr[] byteArrayPointers)
|
static IntPtr AllocateUtf8IntPtrArrayWithSentinel(string[] arr, out IntPtr[] byteArrayPointers)
|
||||||
{
|
{
|
||||||
int numberOfStrings = arr.Length + 1; // add extra element for extra null pointer last (sentinel)
|
int numberOfStrings = arr.Length + 1; // add extra element for extra null pointer last (sentinel)
|
||||||
byteArrayPointers = new IntPtr[numberOfStrings];
|
byteArrayPointers = new IntPtr[numberOfStrings];
|
||||||
@@ -551,7 +579,7 @@ namespace mpvnet
|
|||||||
return rootPointer;
|
return rootPointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string[] NativeUtf8StrArray2ManagedStrArray(IntPtr pUnmanagedStringArray, int StringCount)
|
static string[] NativeUtf8StrArray2ManagedStrArray(IntPtr pUnmanagedStringArray, int StringCount)
|
||||||
{
|
{
|
||||||
IntPtr[] pIntPtrArray = new IntPtr[StringCount];
|
IntPtr[] pIntPtrArray = new IntPtr[StringCount];
|
||||||
string[] ManagedStringArray = new string[StringCount];
|
string[] ManagedStringArray = new string[StringCount];
|
||||||
@@ -563,7 +591,7 @@ namespace mpvnet
|
|||||||
return ManagedStringArray;
|
return ManagedStringArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string StringFromNativeUtf8(IntPtr nativeUtf8)
|
static string StringFromNativeUtf8(IntPtr nativeUtf8)
|
||||||
{
|
{
|
||||||
int len = 0;
|
int len = 0;
|
||||||
while (Marshal.ReadByte(nativeUtf8, len) != 0) ++len;
|
while (Marshal.ReadByte(nativeUtf8, len) != 0) ++len;
|
||||||
@@ -572,7 +600,124 @@ namespace mpvnet
|
|||||||
return Encoding.UTF8.GetString(buffer);
|
return Encoding.UTF8.GetString(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte[] GetUtf8Bytes(string s) => Encoding.UTF8.GetBytes(s + "\0");
|
static byte[] GetUtf8Bytes(string s) => Encoding.UTF8.GetBytes(s + "\0");
|
||||||
|
|
||||||
|
static string LastHistoryPath;
|
||||||
|
static DateTime LastHistoryStartDateTime;
|
||||||
|
|
||||||
|
static void WriteHistory(string filePath)
|
||||||
|
{
|
||||||
|
int totalMinutes = Convert.ToInt32((DateTime.Now - LastHistoryStartDateTime).TotalMinutes);
|
||||||
|
|
||||||
|
if (File.Exists(LastHistoryPath) && totalMinutes > 1)
|
||||||
|
{
|
||||||
|
string historyFilepath = mp.MpvConfFolderPath + "history.txt";
|
||||||
|
|
||||||
|
File.AppendAllText(historyFilepath, DateTime.Now.ToString().Substring(0, 16) +
|
||||||
|
" " + totalMinutes.ToString().PadLeft(3) + " " +
|
||||||
|
Path.GetFileNameWithoutExtension(LastHistoryPath) + "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
LastHistoryPath = filePath;
|
||||||
|
LastHistoryStartDateTime = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ReadMetaData()
|
||||||
|
{
|
||||||
|
lock (MediaTracks)
|
||||||
|
{
|
||||||
|
MediaTracks.Clear();
|
||||||
|
|
||||||
|
using (MediaInfo mi = new MediaInfo(mp.get_property_string("path")))
|
||||||
|
{
|
||||||
|
int count = mi.GetCount(MediaInfoStreamKind.Video);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
MediaTrack 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, "FrameRate") + " FPS");
|
||||||
|
Add(track, mi.GetVideo(i, "Language/String"));
|
||||||
|
Add(track, mi.GetVideo(i, "Forced") == "Yes" ? "Forced" : "");
|
||||||
|
Add(track, mi.GetVideo(i, "Default") == "Yes" ? "Default" : "");
|
||||||
|
Add(track, mi.GetVideo(i, "Title"));
|
||||||
|
track.Text = "V: " + track.Text.Trim(" ,".ToCharArray());
|
||||||
|
track.Type = "v";
|
||||||
|
track.ID = i + 1;
|
||||||
|
MediaTracks.Add(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = mi.GetCount(MediaInfoStreamKind.Audio);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
MediaTrack track = new MediaTrack();
|
||||||
|
Add(track, mi.GetAudio(i, "Language/String"));
|
||||||
|
Add(track, mi.GetAudio(i, "Format"));
|
||||||
|
Add(track, mi.GetAudio(i, "Format_Profile"));
|
||||||
|
Add(track, mi.GetAudio(i, "BitRate/String"));
|
||||||
|
Add(track, mi.GetAudio(i, "Channel(s)/String"));
|
||||||
|
Add(track, mi.GetAudio(i, "SamplingRate/String"));
|
||||||
|
Add(track, mi.GetAudio(i, "Forced") == "Yes" ? "Forced" : "");
|
||||||
|
Add(track, mi.GetAudio(i, "Default") == "Yes" ? "Default" : "");
|
||||||
|
Add(track, mi.GetAudio(i, "Title"));
|
||||||
|
track.Text = "A: " + track.Text.Trim(" ,".ToCharArray());
|
||||||
|
track.Type = "a";
|
||||||
|
track.ID = i + 1;
|
||||||
|
MediaTracks.Add(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = mi.GetCount(MediaInfoStreamKind.Text);
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
MediaTrack track = new MediaTrack();
|
||||||
|
Add(track, mi.GetText(i, "Language/String"));
|
||||||
|
Add(track, mi.GetText(i, "Format"));
|
||||||
|
Add(track, mi.GetText(i, "Format_Profile"));
|
||||||
|
Add(track, mi.GetText(i, "Forced") == "Yes" ? "Forced" : "");
|
||||||
|
Add(track, mi.GetText(i, "Default") == "Yes" ? "Default" : "");
|
||||||
|
Add(track, mi.GetText(i, "Title"));
|
||||||
|
track.Text = "S: " + track.Text.Trim(" ,".ToCharArray());
|
||||||
|
track.Type = "s";
|
||||||
|
track.ID = i + 1;
|
||||||
|
MediaTracks.Add(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
count = get_property_int("edition-list/count");
|
||||||
|
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
MediaTrack track = new MediaTrack();
|
||||||
|
track.Text = "E: " + get_property_string($"edition-list/{i}/title");
|
||||||
|
track.Type = "e";
|
||||||
|
track.ID = i;
|
||||||
|
MediaTracks.Add(track);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Add(MediaTrack track, string val)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(val) && !(track.Text != null && track.Text.Contains(val)))
|
||||||
|
track.Text += " " + val + ",";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (Chapters)
|
||||||
|
{
|
||||||
|
Chapters.Clear();
|
||||||
|
int count = get_property_int("chapter-list/count");
|
||||||
|
|
||||||
|
for (int x = 0; x < count; x++)
|
||||||
|
{
|
||||||
|
string text = get_property_string($"chapter-list/{x}/title");
|
||||||
|
double time = get_property_number($"chapter-list/{x}/time");
|
||||||
|
Chapters.Add(new KeyValuePair<string, double>(text, time));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum EndFileEventMode
|
public enum EndFileEventMode
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ namespace DynamicGUI
|
|||||||
baseSetting = optionSetting;
|
baseSetting = optionSetting;
|
||||||
optionSetting.Default = setting["default"];
|
optionSetting.Default = setting["default"];
|
||||||
optionSetting.Value = optionSetting.Default;
|
optionSetting.Value = optionSetting.Default;
|
||||||
|
optionSetting.StartValue = optionSetting.Default;
|
||||||
|
|
||||||
foreach (TomlTable option in setting["options"])
|
foreach (TomlTable option in setting["options"])
|
||||||
{
|
{
|
||||||
@@ -65,6 +66,7 @@ namespace DynamicGUI
|
|||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
public string Value { get; set; }
|
public string Value { get; set; }
|
||||||
|
public string StartValue { get; set; }
|
||||||
public string Help { get; set; }
|
public string Help { get; set; }
|
||||||
public string Default { get; set; }
|
public string Default { get; set; }
|
||||||
public string HelpURL { get; set; }
|
public string HelpURL { get; set; }
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Windows;
|
|||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
|
|
||||||
using DynamicGUI;
|
using DynamicGUI;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
|
|
||||||
@@ -54,22 +55,9 @@ namespace mpvConfEdit
|
|||||||
if (!((darkMode == "system" && isDarkTheme) || darkMode == "always"))
|
if (!((darkMode == "system" && isDarkTheme) || darkMode == "always"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
//Background = new SolidColorBrush(Colors.Black);
|
|
||||||
|
|
||||||
Foreground = Brushes.White;
|
Foreground = Brushes.White;
|
||||||
Foreground2 = Brushes.Silver;
|
Foreground2 = Brushes.Silver;
|
||||||
Background = Brushes.Black;
|
Background = Brushes.Black;
|
||||||
|
|
||||||
//foreach (var i in MainStackPanel.Children)
|
|
||||||
//{
|
|
||||||
// switch (i)
|
|
||||||
// {
|
|
||||||
// case OptionSettingControl c:
|
|
||||||
// c.Foreground = Brushes.White;
|
|
||||||
// c.Background = Brushes.Black;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadSettings(List<SettingBase> settingsDefinitions,
|
private void LoadSettings(List<SettingBase> settingsDefinitions,
|
||||||
@@ -85,6 +73,7 @@ namespace mpvConfEdit
|
|||||||
if (setting.Name == pair.Key)
|
if (setting.Name == pair.Key)
|
||||||
{
|
{
|
||||||
setting.Value = pair.Value;
|
setting.Value = pair.Value;
|
||||||
|
setting.StartValue = pair.Value;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,6 +151,19 @@ namespace mpvConfEdit
|
|||||||
|
|
||||||
void WriteToDisk()
|
void WriteToDisk()
|
||||||
{
|
{
|
||||||
|
bool isDirty = false;
|
||||||
|
|
||||||
|
foreach (SettingBase i in MpvSettingsDefinitions)
|
||||||
|
if (i.StartValue != i.Value)
|
||||||
|
isDirty = true;
|
||||||
|
|
||||||
|
foreach (SettingBase i in MpvNetSettingsDefinitions)
|
||||||
|
if (i.StartValue != i.Value)
|
||||||
|
isDirty = true;
|
||||||
|
|
||||||
|
if (!isDirty)
|
||||||
|
return;
|
||||||
|
|
||||||
WriteToDisk(MpvConfPath, MpvConf, MpvSettingsDefinitions);
|
WriteToDisk(MpvConfPath, MpvConf, MpvSettingsDefinitions);
|
||||||
WriteToDisk(MpvNetConfPath, MpvNetConf, MpvNetSettingsDefinitions);
|
WriteToDisk(MpvNetConfPath, MpvNetConf, MpvNetSettingsDefinitions);
|
||||||
|
|
||||||
@@ -227,6 +229,7 @@ namespace mpvConfEdit
|
|||||||
|
|
||||||
private void MainWindow1_Loaded(object sender, RoutedEventArgs e)
|
private void MainWindow1_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
|
SearchControl.SearchTextBox.SelectAll();
|
||||||
Keyboard.Focus(SearchControl.SearchTextBox);
|
Keyboard.Focus(SearchControl.SearchTextBox);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,5 +51,5 @@ using System.Windows;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.7.0.0")]
|
[assembly: AssemblyVersion("1.8.0.0")]
|
||||||
[assembly: AssemblyFileVersion("1.7.0.0")]
|
[assembly: AssemblyFileVersion("1.8.0.0")]
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
@@ -10,7 +9,6 @@ namespace mpvInputEdit
|
|||||||
{
|
{
|
||||||
public partial class InputWindow : Window
|
public partial class InputWindow : Window
|
||||||
{
|
{
|
||||||
string InputString = "";
|
|
||||||
public InputItem InputItem { get; set; }
|
public InputItem InputItem { get; set; }
|
||||||
public string NewKey { get; set; } = "";
|
public string NewKey { get; set; } = "";
|
||||||
|
|
||||||
@@ -32,22 +30,21 @@ namespace mpvInputEdit
|
|||||||
|
|
||||||
void OnKeyUp(WF.KeyEventArgs e)
|
void OnKeyUp(WF.KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.KeyCode == WF.Keys.None) return;
|
|
||||||
char c = Convert.ToChar(e.KeyCode);
|
|
||||||
string text = InputString;
|
|
||||||
|
|
||||||
if (e.KeyCode == WF.Keys.ControlKey || e.KeyCode == WF.Keys.ShiftKey ||
|
if (e.KeyCode == WF.Keys.ControlKey || e.KeyCode == WF.Keys.ShiftKey ||
|
||||||
e.KeyCode == WF.Keys.Menu)
|
e.KeyCode == WF.Keys.Menu || e.KeyCode == WF.Keys.None)
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text == "")
|
return;
|
||||||
{
|
|
||||||
text = e.KeyCode.ToString();
|
string text = "";
|
||||||
if (text.Length == 1)
|
uint charValue = MapVirtualKey((uint)e.KeyCode, 2);
|
||||||
text = text.ToLowerInvariant();
|
|
||||||
|
if (charValue == 0 || (charValue & 1 << 31) == 1 << 31)
|
||||||
|
text = e.KeyCode.ToString().Trim();
|
||||||
|
else
|
||||||
|
try {
|
||||||
|
text = Convert.ToChar(charValue).ToString().ToLower().Trim();
|
||||||
}
|
}
|
||||||
|
catch {}
|
||||||
|
|
||||||
for (int i = 0; i < 13; i++)
|
for (int i = 0; i < 13; i++)
|
||||||
if ("D" + i.ToString() == text)
|
if ("D" + i.ToString() == text)
|
||||||
@@ -90,14 +87,6 @@ namespace mpvInputEdit
|
|||||||
text = "Esc"; break;
|
text = "Esc"; break;
|
||||||
case WF.Keys.PrintScreen:
|
case WF.Keys.PrintScreen:
|
||||||
text = "Print"; break;
|
text = "Print"; break;
|
||||||
case WF.Keys.Right:
|
|
||||||
text = "Right"; break;
|
|
||||||
case WF.Keys.Left:
|
|
||||||
text = "Left"; break;
|
|
||||||
case WF.Keys.Up:
|
|
||||||
text = "Up"; break;
|
|
||||||
case WF.Keys.Down:
|
|
||||||
text = "Down"; break;
|
|
||||||
case WF.Keys.Play:
|
case WF.Keys.Play:
|
||||||
text = "Play"; break;
|
text = "Play"; break;
|
||||||
case WF.Keys.Pause:
|
case WF.Keys.Pause:
|
||||||
@@ -130,20 +119,24 @@ namespace mpvInputEdit
|
|||||||
text = "Cancel"; break;
|
text = "Cancel"; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text == "#")
|
bool shiftWasHandled = false;
|
||||||
text = "Sharp";
|
|
||||||
|
|
||||||
bool isAlt = GetKeyState(18) < (short)0;
|
bool isAlt = GetKeyState(18) < (short)0;
|
||||||
bool isShift = GetKeyState(16) < (short)0;
|
bool isShift = GetKeyState(16) < (short)0;
|
||||||
bool isCtrl = GetKeyState(17) < (short)0;
|
bool isCtrl = GetKeyState(17) < (short)0;
|
||||||
|
|
||||||
if (!(isAlt && isCtrl && !isShift) && !(isShift && !isAlt && !isCtrl && !int.TryParse(text.Replace("F", ""), out int value)))
|
if (text.Length == 1 && isShift && text[0] != GetModifiedKey(text[0]))
|
||||||
{
|
{
|
||||||
if (isAlt) text = "Alt+" + text;
|
text = GetModifiedKey(text[0]).ToString();
|
||||||
if (isShift) text = "Shift+" + text;
|
shiftWasHandled = true;
|
||||||
if (isCtrl) text = "Ctrl+" + text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text == "#") text = "Sharp";
|
||||||
|
|
||||||
|
if (isAlt) text = "Alt+" + text;
|
||||||
|
if (isShift && !shiftWasHandled) text = "Shift+" + text;
|
||||||
|
if (isCtrl) text = "Ctrl+" + text;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(text))
|
if (!string.IsNullOrEmpty(text))
|
||||||
SetKey(text);
|
SetKey(text);
|
||||||
}
|
}
|
||||||
@@ -155,13 +148,8 @@ namespace mpvInputEdit
|
|||||||
KeyLabel.Content = key;
|
KeyLabel.Content = key;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnKeyPress(WF.KeyPressEventArgs e)
|
[DllImport("user32.dll")]
|
||||||
{
|
static extern uint MapVirtualKey(uint uCode, uint uMapType);
|
||||||
if (char.IsControl(e.KeyChar))
|
|
||||||
InputString = "";
|
|
||||||
else
|
|
||||||
InputString = e.KeyChar.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static WF.Keys ModifierKeys {
|
public static WF.Keys ModifierKeys {
|
||||||
get {
|
get {
|
||||||
@@ -176,29 +164,31 @@ namespace mpvInputEdit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static char GetModifiedKey(char c)
|
||||||
|
{
|
||||||
|
short vkKeyScanResult = VkKeyScan(c);
|
||||||
|
|
||||||
|
if (vkKeyScanResult == -1)
|
||||||
|
return c;
|
||||||
|
|
||||||
|
uint code = (uint)vkKeyScanResult & 0xff;
|
||||||
|
byte[] b = new byte[256];
|
||||||
|
b[0x10] = 0x80;
|
||||||
|
uint r;
|
||||||
|
|
||||||
|
if (1 != ToAscii(code, code, b, out r, 0))
|
||||||
|
return c;
|
||||||
|
|
||||||
|
return (char)r;
|
||||||
|
}
|
||||||
|
|
||||||
void ProcessKeyEventArgs(ref WF.Message m)
|
void ProcessKeyEventArgs(ref WF.Message m)
|
||||||
{
|
{
|
||||||
int WM_CHAR = 258, WM_SYSCHAR = 262, /*WM_KEYDOWN = 256, WM_SYSKEYDOWN = 260,*/
|
int WM_KEYUP = 0x0101, WM_SYSKEYUP = 0x0105, WM_APPCOMMAND = 0x0319;
|
||||||
WM_KEYUP = 0x0101, WM_SYSKEYUP = 0x0105, WM_APPCOMMAND = 0x0319;
|
|
||||||
|
|
||||||
IntPtr newWParam = IntPtr.Zero;
|
|
||||||
WF.KeyEventArgs ke = null;
|
|
||||||
WF.KeyPressEventArgs kpe = null;
|
|
||||||
|
|
||||||
if (m.Msg == WM_CHAR || m.Msg == WM_SYSCHAR)
|
|
||||||
{
|
|
||||||
kpe = new WF.KeyPressEventArgs(unchecked((char)(long)m.WParam));
|
|
||||||
OnKeyPress(kpe);
|
|
||||||
newWParam = (IntPtr)kpe.KeyChar;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ke = new WF.KeyEventArgs((WF.Keys)(unchecked((int)(long)m.WParam)) | ModifierKeys);
|
|
||||||
|
|
||||||
if (m.Msg == WM_KEYUP || m.Msg == WM_SYSKEYUP)
|
if (m.Msg == WM_KEYUP || m.Msg == WM_SYSKEYUP)
|
||||||
OnKeyUp(ke);
|
OnKeyUp(new WF.KeyEventArgs((WF.Keys)(unchecked((int)(long)m.WParam)) | ModifierKeys));
|
||||||
|
else if (m.Msg == WM_APPCOMMAND)
|
||||||
if (m.Msg == WM_APPCOMMAND)
|
|
||||||
{
|
{
|
||||||
switch ((AppCommand)(m.LParam.ToInt32() >> 16))
|
switch ((AppCommand)(m.LParam.ToInt32() >> 16))
|
||||||
{
|
{
|
||||||
@@ -248,10 +238,6 @@ namespace mpvInputEdit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (kpe != null)
|
|
||||||
m.WParam = newWParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal enum AppCommand
|
internal enum AppCommand
|
||||||
{
|
{
|
||||||
MEDIA_CHANNEL_DOWN = 52,
|
MEDIA_CHANNEL_DOWN = 52,
|
||||||
@@ -273,6 +259,16 @@ namespace mpvInputEdit
|
|||||||
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
[DllImport("user32.dll", CharSet = CharSet.Auto)]
|
||||||
public static extern short GetKeyState(int keyCode);
|
public static extern short GetKeyState(int keyCode);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
static extern short VkKeyScan(char c);
|
||||||
|
|
||||||
|
[DllImport("user32.dll", SetLastError = true)]
|
||||||
|
static extern int ToAscii(uint uVirtKey,
|
||||||
|
uint uScanCode,
|
||||||
|
byte[] lpKeyState,
|
||||||
|
out uint lpChar,
|
||||||
|
uint flags);
|
||||||
|
|
||||||
private void Window_Loaded(object sender, RoutedEventArgs e)
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
|
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
|
||||||
|
|||||||
@@ -37,8 +37,9 @@ namespace mpvInputEdit
|
|||||||
string searchText = SearchControl.SearchTextBox.Text.ToLower();
|
string searchText = SearchControl.SearchTextBox.Text.ToLower();
|
||||||
if (searchText == "") return true;
|
if (searchText == "") return true;
|
||||||
|
|
||||||
if (searchText.StartsWith("i ") || searchText.StartsWith("i:"))
|
if (searchText.StartsWith("i ") || searchText.StartsWith("i:") || searchText.Length == 1)
|
||||||
{
|
{
|
||||||
|
if (searchText.Length > 1)
|
||||||
searchText = searchText.Substring(2).Trim();
|
searchText = searchText.Substring(2).Trim();
|
||||||
|
|
||||||
if (searchText.Length < 3)
|
if (searchText.Length < 3)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ using System.Windows;
|
|||||||
[assembly: AssemblyConfiguration("")]
|
[assembly: AssemblyConfiguration("")]
|
||||||
[assembly: AssemblyCompany("")]
|
[assembly: AssemblyCompany("")]
|
||||||
[assembly: AssemblyProduct("mpv(.net) input edit")]
|
[assembly: AssemblyProduct("mpv(.net) input edit")]
|
||||||
[assembly: AssemblyCopyright("Copyright © 2017 stax76")]
|
[assembly: AssemblyCopyright("Copyright © 2017-2019 stax76")]
|
||||||
[assembly: AssemblyTrademark("")]
|
[assembly: AssemblyTrademark("")]
|
||||||
[assembly: AssemblyCulture("")]
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
@@ -51,5 +51,5 @@ using System.Windows;
|
|||||||
// You can specify all the values or you can default the Build and Revision Numbers
|
// You can specify all the values or you can default the Build and Revision Numbers
|
||||||
// by using the '*' as shown below:
|
// by using the '*' as shown below:
|
||||||
// [assembly: AssemblyVersion("1.0.*")]
|
// [assembly: AssemblyVersion("1.0.*")]
|
||||||
[assembly: AssemblyVersion("1.3.0.0")]
|
[assembly: AssemblyVersion("1.5.0.0")]
|
||||||
[assembly: AssemblyFileVersion("1.3.0.0")]
|
[assembly: AssemblyFileVersion("1.5.0.0")]
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace Controls
|
|||||||
SearchClearButton.Visibility = Visibility.Visible;
|
SearchClearButton.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
if (SearchTextBox.Text == "?")
|
if (SearchTextBox.Text == "?")
|
||||||
MessageBox.Show("Filtering works by searching in the Input, Menu and Command but it's possible to reduce the filter scope to either of Input, Menu or Command by prefixing as follows:\n\ni <input search>\ni: <input search>\n\nm <menu search>\nm: <menu search>\n\nc <command search>\nc: <command search>", "Filtering", MessageBoxButton.OK, MessageBoxImage.Information);
|
MessageBox.Show("Filtering works by searching in the Input, Menu and Command but it's possible to reduce the filter scope to either of Input, Menu or Command by prefixing as follows:\n\ni <input search>\ni: <input search>\n\nm <menu search>\nm: <menu search>\n\nc <command search>\nc: <command search>\n\nIf only one character is entered the search will be performed only in the input.", "Filtering", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,6 +110,9 @@
|
|||||||
<Generator>ResXFileCodeGenerator</Generator>
|
<Generator>ResXFileCodeGenerator</Generator>
|
||||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||||
</EmbeddedResource>
|
</EmbeddedResource>
|
||||||
|
<None Include="..\README.md">
|
||||||
|
<Link>README.md</Link>
|
||||||
|
</None>
|
||||||
<None Include="app.manifest" />
|
<None Include="app.manifest" />
|
||||||
<None Include="Properties\Settings.settings">
|
<None Include="Properties\Settings.settings">
|
||||||
<Generator>SettingsSingleFileGenerator</Generator>
|
<Generator>SettingsSingleFileGenerator</Generator>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 2.9 MiB |
BIN
screenshots/mpvnetContextMenu.png
Normal file
|
After Width: | Height: | Size: 1.3 MiB |