Compare commits

...

134 Commits
3.7 ... 4.7.7

Author SHA1 Message Date
Frank Skare
d321ab477f key binding MBTN_Mid 2019-07-21 20:31:47 +02:00
Frank Skare
1cd0f87278 readme, manual and AssemblyInfo.cs updated 2019-07-21 20:10:35 +02:00
Frank Skare
936274fc3b new main screenshot, about dialog for rating addon 2019-07-21 19:56:19 +02:00
Frank Skare
2bbaa30322 options autofit-smaller, autofit-larger added 2019-07-21 19:22:17 +02:00
Frank Skare
cedd54d64d start-threshold setting added 2019-07-21 17:06:55 +02:00
Frank Skare
38604459a6 update changelog 2019-07-20 17:54:52 +02:00
Frank Skare
0a48a0dc6f new logic to find the settings folder 2019-07-20 17:53:34 +02:00
Frank Skare
85a23fbae1 tweaks and refactoring 2019-07-18 05:47:16 +02:00
Frank Skare
01da76bdc4 fix changelog 2019-07-17 19:22:47 +02:00
Frank Skare
1f1553e159 - 2019-07-17 19:04:24 +02:00
Frank Skare
2af84941ca color theme related improvements 2019-07-17 18:18:33 +02:00
Frank Skare
a82f558acf - 2019-07-14 12:22:50 +02:00
Frank Skare
d13f7201ba - 2019-07-14 06:16:52 +02:00
Frank Skare
89aa0c6133 added image format support to the info command, to file association management, to folder loading, to Everything 2019-07-14 06:07:25 +02:00
Frank Skare
1699467e0d added image format support for the info command 2019-07-14 05:52:23 +02:00
Frank Skare
7a27ab0513 added file size to info command using audio files 2019-07-14 05:38:53 +02:00
Frank Skare
2a0c68e4ec - 2019-07-14 05:15:59 +02:00
Frank Skare
91c42d4d3f - 2019-07-14 05:04:22 +02:00
Frank Skare
49d56166d7 - 2019-07-14 04:34:45 +02:00
Frank Skare
1ac603b904 - 2019-07-14 04:03:58 +02:00
Frank Skare
d5b2923c68 reorganize code for easier scripting 2019-07-14 02:06:23 +02:00
Frank Skare
b80b375df1 move code before base.OnShown so scripts can use the ContextMenu 2019-07-14 01:40:13 +02:00
Frank Skare
2b2b60b90f fix rotating cursor 2019-07-13 19:22:51 +02:00
Frank Skare
3977283ecb minor tweak 2019-07-13 17:49:39 +02:00
Frank Skare
d27778775d few layout problems were fixed, autosize for instance did not work 2019-07-13 16:35:42 +02:00
Frank Skare
5c053bb5eb fix borderless window not minimizable from taskbar 2019-07-13 03:24:15 +02:00
Frank Skare
83658aa476 - 2019-07-12 23:53:43 +02:00
Frank Skare
e0111b6f12 - 2019-07-12 06:23:53 +02:00
Frank Skare
e308bbf928 - 2019-07-11 23:55:07 +02:00
Frank Skare
1817fbc0fd - 2019-07-09 09:17:35 +02:00
Frank Skare
3b149aea54 - 2019-07-08 18:18:55 +02:00
Frank Skare
7575a2d736 - 2019-07-06 23:51:28 +02:00
Frank Skare
17c198e711 - 2019-07-06 23:46:14 +02:00
Frank Skare
1f8635e9c4 - 2019-07-06 09:35:14 +02:00
Frank Skare
37e6c198a1 - 2019-07-06 03:46:36 +02:00
Frank Skare
751c8779f4 - 2019-07-06 02:39:25 +02:00
Frank Skare
9e2bf46636 - 2019-07-05 07:17:58 +02:00
Frank Skare
3529323d93 - 2019-07-05 06:36:15 +02:00
Frank Skare
ba52c1f8b5 - 2019-07-05 05:40:14 +02:00
Frank Skare
bf2070d758 - 2019-07-05 05:38:52 +02:00
Frank Skare
7a1de4cf0f - 2019-07-05 05:05:37 +02:00
Frank Skare
caf0bee161 - 2019-07-01 04:17:16 +02:00
Frank Skare
cedd694e8e - 2019-07-01 02:45:22 +02:00
Frank Skare
d1c58585f5 - 2019-06-30 07:16:28 +02:00
Frank Skare
0fce626c46 - 2019-06-30 07:14:13 +02:00
Frank Skare
adb8f38c0d - 2019-06-30 02:09:30 +02:00
Frank Skare
735ee42580 - 2019-06-30 02:07:16 +02:00
Frank Skare
103ae81b8b - 2019-06-29 12:36:35 +02:00
Frank Skare
8e351193e1 - 2019-06-27 10:35:37 +02:00
Frank Skare
d57b9281a1 - 2019-06-27 07:51:19 +02:00
Frank Skare
e07627377e - 2019-06-27 07:44:23 +02:00
Frank Skare
8899f4b772 - 2019-06-27 06:59:34 +02:00
Frank Skare
e7fd268927 - 2019-06-27 02:43:17 +02:00
Frank Skare
c8fe3a4c43 - 2019-06-26 16:06:41 +02:00
Frank Skare
19c2502c31 - 2019-06-26 03:23:40 +02:00
Frank Skare
56dae4babd - 2019-06-26 03:16:40 +02:00
Frank Skare
bc20d7527c - 2019-06-26 02:54:59 +02:00
Frank Skare
3d325d4fe5 - 2019-06-25 05:14:58 +02:00
Frank Skare
ba0141cfb1 - 2019-06-24 09:04:18 +02:00
Frank Skare
d32c7b4056 - 2019-06-24 09:00:01 +02:00
Frank Skare
63f064ba1c - 2019-06-24 07:52:30 +02:00
Frank Skare
e5c9df31e4 - 2019-06-22 06:27:22 +02:00
Frank Skare
c0a8e895f7 - 2019-06-19 18:22:40 +02:00
Frank Skare
064ae44ecf - 2019-06-19 18:03:14 +02:00
Frank Skare
005ba6e5ea - 2019-06-18 16:20:02 +02:00
Frank Skare
9f6aba6cbd - 2019-06-18 16:08:28 +02:00
Frank Skare
87e3d9ca3e - 2019-06-17 21:35:14 +02:00
Frank Skare
29a0eae3d5 - 2019-06-16 12:46:04 +02:00
Frank Skare
01b2492cde - 2019-06-16 12:28:15 +02:00
Frank Skare
d2b1ca0c69 - 2019-06-16 12:18:36 +02:00
Frank Skare
91b429e6b0 4.2 2019-06-16 12:12:51 +02:00
Frank Skare
fa05e95892 - 2019-06-16 12:02:46 +02:00
Frank Skare
701a58a4bf - 2019-06-16 11:53:28 +02:00
Frank Skare
387c3f7896 - 2019-06-16 11:48:15 +02:00
Frank Skare
eeb0189577 - 2019-06-16 11:47:03 +02:00
Frank Skare
de4c2d6d70 - 2019-06-16 11:44:41 +02:00
Frank Skare
63ee598ebd support added to force single instance 2019-06-16 10:00:14 +02:00
Frank Skare
1bf8e568c5 - 2019-06-14 01:03:18 +02:00
Frank Skare
f8f58dba5e - 2019-06-14 00:23:51 +02:00
Frank Skare
ebb8ad351a - 2019-06-13 22:29:23 +02:00
Frank Skare
0202c34c6f - 2019-06-07 23:58:25 +02:00
Frank Skare
c3f2b22ff5 - 2019-06-01 15:15:41 +02:00
Frank Skare
c1a03c3e4b - 2019-05-31 04:19:41 +02:00
Frank Skare
e041908f04 drag & drop support for subtitles added 2019-05-31 04:18:05 +02:00
Frank Skare
270559c3f7 - 2019-05-27 18:53:27 +02:00
Frank Skare
0dfd19eb45 - 2019-05-27 18:48:12 +02:00
Frank Skare
9cdba6d2f2 - 2019-05-27 18:43:23 +02:00
Frank Skare
50bf09ff8d - 2019-05-27 18:37:59 +02:00
Frank Skare
c54fa708f8 - 2019-05-27 16:29:44 +02:00
Frank Skare
7698e873ca - 2019-05-27 16:06:21 +02:00
Frank Skare
89b8de2c40 - 2019-05-27 15:51:15 +02:00
Frank Skare
259d2d882a - 2019-05-27 15:33:24 +02:00
Frank Skare
b7fbef5a88 - 2019-05-27 15:29:45 +02:00
Frank Skare
0ca9ac93f1 - 2019-05-27 05:10:38 +02:00
Frank Skare
f7b3900a0a - 2019-05-27 04:53:47 +02:00
Frank Skare
c98036dc42 - 2019-05-27 04:48:09 +02:00
Frank Skare
a9e7ae4b97 - 2019-05-27 04:45:35 +02:00
Frank Skare
30fb744114 - 2019-05-27 04:39:52 +02:00
Frank Skare
d465516a6a - 2019-05-27 04:34:59 +02:00
Frank Skare
0b951875e6 - 2019-05-27 01:45:37 +02:00
Frank Skare
5ed65733a0 - 2019-05-27 01:44:56 +02:00
Frank Skare
caac45a871 - 2019-05-27 01:41:04 +02:00
Frank Skare
f4f69816a7 - 2019-05-27 01:20:45 +02:00
Frank Skare
1dddf129df - 2019-05-27 01:19:44 +02:00
Frank Skare
ca9690c3b3 - 2019-05-26 06:23:47 +02:00
Frank Skare
3b0b098bda - 2019-05-26 06:21:43 +02:00
Frank Skare
c72080e92c - 2019-05-26 06:18:21 +02:00
Frank Skare
237a3cf132 - 2019-05-26 06:01:31 +02:00
Frank Skare
562f149fff - 2019-05-26 05:54:21 +02:00
Frank Skare
dc85372c36 - 2019-05-26 05:53:10 +02:00
Frank Skare
2595465ec7 - 2019-05-26 05:51:31 +02:00
Frank Skare
1db733ae13 - 2019-05-26 05:39:00 +02:00
Frank Skare
ba3bc48297 - 2019-05-26 05:34:52 +02:00
Frank Skare
5e16c47100 - 2019-05-26 05:32:23 +02:00
Frank Skare
a13c6648a3 - 2019-05-26 04:54:17 +02:00
Frank Skare
5dc244d6f6 - 2019-05-26 04:47:23 +02:00
Frank Skare
da9bc4199b - 2019-05-26 04:45:28 +02:00
Frank Skare
1868957388 - 2019-05-26 04:36:04 +02:00
Frank Skare
0139a1ef70 - 2019-05-26 04:32:19 +02:00
Frank Skare
0989b7ef95 - 2019-05-26 04:30:24 +02:00
Frank Skare
16924e5a3b - 2019-05-26 04:28:11 +02:00
Frank Skare
502e1a9651 - 2019-05-26 04:07:26 +02:00
Frank Skare
36a7bbcfab - 2019-05-26 03:48:06 +02:00
Frank Skare
8fda90e09a - 2019-05-26 03:10:28 +02:00
Frank Skare
269457dbde - 2019-05-26 00:38:00 +02:00
Frank Skare
c93647841e - 2019-05-25 23:54:53 +02:00
Frank Skare
0f78ea38ac - 2019-05-25 14:24:18 +02:00
Frank Skare
7eb60f3e4c - 2019-05-25 04:41:16 +02:00
Frank Skare
2636da446e - 2019-05-25 04:40:03 +02:00
Frank Skare
c7fab24044 - 2019-05-25 04:16:14 +02:00
Frank Skare
2ce1f3d9c1 - 2019-05-25 03:40:14 +02:00
Frank Skare
d2f9108c22 - 2019-05-25 03:33:33 +02:00
Frank Skare
3a2b9ee542 - 2019-05-25 03:20:35 +02:00
Frank Skare
5c93bc00f7 - 2019-05-25 03:09:15 +02:00
59 changed files with 2921 additions and 1326 deletions

173
Changelog.md Normal file
View File

@@ -0,0 +1,173 @@
### 4.7.7
- on Win 7 the theme color was hardcoded to DarkSlateGrey because
WPF was returning a bad color on Win 7, this was fixed by reading
the theme color from the Registry on Win 7. If the theme color
is identical to the background color it's corrected
- dark-color setting was added to overwrite the OS theme color used in dark mode,
find the setting on the General tab
- light-color setting was added to overwrite the OS theme color used in non dark mode,
find the setting on the General tab
- various changes regarding input handling, multi media keys and
mouse forward/backward were successfully tested
- it's now possible to use a custom folder as config folder,
A TaskDialog shows five options: 1. appdata mpv.net, 2. appdata mpv (shared with mpv),
3. portable_config, 4. startup, 5. custom
- slightly increased startup performance, start-threshold setting added.
Threshold in milliseconds to wait for libmpv returning the video resolution
before the window is shown, otherwise default dimensions are used as defined
by autofit and start-size. Default: 1500
- autofit-smaller setting added. Minimum window height in percent. Default: 40%
- autofit-larger setting added. Maximum window height in percent. Default: 75%
### 4.7.3
- fix cursor showing load activity on startup
- added file size and type to info command using audio files
- added image format support to the info command, to file
association management, to folder loading, to Everything
- readme/github have a updated features and architecture section
### 4.7.1
- few layout problems were fixed, autosize for instance did not work
### 4.7
- remember-height was replaced with start-size, when start-size is set
to video the main video starts directly with the native video size,
before it was starting with the autofit size first and was only
afterwards resized to the native video size
- on exit the window location can be saved with remember-position
- in the learn window of the input editor underscores were stripped
because they have a special meaning in WPF labels
- fix for keys/input not working for MBTN_LEFT_DBL, MBTN_BACK, MBTN_FORWARD
- in the learn window of the input editor support was added for
mouse left, mouse left double, mouse mid, mouse forward, mouse back
- libmpv was updated to shinchiro 2019-07-07
- when border is none it wasn't possible to minimize the window from
the task bar because this is the WinForms default behavier. This
was fixed by calling Spy++ to the rescue and adding WS_MINIMIZEBOX
in CreateParams
### 4.6
- fix for middle mouse button not working
- fix of logo overlay using a huge amount of memory (thx for the [ghacks article](https://www.ghacks.net/2019/07/05/a-look-at-mpv-net-a-mpv-frontend-with-everything-integration/))
- fix config dialog showing a message about app restart without reason
- when multiple files are selected in Windows File Explorer and enter is
pressed, the files are opened as selected, the order is random though
because Explorer starts multiple mpv.net processes concurrently
- libmpv was updated to shinchiro 2019-06-30
- the [mpv.conf defaults](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/mpvConf.txt) were changed to show a larger OSC
- in case a file is opened that has a aspect ratio smaller then 1.2 then
the window size will use a aspect ratio of 1.8
- new JavaScript script osc-visibility.js included in the distribution
under startup\scripts. It sets the OSC to be always on for audio files
and auto for non audio files
### 4.5
- opening a URL manually no longer uses a input box but uses the clipboard directly
- the manifest was missing the company attribute which caused
mpv.net not appearing in the 'Open with' menu of the Windows File Explorer,
thanks to 44vince44 for pointing this out!!!
- new Python and C# script examples were added to the wiki and the scripting and
add-on documentation was improved
- invalid command line arguments were ignored, now an error message is shown
- a description on how to start mpv.net from Google Chrome was added to the
manual, it's useful to play videos from sites like YouTube, find the
description [here](https://github.com/stax76/mpv.net/blob/master/Manual.md#chrome-extension)
- new config setting remember-height added to remember the window height,
otherwise the video's native resolution is used
- support for protocols other then http added
### 4.4
- clipboard-monitoring was replaced by url-whitelist:
Keyword whitelist to monitor the clipboard for URLs to play.
Default: tube vimeo ard zdf
- some settings like colors didn't work because enclosing quotes were missing
- when single process queue is used the folder is no longer loaded
- the playlist is never cleared whenever the control key is down but
files and URLs are appended instead
- powershell script hosting bugs were fixed and a new powershell example script
was added to the [scripting wiki page](https://github.com/stax76/mpv.net/wiki/Scripting#powershell)
- the menu entry for the command palette was renamed to 'Show All Commands' and
the default key binding was changed to F1 which is also the default in VS Code
- the default key binding of the Everything media search was changed to F3
- support for the mpv property 'border' was added to the config editor
to show/hide the window decoration (titlebar, border). A toggle menu item and
key binding (b) was added as well ([Default Binding](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt#L135))
### 4.3.1
- there was a old bug setting the screen property
### 4.3
- there was new bug in file association feature
### 4.2
- the help and layout in the config editor was improved
- clipboard monitoring for URLs can be disabled in the settings
- the context menu has a new feature: Open > Add files to playlist,
it appends files to the playlist [(Default binding)](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt#L33)
- a setting was added to force using a single mpv.net process instance
### 4.1
- drag & drop support for subtitles was added
- libmpv was updated
- command line support for stdin and URLs was added
- there was a crash happening when the player is
minimized in the taskbar
### 4.0
- on the start screen the mpv.NET icon is shown instead of the mpv icon,
feedback and contributions regarding the icon are welcome! The paint.net
pdn and png source is located [here](https://github.com/stax76/mpv.net/tree/master/img)
- everytime only one file is opened the complete folder is loaded in the playlist
- the info command (i key) shows the audio format
- new options osd-font-size, sub-font, sub-font-size
- new color options with dedicated GUI support: sub-color, sub-border-color, sub-back-color
- the config editor no longer shows the command line switches
- the github start page was greatly improved
- the setup.ps1 PowerShell script was greatly improved in regard of error handling and readability
- a [manual to mpv.net](Manual.md) was created
### 3.7
- new icon design, probably better then before but still too simple
- the radio buttons in the config editor have now a Windows 10 like design,
they are larger and use the Windows theme color
### 3.6.1
- there was a bug causing an exception if both the input editor and config editor
is opened, as soon as one is opened, the other can't be opened
### 3.6
- playing files from rar archives caused an exception
- there was a bug that caused underscores beeing removed from input like MBTN_LEFT_DBL
- the search clear button in the input editor had a render issue in dark mode
- new search feature added to search and play media files, requires
[Everything](https://www.voidtools.com) to be installed. [Default Binding](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt#L29)
### 3.5
- when the main windows gets activated and the clipboard content starts with http
mpv.net will ask to play the URL, previously this was restricted to YouTube URLs
- Python script errors show line and column whenever it is supported by IronPython
- if conf files exist in the startup directory mpv.net will use the startup
directory as config directory instead of creating default conf files in appdata
- renamed commands are handled now by migration code instead of being broken
### 3.4
- new feature added to manage file associations from within the app. It can be found in the menu at: Tools > Manage... [Default Binding](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt#L149)
- new zip download option added
- new x86 download option added

820
Manual.md Normal file
View File

@@ -0,0 +1,820 @@
# mpv.net manual
## Table of contents
* [About mpv.net](#about-mpvnet)
+ [Target Audience](#target-audience)
* [Requirements](#requirements)
* [Installation](#installation)
+ [File Associations](#file-associations)
+ [Chrome Extension](#chrome-extension)
* [Context Menu](#context-menu)
+ [Open > Open Files](#open--open-files)
+ [Open > Open URL](#open--open-url)
+ [Open > Show media search](#open--show-media-search)
+ [Open > Load external audio files](#open--load-external-audio-files)
+ [Open > Load external subtitle files](#open--load-external-subtitle-files)
+ [Play/Pause](#play-pause)
+ [Stop](#stop)
+ [Toggle Fullscreen](#toggle-fullscreen)
+ [Navigate > Previous File](#navigate--previous-file)
+ [Navigate > Next File](#navigate--next-file)
+ [Navigate > Next Chapter](#navigate--next-chapter)
+ [Navigate > Previous Chapter](#navigate--previous-chapter)
+ [Navigate > Jump Next Frame](#navigate--jump-next-frame)
+ [Navigate > Jump Previous Frame](#navigate--jump-previous-frame)
+ [Navigate > Jump](#navigate--jump)
+ [Pan & Scan > Increase Size](#pan--scan--increase-size)
+ [Pan & Scan > Decrease Size](#pan--scan--decrease-size)
+ [Pan & Scan > Move Left](#pan--scan--move-left)
+ [Pan & Scan > Move Right](#pan--scan--move-right)
+ [Pan & Scan > Move Up](#pan--scan--move-up)
+ [Pan & Scan > Move Down](#pan--scan--move-down)
+ [Pan & Scan > Decrease Height](#pan--scan--decrease-height)
+ [Pan & Scan > Increase Height](#pan--scan--increase-height)
+ [Pan & Scan > Reset](#pan--scan--reset)
+ [Video > Decrease Contrast](#video--decrease-contrast)
+ [Video > Increase Contrast](#video--increase-contrast)
+ [Video > Decrease Brightness](#video--decrease-brightness)
+ [Video > Increase Brightness](#video--increase-brightness)
+ [Video > Decrease Gamma](#video--decrease-gamma)
+ [Video > Increase Gamma](#video--increase-gamma)
+ [Video > Decrease Saturation](#video--decrease-saturation)
+ [Video > Increase Saturation](#video--increase-saturation)
+ [Video > Take Screenshot](#video--take-screenshot)
+ [Video > Toggle Deinterlace](#video--toggle-deinterlace)
+ [Video > Cycle Aspect Ratio](#video--cycle-aspect-ratio)
+ [Audio > Cycle/Next](#audio--cycle-next)
+ [Audio > Delay +0.1](#audio--delay--01)
+ [Audio > Delay -0.1](#audio--delay--01)
+ [Subtitle > Cycle/Next](#subtitle--cycle-next)
+ [Subtitle > Toggle Visibility](#subtitle--toggle-visibility)
+ [Subtitle > Delay -0.1](#subtitle--delay--01)
+ [Subtitle > Delay 0.1](#subtitle--delay-01)
+ [Subtitle > Move Up](#subtitle--move-up)
+ [Subtitle > Move Down](#subtitle--move-down)
+ [Subtitle > Decrease Subtitle Font Size](#subtitle--decrease-subtitle-font-size)
+ [Subtitle > Increase Subtitle Font Size](#subtitle--increase-subtitle-font-size)
+ [Volume > Up](#volume--up)
+ [Volume > Down](#volume--down)
+ [Volume > Mute](#volume--mute)
+ [Speed > -10%](#speed---10-)
+ [Speed > 10%](#speed--10-)
+ [Speed > Half](#speed--half)
+ [Speed > Double](#speed--double)
+ [Speed > Reset](#speed--reset)
+ [Extensions > Rating > 0stars](#extensions--rating--0stars)
+ [View > On Top > Enable](#view--on-top--enable)
+ [View > On Top > Disable](#view--on-top--disable)
+ [View > File Info](#view--file-info)
+ [View > Show Statistics](#view--show-statistics)
+ [View > Toggle Statistics](#view--toggle-statistics)
+ [View > Toggle OSC Visibility](#view--toggle-osc-visibility)
+ [View > Show Playlist](#view--show-playlist)
+ [View > Show Audio/Video/Subtitle List](#view--show-audio-video-subtitle-list)
+ [Settings > Show Config Editor](#settings--show-config-editor)
+ [Settings > Show Input Editor](#settings--show-input-editor)
+ [Settings > Open Config Folder](#settings--open-config-folder)
+ [Tools > Command Palette](#tools--command-palette)
+ [Tools > Show History](#tools--show-history)
+ [Tools > Set/clear A-B loop points](#tools--set-clear-a-b-loop-points)
+ [Tools > Toggle infinite file looping](#tools--toggle-infinite-file-looping)
+ [Tools > Cycle Hardware Decoding](#tools--cycle-hardware-decoding)
+ [Tools > Execute mpv command](#tools--execute-mpv-command)
+ [Tools > Manage File Associations](#tools--manage-file-associations)
+ [Help > Show mpv manual](#help--show-mpv-manual)
+ [Help > Show mpv default keys](#help--show-mpv-default-keys)
+ [Help > Show mpv.net default keys](#help--show-mpvnet-default-keys)
+ [Help > Show mpv.net manual](#help--show-mpvnet-manual)
+ [Help > About mpv.net](#help--about-mpvnet)
+ [Exit](#exit)
+ [Exit Watch Later](#exit-watch-later)
* [Command Line Interface](#command-line-interface)
## About mpv.net
mpv.net is a modern media player for Windows. mpv is similar to VLC not based on DirectShow like MPC, mpv.net is based on libmpv which in return is based on ffmpeg.
libmpv provides the majority of the features of the mpv media player, a fork of mplayer. mpv focuses on the usage of the command line interface, mpv.net retains the ability to be used from the command line and adds a modern GUI on top of it.
mpv.net is meant to be a small single person project, it's designed to be mpv compatible, can use the same settings as mpv and offers almost all mpv features because they are all contained in libmpv, this means the official [mpv manual](https://mpv.io/manual/master/) fully applies to mpv.net.
### Target Audience
The target audience of mpv.net are programmers, nerds and software enthusiasts that need something more advanced than typical media players.
Furthermore mpv.net is well suited for users who are interested to learn mpv, Linux, portable apps and the command line.
## Requirements
mpv.net is based on the .NET Framework and requires Windows 7 as minimum version of the Windows operating system. As of the writing of this manual the minimum required .NET Framework version is 4.7.2.
For optimal results a modern graphics card is recommended.
## Installation
mpv.net is available as setup and as portable download in the 7zip and zip archive format, to unpack the portable download 7zip can be used, it is available at www.7-zip.org.
If you are new to mpv.net the portable download is recommended, for regular mpv.net users the setup is typically more easy to use.
The x64 editions require more memory and have the advantage of decoders being typically first and better optimized for x64.
x86 should though still be well supported and work but it's not as well tested than x64.
### File Associations
File Associations can be created using the setup or from within the apps context menu under 'Tools > Manage File Associations'.
Windows 10 prevents apps to register as the default app, to define the default video or audio player app in Windows 10 go to the Windows settings under 'Settings > Apps > Default apps' or shell execute 'ms-settings:defaultapps'.
It's also possible to change the default application using the Open With feature of the context menu in Windows File Explorer.
### Chrome Extension
In order to play videos from sites such as YouTube the Chrome Extension [Play with mpv](https://chrome.google.com/webstore/detail/play-with-mpv/hahklcmnfgffdlchjigehabfbiigleji) can be used.
Due to Chrome Extensions not being able to start a app, another app that communicates with the extension is required, this app can be downloaded [here](http://www.mediafire.com/file/6tmwdjsfknhmsxy/play-with-mpvnet-server.7z/file). The extension works only when the app is running, to have the app always running a link can be created in the auto start folder located at:
`C:\Users\%username%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup`
This will start the app on system start and have it running in the background. When the file association registration of mpv.net was executed then the app should find the location of mpv.net, alternativly the mpv.net folder can be added to the PATH environment variable.
## Context Menu
The most important part of the user interface in mpv.net is the context menu, the menu can be customized as it is defined in the same file where the key bindings are defined.
### Open > Open Files
The Open Files menu entry is one way to open files in mpv.net, it supports multi selection.
Another way to open files is the command line, it is used by the Windows File Explorer if file associations exist.
A third way is to drag and drop files on the main window.
Whenever the control key is pressed when files are opened, the playlist is not cleared but the files are appended to the playlist.
### Open > Open URL
The Open URL menu entry can be used to open URLs for example from YouTube.
mpv.net monitors the Windows clipboard and ask if URLs should be played in case it finds a URL in the clipboard. This feature uses a keyword whitelist that can be configured in the config editor.
Whenever the control key is pressed when URLs are opened, the playlist is not cleared but the URLs are appended to the playlist.
### Open > Show media search
mpv.net supports system wide media searches using the Everything indexing service installed by the popular file search tool Everything (www.voidtools.com).
### Open > Load external audio files
Allows to load an external audio file. It's also possible to auto detect external audio files based on the file name, the option for this can be found in the settings under 'Settings > Show Config Editor > Audio > audio-file-auto'.
### Open > Load external subtitle files
Allows to load an external subtitle file. It's also possible to auto detect external subtitle files based on the file name, the option for this can be found in the settings under 'Settings > Show Config Editor > Subtitles > sub-auto'.
### Play/Pause
Play/Pause using the command:
`cycle pause`
[cycle command](https://mpv.io/manual/master/#command-interface-cycle-%3Cproperty%3E-[up|down])
[pause property](https://mpv.io/manual/master/#options-pause)
### Stop
Stops the player and unloads the playlist using the command:
`stop`
[stop command](https://mpv.io/manual/master/#command-interface-stop)
### Toggle Fullscreen
Toggles fullscreen using the command:
`cycle fullscreen`
[cycle command](https://mpv.io/manual/master/#command-interface-cycle-%3Cproperty%3E-[up|down])
[fullscreen property](https://mpv.io/manual/master/#options-fs)
### Navigate > Previous File
Navigates to the previous file in the playlist using the command:
`playlist-prev`
[playlist-prev command](https://mpv.io/manual/master/#command-interface-playlist-prev)
### Navigate > Next File
Navigates to the next file in the playlist using the command:
`playlist-next`
[playlist-next command](https://mpv.io/manual/master/#command-interface-playlist-next)
### Navigate > Next Chapter
Navigates to the next chapter using the command:
`add chapter 1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[chapter property](https://mpv.io/manual/master/#command-interface-chapter)
### Navigate > Previous Chapter
Navigates to the previous chapter using the command:
`add chapter -1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[chapter property](https://mpv.io/manual/master/#command-interface-chapter)
### Navigate > Jump Next Frame
Jumps to the next frame using the command:
`frame-step`
[frame-step command](https://mpv.io/manual/master/#command-interface-frame-step)
### Navigate > Jump Previous Frame
Jumps to the previous frame using the command:
`frame-back-step`
[frame-back-step command](https://mpv.io/manual/master/#command-interface-frame-back-step)
### Navigate > Jump
Seeking using the command:
`no-osd seek sec`
sec is the relative amount of seconds to jump, the no-osd prefix
is used because mpv.net includes a script that shows the position
when a seek operation is performed, the script uses a more simple
time format.
[no-osd command prefix](https://mpv.io/manual/master/#command-interface-no-osd)
[seek command](https://mpv.io/manual/master/#command-interface-[relative|absolute|absolute-percent|relative-percent|exact|keyframes])
### Pan & Scan > Increase Size
Adds video zoom using the command:
`add video-zoom 0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[video-zoom property](https://mpv.io/manual/master/#options-video-zoom)
### Pan & Scan > Decrease Size
Adds negative video zoom using the command:
`add video-zoom -0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[video-zoom property](https://mpv.io/manual/master/#options-video-zoom)
### Pan & Scan > Move Left
`add video-pan-x -0.01`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[video-pan-x, video-pan-y property](https://mpv.io/manual/master/#options-video-pan-y)
### Pan & Scan > Move Right
`add video-pan-x 0.01`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[video-pan-x, video-pan-y property](https://mpv.io/manual/master/#options-video-pan-y)
### Pan & Scan > Move Up
`add video-pan-y -0.01`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[video-pan-x, video-pan-y property](https://mpv.io/manual/master/#options-video-pan-y)
### Pan & Scan > Move Down
`add video-pan-y 0.01`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[video-pan-x, video-pan-y property](https://mpv.io/manual/master/#options-video-pan-y)
### Pan & Scan > Decrease Height
`add panscan -0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[panscan property](https://mpv.io/manual/master/#options-panscan)
### Pan & Scan > Increase Height
`add panscan 0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[panscan property](https://mpv.io/manual/master/#options-panscan)
### Pan & Scan > Reset
Resets Pan & Scan, multiple commands in the same line are separated with semicolon.
`set video-zoom 0; set video-pan-x 0; set video-pan-y 0`
[video-zoom property](https://mpv.io/manual/master/#options-video-zoom)
[video-pan-x, video-pan-y property](https://mpv.io/manual/master/#options-video-pan-y)
### Video > Decrease Contrast
Decreases contrast with the following command:
`add contrast -1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[contrast property](https://mpv.io/manual/master/#options-contrast)
### Video > Increase Contrast
Increases contrast with the following command:
`add contrast 1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[contrast property](https://mpv.io/manual/master/#options-contrast)
### Video > Decrease Brightness
Decreases brightness using the following command:
`add brightness -1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[brightness property](https://mpv.io/manual/master/#options-brightness)
### Video > Increase Brightness
Increases brightness using the following command:
`add brightness 1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[brightness property](https://mpv.io/manual/master/#options-brightness)
### Video > Decrease Gamma
Decreases gamma using the following command:
`add gamma -1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[gamma property](https://mpv.io/manual/master/#options-gamma)
### Video > Increase Gamma
Increases gamma using the following command:
`add gamma 1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[gamma property](https://mpv.io/manual/master/#options-gamma)
### Video > Decrease Saturation
Decreases saturation using the following command:
`add saturation -1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[saturation property](https://mpv.io/manual/master/#options-saturation)
### Video > Increase Saturation
Increases saturation using the following command:
`add saturation 1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[saturation property](https://mpv.io/manual/master/#options-saturation)
### Video > Take Screenshot
`async screenshot`
[async command prefix](https://mpv.io/manual/master/#command-interface-async)
[screenshot command](https://mpv.io/manual/master/#command-interface-[subtitles|video|window|single|each-frame])
### Video > Toggle Deinterlace
Cycles the deinterlace property using the following command:
`cycle deinterlace`
[cycle command](https://mpv.io/manual/master/#command-interface-cycle-%3Cproperty%3E-[up|down])
[deinterlace property](https://mpv.io/manual/master/#options-deinterlace)
### Video > Cycle Aspect Ratio
Cycles the aspect ratio using the following command:
`cycle-values video-aspect "16:9" "4:3" "2.35:1" "-1"`
[cycle-values command](https://mpv.io/manual/master/#command-interface-cycle-values)
[video-aspect property](https://mpv.io/manual/master/#options-video-aspect)
### Audio > Cycle/Next
This uses a mpv.net command that shows better info then the mpv preset
and also has the advantage of not showing no audio.
### Audio > Delay +0.1
Adds a audio delay using the following command:
`add audio-delay 0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[audio-delay property](https://mpv.io/manual/master/#options-audio-delay)
### Audio > Delay -0.1
Adds a negative audio delay using the following command:
`add audio-delay -0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[audio-delay property](https://mpv.io/manual/master/#options-audio-delay)
### Subtitle > Cycle/Next
Shows the next subtitle track using the following command:
`cycle sub`
[cycle command](https://mpv.io/manual/master/#command-interface-cycle-%3Cproperty%3E-[up|down])
[sub/sid property](https://mpv.io/manual/master/#options-sid)
### Subtitle > Toggle Visibility
Cycles the subtitle visibility using the following command:
`cycle sub-visibility`
[cycle command](https://mpv.io/manual/master/#command-interface-cycle-%3Cproperty%3E-[up|down])
[sub-visibility property](https://mpv.io/manual/master/#options-no-sub-visibility)
### Subtitle > Delay -0.1
Adds a negative subtitle delay using the following command:
`add sub-delay -0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[sub-delay property](https://mpv.io/manual/master/#options-sub-delay)
### Subtitle > Delay 0.1
Adds a positive subtitle delay using the following command:
`add sub-delay 0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[sub-delay property](https://mpv.io/manual/master/#options-sub-delay)
### Subtitle > Move Up
Moves the subtitle up using the following command:
`add sub-pos -1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[sub-pos property](https://mpv.io/manual/master/#options-sub-pos)
### Subtitle > Move Down
Moves the subtitle down using the following command:
`add sub-pos 1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[sub-pos property](https://mpv.io/manual/master/#options-sub-pos)
### Subtitle > Decrease Subtitle Font Size
Decreases the subtitle font size using the following command:
`add sub-scale -0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[sub-scale property](https://mpv.io/manual/master/#options-sub-scale)
### Subtitle > Increase Subtitle Font Size
Increases the subtitle font size using the following command:
`add sub-scale 0.1`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[sub-scale property](https://mpv.io/manual/master/#options-sub-scale)
### Volume > Up
Increases the volume using the following command:
`add volume 10`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[volume property](https://mpv.io/manual/master/#options-volume)
### Volume > Down
Decreases the volume using the following command:
`add volume -10`
[add command](https://mpv.io/manual/master/#command-interface-add-%3Cproperty%3E-[%3Cvalue%3E])
[volume property](https://mpv.io/manual/master/#options-volume)
### Volume > Mute
Cycles the mute property using the following command:
`cycle mute`
[cycle command](https://mpv.io/manual/master/#command-interface-cycle-%3Cproperty%3E-[up|down])
[mute property](https://mpv.io/manual/master/#options-mute)
### Speed > -10%
Decreases the speed by 10% using the following command:
`multiply speed 1/1.1`
[multiply command](https://mpv.io/manual/master/#command-interface-multiply-%3Cproperty%3E-%3Cfactor%3E)
[speed property](https://mpv.io/manual/master/#options-speed)
### Speed > 10%
Increases the speed by 10% using the following command:
`multiply speed 1.1`
[multiply command](https://mpv.io/manual/master/#command-interface-multiply-%3Cproperty%3E-%3Cfactor%3E)
[speed property](https://mpv.io/manual/master/#options-speed)
### Speed > Half
Halfs the speed using the following command:
`multiply speed 0.5`
[multiply command](https://mpv.io/manual/master/#command-interface-multiply-%3Cproperty%3E-%3Cfactor%3E)
[speed property](https://mpv.io/manual/master/#options-speed)
### Speed > Double
Doubles the speed using the following command:
`multiply speed 2`
[multiply command](https://mpv.io/manual/master/#command-interface-multiply-%3Cproperty%3E-%3Cfactor%3E)
[speed property](https://mpv.io/manual/master/#options-speed)
### Speed > Reset
Resets the speed using the following command:
`set speed 1`
[set command](https://mpv.io/manual/master/#command-interface-set-%3Cproperty%3E-%22%3Cvalue%3E%22)
[speed property](https://mpv.io/manual/master/#options-speed)
### Extensions > Rating > 0stars
A plugin the writes the rating to the filename.
### View > On Top > Enable
Forces the player to stay on top of other windows using the following command:
`set ontop yes`
[set command](https://mpv.io/manual/master/#command-interface-set-%3Cproperty%3E-%22%3Cvalue%3E%22)
[ontop property](https://mpv.io/manual/master/#options-ontop)
### View > On Top > Disable
Disables the player to stay on top of other windows using the following command:
`set ontop no`
[set command](https://mpv.io/manual/master/#command-interface-set-%3Cproperty%3E-%22%3Cvalue%3E%22)
[ontop property](https://mpv.io/manual/master/#options-ontop)
### View > File Info
Shows info using a mpv.net command about the current file, shows length, position, formats, size and filename.
### View > Show Statistics
Show statistics using the following command:
`script-binding stats/display-stats`
[script-binding command](https://mpv.io/manual/master/#command-interface-script-binding)
### View > Toggle Statistics
Toggles statistics using the following command:
`script-binding stats/display-stats-toggle`
[script-binding command](https://mpv.io/manual/master/#command-interface-script-binding)
### View > Toggle OSC Visibility
Toggles OSC Visibility using the following command:
`script-binding osc/visibility`
[script-binding command](https://mpv.io/manual/master/#command-interface-script-binding)
### View > Show Playlist
Shows the playlist for 5 seconds using the following command:
`show-text ${playlist} 5000`
[show-text command](https://mpv.io/manual/master/#command-interface-show-text)
### View > Show Audio/Video/Subtitle List
Shows the Audio/Video/Subtitle list for 5 seconds using the following command:
`show-text ${track-list} 5000`
[show-text command](https://mpv.io/manual/master/#command-interface-show-text)
### Settings > Show Config Editor
Shows mpv.net's config editor.
### Settings > Show Input Editor
Shows mpv.net's key binding editor.
### Settings > Open Config Folder
Opens the config folder which contains:
mpv.conf file containing mpv settings
mpvnet.conf file containing mpv.net settings
input.conf containing mpv key bindings
User scripts and user extensions
### Tools > Command Palette
Shows the command palette window which allows to find mpv.net commands, it shows the command's location in the context menu and the key shortcut.
### Tools > Show History
Shows a text file that contains the file history. If the file don't exist it asks if the file should be created in the settings folder. Once the file exist then the history is logged. It logges the playback history containing the time and filename.
### Tools > Set/clear A-B loop points
Enables to set loop start and end points using the following command:
`ab-loop`
[ab-loop command](https://mpv.io/manual/master/#command-interface-ab-loop)
### Tools > Toggle infinite file looping
Loops the current file infinitely using the following command:
cycle-values loop-file "inf" "no"
[cycle-values command](https://mpv.io/manual/master/#command-interface-cycle-values)
[loop-file command](https://mpv.io/manual/master/#options-loop)
### Tools > Cycle Hardware Decoding
Cycles the hwdec property to enable/disable hardware decoding using the following command:
`cycle-values hwdec "auto" "no"`
[cycle-values command](https://mpv.io/manual/master/#command-interface-cycle-values)
[hwdec property](https://mpv.io/manual/master/#options-hwdec)
### Tools > Execute mpv command
Allows to execute mpv commands.
### Tools > Manage File Associations
Allows to manage file associations.
### Help > Show mpv manual
Shows the [mpv manual](https://mpv.io/manual/stable/).
### Help > Show mpv default keys
Shows the [mpv default keys](https://github.com/mpv-player/mpv/blob/master/etc/input.conf)
### Help > Show mpv.net default keys
Shows the [mpv.net default keys](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt)
Help > Show mpv.net web site
Shows the [mpv.net web site](https://mpv-net.github.io/mpv.net-web-site/)
### Help > Show mpv.net manual
Shows the [mpv.net manual](https://github.com/stax76/mpv.net/blob/master/Manual.md)
### Help > About mpv.net
Shows the mpv.net about dialog.
### Exit
Exits mpv.net using the following command:
`quit`
[quit command](https://mpv.io/manual/master/#command-interface-quit-[%3Ccode%3E])
### Exit Watch Later
Exits mpv.net and remembers the position in the file using the following command:
`quit-watch-later`
[quit-watch-later command](https://mpv.io/manual/master/#command-interface-quit-watch-later)
## Command Line Interface
mpvnet implements a basic CLI to set mpv commands.
Example:
mpvnet --mute=yes <file|URL>

177
README.md
View File

@@ -1,26 +1,28 @@
# mpv.net
![](https://raw.githubusercontent.com/stax76/mpv.net/master/img/mpvnet.png)
mpv.net is a modern media player for Windows that looks and works just like [mpv](https://mpv.io).
# 🎞 mpv.net
mpv.net is a modern media player for Windows that works just like [mpv](https://mpv.io).
#### Based on libmpv
mpv.net is based on libmpv which offers a straightforward C API that was designed from the ground up to make mpv usable as a library and facilitate easy integration into other applications.
mpv.net is based on libmpv which offers a straightforward C API that was designed from the ground up to make mpv usable as a library and facilitate easy integration into other applications. mpv is like vlc not based on DirectShow or Media Foundation.
#### CLI options
#### Command Line Interface
mpv.net has the same [CLI options](https://mpv.io/manual/master/#options) as mpv.
mpv.net has the [CLI of mpv](https://mpv.io/manual/master/#options).
#### High quality video output
mpv/libmpv has an OpenGL based video output that is capable of many features loved by videophiles, such as video scaling with popular high quality algorithms, color management, frame timing, interpolation, HDR, and more.
libmpv has an OpenGL based video output that is capable of many features loved by videophiles, such as video scaling with popular high quality algorithms, color management, frame timing, interpolation, HDR, and more.
#### On Screen Controller
mpv.net uses the OSC of mpv/libmpv offering play controls with a modern flat design.
The OSC of libmpv offers play controls with a modern flat design. ([Screenshot](#main-window-screenshot))
#### GPU video decoding
mpv/libmpv leverages the FFmpeg hwaccel APIs to support DXVA2 video decoding acceleration.
libmpv leverages the FFmpeg hwaccel APIs to support DXVA2 video decoding acceleration.
#### Active development
@@ -31,6 +33,7 @@ Table of contents
- [Features](#features)
- [Screenshots](#screenshots)
- [Manual](#manual)
- [Context Menu](#context-menu)
- [Settings](#settings)
- [Scripting](#scripting)
@@ -38,23 +41,43 @@ Table of contents
- [Architecture](#architecture)
- [Support](#support)
- [Links](#links)
- [Download](#download)
- [Changelog](#changelog)
- [Download](#download)
### Features
- Customizable context menu defined in the same file as the key bindings ([Screenshot](#context-menu-screenshot))
- Searchable config dialog ([Screenshot](#config-editor-screenshot))
- Searchable input (key/mouse) binding editor ([Screenshot](#input-editor-screenshot))
- Very high degree of mpv compatibility, almost all mpv features are available
- Great usability due to everything in the application being searchable
- Open source built with modern tools
- Customizable context menu defined in the same file as the key bindings ([Screenshot](#context-menu-screenshot), [Defaults](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt))
- Searchable config dialog ([Screenshot](#config-editor-screenshot), [Defaults](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/mpvConf.txt))
- Searchable input (key/mouse) binding editor ([Screenshot](#input-editor-screenshot), [Defaults](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt))
- Configuration files that are easy to read and edit
- Searchable command palette to quickly launch commands and look for keys ([Screenshot](#command-palette-screenshot))
- Modern UI with dark mode ([Screenshot](#config-editor-screenshot))
- Modern WPF based graphical user interface with dark mode ([Screenshot](#config-editor-screenshot))
- Addon/extension API for .NET languages
- Scripting API for Python, C#, Lua, JavaScript and PowerShell ([wiki](https://github.com/stax76/mpv.net/wiki/Scripting))
- mpv's OSC, IPC and conf files
- Support of the same [CLI options](https://mpv.io/manual/master/#options) as mpv
- Scripting API for Python, C#, Lua, JavaScript and PowerShell ([Wiki](https://github.com/stax76/mpv.net/wiki/Scripting))
- Language agnostic JSON IPC to control the player with a external programs
- On Screen Controler (OSC, play control buttons)
- [Command Line Interface](https://mpv.io/manual/master/#options)
- DXVA2 video decoding acceleration
- OpenGL based video output capable of features loved by videophiles, such as video scaling with popular high quality algorithms, color management, frame timing, interpolation, HDR, and more
- Search feature powered by [Everything](https://www.voidtools.com) to find and play media ([Screenshot](#media-search-screenshot))
- Extension to start mpv.net from Google Chrome ([Manual](Manual.md#chrome-extension))
- Extremely fast seek performance
- Very fast startup performance, video is usally ready to play in less then a second
- Usable as video player, audio player and image viewer with a wide range of supported formats
- All decoders are built-in, no external codecs have to be installed
- Setup and portable download options, setup is recommended but not required
- Build-in media streaming via youtube-dl
- x64 and x86 Support (64-bit and 32-bit)
- File associations can be created by the setup and from the player
- External audio and subtitle files can either be loaded manually or automatically
- Screenshot feature with many options
- File history feature to log time and filename
- A-B loop feature
- watch later feature to save the position
- [Manual](#manual)
### Screenshots
@@ -83,7 +106,7 @@ A searchable key and mouse binding editor.
#### Command Palette Screenshot
Forgot where a command in the menu is located or what shortcut key it has?
Just press Ctrl+Shift+P and find it easily in the searchable command palette.
Just press F1 and find it easily in the searchable command palette.
![Command Palette](https://raw.githubusercontent.com/stax76/mpv.net/master/img/CommandPalette.png)
@@ -93,6 +116,10 @@ Media search feature powered by [Everything](https://www.voidtools.com) to find
![Media Search](https://raw.githubusercontent.com/stax76/mpv.net/master/img/MediaSearch.png)
### Manual
[Manual](Manual.md)
### Context Menu
The context menu can be customized via input.conf file located in the config directory:
@@ -109,23 +136,32 @@ input.conf defines mpv's key and mouse bindings and mpv.net uses comments to def
### Settings
mpv.net is able to share the settings with mpv and mpv.net uses the same logic to decide from where the settings are loaded. The default location is:
mpv.net is able to share the settings with mpv.
If a directory named portable_config next to the mpvnet.exe exists,
all config will be loaded from this directory only.
```Text
C:\Users\%username%\AppData\Roaming\mpv\mpv.conf
<startup>\portable_config\
```
If a directory named portable_config next to the mpvnet.exe exists, all config will be loaded from this directory only.
On first start if no portable config folder exists mpv.net asks
which folder should be used as config folder.
```Text
<startup>\portable_config\mpv.conf
```
In case there isn't any config folder mpv.net asks where to create it. If no mpv.conf file exists mpv.net generates it with the following defaults:
mpv specific settings are stored in the file mpv.conf, if no mpv.conf file exists
mpv.net generates it with the following defaults:
<https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/mpvConf.txt>
Config files located in the same directory as mpvnet.exe are loaded with lower priority. Some config files are loaded only once, which means that e.g. of 2 input.conf files located in two config directories, only the one from the directory with higher priority will be loaded.
mpv.net specific settings are stored in the file mpvnet.conf
The input (key/mouse) bindings and the context menu definitions are stored in the
input.conf file, if it's missing mpv.net generates it with the following defaults:
<https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt>
mpv.net supports almost all mpv settings and features,
[limitations are listed in the wiki](https://github.com/stax76/mpv.net/wiki/Limitations).
### Scripting
@@ -137,28 +173,45 @@ Config files located in the same directory as mpvnet.exe are loaded with lower p
### Architecture
mpv.net is written in C# 7.0 and runs on the .NET framework 4.7 or higher.
Coding mpv.net was great fun because libmpv is such a awesome
library with a very clever design, I'm having a great experience
with libmpv.
The Add-on implementation is based on the Managed Extensibility Framework,
the entire application code is accessible for add-ons and Python scripts.
The player does not contain any feature that was more work than 1-2 days or
was difficult to build, the hard parts are totally covered by libmpv.
mpv.net is written in C# 7 and runs on .NET 4.7, I've not yet decided
if I will port it to C# 8 and .NET 5 once available.
The Add-on implementation is based on the [Managed Extensibility Framework](https://docs.microsoft.com/en-us/dotnet/framework/mef/).
There are no specific extension or scripting interfaces but instead everyting
is accessible for .NET compatible languages (C#, VB.NET, F#, Python, PowerShell),
this decision was made to keep the code simple and lightweight.
Python scripting is implemented with IronPython which uses Python 2.7.
The main/video window is WinForms based, other windows are WPF based.
The main window is WinForms based and uses less than 600 lines of code,
all other windows are WPF based and use even less code.
The config editor adds it's controls dynamically and uses TOML to define it's
The config editor adds it's controls dynamically and uses [TOML](https://en.wikipedia.org/wiki/TOML) to define it's
content, there are only two simple types, StringSetting and OptionSetting.
mpv.net was started 2017 and consists of about 9000 lines of code.
mpv.net was started 2017 and consists of about 6000 lines of code and markup.
Third party components are:
IDE, Editor: Visual Studio, Visual Studio Code.
- [libmpv](https://mpv.io/)
- [MediaInfo](https://mediaarea.net/en/MediaInfo)
- [Tommy (TOML parser)](https://github.com/dezhidki/Tommy)
- [IronPython](https://ironpython.net/)
- [CS-Script](http://www.csscript.net/)
- [Everything](https://www.voidtools.com)
Due to mpv.net being my first WPF app and mpv.net never meant to be a large
application best practices and design pattern are not always applied.
Third party components:
- [libmpv, the heard and soul of mpv.net](https://mpv.io/)
- [MediaInfo, no media related project could do without](https://mediaarea.net/en/MediaInfo)
- [Tommy, a single file TOML parser](https://github.com/dezhidki/Tommy)
- [IronPython, bringing Python to dotnet](https://ironpython.net/)
- [CS-Script, scripting with C#](http://www.csscript.net/)
- [Everything, a blazing fast file search service](https://www.voidtools.com)
### Support
@@ -168,6 +221,14 @@ Third party components are:
[Issue tracker to report bugs and request features](https://github.com/stax76/mpv.net/issues)
[frank.skare.de@gmail.com](mailto:frank.skare.de@gmail.com?Subject=mpv.net%20support)
Please click on the star at the top of the page and like mpv.net at [alternativeto.net](https://alternativeto.net/software/mpv-net/).
If you like you can express your appreciation for my player by sending little beer money with paypal.
<https://www.paypal.me/stax76>
### Links
mpv manual: <https://mpv.io/manual/master/>
@@ -192,42 +253,10 @@ mpv bugs and requests: <https://mpv.io/bug-reports/>
mpv.net bugs and requests: <https://github.com/stax76/mpv.net/issues>
### Download
<https://github.com/stax76/mpv.net/releases>
### Changelog
### 3.7
[Changelog](Changelog.md)
- new icon design
- the radio buttons in the config editor have now a Windows 10 like design,
they are larger and use the Windows theme color
### Download
### 3.6.1
- there was a bug causing an exception if both the input editor and config editor
is opened, as soon as one is opened, the other can't be opened
### 3.6
- playing files from rar archives caused an exception
- there was a bug that caused underscores beeing removed from input like MBTN_LEFT_DBL
- the search clear button in the input editor had a render issue in dark mode
- new search feature added to search and play media files, requires
[Everything](https://www.voidtools.com) to be installed. [Default Binding](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt#L29)
### 3.5
- when the main windows gets activated and the clipboard content starts with http
mpv.net will ask to play the URL, previously this was restricted to YouTube URLs
- Python script errors show line and column whenever it is supported by IronPython
- if conf files exist in the startup directory mpv.net will use the startup
directory as config directory instead of creating default conf files in appdata
- renamed commands are handled now by migration code instead of being broken
### 3.4
- new feature added to manage file associations from within the app. It can be found in the menu at: Tools > Manage... [Default Binding](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt#L149)
- new zip download option added
- new x86 download option added
<https://github.com/stax76/mpv.net/releases>

View File

@@ -1,4 +1,7 @@
Imports System.ComponentModel.Composition
'this addon implements the C# scripting feature of mpv.net,
'it is based on CS-Script: https://www.cs-script.net/
Imports System.ComponentModel.Composition
Imports System.IO
Imports System.Windows.Forms
@@ -13,8 +16,8 @@ Public Class CSScriptAddon
Sub New()
Dim scriptFiles As New List(Of String)
If Directory.Exists(mp.MpvConfFolder + "scripts") Then
scriptFiles.AddRange(Directory.GetFiles(mp.MpvConfFolder + "scripts", "*.cs"))
If Directory.Exists(mp.ConfigFolder + "scripts") Then
scriptFiles.AddRange(Directory.GetFiles(mp.ConfigFolder + "scripts", "*.cs"))
End If
If Directory.Exists(Application.StartupPath + "\scripts") Then

View File

@@ -1,33 +1,41 @@
using System;
// sometimes the add-on don't work, it's containing a lot of trace code at the moment
using System;
using System.ComponentModel.Composition;
using System.Collections.Generic;
using System.IO;
using mpvnet;
using System.Diagnostics;
// the assembly name must end with 'Addon'
namespace RatingAddon
{
[Export(typeof(IAddon))]
public class RatingAddon : IAddon
{
// dictionory to store the filename and the rating
Dictionary<string, int> Dic = new Dictionary<string, int>();
public RatingAddon()
public RatingAddon() // plugin initialization
{
mp.ClientMessage += mpv_ClientMessage;
mp.Shutdown += mpv_Shutdown;
mp.ClientMessage += ClientMessage; //handles keys defined in input.conf
mp.Shutdown += Shutdown; // handles MPV_EVENT_SHUTDOWN
}
void mpv_Shutdown()
// handles MPV_EVENT_SHUTDOWN
void Shutdown()
{
if (App.DebugMode) Trace.WriteLine("aaa");
foreach (var i in Dic)
{
string filepath = i.Key;
int rating = i.Value;
if (App.DebugMode) Trace.WriteLine("bbb");
if (String.IsNullOrEmpty(filepath) || ! File.Exists(filepath))
return;
if (App.DebugMode) Trace.WriteLine("ccc");
string basename = Path.GetFileNameWithoutExtension(filepath);
for (int x = 0; x < 6; x++)
@@ -36,23 +44,29 @@ namespace RatingAddon
basename += $" ({rating}stars)";
string newPath = Path.Combine(Path.GetDirectoryName(filepath), basename + Path.GetExtension(filepath));
if (App.DebugMode) Trace.WriteLine("ddd");
if (filepath.ToLower() != newPath.ToLower())
File.Move(filepath, newPath);
if (App.DebugMode) Trace.WriteLine("eee");
File.SetLastWriteTime(newPath, DateTime.Now);
if (App.DebugMode) Trace.WriteLine("fff");
}
}
void mpv_ClientMessage(string[] args)
//handles keys defined in input.conf
void ClientMessage(string[] args)
{
int rating;
if (args[0] != "rate-file") return;
if (args?.Length != 2 || args[0] != "rate-file" || ! int.TryParse(args[1], out rating))
return;
Dic[mp.get_property_string("path")] = rating;
mp.commandv("show-text", $"Rating: {rating}");
if (int.TryParse(args[1], out int rating))
{
string path = mp.get_property_string("path");
if (!File.Exists(path)) return;
Dic[path] = rating;
mp.commandv("show-text", $"Rating: {rating}");
}
else if (args[1] == "about")
Msg.Show("Rating Extension", "This extension writes a rating to the filename of rated videos when mpv.net shuts down.\n\nThe input.conf defaults contain key bindings for this addon to set ratings.");
}
}
}

View File

@@ -30,6 +30,42 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.ComponentModel.Composition" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 277 KiB

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

View File

@@ -59,10 +59,10 @@ Global
{55C88710-539D-4402-84C8-31694841C731}.Release|x86.Build.0 = Release|x86
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|x64.ActiveCfg = Debug|Any CPU
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|x64.Build.0 = Debug|Any CPU
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|x86.ActiveCfg = Debug|Any CPU
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|x86.Build.0 = Debug|Any CPU
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|x64.ActiveCfg = Debug|x64
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|x64.Build.0 = Debug|x64
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|x86.ActiveCfg = Debug|x86
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Debug|x86.Build.0 = Debug|x86
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Release|Any CPU.Build.0 = Release|Any CPU
{06F854B0-00F9-4B53-94D9-0BE65A7C55D8}.Release|x64.ActiveCfg = Release|Any CPU

View File

@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Navigation;
@@ -44,19 +45,21 @@ namespace DynamicGUI
optionSetting.Options.Add(opt);
}
}
else if (setting["default"].IsString)
else
{
StringSetting stringSetting = new StringSetting();
baseSetting = stringSetting;
stringSetting.Default = setting["default"];
if (setting.HasKey("folder")) stringSetting.IsFolder = true;
stringSetting.Default = setting.HasKey("default") ? setting["default"].ToString() : "";
}
baseSetting.Name = setting["name"];
baseSetting.Filter = setting["filter"];
if (setting.HasKey("help")) baseSetting.Help = setting["help"];
if (setting.HasKey("helpurl")) baseSetting.HelpURL = setting["helpurl"];
if (setting.HasKey("width")) baseSetting.Width = setting["width"];
if (setting.HasKey("type")) baseSetting.Type = setting["type"];
settingsList.Add(baseSetting);
}
return settingsList;
@@ -72,12 +75,12 @@ namespace DynamicGUI
public string Default { get; set; }
public string HelpURL { get; set; }
public string Filter { get; set; }
public string Type { get; set; }
public int Width { get; set; }
}
public class StringSetting : SettingBase
{
public bool IsFolder { get; set; }
}
public class OptionSetting : SettingBase
@@ -100,13 +103,15 @@ namespace DynamicGUI
set => _Text = value;
}
public bool IsChecked
public bool Checked
{
get => OptionSetting.Value == Name ;
set {
if (value)
OptionSetting.Value = Name;
}
set { if (value) OptionSetting.Value = Name; }
}
public Visibility Visibility
{
get => string.IsNullOrEmpty(Help) ? Visibility.Collapsed : Visibility.Visible;
}
}

View File

@@ -13,8 +13,8 @@
<ItemsControl.ItemTemplate>
<DataTemplate>
<WrapPanel Orientation="Vertical">
<RadioButton x:Name="RadioButton" VerticalContentAlignment="Center" IsChecked="{Binding IsChecked}" GroupName="{Binding OptionSetting.Name}" Content="{Binding Text}" FontSize="16" FontWeight="Normal" VerticalAlignment="Top" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></RadioButton>
<TextBox x:Name="ItemHelpTextBox" TextWrapping="WrapWithOverflow" Text="{Binding Help}" Margin="10,0,0,0" BorderThickness="0" IsReadOnly="True" Padding="7,0,0,0" MinHeight="0" Foreground="{Binding Path=Foreground2, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Background="{Binding Path=Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></TextBox>
<RadioButton x:Name="RadioButton" VerticalContentAlignment="Center" IsChecked="{Binding Checked}" GroupName="{Binding OptionSetting.Name}" Content="{Binding Text}" FontSize="16" FontWeight="Normal" VerticalAlignment="Top" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></RadioButton>
<TextBox x:Name="ItemHelpTextBox" TextWrapping="WrapWithOverflow" Text="{Binding Help}" Visibility="{Binding Visibility}" Margin="10,0,0,0" BorderThickness="0" IsReadOnly="True" Padding="7,0,0,0" MinHeight="0" Foreground="{Binding Path=Foreground2, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Background="{Binding Path=Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></TextBox>
</WrapPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>

View File

@@ -12,11 +12,13 @@ namespace DynamicGUI
OptionSetting = optionSetting;
InitializeComponent();
TitleTextBox.Text = optionSetting.Name;
if (string.IsNullOrEmpty(optionSetting.Help))
HelpTextBox.Visibility = Visibility.Collapsed;
HelpTextBox.Text = optionSetting.Help;
ItemsControl.ItemsSource = optionSetting.Options;
Link.SetURL(optionSetting.HelpURL);
if (string.IsNullOrEmpty(optionSetting.HelpURL))
LinkTextBlock.Visibility = Visibility.Collapsed;
Link.SetURL(optionSetting.HelpURL);
}
private string _SearchableText;

View File

@@ -6,7 +6,7 @@
xmlns:local="clr-namespace:DynamicGUI"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800" >
d:DesignWidth="800">
<Grid Margin="20,0">
<StackPanel>
<TextBox x:Name="TitleTextBox" FontSize="24" Margin="0,10" BorderThickness="0" IsReadOnly="True" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Background="{Binding Path=Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></TextBox>
@@ -15,10 +15,10 @@
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="ValueTextBox" Text="{Binding Path=Text, ElementName=StringSettingControl1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="150" HorizontalAlignment="Left" Height="20" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Background="{Binding Path=Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
<TextBox x:Name="ValueTextBox" Text="{Binding Path=Text, ElementName=StringSettingControl1, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="150" HorizontalAlignment="Left" Height="20" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Background="{Binding Path=Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" CaretBrush="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" TextChanged="ValueTextBox_TextChanged"/>
<Button x:Name="Button" Height="20" Grid.Column="1" Visibility="{Binding Path=Text, ElementName=StringSettingControl1}" Margin="5,0,0,0" Width="20" Click="Button_Click">...</Button>
</Grid>
<TextBox x:Name="HelpTextBox" TextWrapping="WrapWithOverflow" Margin="0,0,0,10" BorderThickness="0" IsReadOnly="True" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Background="{Binding Path=Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></TextBox>
<TextBox x:Name="HelpTextBox" TextWrapping="WrapWithOverflow" BorderThickness="0" IsReadOnly="True" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}" Background="{Binding Path=Background, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"></TextBox>
<TextBlock x:Name="LinkTextBlock" Margin="0,10">
<local:HyperlinkEx x:Name="Link"></local:HyperlinkEx>
</TextBlock>

View File

@@ -1,5 +1,10 @@
using System.Windows;
using System;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using WinForms = System.Windows.Forms;
namespace DynamicGUI
{
@@ -13,10 +18,10 @@ namespace DynamicGUI
InitializeComponent();
TitleTextBox.Text = stringSetting.Name;
HelpTextBox.Text = stringSetting.Help;
ValueTextBox.Text = stringSetting.Value;
if (stringSetting.Width > 0)
ValueTextBox.Width = stringSetting.Width;
if (!StringSetting.IsFolder)
ValueTextBox.Text = StringSetting.Value;
if (StringSetting.Width > 0)
ValueTextBox.Width = StringSetting.Width;
if (StringSetting.Type != "folder" && StringSetting.Type != "color")
Button.Visibility = Visibility.Hidden;
Link.SetURL(StringSetting.HelpURL);
if (string.IsNullOrEmpty(stringSetting.HelpURL))
@@ -29,7 +34,6 @@ namespace DynamicGUI
get {
if (_SearchableText is null)
_SearchableText = (TitleTextBox.Text + HelpTextBox.Text +ValueTextBox.Text).ToLower();
return _SearchableText;
}
}
@@ -45,12 +49,61 @@ namespace DynamicGUI
private void Button_Click(object sender, RoutedEventArgs e)
{
using (var d = new System.Windows.Forms.FolderBrowserDialog())
switch (StringSetting.Type)
{
d.Description = "Choose a folder.";
d.SelectedPath = ValueTextBox.Text;
if (d.ShowDialog() == System.Windows.Forms.DialogResult.OK)
ValueTextBox.Text = d.SelectedPath;
case "folder":
using (var d = new WinForms.FolderBrowserDialog())
{
d.Description = "Choose a folder.";
d.SelectedPath = ValueTextBox.Text;
if (d.ShowDialog() == WinForms.DialogResult.OK)
ValueTextBox.Text = d.SelectedPath;
}
break;
case "color":
using (var d = new WinForms.ColorDialog())
{
d.FullOpen = true;
try {
if (!string.IsNullOrEmpty(ValueTextBox.Text))
{
Color col = GetColor(ValueTextBox.Text);
d.Color = System.Drawing.Color.FromArgb(col.A, col.R, col.G, col.B);
}
} catch {}
if (d.ShowDialog() == WinForms.DialogResult.OK)
ValueTextBox.Text = "#" + d.Color.ToArgb().ToString("X8");
}
break;
}
}
private void ValueTextBox_TextChanged(object sender, TextChangedEventArgs e) => Update();
Color GetColor(string value)
{
if (value.Contains("/"))
{
string[] a = value.Split('/');
if (a.Length == 3)
return Color.FromRgb(ToByte(a[0]), ToByte(a[1]), ToByte(a[2]));
else if (a.Length == 4)
return Color.FromArgb(ToByte(a[3]), ToByte(a[0]), ToByte(a[1]), ToByte(a[2]));
}
return (Color)ColorConverter.ConvertFromString(value);
Byte ToByte(string val) => Convert.ToByte(Convert.ToSingle(val, CultureInfo.InvariantCulture) * 255);
}
public void Update()
{
if (StringSetting.Type == "color")
{
Color c = Colors.Transparent;
if (ValueTextBox.Text != "") try { c = GetColor(ValueTextBox.Text); } catch {}
ValueTextBox.Background = new SolidColorBrush(c);
}
}
}

View File

@@ -1752,7 +1752,6 @@ namespace Tommy
}
else
stringBuilder.Append(c);
break;
}
}

View File

@@ -26,7 +26,7 @@ namespace mpvnet
foreach (string i in Directory.GetDirectories(dir))
catalog.Catalogs.Add(new DirectoryCatalog(i, "*Addon.dll"));
dir = mp.MpvConfFolder + "\\Addons";
dir = mp.ConfigFolder + "\\Addons";
if (Directory.Exists(dir))
foreach (string i in Directory.GetDirectories(dir))

View File

@@ -1,108 +1,74 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Interop;
using VB = Microsoft.VisualBasic;
namespace mpvnet
{
public class Command
{
public string Name { get; set; }
public Action<string[]> Action { get; set; }
static List<Command> commands;
public static List<Command> Commands
public static void Execute(string id, string[] args)
{
get
switch (id)
{
if (commands == null)
{
commands = new List<Command>();
Type type = typeof(Command);
MethodInfo[] methods = type.GetMethods(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
foreach (MethodInfo i in methods)
{
ParameterInfo[] parameters = i.GetParameters();
if (parameters == null || parameters.Length != 1 || parameters[0].ParameterType != typeof(string[]))
continue;
Command cmd = new Command() { Name = i.Name.Replace("_","-"), Action = (Action<string[]>)i.CreateDelegate(typeof(Action<string[]>)) };
commands.Add(cmd);
}
}
return commands;
case "manage-file-associations": ManageFileAssociations(); break;
case "cycle-audio": CycleAudio(); break;
case "load-audio": LoadAudio(); break;
case "load-sub": LoadSubtitle(); break;
case "open-url": OpenURL(); break;
case "execute-mpv-command": ExecuteMpvCommand(); break;
case "show-history": ShowHistory(); break;
case "show-media-search": ShowDialog(typeof(EverythingWindow)); break;
case "show-command-palette": ShowDialog(typeof(CommandPaletteWindow)); break;
case "show-about": ShowDialog(typeof(AboutWindow)); break;
case "show-conf-editor": ShowDialog(typeof(ConfWindow)); break;
case "show-input-editor": ShowDialog(typeof(InputWindow)); break;
case "open-conf-folder": Process.Start(mp.ConfigFolder); break;
case "open-files": OpenFiles(args); break;
case "shell-execute": Process.Start(args[0]); break;
case "show-info": ShowInfo(); break;
case "add-files-to-playlist": OpenFiles("append"); break; // deprecated 2019
default: Msg.ShowError($"No command '{id}' found."); break;
}
}
public static void open_files(string[] args)
public static void InvokeOnMainThread(Action action) => MainForm.Instance.Invoke(action);
public static void ShowDialog(Type winType)
{
MainForm.Instance.Invoke(new Action(() => {
InvokeOnMainThread(new Action(() => {
Window win = Activator.CreateInstance(winType) as Window;
new WindowInteropHelper(win).Owner = MainForm.Instance.Handle;
win.ShowDialog();
}));
}
public static void OpenFiles(params string[] args)
{
bool append = Control.ModifierKeys.HasFlag(Keys.Control);
bool loadFolder = true;
foreach (string arg in args)
{
if (arg == "append") append = true;
if (arg == "no-folder") loadFolder = false;
}
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog() { Multiselect = true })
if (d.ShowDialog() == DialogResult.OK)
mp.LoadFiles(d.FileNames);
mp.Load(d.FileNames, loadFolder, append);
}));
}
public static void open_conf_folder(string[] args)
public static void ShowHistory()
{
Process.Start(mp.MpvConfFolder);
}
public static void show_input_editor(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
InputWindow w = new InputWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_conf_editor(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
ConfWindow w = new ConfWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_about(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
AboutWindow w = new AboutWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_command_palette(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
var w = new CommandPaletteWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_media_search(string[] args)
{
MainForm.Instance.Invoke(new Action(() => {
var w = new EverythingWindow();
new WindowInteropHelper(w).Owner = MainForm.Instance.Handle;
w.ShowDialog();
}));
}
public static void show_history(string[] args)
{
var fp = mp.MpvConfFolder + "history.txt";
var fp = mp.ConfigFolder + "history.txt";
if (File.Exists(fp))
Process.Start(fp);
@@ -112,9 +78,7 @@ namespace mpvnet
File.WriteAllText(fp, "");
}
public static void shell_execute(string[] args) => Process.Start(args[0]);
public static void show_info(string[] args)
public static void ShowInfo()
{
try
{
@@ -145,6 +109,22 @@ namespace mpvnet
if (genre != "") text += "Genre: " + genre + "\n";
if (date != "") text += "Year: " + date + "\n";
if (duration != "") text += "Length: " + duration + "\n";
text += "Size: " + mediaInfo.GetInfo(MediaInfoStreamKind.General, "FileSize/String") + "\n";
text += "Type: " + Path.GetExtension(path).ToUpper().TrimStart('.');
mp.commandv("show-text", text, "5000");
return;
}
}
else if (App.ImageTypes.Contains(Path.GetExtension(path).ToLower().TrimStart('.')))
{
using (MediaInfo mediaInfo = new MediaInfo(path))
{
text =
"Width: " + mediaInfo.GetInfo(MediaInfoStreamKind.Image, "Width") + "\n" +
"Height: " + mediaInfo.GetInfo(MediaInfoStreamKind.Image, "Height") + "\n" +
"Size: " + mediaInfo.GetInfo(MediaInfoStreamKind.General, "FileSize/String") + "\n" +
"Type: " + Path.GetExtension(path).ToUpper().TrimStart('.');
mp.commandv("show-text", text, "5000");
return;
@@ -154,7 +134,8 @@ namespace mpvnet
TimeSpan position = TimeSpan.FromSeconds(mp.get_property_number("time-pos"));
TimeSpan duration2 = TimeSpan.FromSeconds(mp.get_property_number("duration"));
string videoCodec = mp.get_property_string("video-format").ToUpper();
string videoFormat = mp.get_property_string("video-format").ToUpper();
string audioCodec = mp.get_property_string("audio-codec-name").ToUpper();
text = Path.GetFileName(path) + "\n" +
FormatTime(position.TotalMinutes) + ":" +
@@ -166,43 +147,48 @@ namespace mpvnet
if (fileSize > 0)
text += Convert.ToInt32(fileSize / 1024.0 / 1024.0).ToString() + " MB\n";
text += $"{videoCodec}\n";
text += $"{videoFormat}\n{audioCodec}";
mp.commandv("show-text", text, "5000");
string FormatTime(double value) => ((int)value).ToString("00");
}
catch (Exception)
catch (Exception e)
{
Msg.ShowException(e);
}
}
public static void execute_mpv_command(string[] args)
public static void ExecuteMpvCommand()
{
MainForm.Instance.Invoke(new Action(() => {
string command = Microsoft.VisualBasic.Interaction.InputBox("Enter a mpv command to be executed.", "Execute Command", RegistryHelp.GetString("HKCU\\Software\\" + Application.ProductName, "RecentExecutedCommand"));
InvokeOnMainThread(new Action(() => {
string command = VB.Interaction.InputBox("Enter a mpv command to be executed.", "Execute Command", RegHelp.GetString(App.RegPath, "RecentExecutedCommand"));
if (string.IsNullOrEmpty(command)) return;
RegistryHelp.SetObject("HKCU\\Software\\" + Application.ProductName, "RecentExecutedCommand", command);
RegHelp.SetObject(App.RegPath, "RecentExecutedCommand", command);
mp.command_string(command, false);
}));
}
public static void open_url(string[] args)
public static void OpenURL()
{
MainForm.Instance.Invoke(new Action(() => {
string command = Microsoft.VisualBasic.Interaction.InputBox("Enter URL to be opened.");
if (string.IsNullOrEmpty(command)) return;
mp.LoadFiles(command);
InvokeOnMainThread(new Action(() => {
string clipboard = System.Windows.Forms.Clipboard.GetText();
if (string.IsNullOrEmpty(clipboard) || (!clipboard.Contains("://") && !File.Exists(clipboard)) || clipboard.Contains("\n"))
{
Msg.ShowError("The clipboard does not contain a valid URL or file, URLs have to contain :// and are not allowed to contain a newline character.");
return;
}
mp.Load(new [] { clipboard }, false, Control.ModifierKeys.HasFlag(Keys.Control));
}));
}
public static void load_sub(string[] args)
public static void LoadSubtitle()
{
MainForm.Instance.Invoke(new Action(() => {
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog())
{
d.InitialDirectory = Path.GetDirectoryName(mp.get_property_string("path", false));
string path = mp.get_property_string("path");
if (File.Exists(path)) d.InitialDirectory = Path.GetDirectoryName(path);
d.Multiselect = true;
if (d.ShowDialog() == DialogResult.OK)
foreach (string i in d.FileNames)
mp.commandv("sub-add", i);
@@ -210,12 +196,14 @@ namespace mpvnet
}));
}
public static void load_audio(string[] args)
public static void LoadAudio()
{
MainForm.Instance.Invoke(new Action(() => {
InvokeOnMainThread(new Action(() => {
using (var d = new OpenFileDialog())
{
d.InitialDirectory = Path.GetDirectoryName(mp.get_property_string("path", false));
string path = mp.get_property_string("path");
if (File.Exists(path))
d.InitialDirectory = Path.GetDirectoryName(path);
d.Multiselect = true;
if (d.ShowDialog() == DialogResult.OK)
@@ -225,12 +213,12 @@ namespace mpvnet
}));
}
public static void cycle_audio(string[] args)
public static void CycleAudio()
{
string filePath = mp.get_property_string("path", false);
if (!File.Exists(filePath)) return;
string path = mp.get_property_string("path");
if (!File.Exists(path)) return;
using (MediaInfo mi = new MediaInfo(filePath))
using (MediaInfo mi = new MediaInfo(path))
{
MediaTrack[] audTracks = mp.MediaTracks.Where(track => track.Type == "a").ToArray();
if (audTracks.Length < 2) return;
@@ -242,7 +230,7 @@ namespace mpvnet
}
}
public static void manage_file_associations(string[] args)
public static void ManageFileAssociations()
{
using (var td = new TaskDialog<string>())
{
@@ -251,7 +239,8 @@ namespace mpvnet
td.AddCommandLink("Register video file extensions", "video");
td.AddCommandLink("Register audio file extensions", "audio");
td.AddCommandLink("Unregister file extensions", "unreg");
td.AddCommandLink("Register audio file extensions", "image");
td.AddCommandLink("Unregister file extensions", "unreg");
string result = td.Show();
@@ -259,14 +248,10 @@ namespace mpvnet
{
using (var proc = new Process())
{
proc.StartInfo.FileName = Application.ExecutablePath;
proc.StartInfo.FileName = System.Windows.Forms.Application.ExecutablePath;
proc.StartInfo.Arguments = "--reg-file-assoc " + result;
proc.StartInfo.Verb = "runas";
try {
proc.Start();
}
catch (Exception)
{ }
try { proc.Start(); } catch { }
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
@@ -17,13 +18,113 @@ namespace mpvnet
{
public class App
{
public static string[] VideoTypes { get; } = "mkv mp4 mpg avi mov webm vob wmv flv avs 264 h264 asf webm mpeg mpv y4m avc hevc 265 h265 m2v m2ts vpy mts m4v".Split(" ".ToCharArray());
public static string[] AudioTypes { get; } = "mp3 mp2 ac3 ogg opus flac wav w64 m4a dts dtsma dtshr dtshd eac3 thd thd+ac3 mka aac mpa".Split(" ".ToCharArray());
public static string RegPath { get; } = @"HKCU\Software\" + Application.ProductName;
public static string ConfFilePath { get; } = mp.ConfigFolder + "\\mpvnet.conf";
public static string DarkMode { get; set; } = "always";
public static string ProcessInstance { get; set; } = "single";
public static string DarkColor { get; set; }
public static string LightColor { get; set; }
public static bool IsDarkMode {
public static string[] VideoTypes { get; } = "mkv mp4 mpg avi mov webm vob wmv flv avs 264 h264 asf webm mpeg mpv y4m avc hevc 265 h265 m2v m2ts vpy mts m4v".Split(' ');
public static string[] AudioTypes { get; } = "mp3 mp2 ac3 ogg opus flac wav w64 m4a dts dtsma dtshr dtshd eac3 thd thd+ac3 mka aac mpa".Split(' ');
public static string[] ImageTypes { get; } = "jpg bmp gif".Split(' ');
public static string[] SubtitleTypes { get; } = "srt ass idx sup ttxt ssa smi".Split(' ');
public static string[] UrlWhitelist { get; set; } = { "tube", "vimeo", "ard", "zdf" };
public static bool RememberHeight { get; set; } = true;
public static bool RememberPosition { get; set; }
public static bool DebugMode { get; set; }
public static int StartThreshold { get; set; } = 1500;
public static bool IsDarkMode {
get => (DarkMode == "system" && Sys.IsDarkTheme) || DarkMode == "always";
}
public static void Init()
{
string dummy = mp.ConfigFolder;
var dummy2 = mp.Conf;
foreach (var i in Conf)
ProcessProperty(i.Key, i.Value);
if (App.DebugMode)
{
try
{
string filePath = mp.ConfigFolder + "\\mpvnet-debug.log";
if (File.Exists(filePath)) File.Delete(filePath);
Trace.Listeners.Add(new TextWriterTraceListener(filePath));
Trace.AutoFlush = true;
}
catch (Exception e)
{
Msg.ShowException(e);
}
}
}
static Dictionary<string, string> _Conf;
public static Dictionary<string, string> Conf {
get {
string darkMode = MainForm.Instance.MpvNetDarkMode;
return (darkMode == "system" && Sys.IsDarkTheme) || darkMode == "always";
if (_Conf == null)
{
_Conf = new Dictionary<string, string>();
if (File.Exists(ConfFilePath))
foreach (string i in File.ReadAllLines(ConfFilePath))
if (i.Contains("=") && !i.StartsWith("#"))
_Conf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
}
return _Conf;
}
}
public static void ProcessProperty(string name, string value)
{
switch (name)
{
case "remember-position": RememberPosition = value == "yes"; break;
case "start-size": RememberHeight = value == "previous"; break;
case "process-instance": ProcessInstance = value; break;
case "dark-mode": DarkMode = value; break;
case "debug-mode": DebugMode = value == "yes"; break;
case "dark-color": DarkColor = value.Trim('\'', '"'); break;
case "light-color": LightColor = value.Trim('\'', '"'); break;
case "url-whitelist":
UrlWhitelist = value.Split(' ', ',', ';');
break;
case "start-threshold":
int.TryParse(value, out int result);
StartThreshold = result;
break;
}
}
public static void ProcessCommandLineEarly()
{
var args = Environment.GetCommandLineArgs().Skip(1);
foreach (string i in args)
{
if (i.StartsWith("--"))
{
if (i.Contains("="))
{
string left = i.Substring(2, i.IndexOf("=") - 2);
string right = i.Substring(left.Length + 3);
mp.ProcessProperty(left, right);
ProcessProperty(left, right);
}
else
{
string name = i.Substring(2);
mp.ProcessProperty(name, "yes");
ProcessProperty(name, "yes");
}
}
}
}
}
@@ -75,49 +176,51 @@ namespace mpvnet
{
Types = types;
RegistryHelp.SetObject(@"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\" + ExeFilename, null, ExePath);
RegistryHelp.SetObject($"HKCR\\Applications\\{ExeFilename}", "FriendlyAppName", "mpv.net media player");
RegistryHelp.SetObject($"HKCR\\Applications\\{ExeFilename}\\shell\\open\\command", null, $"\"{ExePath}\" \"%1\"");
RegistryHelp.SetObject(@"HKLM\SOFTWARE\Clients\Media\mpv\Capabilities", "ApplicationDescription", "mpv.net media player");
RegistryHelp.SetObject(@"HKLM\SOFTWARE\Clients\Media\mpv\Capabilities", "ApplicationName", "mpv.net");
RegistryHelp.SetObject($"HKCR\\SystemFileAssociations\\video\\OpenWithList\\{ExeFilename}", null, "");
RegistryHelp.SetObject($"HKCR\\SystemFileAssociations\\audio\\OpenWithList\\{ExeFilename}", null, "");
RegHelp.SetObject(@"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\" + ExeFilename, null, ExePath);
RegHelp.SetObject($"HKCR\\Applications\\{ExeFilename}", "FriendlyAppName", "mpv.net media player");
RegHelp.SetObject($"HKCR\\Applications\\{ExeFilename}\\shell\\open\\command", null, $"\"{ExePath}\" \"%1\"");
RegHelp.SetObject(@"HKLM\SOFTWARE\Clients\Media\mpv\Capabilities", "ApplicationDescription", "mpv.net media player");
RegHelp.SetObject(@"HKLM\SOFTWARE\Clients\Media\mpv\Capabilities", "ApplicationName", "mpv.net");
RegHelp.SetObject($"HKCR\\SystemFileAssociations\\video\\OpenWithList\\{ExeFilename}", null, "");
RegHelp.SetObject($"HKCR\\SystemFileAssociations\\audio\\OpenWithList\\{ExeFilename}", null, "");
foreach (string ext in Types)
{
RegistryHelp.SetObject($"HKCR\\Applications\\{ExeFilename}\\SupportedTypes", "." + ext, "");
RegistryHelp.SetObject($"HKCR\\" + "." + ext, null, ExeFilenameNoExt + "." + ext);
RegistryHelp.SetObject($"HKCR\\" + "." + ext + "\\OpenWithProgIDs", ExeFilenameNoExt + "." + ext, "");
RegHelp.SetObject($"HKCR\\Applications\\{ExeFilename}\\SupportedTypes", "." + ext, "");
RegHelp.SetObject($"HKCR\\" + "." + ext, null, ExeFilenameNoExt + "." + ext);
RegHelp.SetObject($"HKCR\\" + "." + ext + "\\OpenWithProgIDs", ExeFilenameNoExt + "." + ext, "");
if (App.VideoTypes.Contains(ext))
RegistryHelp.SetObject($"HKCR\\" + "." + ext, "PerceivedType", "video");
RegHelp.SetObject($"HKCR\\" + "." + ext, "PerceivedType", "video");
if (App.AudioTypes.Contains(ext))
RegistryHelp.SetObject($"HKCR\\" + "." + ext, "PerceivedType", "audio");
RegistryHelp.SetObject($"HKCR\\" + ExeFilenameNoExt + "." + ext + "\\shell\\open", null, "Play with " + Application.ProductName);
RegistryHelp.SetObject($"HKCR\\" + ExeFilenameNoExt + "." + ext + "\\shell\\open\\command", null, $"\"{ExePath}\" \"%1\"");
RegistryHelp.SetObject(@"HKLM\SOFTWARE\Clients\Media\mpv.net\Capabilities\FileAssociations", "." + ext, ExeFilenameNoExt + "." + ext);
RegHelp.SetObject($"HKCR\\" + "." + ext, "PerceivedType", "audio");
if (App.ImageTypes.Contains(ext))
RegHelp.SetObject($"HKCR\\" + "." + ext, "PerceivedType", "image");
RegHelp.SetObject($"HKCR\\" + ExeFilenameNoExt + "." + ext + "\\shell\\open", null, "Play with " + Application.ProductName);
RegHelp.SetObject($"HKCR\\" + ExeFilenameNoExt + "." + ext + "\\shell\\open\\command", null, $"\"{ExePath}\" \"%1\"");
RegHelp.SetObject(@"HKLM\SOFTWARE\Clients\Media\mpv.net\Capabilities\FileAssociations", "." + ext, ExeFilenameNoExt + "." + ext);
}
}
public static void Unregister()
{
RegistryHelp.RemoveKey(@"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\" + ExeFilename);
RegistryHelp.RemoveKey($"HKCR\\Applications\\{ExeFilename}");
RegistryHelp.RemoveKey(@"HKLM\SOFTWARE\Clients\Media\mpv.net");
RegistryHelp.RemoveKey($"HKCR\\SystemFileAssociations\\video\\OpenWithList\\{ExeFilename}");
RegistryHelp.RemoveKey($"HKCR\\SystemFileAssociations\\audio\\OpenWithList\\{ExeFilename}");
RegHelp.RemoveKey(@"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\" + ExeFilename);
RegHelp.RemoveKey($"HKCR\\Applications\\{ExeFilename}");
RegHelp.RemoveKey(@"HKLM\SOFTWARE\Clients\Media\mpv.net");
RegHelp.RemoveKey($"HKCR\\SystemFileAssociations\\video\\OpenWithList\\{ExeFilename}");
RegHelp.RemoveKey($"HKCR\\SystemFileAssociations\\audio\\OpenWithList\\{ExeFilename}");
foreach (string id in Registry.ClassesRoot.GetSubKeyNames())
{
if (id.StartsWith(ExeFilenameNoExt + "."))
Registry.ClassesRoot.DeleteSubKeyTree(id);
RegistryHelp.RemoveValue($"HKCR\\Software\\Classes\\" + id + "\\OpenWithProgIDs", ExeFilenameNoExt + id);
RegistryHelp.RemoveValue($"HKLM\\Software\\Classes\\" + id + "\\OpenWithProgIDs", ExeFilenameNoExt + id);
RegHelp.RemoveValue($"HKCR\\Software\\Classes\\" + id + "\\OpenWithProgIDs", ExeFilenameNoExt + id);
RegHelp.RemoveValue($"HKLM\\Software\\Classes\\" + id + "\\OpenWithProgIDs", ExeFilenameNoExt + id);
}
}
}
public class RegistryHelp
public class RegHelp
{
public static void SetObject(string path, string name, object value)
{
@@ -132,6 +235,13 @@ namespace mpvnet
return val.ToString();
}
public static int GetInt(string path, string name)
{
object val = GetObject(path, name);
if (val == null || !(val is int)) return 0;
return (int)val;
}
public static object GetObject(string path, string name)
{
using (RegistryKey rk = GetRootKey(path).OpenSubKey(path.Substring(5)))
@@ -143,14 +253,18 @@ namespace mpvnet
public static void RemoveKey(string path)
{
GetRootKey(path).DeleteSubKeyTree(path.Substring(5), false);
try {
GetRootKey(path).DeleteSubKeyTree(path.Substring(5), false);
} catch { }
}
public static void RemoveValue(string path, string name)
{
using (RegistryKey rk = GetRootKey(path).OpenSubKey(path.Substring(5), true))
if (!(rk is null))
rk.DeleteValue(name, false);
try {
using (RegistryKey rk = GetRootKey(path).OpenSubKey(path.Substring(5), true))
if (!(rk is null))
rk.DeleteValue(name, false);
} catch {}
}
static RegistryKey GetRootKey(string path)

76
mpv.net/Misc/Program.cs Normal file
View File

@@ -0,0 +1,76 @@
using System;
using System.Windows.Forms;
using System.Linq;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Diagnostics;
namespace mpvnet
{
static class Program
{
[STAThread]
static void Main()
{
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string[] args = Environment.GetCommandLineArgs().Skip(1).ToArray();
if (args.Length == 2 && args[0] == "--reg-file-assoc")
{
if (args[1] == "audio") FileAssociation.Register(App.AudioTypes);
if (args[1] == "video") FileAssociation.Register(App.VideoTypes);
if (args[1] == "image") FileAssociation.Register(App.ImageTypes);
if (args[1] == "unreg") FileAssociation.Unregister();
return;
}
App.Init();
Mutex mutex = new Mutex(true, "mpvnetProcessInstance", out bool isFirst);
if ((App.ProcessInstance == "single" || App.ProcessInstance == "queue") && !isFirst)
{
List<string> files = new List<string>();
files.Add(App.ProcessInstance);
foreach (string arg in args)
if (!arg.StartsWith("--") && (arg == "-" || arg.Contains("://") || File.Exists(arg)))
files.Add(arg);
Process[] procs = Process.GetProcessesByName("mpvnet");
for (int i = 0; i < 20; i++)
{
foreach (Process proc in procs)
{
if (proc.MainWindowHandle != IntPtr.Zero)
{
Native.AllowSetForegroundWindow(proc.Id);
var data = new Native.COPYDATASTRUCT();
data.lpData = string.Join("\n", files.ToArray());
data.cbData = data.lpData.Length * 2 + 1;
Native.SendMessage(proc.MainWindowHandle, 0x004A /*WM_COPYDATA*/, IntPtr.Zero, ref data);
mutex.Dispose();
return;
}
}
Thread.Sleep(50);
}
mutex.Dispose();
return;
}
Application.Run(new MainForm());
mutex.Dispose();
}
catch (Exception ex)
{
Msg.ShowException(ex);
}
}
}
}

View File

@@ -15,9 +15,15 @@ namespace mpvnet
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, ref COPYDATASTRUCT lParam);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern IntPtr PostMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool AllowSetForegroundWindow(int dwProcessId);
[DllImport("user32.dll")]
public static extern void ReleaseCapture();
@@ -33,7 +39,7 @@ namespace mpvnet
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
private static extern IntPtr GetWindowLong64(IntPtr hWnd, int nIndex);
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
public static IntPtr GetWindowLong(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLong64(hWnd, nIndex);
@@ -70,5 +76,14 @@ namespace mpvnet
public int Width => Right - Left;
public int Height => Bottom - Top;
}
[StructLayout(LayoutKind.Sequential)]
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
[MarshalAs(UnmanagedType.LPStr)]
public string lpData;
}
}
}

View File

@@ -32,7 +32,7 @@ namespace mpvnet
public static void AddWindowBorders(IntPtr hwnd, ref Native.RECT rc)
{
Native.AdjustWindowRect(ref rc, (uint)Native.GetWindowLongPtr(hwnd, -16 /* GWL_STYLE */), false);
Native.AdjustWindowRect(ref rc, (uint)Native.GetWindowLong(hwnd, -16 /* GWL_STYLE */), false);
}
}
}

View File

@@ -1,33 +0,0 @@
using System;
using System.Windows.Forms;
namespace mpvnet
{
static class Program
{
[STAThread]
static void Main()
{
try
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length == 3 && args[1] == "--reg-file-assoc")
{
if (args[2] == "audio") FileAssociation.Register(App.AudioTypes);
if (args[2] == "video") FileAssociation.Register(App.VideoTypes);
if (args[2] == "unreg") FileAssociation.Unregister();
return;
}
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
catch (Exception ex)
{
Msg.ShowException(ex);
}
}
}
}

View File

@@ -6,11 +6,11 @@ using System.Runtime.InteropServices;
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("mpv.net")]
[assembly: AssemblyDescription("A lightweight media player")]
[assembly: AssemblyDescription("media player")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyCompany("Frank Skare (stax76)")]
[assembly: AssemblyProduct("mpv.net")]
[assembly: AssemblyCopyright("Copyright © 2017-2019 stax76")]
[assembly: AssemblyCopyright("Copyright © 2017-2019 Frank Skare (stax76)")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.7.0.0")]
[assembly: AssemblyFileVersion("3.7.0.0")]
[assembly: AssemblyVersion("4.7.7.0")]
[assembly: AssemblyFileVersion("4.7.7.0")]

View File

@@ -60,30 +60,98 @@ namespace mpvnet.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to
/// # This file defines the input (keys and mouse) bindings of mpv and mpv.net
/// # and it also defines the context menu of mpv.net. mpv.net has an input
/// # editor and an config editor as alternative to editing conf text files.
/// # The input and config editor can be found in mpv.net&apos;s context menu at:
///
/// # Settings &gt; Show Config Editor
/// # Settings &gt; Show Input Editor
///
/// # The defaults of this file can be found at:
///
/// # https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt
///
/// # th [rest of string was truncated]&quot;;.
/// </summary>
internal static string inputConf {
get {
return ResourceManager.GetString("inputConf", resourceCulture);
}
}
internal static string inputConfHeader {
get {
return ResourceManager.GetString("inputConfHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to
///# manual: https://mpv.io/manual/master/
///
///# defaults: https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/mpvConf.txt
///
///input-ar-delay = 500
///input-ar-rate = 20
///volume = 50
///hwdec = yes
///keep-open = yes
///keep-open-pause = no
///osd-playing-msg = &apos;${filename}&apos;
///screenshot-directory = &apos;~~desktop/&apos;
///input-default-bindings = no.
/// </summary>
internal static string mpvConf {
get {
return ResourceManager.GetString("mpvConf", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to [[settings]]
///name = &quot;hwdec&quot;
///default = &quot;no&quot;
///filter = &quot;Video&quot;
///helpurl = &quot;https://mpv.io/manual/master/#options-hwdec&quot;
///help = &quot;Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If hardware decoding is not possible, mpv will fall back on software decoding.\n\nFor more information visit:&quot;
///options = [{ name = &quot;no&quot;, help = &quot;always use software decoding&quot; },
/// { name = &quot;auto&quot;, help = &quot;enabl [rest of string was truncated]&quot;;.
/// </summary>
internal static string mpvConfToml {
get {
return ResourceManager.GetString("mpvConfToml", resourceCulture);
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap mpvnet {
get {
object obj = ResourceManager.GetObject("mpvnet", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized string similar to [[settings]]
///name = &quot;dark-mode&quot;
///default = &quot;always&quot;
///filter = &quot;mpv.net&quot;
///help = &quot;Enables a dark theme.&quot;
///options = [{ name = &quot;always&quot; },
/// { name = &quot;system&quot; , help = &quot;Available on Windows 10 or higher&quot; },
/// { name = &quot;never&quot; }]
///
///[[settings]]
///name = &quot;url-whitelist&quot;
///filter = &quot;mpv.net&quot;
///type = &quot;string&quot;
///help = &quot;Whitelist to monitor the clipboard for URLs to play.\n\nDefault: tube vimeo ard zdf&quot;
///
///[[settings]]
///name = &quot;process-instance&quot;
///default = &quot;single&quot;
///filter = &quot;mpv.net&quot;
///help = &quot;Defines [rest of string was truncated]&quot;;.
/// </summary>
internal static string mpvNetConfToml {
get {
return ResourceManager.GetString("mpvNetConfToml", resourceCulture);

View File

@@ -121,15 +121,15 @@
<data name="inputConf" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\inputConf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252</value>
</data>
<data name="inputConfHeader" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\inputConfHeader.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
<data name="mpvConf" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\mpvConf.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
<data name="mpvConfToml" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\mpvConfToml.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>
<data name="mpvnet" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\mpvnet.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="mpvNetConfToml" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\mpvNetConfToml.txt;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8</value>
</data>

View File

@@ -24,13 +24,17 @@
# mpv input keys: https://github.com/stax76/mpv.net/wiki/mpv-input-keys
# run mpv.net in input test mode with: mpvnet --input-test
o script-message mpv.net open-files #menu: Open > Open Files...
u script-message mpv.net open-url #menu: Open > Open URL...
Ctrl+S script-message mpv.net show-media-search #menu: Open > Show media search...
u script-message mpv.net open-url #menu: Open > Open URL or file path from clipboard
_ ignore #menu: Open > -
Alt+a script-message mpv.net load-audio #menu: Open > Load external audio files...
Alt+s script-message mpv.net load-sub #menu: Open > Load external subtitle files...
_ ignore #menu: Open > -
_ script-message mpv.net open-files append #menu: Open > Add files to playlist...
F3 script-message mpv.net show-media-search #menu: Open > Show media search...
_ ignore #menu: Open > -
_ ignore #menu: Open > Recent
_ ignore #menu: -
@@ -74,7 +78,7 @@
Ctrl+BS set video-zoom 0; set video-pan-x 0; set video-pan-y 0 #menu: Pan & Scan > Reset
Ctrl+1 add contrast -1 #menu: Video > Decrease Contrast
Ctrl+2 add contrast #menu: Video > Increase Contrast
Ctrl+2 add contrast 1 #menu: Video > Increase Contrast
_ ignore #menu: Video > -
Ctrl+3 add brightness -1 #menu: Video > Decrease Brightness
Ctrl+4 add brightness 1 #menu: Video > Increase Brightness
@@ -127,9 +131,12 @@
KP3 script-message rate-file 3 #menu: Extensions > Rating > 3stars
KP4 script-message rate-file 4 #menu: Extensions > Rating > 4stars
KP5 script-message rate-file 5 #menu: Extensions > Rating > 5stars
_ ignore #menu: Extensions > Rating > -
_ script-message rate-file about #menu: Extensions > Rating > About
Ctrl+t set ontop yes #menu: View > On Top > Enable
Ctrl+T set ontop no #menu: View > On Top > Disable
b cycle border #menu: View > Toggle Border
i script-message mpv.net show-info #menu: View > File Info
t script-binding stats/display-stats #menu: View > Show Statistics
T script-binding stats/display-stats-toggle #menu: View > Toggle Statistics
@@ -137,15 +144,15 @@
F8 show-text ${playlist} 5000 #menu: View > Show Playlist
F9 show-text ${track-list} 5000 #menu: View > Show Audio/Video/Subtitle List
Ctrl+c script-message mpv.net show-conf-editor #menu: Settings > Show Config Editor
c script-message mpv.net show-conf-editor #menu: Settings > Show Config Editor
Ctrl+i script-message mpv.net show-input-editor #menu: Settings > Show Input Editor
Ctrl+f script-message mpv.net open-conf-folder #menu: Settings > Open Config Folder
Ctrl+P script-message mpv.net show-command-palette #menu: Tools > Command Palette
F1 script-message mpv.net show-command-palette #menu: Tools > Show All Commands
h script-message mpv.net show-history #menu: Tools > Show History
l ab-loop #menu: Tools > Set/clear A-B loop points
L cycle-values loop-file "inf" "no" #menu: Tools > Toggle infinite file looping
Ctrl+h cycle-values hwdec "auto" "no" #menu: Tools > Cycle Hardware Decoding
Ctrl+h cycle-values hwdec "auto" "no" #menu: Tools > Toggle Hardware Decoding
_ script-message mpv.net execute-mpv-command #menu: Tools > Execute mpv command...
_ script-message mpv.net manage-file-associations #menu: Tools > Manage File Associations...
@@ -153,6 +160,7 @@
_ script-message mpv.net shell-execute https://github.com/mpv-player/mpv/blob/master/etc/input.conf #menu: Help > Show mpv default keys
_ script-message mpv.net shell-execute https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt #menu: Help > Show mpv.net default keys
_ script-message mpv.net shell-execute https://mpv-net.github.io/mpv.net-web-site/ #menu: Help > Show mpv.net web site
_ script-message mpv.net shell-execute https://github.com/stax76/mpv.net/blob/master/Manual.md #menu: Help > Show mpv.net manual
_ ignore #menu: Help > -
_ script-message mpv.net show-about #menu: Help > About mpv.net
@@ -160,21 +168,26 @@
Esc quit #menu: Exit
Q quit-watch-later #menu: Exit Watch Later
> playlist-next
< playlist-prev
Power quit
Play cycle pause
Pause cycle pause
PlayPause cycle pause
Stop stop
Forward seek 60
Rewind seek -60
Mute cycle mute
Volume_Up add volume 10
Volume_Down add volume -10
Wheel_Up add volume 10
Wheel_Down add volume -10
Prev playlist-prev
Next playlist-next
MBTN_LEFT_DBL cycle fullscreen
Power quit
Play cycle pause
Pause cycle pause
PlayPause cycle pause
MBTN_Mid cycle pause
Stop stop
Forward seek 60
Rewind seek -60
Mute cycle mute
Volume_Up add volume 10
Volume_Down add volume -10
Wheel_Up add volume 10
Wheel_Down add volume -10
Prev playlist-prev
Next playlist-next
MBTN_Forward playlist-next
MBTN_Back playlist-prev
> playlist-next
< playlist-prev
Ctrl+Wheel_Up no-osd seek 7
Ctrl+Wheel_Down no-osd seek -7
MBTN_Left_DBL cycle fullscreen
KP_Enter cycle fullscreen

View File

@@ -1,25 +0,0 @@
# This file defines the input (keys and mouse) bindings of mpv and mpv.net
# and it also defines the context menu of mpv.net. mpv.net has an input
# editor and an config editor as alternatives to editing conf text files.
# The input and config editor can be found in mpv.net's context menu at:
# Settings > Show Config Editor
# Settings > Show Input Editor
# The defaults of this file can be found at:
# https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt
# the defaults of mpv can be found at:
# https://github.com/mpv-player/mpv/blob/master/etc/inputConf
# mpv.net's defaults of mpv.conf contain: 'input-default-bindings = no'
# which disables mpv's input defaults. Every line in this file begins with a
# space character to make it easier to do a text search, so if you want to know
# if 'o' has already a binding you can make a text search on ' o '.
# mpv input commands: https://github.com/stax76/mpv.net/wiki/mpv-input-commands
# mpv input keys: https://github.com/stax76/mpv.net/wiki/mpv-input-keys

View File

@@ -1,7 +1,7 @@
# mpv manual: https://mpv.io/manual/master/
# manual: https://mpv.io/manual/master/
# mpv.net mpv.conf defaults: https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/mpvConf.txt
# defaults: https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/mpvConf.txt
input-ar-delay = 500
input-ar-rate = 20
@@ -9,6 +9,7 @@ volume = 50
hwdec = yes
keep-open = yes
keep-open-pause = no
osd-playing-msg = ${filename}
screenshot-directory = ~~desktop/
osd-playing-msg = '${filename}'
screenshot-directory = '~~desktop/'
input-default-bindings = no
script-opts=osc-scalewindowed=1.5

View File

@@ -3,38 +3,37 @@ name = "hwdec"
default = "no"
filter = "Video"
helpurl = "https://mpv.io/manual/master/#options-hwdec"
help = "--hwdec=<mode> Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If hardware decoding is not possible, mpv will fall back on software decoding.\n\nFor more information visit:"
options = [{ name = "no", help = "always use software decoding (Default)" },
help = "Specify the hardware video decoding API that should be used if possible. Whether hardware decoding is actually done depends on the video codec. If hardware decoding is not possible, mpv will fall back on software decoding.\n\nFor more information visit:"
options = [{ name = "no", help = "always use software decoding" },
{ name = "auto", help = "enable best hw decoder (see below)" },
{ name = "yes", help = "exactly the same as auto" },
{ name = "auto-copy", help = "enable best hw decoder with copy-back (see below)" },
{ name = "dxva2", help = "requires --vo=gpu with --gpu-context=d3d11, --gpu-context=angle or --gpu-context=dxinterop (Windows only)" },
{ name = "dxva2", help = "requires vo=gpu with gpu-context=d3d11, gpu-context=angle or gpu-context=dxinterop (Windows only)" },
{ name = "dxva2-copy", help = "copies video back to system RAM (Windows only)" },
{ name = "d3d11va", help = "requires --vo=gpu with --gpu-context=d3d11 or --gpu-context=angle (Windows 8+ only)" },
{ name = "d3d11va", help = "requires vo=gpu with gpu-context=d3d11 or gpu-context=angle (Windows 8+ only)" },
{ name = "d3d11va-copy", help = "copies video back to system RAM (Windows 8+ only)" },
{ name = "cuda", help = "requires --vo=gpu (Any platform CUDA is available)" },
{ name = "cuda", help = "requires vo=gpu (Any platform CUDA is available)" },
{ name = "cuda-copy", help = "copies video back to system RAM (Any platform CUDA is available)" },
{ name = "nvdec", help = "requires --vo=gpu (Any platform CUDA is available)" },
{ name = "nvdec", help = "requires vo=gpu (Any platform CUDA is available)" },
{ name = "nvdec-copy", help = "copies video back to system RAM (Any platform CUDA is available)" },
{ name = "crystalhd", help = "copies video back to system RAM (Any platform supported by hardware)" },
{ name = "rkmpp", help = "requires --vo=gpu (some RockChip devices only)" }]
{ name = "rkmpp", help = "requires vo=gpu (some RockChip devices only)" }]
[[settings]]
name = "gpu-api"
default = "auto"
filter = "Video"
help = "--gpu-api=<type> Controls which type of graphics APIs will be accepted."
options = [{ name = "auto", help = "Use any available API (Default)" },
help = "Controls which type of graphics APIs will be accepted."
options = [{ name = "auto", help = "Use any available API" },
{ name = "opengl", help = "Allow only OpenGL (requires OpenGL 2.1+ or GLES 2.0+)" },
{ name = "vulkan", help = "Allow only Vulkan (requires a valid/working --spirv-compiler)" },
{ name = "d3d11", help = "Allow only --gpu-context=d3d11" }]
{ name = "vulkan", help = "Allow only Vulkan (requires a valid/working spirv-compiler)" },
{ name = "d3d11", help = "Allow only gpu-context=d3d11" }]
[[settings]]
name = "gpu-context"
default = "auto"
filter = "Video"
help = "--gpu-context=<sys> The value auto (the default) selects the GPU context. You can also pass help to get a complete list of compiled in backends (sorted by autoprobe order)."
options = [{ name = "auto", help = "auto-select (Default)" },
options = [{ name = "auto", help = "auto-select" },
{ name = "win", help = "Win32/WGL" },
{ name = "winvk", help = "VK_KHR_win32_surface" },
{ name = "angle", help = "Direct3D11 through the OpenGL ES translation layer ANGLE. This supports almost everything the win backend does (if the ANGLE build is new enough)." },
@@ -45,16 +44,16 @@ options = [{ name = "auto", help = "auto-select (Default)" },
name = "vo"
default = "gpu"
filter = "Video"
help = "Video output drivers to be used.\n\nFor more information visit:"
helpurl = "https://mpv.io/manual/master/#video-output-drivers-vo"
help = "--gpu=<mode> Video output drivers to be used. Default = gpu.\n\nFor more information visit:"
options = [{ name = "gpu", help = "General purpose, customizable, GPU-accelerated video output driver. It supports extended scaling methods, dithering, color management, custom shaders, HDR, and more. (Default)" },
options = [{ name = "gpu", help = "General purpose, customizable, GPU-accelerated video output driver. It supports extended scaling methods, dithering, color management, custom shaders, HDR, and more." },
{ name = "direct3d", help = "Video output driver that uses the Direct3D interface" }]
[[settings]]
name = "video-sync"
default = "audio"
filter = "Video"
help = "--video-sync=<audio|...> How the player synchronizes audio and video.\n\nFor more information visit:"
help = "How the player synchronizes audio and video.\n\nFor more information visit:"
helpurl = "https://mpv.io/manual/master/#options-video-sync"
options = [{ name = "audio" },
{ name = "display-resample" },
@@ -69,49 +68,49 @@ options = [{ name = "audio" },
name = "scale"
default = "bilinear"
filter = "Video"
help = "--scale=<filter> The GPU renderer filter function to use when upscaling video. There are some more filters, but most are not as useful. For a complete list, pass help as value, e.g.: mpv --scale=help"
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality). This is the default for compatibility reasons." },
help = "The GPU renderer filter function to use when upscaling video. There are some more filters, but most are not as useful. For a complete list, pass help as value, e.g.: mpv --scale=help"
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality)." },
{ name = "spline36", help = "Mid quality and speed. This is the default when using gpu-hq." },
{ name = "lanczos", help = "Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)" },
{ name = "ewa_lanczos", help = "Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)" },
{ name = "ewa_lanczossharp", help = "A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default." },
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with --scale-param1 and --scale-param2. This filter is very good at downscaling (see --dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see --tscale)." },
{ name = "linear", help = "A --tscale filter." }]
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see tscale)." },
{ name = "linear", help = "A tscale filter." }]
[[settings]]
name = "cscale"
default = "bilinear"
filter = "Video"
help = "--cscale=<filter> As --scale, but for interpolating chroma information. If the image is not subsampled, this option is ignored entirely."
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality). This is the default for compatibility reasons." },
help = "As scale, but for interpolating chroma information. If the image is not subsampled, this option is ignored entirely."
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality)." },
{ name = "spline36", help = "Mid quality and speed. This is the default when using gpu-hq." },
{ name = "lanczos", help = "Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)" },
{ name = "ewa_lanczos", help = "Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)" },
{ name = "ewa_lanczossharp", help = "A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default." },
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with --scale-param1 and --scale-param2. This filter is very good at downscaling (see --dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see --tscale)." },
{ name = "linear", help = "A --tscale filter." }]
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see tscale)." },
{ name = "linear", help = "A tscale filter." }]
[[settings]]
name = "dscale"
default = "bilinear"
filter = "Video"
help = "--dscale=<filter> Like --scale, but apply these filters on downscaling instead. If this option is unset, the filter implied by --scale will be applied."
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality). This is the default for compatibility reasons." },
help = "Like scale, but apply these filters on downscaling instead. If this option is unset, the filter implied by scale will be applied."
options = [{ name = "bilinear", help = "Bilinear hardware texture filtering (fastest, very low quality)." },
{ name = "spline36", help = "Mid quality and speed. This is the default when using gpu-hq." },
{ name = "lanczos", help = "Lanczos scaling. Provides mid quality and speed. Generally worse than spline36, but it results in a slightly sharper image which is good for some content types. The number of taps can be controlled with scale-radius, but is best left unchanged. (This filter is an alias for sinc-windowed sinc)" },
{ name = "ewa_lanczos", help = "Elliptic weighted average Lanczos scaling. Also known as Jinc. Relatively slow, but very good quality. The radius can be controlled with scale-radius. Increasing the radius makes the filter sharper but adds more ringing. (This filter is an alias for jinc-windowed jinc)" },
{ name = "ewa_lanczossharp", help = "A slightly sharpened version of ewa_lanczos, preconfigured to use an ideal radius and parameter. If your hardware can run it, this is probably what you should use by default." },
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with --scale-param1 and --scale-param2. This filter is very good at downscaling (see --dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see --tscale)." },
{ name = "linear", help = "A --tscale filter." }]
{ name = "mitchell", help = "Mitchell-Netravali. The B and C parameters can be set with scale-param1 and scale-param2. This filter is very good at downscaling (see dscale)." },
{ name = "oversample", help = "A version of nearest neighbour that (naively) oversamples pixels, so that pixels overlapping edges get linearly interpolated instead of rounded. This essentially removes the small imperfections and judder artifacts caused by nearest-neighbour interpolation, in exchange for adding some blur. This filter is good at temporal interpolation, and also known as \"smoothmotion\" (see tscale)." },
{ name = "linear", help = "A tscale filter." }]
[[settings]]
name = "dither-depth"
default = "no"
filter = "Video"
help = "--dither-depth=<N|no|auto> Set dither target depth to N. Default: no. Note that the depth of the connected video display device cannot be detected. Often, LCD panels will do dithering on their own, which conflicts with this option and leads to ugly output."
help = "Set dither target depth to N. Note that the depth of the connected video display device cannot be detected. Often, LCD panels will do dithering on their own, which conflicts with this option and leads to ugly output."
options = [{ name = "no", help = "Disable any dithering done by mpv." },
{ name = "auto", help = "Automatic selection. If output bit depth cannot be detected, 8 bits per component are assumed." },
{ name = "8", help = "Dither to 8 bit output." }]
@@ -120,7 +119,7 @@ options = [{ name = "no", help = "Disable any dithering done by mpv." },
name = "correct-downscaling"
default = "no"
filter = "Video"
help = "--correct-downscaling When using convolution based filters, extend the filter size when downscaling. Increases quality, but reduces performance while downscaling.\n\nThis will perform slightly sub-optimally for anamorphic video (but still better than without it) since it will extend the size to match only the milder of the scale factors between the axes."
help = "When using convolution based filters, extend the filter size when downscaling. Increases quality, but reduces performance while downscaling.\n\nThis will perform slightly sub-optimally for anamorphic video (but still better than without it) since it will extend the size to match only the milder of the scale factors between the axes."
options = [{ name = "yes" },
{ name = "no" }]
@@ -128,7 +127,7 @@ options = [{ name = "yes" },
name = "sigmoid-upscaling"
default = "no"
filter = "Video"
help = "--sigmoid-upscaling When upscaling, use a sigmoidal color transform to avoid emphasizing ringing artifacts. This also implies --linear-scaling."
help = "When upscaling, use a sigmoidal color transform to avoid emphasizing ringing artifacts. This also implies linear-scaling."
options = [{ name = "yes" },
{ name = "no" }]
@@ -136,7 +135,7 @@ options = [{ name = "yes" },
name = "deband"
default = "no"
filter = "Video"
help = "--deband Enable the debanding algorithm. This greatly reduces the amount of visible banding, blocking and other quantization artifacts, at the expense of very slightly blurring some of the finest details. In practice, it's virtually always an improvement - the only reason to disable it would be for performance."
help = "Enable the debanding algorithm. This greatly reduces the amount of visible banding, blocking and other quantization artifacts, at the expense of very slightly blurring some of the finest details. In practice, it's virtually always an improvement - the only reason to disable it would be for performance."
options = [{ name = "yes" },
{ name = "no" }]
@@ -144,81 +143,131 @@ options = [{ name = "yes" },
name = "volume"
default = "100"
filter = "Audio"
help = "--volume=<integer> Set the startup volume. 0 means silence, 100 means no volume reduction or amplification. Negative values can be passed for compatibility, but are treated as 0. Since mpv 0.18.1, this always controls the internal mixer (aka \"softvol\"). Default: 100"
help = "Set the startup volume. 0 means silence, 100 means no volume reduction or amplification. Negative values can be passed for compatibility, but are treated as 0. Since mpv 0.18.1, this always controls the internal mixer (aka \"softvol\"). Default: 100"
[[settings]]
name = "alang"
default = ""
filter = "Audio"
help = "--alang=<languagecode[,languagecode,...]> Specify a priority list of audio languages to use. Different container formats employ different language codes. DVDs use ISO 639-1 two-letter language codes, Matroska, MPEG-TS and NUT use ISO 639-2 three-letter language codes, while OGM uses a free-form identifier. See also --aid.\n\nExamples\n\nmpv dvd://1 --alang=hu,en chooses the Hungarian language track on a DVD and falls back on English if Hungarian is not available.\n\nmpv --alang=jpn example.mkv plays a Matroska file with Japanese audio."
type = "string"
help = "Specify a priority list of audio languages to use. Different container formats employ different language codes. DVDs use ISO 639-1 two-letter language codes, Matroska, MPEG-TS and NUT use ISO 639-2 three-letter language codes, while OGM uses a free-form identifier. See also aid.\n\nExamples\n\nmpv dvd://1 alang=hu,en chooses the Hungarian language track on a DVD and falls back on English if Hungarian is not available.\n\nmpv alang=jpn example.mkv plays a Matroska file with Japanese audio."
[[settings]]
name = "audio-file-auto"
default = "no"
filter = "Audio"
help = "--audio-file-auto=<no|exact|fuzzy|all>, --no-audio-file-auto Load additional audio files matching the video filename. The parameter specifies how external audio files are matched."
options = [{ name = "no", help = "Don't automatically load external audio files (default)." },
help = "Load additional audio files matching the video filename. The parameter specifies how external audio files are matched."
options = [{ name = "no", help = "Don't automatically load external audio files." },
{ name = "exact", help = "Load the media filename with audio file extension." },
{ name = "fuzzy", help = "Load all audio files containing media filename." },
{ name = "all", help = "Load all audio files in the current and --audio-file-paths directories." }]
{ name = "all", help = "Load all audio files in the current and audio-file-paths directories." }]
[[settings]]
name = "slang"
default = ""
filter = "Subtitle"
help = "--slang=<languagecode[,languagecode,...]> Specify a priority list of subtitle languages to use. Different container formats employ different language codes. DVDs use ISO 639-1 two letter language codes, Matroska uses ISO 639-2 three letter language codes while OGM uses a free-form identifier. See also --sid."
type = "string"
help = "Specify a priority list of subtitle languages to use. Different container formats employ different language codes. DVDs use ISO 639-1 two letter language codes, Matroska uses ISO 639-2 three letter language codes while OGM uses a free-form identifier. See also sid."
[[settings]]
name = "sub-auto"
default = "exact"
filter = "Subtitle"
help = "--sub-auto=<no|exact|fuzzy|all>, --no-sub-auto Load additional subtitle files matching the video filename. The parameter specifies how external subtitle files are matched. exact is enabled by default."
help = "Load additional subtitle files matching the video filename. The parameter specifies how external subtitle files are matched. exact is enabled by default."
options = [{ name = "no", help = "Don't automatically load external subtitle files." },
{ name = "exact", help = "Load the media filename with subtitle file extension (Default)." },
{ name = "exact", help = "Load the media filename with subtitle file extension." },
{ name = "fuzzy", help = "Load all subs containing media filename." },
{ name = "all", help = "Load all subs in the current and --sub-file-paths directories." }]
{ name = "all", help = "Load all subs in the current and sub-file-paths directories." }]
[[settings]]
name = "screen"
default = ""
filter = "Screen"
help = "--screen=<default|0-32> In multi-monitor configurations (i.e. a single desktop that spans across multiple displays), this option tells mpv which screen to display the video on. Default: default."
name = "sub-font"
filter = "Subtitle"
type = "string"
help = "Specify font to use for subtitles that do not themselves specify a particular font. The default is sans-serif."
[[settings]]
name = "osd-playing-msg"
default = ""
width = 300
filter = "Screen"
help = "--osd-playing-msg=<value> Show a message on OSD when playback starts. The string is expanded for properties, e.g. --osd-playing-msg='file: ${filename}' will show the message file: followed by a space and the currently played filename. For more information visit:"
helpurl = "https://mpv.io/manual/master/#property-expansion"
name = "sub-font-size"
filter = "Subtitle"
help = "Specify the sub font size. The unit is the size in scaled pixels at a window height of 720. The actual pixel size is scaled with the window height: if the window height is larger or smaller than 720, the actual size of the text increases or decreases as well. Default: 55"
[[settings]]
name = "sub-color"
type = "color"
filter = "Subtitle"
help = "Specify the color used for unstyled text subtitles.\n\nThe color is specified in the form r/g/b, where each color component is specified as number in the range 0.0 to 1.0. It's also possible to specify the transparency by using r/g/b/a, where the alpha value 0 means fully transparent, and 1.0 means opaque. If the alpha component is not given, the color is 100% opaque.\n\nPassing a single number to the option sets the sub to gray, and the form gray/a lets you specify alpha additionally.\n\nExamples\n\n1.0/0.0/0.0 set sub to opaque red\n1.0/0.0/0.0/0.75 set sub to opaque red with 75% alpha\n0.5/0.75 set sub to 50% gray with 75% alpha\n\nAlternatively, the color can be specified as a RGB hex triplet in the form #RRGGBB, where each 2-digit group expresses a color value in the range 0 (00) to 255 (FF). For example, #FF0000 is red. This is similar to web colors. Alpha is given with #AARRGGBB.\n\nExamples\n\n#FF0000 set sub to opaque red\n#C0808080 set sub to 50% gray with 75% alpha"
[[settings]]
name = "sub-border-color"
type = "color"
filter = "Subtitle"
help = "See sub-color. Color used for the sub font border. Ignored when sub-back-color is specified (or more exactly: when that option is not set to completely transparent)."
[[settings]]
name = "sub-back-color"
type = "color"
filter = "Subtitle"
help = "See sub-color. Color used for sub text background. You can use sub-shadow-offset to change its size relative to the text."
[[settings]]
name = "fullscreen"
default = "no"
filter = "Screen"
help = "--fullscreen=<yes|no>, fs=<yes|no> Start the player in fullscreen mode. Default: no."
help = "Start the player in fullscreen mode."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "screenshot-directory"
default = ""
width = 500
folder = true
name = "border"
default = "yes"
filter = "Screen"
help = "--screenshot-directory=<value> Store screenshots in this directory. This path is joined with the filename generated by --screenshot-template. If the template filename is already absolute, the directory is ignored.\n\nIf the directory does not exist, it is created on the first screenshot. If it is not a directory, an error is generated when trying to write a screenshot.\n\nThis option is not set by default, and thus will write screenshots to the directory from which mpv was started. In pseudo-gui mode (see PSEUDO GUI MODE), this is set to the desktop."
help = "Show window with decoration (titlebar, border)."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "screen"
filter = "Screen"
help = "<0-32> In multi-monitor configurations (i.e. a single desktop that spans across multiple displays), this option tells mpv which screen to display the video on."
[[settings]]
name = "osd-playing-msg"
width = 300
filter = "Screen"
type = "string"
help = "Show a message on OSD when playback starts. The string is expanded for properties, e.g. osd-playing-msg='file: ${filename}' will show the message file: followed by a space and the currently played filename. For more information visit:"
helpurl = "https://mpv.io/manual/master/#property-expansion"
[[settings]]
name = "osd-font-size"
default = "55"
filter = "Screen"
help = "Specify the OSD font size. See sub-font-size for details. Default: 55"
[[settings]]
name = "screenshot-directory"
width = 500
type = "folder"
filter = "Screen"
help = "Store screenshots in this directory. This path is joined with the filename generated by screenshot-template. If the template filename is already absolute, the directory is ignored.\n\nIf the directory does not exist, it is created on the first screenshot. If it is not a directory, an error is generated when trying to write a screenshot.\n\nThis option is not set by default, and thus will write screenshots to the directory from which mpv was started. In pseudo-gui mode (see PSEUDO GUI MODE), this is set to the desktop."
[[settings]]
name = "autofit"
default = "50%"
filter = "Screen"
help = "--autofit=<percent value> Set the initial window size in percent. Please note that this setting is only partly implemented in mpv.net, accepted are only integer values with percent sign added. Default: 50%."
help = "<int> Initial window height in percent. Default: 50%"
[[settings]]
name = "autofit-smaller"
filter = "Screen"
help = "<int> Minimum window height in percent. Default: 40%"
[[settings]]
name = "autofit-larger"
filter = "Screen"
help = "<int> Maximum window height in percent. Default: 75%"
[[settings]]
name = "keep-open-pause"
default = "yes"
filter = "Playback"
help = "--keep-open-pause=<yes|no> If set to no, instead of pausing when --keep-open is active, just stop at end of file and continue playing forward when you seek backwards until end where it stops again. Default: yes."
help = "If set to no, instead of pausing when keep-open is active, just stop at end of file and continue playing forward when you seek backwards until end where it stops again."
options = [{ name = "yes" },
{ name = "no" }]
@@ -226,57 +275,53 @@ options = [{ name = "yes" },
name = "keep-open"
default = "no"
filter = "Playback"
help = "--keep-open=<yes|no|always> Do not terminate when playing or seeking beyond the end of the file, and there is not next file to be played (and --loop is not used). Instead, pause the player. When trying to seek beyond end of the file, the player will attempt to seek to the last frame.\n\nNormally, this will act like set pause yes on EOF, unless the --keep-open-pause=no option is set."
options = [{ name = "yes", help = "Don't terminate if the current file is the last playlist entry. Equivalent to --keep-open without arguments."},
{ name = "no", help = "If the current file ends, go to the next file or terminate. (Default.)" },
help = "Do not terminate when playing or seeking beyond the end of the file, and there is not next file to be played (and loop is not used). Instead, pause the player. When trying to seek beyond end of the file, the player will attempt to seek to the last frame.\n\nNormally, this will act like set pause yes on EOF, unless the keep-open-pause=no option is set."
options = [{ name = "yes", help = "Don't terminate if the current file is the last playlist entry. Equivalent to keep-open without arguments."},
{ name = "no", help = "If the current file ends, go to the next file or terminate." },
{ name = "always", help = "Like yes, but also applies to files before the last playlist entry. This means playback will never automatically advance to the next file."}]
[[settings]]
name = "loop-file"
default = ""
filter = "Playback"
help = "--loop-file=<N|inf|no>, loop=<N|inf|no> Loop a single file N times. inf means forever, no means normal playback. For compatibility, --loop-file and --loop-file=yes are also accepted, and are the same as --loop-file=inf.\n\nThe difference to --loop-playlist is that this doesn't loop the playlist, just the file itself. If the playlist contains only a single file, the difference between the two option is that this option performs a seek on loop, instead of reloading the file.\n\n--loop is an alias for this option."
help = "<N|inf|no> Loop a single file N times. inf means forever, no means normal playback.\n\nThe difference to loop-playlist is that this doesn't loop the playlist, just the file itself. If the playlist contains only a single file, the difference between the two option is that this option performs a seek on loop, instead of reloading the file. loop is an alias for this option."
[[settings]]
name = "save-position-on-quit"
default = "no"
filter = "Playback"
help = "--save-position-on-quit=<yes|no> Always save the current playback position on quit. When this file is played again later, the player will seek to the old playback position on start. This does not happen if playback of a file is stopped in any other way than quitting. For example, going to the next file in the playlist will not save the position, and start playback at beginning the next time the file is played.\n\nThis behavior is disabled by default, but is always available when quitting the player with Shift+Q."
help = "Always save the current playback position on quit. When this file is played again later, the player will seek to the old playback position on start. This does not happen if playback of a file is stopped in any other way than quitting. For example, going to the next file in the playlist will not save the position, and start playback at beginning the next time the file is played.\n\nThis behavior is disabled by default, but is always available when quitting the player with Shift+Q."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "input-ar-delay"
default = ""
filter = "Input"
help = "--input-ar-delay=<integer> Delay in milliseconds before we start to autorepeat a key (0 to disable)."
help = "Delay in milliseconds before we start to autorepeat a key (0 to disable)."
[[settings]]
name = "input-ar-rate"
default = ""
filter = "Input"
help = "--input-ar-rate=<integer> Number of key presses to generate per second on autorepeat."
help = "Number of key presses to generate per second on autorepeat."
[[settings]]
name = "hr-seek"
default = "absolute"
filter = "Playback"
help = "--hr-seek=<no|absolute|yes> Select when to use precise seeks that are not limited to keyframes. Such seeks require decoding video from the previous keyframe up to the target position and so can take some time depending on decoding performance. For some video formats, precise seeks are disabled. This option selects the default choice to use for seeks; it is possible to explicitly override that default in the definition of key bindings and in input commands."
help = "Select when to use precise seeks that are not limited to keyframes. Such seeks require decoding video from the previous keyframe up to the target position and so can take some time depending on decoding performance. For some video formats, precise seeks are disabled. This option selects the default choice to use for seeks; it is possible to explicitly override that default in the definition of key bindings and in input commands."
options = [{ name = "yes", help = "Use precise seeks whenever possible." },
{ name = "no", help = "Never use precise seeks." },
{ name = "absolute", help = "Use precise seeks if the seek is to an absolute position in the file, such as a chapter seek, but not for relative seeks like the default behavior of arrow keys (default)." },
{ name = "absolute", help = "Use precise seeks if the seek is to an absolute position in the file, such as a chapter seek, but not for relative seeks like the default behavior of arrow keys." },
{ name = "always", help = "Same as yes (for compatibility)." }]
[[settings]]
name = "track-auto-selection"
default = "yes"
filter = "Playback"
help = "--track-auto-selection=<yes|no> Enable the default track auto-selection (default: yes). Enabling this will make the player select streams according to --aid, --alang, and others. If it is disabled, no tracks are selected. In addition, the player will not exit if no tracks are selected, and wait instead (this wait mode is similar to pausing, but the pause option is not set).\n\nThis is useful with --lavfi-complex: you can start playback in this mode, and then set select tracks at runtime by setting the filter graph. Note that if --lavfi-complex is set before playback is started, the referenced tracks are always selected."
help = "Enable the default track auto-selection. Enabling this will make the player select streams according to aid, alang, and others. If it is disabled, no tracks are selected. In addition, the player will not exit if no tracks are selected, and wait instead (this wait mode is similar to pausing, but the pause option is not set).\n\nThis is useful with lavfi-complex: you can start playback in this mode, and then set select tracks at runtime by setting the filter graph. Note that if lavfi-complex is set before playback is started, the referenced tracks are always selected."
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "loop-playlist"
default = ""
filter = "Playback"
help = "--loop-playlist=<N|inf|force|no>, --loop-playlist Loops playback N times. A value of 1 plays it one time (default), 2 two times, etc. inf means forever. no is the same as 1 and disables looping. If several files are specified on command line, the entire playlist is looped. --loop-playlist is the same as --loop-playlist=inf.\n\nThe force mode is like inf, but does not skip playlist entries which have been marked as failing. This means the player might waste CPU time trying to loop a file that doesn't exist. But it might be useful for playing webradios under very bad network conditions."
help = "<N|inf|force|no> Loops playback N times. A value of 1 plays it one time (default), 2 two times, etc. inf means forever. no is the same as 1 and disables looping. If several files are specified on command line, the entire playlist is looped. The force mode is like inf, but does not skip playlist entries which have been marked as failing. This means the player might waste CPU time trying to loop a file that doesn't exist. But it might be useful for playing webradios under very bad network conditions."

View File

@@ -1,8 +1,64 @@
[[settings]]
name = "dark-mode"
default = "always"
filter = "mpv.net"
help = "Enables a dark theme."
filter = "General"
help = "Enables a dark theme. (mpv.net specific setting)"
options = [{ name = "always" },
{ name = "system" , help = "Available on Windows 10 or higher" },
{ name = "never" }]
{ name = "never" }]
[[settings]]
name = "dark-color"
type = "color"
filter = "General"
help = "Theme color used in dark-mode. Leave empty to use OS theme. (mpv.net specific setting)"
[[settings]]
name = "light-color"
type = "color"
filter = "General"
help = "Theme color used when dark-mode is disabled. Leave empty to use OS theme. (mpv.net specific setting)"
[[settings]]
name = "url-whitelist"
filter = "General"
type = "string"
help = "Whitelist setting to monitor the clipboard for URLs to play. (mpv.net specific setting)\n\nDefault: tube vimeo ard zdf"
[[settings]]
name = "process-instance"
default = "single"
filter = "General"
help = "Defines if more then one mpv.net process is allowed. (mpv.net specific setting)\n\nTip: Whenever the control key is pressed when files or URLs are opened, the playlist is not cleared but the files or URLs are appended to the playlist. This not only works on process startup but in all mpv.net features that open files."
options = [{ name = "multi", help = "Create a new process everytime the shell starts mpv.net" },
{ name = "single", help = "Force a single process everytime the shell starts mpv.net" },
{ name = "queue", help = "Force a single process and add files to playlist" }]
[[settings]]
name = "debug-mode"
default = "no"
filter = "General"
help = "Enable this only when a developer asks for it. (mpv.net specific setting)"
options = [{ name = "yes" },
{ name = "no" }]
[[settings]]
name = "start-size"
default = "previous"
filter = "Screen"
help = "Setting to remember the window height in the current session, otherwise the video's native resolution is used. (mpv.net specific setting)"
options = [{ name = "video", help = "Window size is set to native video resolution" },
{ name = "previous", help = "Window size is remembered but only from the current session" }]
[[settings]]
name = "start-threshold"
filter = "Screen"
help = "Threshold in milliseconds to wait for libmpv returning the video resolution before the window is shown, otherwise default dimensions are used as defined by autofit and start-size. (mpv.net specific setting) Default: 1500"
[[settings]]
name = "remember-position"
default = "no"
filter = "Screen"
help = "Setting to save the window position on exit. (mpv.net specific setting)"
options = [{ name = "yes" },
{ name = "no" }]

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -14,26 +14,27 @@ namespace mpvnet
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.ApartmentState = ApartmentState.STA;
runspace.ThreadOptions = PSThreadOptions.UseCurrentThread;
runspace.Open();
using (Pipeline pipeline = runspace.CreatePipeline())
{
pipeline.Commands.AddScript(
@"Using namespace mpvnet;
Using namespace System;
[System.Reflection.Assembly]::LoadWithPartialName(""mpvnet"")");
"Using namespace mpvnet\n" +
"Using namespace System\n" +
"[System.Reflection.Assembly]::LoadWithPartialName(\"mpvnet\")\n");
pipeline.Commands.AddScript(code);
if (parameters != null)
foreach (var i in parameters)
pipeline.Commands[1].Parameters.Add(null, i);
try
{
var ret = pipeline.Invoke(parameters);
if (ret.Count > 0)
return ret[0];
var ret = pipeline.Invoke();
if (ret.Count > 0) return ret[0];
}
catch (Exception ex)
catch (Exception e)
{
try
{
@@ -46,12 +47,12 @@ Using namespace System;
throw new Exception();
}
}
catch (Exception ex2)
catch (Exception e2)
{
Msg.ShowError("PowerShell Setup Problem\n\nEnsure you have at least PowerShell 5.1 installed.", ex2.ToString());
Msg.ShowError("PowerShell Setup Problem\n\nEnsure you have at least PowerShell 5.1 installed.", e2.ToString());
return null;
}
Msg.ShowException(ex);
Msg.ShowException(e);
}
}
}
@@ -70,17 +71,11 @@ Using namespace System;
eventObject.FilePath = filePath;
if (eventInfo.EventHandlerType == typeof(Action))
{
mi = eventObject.GetType().GetMethod(nameof(PowerShellEventObject.Invoke));
}
else if (eventInfo.EventHandlerType == typeof(Action<EndFileEventMode>))
{
mi = eventObject.GetType().GetMethod(nameof(PowerShellEventObject.InvokeEndFileEventMode));
}
else if (eventInfo.EventHandlerType == typeof(Action<string[]>))
{
mi = eventObject.GetType().GetMethod(nameof(PowerShellEventObject.InvokeStrings));
}
else
throw new Exception();
@@ -91,10 +86,7 @@ Using namespace System;
return;
}
}
Task.Run(() =>
{
PowerShellScript.Execute(File.ReadAllText(filePath), new string[] {});
});
Task.Run(() => PowerShellScript.Execute(File.ReadAllText(filePath), null));
}
}
@@ -104,24 +96,16 @@ Using namespace System;
public Delegate Delegate { get; set; }
public string FilePath { get; set; }
public void Invoke()
{
Task.Run(() => { PowerShellScript.Execute(File.ReadAllText(FilePath), new string[] { }); });
}
public void Invoke() => Task.Run(() => { PowerShellScript.Execute(File.ReadAllText(FilePath), null); });
public void InvokeEndFileEventMode(EndFileEventMode arg)
{
Task.Run(() =>
{
PowerShellScript.Execute(File.ReadAllText(FilePath), new string[] { arg.ToString() });
});
Task.Run(() => PowerShellScript.Execute(File.ReadAllText(FilePath), new [] { arg.ToString() }));
}
public void InvokeStrings(string[] args)
{
Task.Run(() => {
PowerShellScript.Execute(File.ReadAllText(FilePath), args);
});
Task.Run(() => PowerShellScript.Execute(File.ReadAllText(FilePath), args));
}
}
}

View File

@@ -1,5 +1,6 @@
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
namespace mpvnet
{
@@ -9,7 +10,6 @@ namespace mpvnet
{
InitializeComponent();
Version.Text = $"Version {System.Windows.Forms.Application.ProductVersion}";
Foreground = WPF.WPF.ThemeBrush;
}
protected override void OnPreviewKeyDown(KeyEventArgs e) => Close();

View File

@@ -13,11 +13,11 @@
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="6*" />
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Controls:SearchTextBoxUserControl HintText="Find a setting" x:Name="SearchControl" Width="250" Margin="0,20,0,10" Grid.ColumnSpan="2" />
<ScrollViewer x:Name="MainScrollViewer" VerticalScrollBarVisibility="Auto" Grid.Row="1" Grid.Column="1" Margin="0,0,0,10">
<Controls:SearchTextBoxUserControl HintText="Find a setting" x:Name="SearchControl" Width="250" Margin="0,20,0,0" Grid.ColumnSpan="2"/>
<ScrollViewer x:Name="MainScrollViewer" VerticalScrollBarVisibility="Auto" Grid.Row="1" Grid.Column="2" Margin="0,0,0,10">
<StackPanel x:Name="MainStackPanel"></StackPanel>
</ScrollViewer>
<StackPanel Margin="20,0,0,0" Grid.Row="1">

View File

@@ -7,6 +7,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Linq;
using DynamicGUI;
@@ -14,8 +15,8 @@ namespace mpvnet
{
public partial class ConfWindow : Window
{
private List<SettingBase> MpvSettingsDefinitions = Settings.LoadSettings(Properties.Resources.mpvConfToml);
private List<SettingBase> MpvNetSettingsDefinitions = Settings.LoadSettings(Properties.Resources.mpvNetConfToml);
private List<SettingBase> SettingsDefinitions = Settings.LoadSettings(Properties.Resources.mpvConfToml);
private List<SettingBase> NetSettingsDefinitions = Settings.LoadSettings(Properties.Resources.mpvNetConfToml);
private Dictionary<string, Dictionary<string, string>> Comments = new Dictionary<string, Dictionary<string, string>>();
public ObservableCollection<string> FilterStrings { get; } = new ObservableCollection<string>();
@@ -25,10 +26,10 @@ namespace mpvnet
InitializeComponent();
DataContext = this;
SearchControl.SearchTextBox.TextChanged += SearchTextBox_TextChanged;
LoadSettings(MpvSettingsDefinitions, MpvConf);
LoadSettings(MpvNetSettingsDefinitions, MpvNetConf);
SearchControl.Text = RegistryHelp.GetString(@"HKCU\Software\mpv.net", "config editor search");
LoadSettings(SettingsDefinitions, Conf);
LoadSettings(NetSettingsDefinitions, NetConf);
SearchControl.Text = RegHelp.GetString(App.RegPath, "config editor search");
if (App.IsDarkMode)
{
Foreground = Brushes.White;
@@ -48,7 +49,7 @@ namespace mpvnet
private void LoadSettings(List<SettingBase> settingsDefinitions,
Dictionary<string, string> confSettings)
{
foreach (var setting in settingsDefinitions)
foreach (SettingBase setting in settingsDefinitions)
{
if (!FilterStrings.Contains(setting.Filter))
FilterStrings.Add(setting.Filter);
@@ -57,8 +58,8 @@ namespace mpvnet
{
if (setting.Name == pair.Key)
{
setting.Value = pair.Value;
setting.StartValue = pair.Value;
setting.Value = pair.Value.Trim('\'', '"');
setting.StartValue = pair.Value.Trim('\'', '"');
continue;
}
}
@@ -79,21 +80,21 @@ namespace mpvnet
}
}
private Dictionary<string, string> _mpvConf;
private Dictionary<string, string> _Conf;
public Dictionary<string, string> MpvConf {
public Dictionary<string, string> Conf {
get {
if (_mpvConf == null) _mpvConf = LoadConf(mp.MpvConfPath);
return _mpvConf;
if (_Conf == null) _Conf = LoadConf(mp.ConfPath);
return _Conf;
}
}
private Dictionary<string, string> _mpvNetConf;
private Dictionary<string, string> _NetConf;
public Dictionary<string, string> MpvNetConf {
public Dictionary<string, string> NetConf {
get {
if (_mpvNetConf == null) _mpvNetConf = LoadConf(mp.MpvNetConfPath);
return _mpvNetConf;
if (_NetConf == null) _NetConf = LoadConf(App.ConfFilePath);
return _NetConf;
}
}
@@ -131,26 +132,26 @@ namespace mpvnet
{
base.OnClosed(e);
WriteToDisk();
RegistryHelp.SetObject(@"HKCU\Software\mpv.net", "config editor search", SearchControl.Text);
RegHelp.SetObject(App.RegPath, "config editor search", SearchControl.Text);
}
void WriteToDisk()
{
bool isDirty = false;
foreach (SettingBase i in MpvSettingsDefinitions)
foreach (SettingBase i in SettingsDefinitions)
if (i.StartValue != i.Value)
isDirty = true;
foreach (SettingBase i in MpvNetSettingsDefinitions)
foreach (SettingBase i in NetSettingsDefinitions)
if (i.StartValue != i.Value)
isDirty = true;
if (!isDirty)
return;
WriteToDisk(mp.MpvConfPath, MpvConf, MpvSettingsDefinitions);
WriteToDisk(mp.MpvNetConfPath, MpvNetConf, MpvNetSettingsDefinitions);
WriteToDisk(mp.ConfPath, Conf, SettingsDefinitions);
WriteToDisk(App.ConfFilePath, NetConf, NetSettingsDefinitions);
Msg.Show("Changes will be available on next mpv.net startup.");
}
@@ -164,17 +165,22 @@ namespace mpvnet
foreach (var i in Comments[filePath])
content += $"#{i.Key} = {i.Value}\r\n";
foreach (var setting in settings)
foreach (SettingBase setting in settings)
{
if ((setting.Value ?? "") != setting.Default)
confSettings[setting.Name] = setting.Value;
if (setting.Type == "string" ||
setting.Type == "folder" ||
setting.Type == "color")
confSettings[setting.Name] = "'" + setting.Value + "'";
else
confSettings[setting.Name] = setting.Value;
if (confSettings.ContainsKey(setting.Name) &&
(setting.Value ?? "") == setting.Default ||
(setting.Value ?? "") == "")
{
confSettings.Remove(setting.Name);
}
}
foreach (var i in confSettings)
@@ -215,6 +221,8 @@ namespace mpvnet
{
SearchControl.SearchTextBox.SelectAll();
Keyboard.Focus(SearchControl.SearchTextBox);
foreach (var i in MainStackPanel.Children.OfType<StringSettingControl>())
i.Update();
}
private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
@@ -225,7 +233,7 @@ namespace mpvnet
private void OpenSettingsTextBlock_MouseUp(object sender, MouseButtonEventArgs e)
{
Process.Start(Path.GetDirectoryName(mp.MpvConfPath));
Process.Start(Path.GetDirectoryName(mp.ConfPath));
}
private void ShowManualTextBlock_MouseUp(object sender, MouseButtonEventArgs e)

View File

@@ -95,12 +95,8 @@ namespace mpvnet
ListView.ScrollIntoView(ListView.SelectedItem);
}
break;
case Key.Escape:
Close();
break;
case Key.Enter:
Execute();
break;
case Key.Escape: Close(); break;
case Key.Enter: Execute(); break;
}
}
@@ -113,14 +109,11 @@ namespace mpvnet
void Execute()
{
if (ListView.SelectedItem != null)
mp.LoadFiles(ListView.SelectedItem as string);
mp.Load(new[] { ListView.SelectedItem as string }, true, Keyboard.Modifiers == ModifierKeys.Control);
Keyboard.Focus(FilterTextBox);
}
private void ListView_MouseUp(object sender, MouseButtonEventArgs e)
{
Execute();
}
private void ListView_MouseUp(object sender, MouseButtonEventArgs e) => Execute();
private void FilterTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
@@ -148,8 +141,12 @@ namespace mpvnet
{
Everything_GetResultFullPathName(i, buf, bufsize);
string ext = Path.GetExtension(buf.ToString()).TrimStart('.').ToLower();
if (App.AudioTypes.Contains(ext) || App.VideoTypes.Contains(ext))
if (App.AudioTypes.Contains(ext) || App.VideoTypes.Contains(ext) ||
App.ImageTypes.Contains(ext))
items.Add(buf.ToString());
if (items.Count > 100) break;
}
Application.Current.Dispatcher.Invoke(() => {

View File

@@ -102,7 +102,15 @@ namespace mpvnet
string GetInputConfContent()
{
string text = Properties.Resources.inputConfHeader + "\r\n";
string text = null;
foreach (string line in Properties.Resources.inputConf.Split(new[] { "\r\n" }, StringSplitOptions.None))
{
string test = line.Trim();
if (test == "" || test.StartsWith("#")) text += test + "\r\n";
}
text = "\r\n" + text.Trim() + "\r\n\r\n";
foreach (CommandItem item in CommandItem.Items)
{

View File

@@ -5,7 +5,7 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="Learn Input" Height="200" Width="400" WindowStartupLocation="CenterOwner"
ResizeMode="NoResize" Loaded="Window_Loaded" Background="Black" MouseWheel="Window_MouseWheel">
ResizeMode="NoResize" Loaded="Window_Loaded" Background="Black" MouseWheel="Window_MouseWheel" MouseUp="Window_MouseUp" MouseDoubleClick="Window_MouseDoubleClick">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
@@ -16,8 +16,8 @@
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label x:Name="MenuLabel" Grid.ColumnSpan="2" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="16"></Label>
<Label x:Name="KeyLabel" Grid.Row="1" Grid.ColumnSpan="2" Foreground="White" VerticalAlignment="Top" HorizontalAlignment="Center" FontSize="16"></Label>
<TextBlock x:Name="MenuTextBlock" Grid.ColumnSpan="2" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="16"></TextBlock>
<TextBlock x:Name="KeyTextBlock" Grid.Row="1" Grid.ColumnSpan="2" Foreground="White" VerticalAlignment="Top" HorizontalAlignment="Center" FontSize="16"></TextBlock>
<Button x:Name="ConfirmButton" Grid.Row="2" Click="ConfirmButton_Click">Confirm</Button>
<Button x:Name="ClearButton" Grid.Row="2" Click="ClearButton_Click" Grid.Column="1">Clear</Button>
</Grid>

View File

@@ -12,10 +12,7 @@ namespace mpvnet
public CommandItem InputItem { get; set; }
public string NewKey { get; set; } = "";
public LearnWindow()
{
InitializeComponent();
}
public LearnWindow() => InitializeComponent();
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
@@ -43,8 +40,7 @@ namespace mpvnet
else
try {
text = Convert.ToChar(charValue).ToString().ToLower().Trim();
}
catch {}
} catch {}
for (int i = 0; i < 13; i++)
if ("D" + i.ToString() == text)
@@ -64,59 +60,59 @@ namespace mpvnet
case WF.Keys.NumPad9:
text = "KP" + e.KeyCode.ToString()[6].ToString(); break;
case WF.Keys.Space:
text = "Space"; break;
text = "SPACE"; break;
case WF.Keys.Enter:
text = "Enter"; break;
text = "ENTER"; break;
case WF.Keys.Tab:
text = "Tab"; break;
text = "TAB"; break;
case WF.Keys.Back:
text = "BS"; break;
case WF.Keys.Delete:
text = "Del"; break;
text = "DEL"; break;
case WF.Keys.Insert:
text = "Ins"; break;
text = "INS"; break;
case WF.Keys.Home:
text = "Home"; break;
text = "HOME"; break;
case WF.Keys.End:
text = "End"; break;
text = "END"; break;
case WF.Keys.PageUp:
text = "PGUP"; break;
case WF.Keys.PageDown:
text = "PGDWN"; break;
case WF.Keys.Escape:
text = "Esc"; break;
text = "ESC"; break;
case WF.Keys.PrintScreen:
text = "Print"; break;
text = "PRINT"; break;
case WF.Keys.Play:
text = "Play"; break;
text = "PLAY"; break;
case WF.Keys.Pause:
text = "Pause"; break;
text = "PAUSE"; break;
case WF.Keys.MediaPlayPause:
text = "PlayPause"; break;
text = "PLAYPAUSE"; break;
case WF.Keys.MediaStop:
text = "Stop"; break;
text = "STOP"; break;
case WF.Keys.MediaNextTrack:
text = "Next"; break;
text = "NEXT"; break;
case WF.Keys.MediaPreviousTrack:
text = "Prev"; break;
text = "PREV"; break;
case WF.Keys.VolumeUp:
text = "Volume_Up"; break;
text = "VOLUME_UP"; break;
case WF.Keys.VolumeDown:
text = "Volume_Down"; break;
text = "VOLUME_DOWN"; break;
case WF.Keys.VolumeMute:
text = "Mute"; break;
text = "MUTE"; break;
case WF.Keys.BrowserHome:
text = "Homepage"; break;
text = "HOMEPAGE"; break;
case WF.Keys.LaunchMail:
text = "Mail"; break;
text = "MAIL"; break;
case WF.Keys.BrowserFavorites:
text = "Favorites"; break;
text = "FAVORITES"; break;
case WF.Keys.BrowserSearch:
text = "Search"; break;
text = "SEARCH"; break;
case WF.Keys.Sleep:
text = "Sleep"; break;
text = "SLEEP"; break;
case WF.Keys.Cancel:
text = "Cancel"; break;
text = "CANCEL"; break;
}
bool shiftWasHandled = false;
@@ -141,11 +137,14 @@ namespace mpvnet
SetKey(text);
}
DateTime LastKeyUp;
void SetKey(string key)
{
NewKey = key;
MenuLabel.Content = InputItem.Path;
KeyLabel.Content = key;
MenuTextBlock.Text = InputItem.Path;
KeyTextBlock.Text = key;
LastKeyUp = DateTime.Now;
}
[DllImport("user32.dll")]
@@ -190,49 +189,66 @@ namespace mpvnet
OnKeyUp(new WF.KeyEventArgs((WF.Keys)(unchecked((int)(long)m.WParam)) | ModifierKeys));
else if (m.Msg == WM_APPCOMMAND)
{
switch ((AppCommand)(m.LParam.ToInt32() >> 16))
var value = (AppCommand)(m.LParam.ToInt64() >> 16 & ~0xf000);
switch (value)
{
case AppCommand.MEDIA_CHANNEL_DOWN:
SetKey("Channel_Down");
case AppCommand.APPCOMMAND_MEDIA_CHANNEL_DOWN:
SetKey("CHANNEL_DOWN");
break;
case AppCommand.MEDIA_CHANNEL_UP:
SetKey("Channel_Up");
case AppCommand.APPCOMMAND_MEDIA_CHANNEL_UP:
SetKey("CHANNEL_UP");
break;
case AppCommand.MEDIA_FAST_FORWARD:
SetKey("Forward");
case AppCommand.APPCOMMAND_MEDIA_FAST_FORWARD:
SetKey("FORWARD");
break;
case AppCommand.MEDIA_REWIND:
SetKey("Rewind");
case AppCommand.APPCOMMAND_MEDIA_REWIND:
SetKey("REWIND");
break;
case AppCommand.MEDIA_PAUSE:
SetKey("Pause");
case AppCommand.APPCOMMAND_MEDIA_PAUSE:
SetKey("PAUSE");
break;
case AppCommand.MEDIA_PLAY:
SetKey("Play");
case AppCommand.APPCOMMAND_MEDIA_PLAY:
SetKey("PLAY");
break;
case AppCommand.MEDIA_PLAY_PAUSE:
SetKey("PlayPause");
case AppCommand.APPCOMMAND_MEDIA_PLAY_PAUSE:
SetKey("PLAYPAUSE");
break;
case AppCommand.MEDIA_NEXTTRACK:
SetKey("Next");
case AppCommand.APPCOMMAND_MEDIA_NEXTTRACK:
SetKey("NEXT");
break;
case AppCommand.MEDIA_PREVIOUSTRACK:
SetKey("Prev");
case AppCommand.APPCOMMAND_MEDIA_PREVIOUSTRACK:
SetKey("PREV");
break;
case AppCommand.MEDIA_RECORD:
SetKey("Record");
case AppCommand.APPCOMMAND_MEDIA_RECORD:
SetKey("RECORD");
break;
case AppCommand.MEDIA_STOP:
SetKey("Stop");
case AppCommand.APPCOMMAND_MEDIA_STOP:
SetKey("STOP");
break;
case AppCommand.VolumeUp:
SetKey("Volume_Up");
case AppCommand.APPCOMMAND_VOLUME_UP:
SetKey("VOLUME_UP");
break;
case AppCommand.VolumeDown:
SetKey("Volume_Down");
case AppCommand.APPCOMMAND_VOLUME_DOWN:
SetKey("VOLUME_DOWN");
break;
case AppCommand.VolumeMute:
SetKey("Mute");
case AppCommand.APPCOMMAND_VOLUME_MUTE:
SetKey("MUTE");
break;
case AppCommand.APPCOMMAND_BROWSER_HOME:
SetKey("HOMEPAGE");
break;
case AppCommand.APPCOMMAND_LAUNCH_MAIL:
SetKey("MAIL");
break;
case AppCommand.APPCOMMAND_BROWSER_FAVORITES:
SetKey("FAVORITES");
break;
case AppCommand.APPCOMMAND_BROWSER_SEARCH:
SetKey("SEARCH");
break;
case AppCommand.APPCOMMAND_PRINT:
SetKey("PRINT");
break;
}
}
@@ -240,20 +256,58 @@ namespace mpvnet
internal enum AppCommand
{
MEDIA_CHANNEL_DOWN = 52,
MEDIA_CHANNEL_UP = 51,
MEDIA_FAST_FORWARD = 49,
MEDIA_NEXTTRACK = 11,
MEDIA_PAUSE = 47,
MEDIA_PLAY = 46,
MEDIA_PLAY_PAUSE = 14,
MEDIA_PREVIOUSTRACK = 12,
MEDIA_RECORD = 48,
MEDIA_REWIND = 50,
MEDIA_STOP = 13,
VolumeMute = 8,
VolumeDown = 9,
VolumeUp = 10
APPCOMMAND_BASS_BOOST = 20,
APPCOMMAND_BASS_DOWN = 19,
APPCOMMAND_BASS_UP = 21,
APPCOMMAND_BROWSER_BACKWARD = 1,
APPCOMMAND_BROWSER_FAVORITES = 6,
APPCOMMAND_BROWSER_FORWARD = 2,
APPCOMMAND_BROWSER_HOME = 7,
APPCOMMAND_BROWSER_REFRESH = 3,
APPCOMMAND_BROWSER_SEARCH = 5,
APPCOMMAND_BROWSER_STOP = 4,
APPCOMMAND_CLOSE = 31,
APPCOMMAND_COPY = 36,
APPCOMMAND_CORRECTION_LIST = 45,
APPCOMMAND_CUT = 37,
APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE = 43,
APPCOMMAND_FIND = 28,
APPCOMMAND_FORWARD_MAIL = 40,
APPCOMMAND_HELP = 27,
APPCOMMAND_LAUNCH_APP1 = 17,
APPCOMMAND_LAUNCH_APP2 = 18,
APPCOMMAND_LAUNCH_MAIL = 15,
APPCOMMAND_LAUNCH_MEDIA_SELECT = 16,
APPCOMMAND_MEDIA_CHANNEL_DOWN = 52,
APPCOMMAND_MEDIA_CHANNEL_UP = 51,
APPCOMMAND_MEDIA_FAST_FORWARD = 49,
APPCOMMAND_MEDIA_NEXTTRACK = 11,
APPCOMMAND_MEDIA_PAUSE = 47,
APPCOMMAND_MEDIA_PLAY = 46,
APPCOMMAND_MEDIA_PLAY_PAUSE = 14,
APPCOMMAND_MEDIA_PREVIOUSTRACK = 12,
APPCOMMAND_MEDIA_RECORD = 48,
APPCOMMAND_MEDIA_REWIND = 50,
APPCOMMAND_MEDIA_STOP = 13,
APPCOMMAND_MIC_ON_OFF_TOGGLE = 44,
APPCOMMAND_MICROPHONE_VOLUME_DOWN = 25,
APPCOMMAND_MICROPHONE_VOLUME_MUTE = 24,
APPCOMMAND_MICROPHONE_VOLUME_UP = 26,
APPCOMMAND_NEW = 29,
APPCOMMAND_OPEN = 30,
APPCOMMAND_PASTE = 38,
APPCOMMAND_PRINT = 33,
APPCOMMAND_REDO = 35,
APPCOMMAND_REPLY_TO_MAIL = 39,
APPCOMMAND_SAVE = 32,
APPCOMMAND_SEND_MAIL = 41,
APPCOMMAND_SPELL_CHECK = 42,
APPCOMMAND_TREBLE_DOWN = 22,
APPCOMMAND_TREBLE_UP = 23,
APPCOMMAND_UNDO = 34,
APPCOMMAND_VOLUME_DOWN = 9,
APPCOMMAND_VOLUME_MUTE = 8,
APPCOMMAND_VOLUME_UP = 10
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
@@ -295,5 +349,38 @@ namespace mpvnet
else
SetKey("WHEEL_DOWN");
}
private void Window_MouseUp(object sender, MouseButtonEventArgs e)
{
switch (e.ChangedButton)
{
case MouseButton.Left:
if (BlockMBTN_LEFT)
BlockMBTN_LEFT = false;
else
SetKey("MBTN_LEFT");
break;
case MouseButton.Middle:
SetKey("MBTN_MID");
break;
case MouseButton.XButton1:
SetKey("MBTN_BACK");
break;
case MouseButton.XButton2:
SetKey("MBTN_FORWARD");
break;
}
}
bool BlockMBTN_LEFT;
private void Window_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (e.ChangedButton == MouseButton.Left)
{
SetKey("MBTN_LEFT_DBL");
BlockMBTN_LEFT = true;
}
}
}
}

View File

@@ -1,7 +1,11 @@
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Media;
using Microsoft.Win32;
using mpvnet;
namespace WPF
{
public class WPF
@@ -22,12 +26,43 @@ namespace WPF
}
}
public static Brush ThemeBrush {
public static Brush ThemeBrush { get; } = new SolidColorBrush(ThemeColor);
static bool WasThemeColorSet;
static Color _ThemeColor;
public static Color ThemeColor {
get {
if (Environment.OSVersion.Version.Major < 10)
return new SolidColorBrush(Colors.DarkSlateGray);
else
return SystemParameters.WindowGlassBrush;
if (!WasThemeColorSet)
{
Color? color = null;
try {
if (App.IsDarkMode && !string.IsNullOrEmpty(App.DarkColor))
color = (Color)ColorConverter.ConvertFromString(App.DarkColor);
else if (!App.IsDarkMode && !string.IsNullOrEmpty(App.LightColor))
color = (Color)ColorConverter.ConvertFromString(App.LightColor);
} catch { }
if (!color.HasValue)
{
if (Environment.OSVersion.Version.Major < 10)
{
int argb = Convert.ToInt32(Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM", "ColorizationColor", 0));
var wfc = System.Drawing.Color.FromArgb(argb);
color = Color.FromArgb(wfc.A, wfc.R, wfc.G, wfc.B);
}
else
color = SystemParameters.WindowGlassColor;
}
if (App.IsDarkMode && color == Colors.Black) color = Colors.Orange;
if (!App.IsDarkMode && color == Colors.White) color = Colors.Orange;
_ThemeColor = color.Value;
WasThemeColorSet = true;
}
return _ThemeColor;
}
}
}

View File

@@ -45,13 +45,12 @@
this.AutoScaleDimensions = new System.Drawing.SizeF(288F, 288F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
this.BackColor = System.Drawing.Color.Black;
this.ClientSize = new System.Drawing.Size(606, 368);
this.ClientSize = new System.Drawing.Size(542, 0);
this.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
this.Name = "MainForm";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "mpv.net";
this.ResumeLayout(false);
}

View File

@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Diagnostics;
using System.Threading.Tasks;
namespace mpvnet
{
@@ -24,50 +25,65 @@ namespace mpvnet
bool IgnoreDpiChanged = true;
List<string> RecentFiles;
public string MpvNetDarkMode { get; set; } = "always";
public bool MpvFullscreen { get; set; }
public float MpvAutofit { get; set; } = 0.50f;
public int MpvScreen { get; set; } = -1;
public string MpvSid { get; set; } = "";
public string MpvAid { get; set; } = "";
public string MpvVid { get; set; } = "";
public int MpvEdition { get; set; }
public MainForm()
{
InitializeComponent();
try
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
Application.ThreadException += Application_ThreadException;
Msg.SupportURL = "https://github.com/stax76/mpv.net#support";
Instance = this;
WPF.WPF.Init();
System.Windows.Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;
Hwnd = Handle;
MinimumSize = new Size(FontHeight * 16, FontHeight * 9);
Text += " " + Application.ProductVersion;
mp.Init();
AppDomain.CurrentDomain.UnhandledException += (sender, e) => Msg.ShowError(e.ExceptionObject.ToString());
Application.ThreadException += (sender, e) => Msg.ShowException(e.Exception);
Msg.SupportURL = "https://github.com/stax76/mpv.net#support";
Text = "mpv.net " + Application.ProductVersion;
object recent = RegistryHelp.GetObject("HKCU\\Software\\" + Application.ProductName, "Recent");
object recent = RegHelp.GetObject(App.RegPath, "Recent");
if (recent is string[] r)
RecentFiles = new List<string>(r);
else
RecentFiles = new List<string>();
foreach (var i in mp.mpvConf)
ProcessMpvProperty(i.Key, i.Value);
ContextMenu = new ContextMenuStripEx(components);
ContextMenu.Opened += ContextMenu_Opened;
ContextMenu.Opening += ContextMenu_Opening;
foreach (var i in mp.mpvNetConf)
ProcessMpvNetProperty(i.Key, i.Value);
App.ProcessCommandLineEarly();
ProcessCommandLineEarly();
if (mp.Screen == -1) mp.Screen = Array.IndexOf(Screen.AllScreens, Screen.PrimaryScreen);
int targetIndex = mp.Screen;
Screen[] screens = Screen.AllScreens;
if (targetIndex < 0) targetIndex = 0;
if (targetIndex > screens.Length - 1) targetIndex = screens.Length - 1;
Screen screen = screens[Array.IndexOf(screens, screens[targetIndex])];
Rectangle target = screen.Bounds;
Left = target.X + (target.Width - Width) / 2;
Top = target.Y + (target.Height - Height) / 2;
if (MpvScreen == -1) MpvScreen = Array.IndexOf(Screen.AllScreens, Screen.PrimaryScreen);
SetScreen(MpvScreen);
int posX = RegHelp.GetInt(App.RegPath, "PosX");
int posY = RegHelp.GetInt(App.RegPath, "PosY");
ChangeFullscreen(MpvFullscreen);
if (posX != 0 && posY != 0 && App.RememberPosition)
{
Left = posX - Width / 2;
Top = posY - Height / 2;
}
mp.Shutdown += Shutdown;
mp.VideoSizeChanged += VideoSizeChanged;
mp.FileLoaded += FileLoaded;
mp.Idle += Idle;
mp.VideoSizeAutoResetEvent.WaitOne(App.StartThreshold);
if (Height < FontHeight * 4) SetFormPosAndSize();
mp.observe_property_bool("fullscreen", PropChangeFullscreen);
mp.observe_property_bool("ontop", PropChangeOnTop);
mp.observe_property_bool("border", PropChangeBorder);
mp.observe_property_string("sid", PropChangeSid);
mp.observe_property_string("aid", PropChangeAid);
mp.observe_property_string("vid", PropChangeVid);
mp.observe_property_int("edition", PropChangeEdition);
}
catch (Exception ex)
{
@@ -75,6 +91,24 @@ namespace mpvnet
}
}
public MenuItem FindMenuItem(string text) => FindMenuItem(text, ContextMenu.Items);
void Idle() => BeginInvoke(new Action(() => Text = "mpv.net " + Application.ProductVersion));
void CM_Popup(object sender, EventArgs e) => CursorHelp.Show();
void VideoSizeChanged() => BeginInvoke(new Action(() => SetFormPosAndSize()));
void Shutdown() => BeginInvoke(new Action(() => Close()));
void PropChangeFullscreen(bool value) => BeginInvoke(new Action(() => CycleFullscreen(value)));
void ContextMenu_Opened(object sender, EventArgs e) => CursorHelp.Show();
bool IsFullscreen => WindowState == FormWindowState.Maximized;
bool IsMouseInOSC() => PointToClient(Control.MousePosition).Y > ClientSize.Height * 0.9;
void ContextMenu_Opening(object sender, CancelEventArgs e)
{
lock (mp.MediaTracks)
@@ -94,7 +128,7 @@ namespace mpvnet
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => { mp.commandv("set", "vid", track.ID.ToString()); };
mi.Checked = MpvVid == track.ID.ToString();
mi.Checked = mp.Vid == track.ID.ToString();
trackMenuItem.DropDownItems.Add(mi);
}
@@ -105,7 +139,7 @@ namespace mpvnet
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => { mp.commandv("set", "aid", track.ID.ToString()); };
mi.Checked = MpvAid == track.ID.ToString();
mi.Checked = mp.Aid == track.ID.ToString();
trackMenuItem.DropDownItems.Add(mi);
}
@@ -116,7 +150,7 @@ namespace mpvnet
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => { mp.commandv("set", "sid", track.ID.ToString()); };
mi.Checked = MpvSid == track.ID.ToString();
mi.Checked = mp.Sid == track.ID.ToString();
trackMenuItem.DropDownItems.Add(mi);
}
@@ -124,7 +158,7 @@ namespace mpvnet
{
MenuItem mi = new MenuItem("S: No subtitles");
mi.Action = () => { mp.commandv("set", "sid", "no"); };
mi.Checked = MpvSid == "no";
mi.Checked = mp.Sid == "no";
trackMenuItem.DropDownItems.Add(mi);
}
@@ -135,7 +169,7 @@ namespace mpvnet
{
MenuItem mi = new MenuItem(track.Text);
mi.Action = () => { mp.commandv("set", "edition", track.ID.ToString()); };
mi.Checked = MpvEdition == track.ID;
mi.Checked = mp.Edition == track.ID;
trackMenuItem.DropDownItems.Add(mi);
}
}
@@ -164,10 +198,8 @@ namespace mpvnet
if (recent != null)
{
recent.DropDownItems.Clear();
foreach (string path in RecentFiles)
MenuItem.Add(recent.DropDownItems, path, () => mp.LoadFiles(path));
MenuItem.Add(recent.DropDownItems, path, () => mp.Load(new[] { path }, true, Control.ModifierKeys.HasFlag(Keys.Control)));
recent.DropDownItems.Add(new ToolStripSeparator());
MenuItem mi = new MenuItem("Clear List");
mi.Action = () => RecentFiles.Clear();
@@ -175,8 +207,6 @@ namespace mpvnet
}
}
public MenuItem FindMenuItem(string text) => FindMenuItem(text, ContextMenu.Items);
MenuItem FindMenuItem(string text, ToolStripItemCollection items)
{
foreach (var item in items)
@@ -195,112 +225,110 @@ namespace mpvnet
return null;
}
protected void SetScreen(int targetIndex)
{
Screen[] screens = Screen.AllScreens;
if (targetIndex < 0) targetIndex = 0;
if (targetIndex > screens.Length - 1) targetIndex = screens.Length - 1;
SetScreen(screens[Array.IndexOf(screens, screens[targetIndex])]);
}
bool WasInitialSizeSet;
protected void SetScreen(Screen screen)
void SetFormPosAndSize()
{
Rectangle target = screen.Bounds;
Left = target.X + Convert.ToInt32((target.Width - Width) / 2.0);
Top = target.Y + Convert.ToInt32((target.Height - Height) / 2.0);
SetStartFormPositionAndSize();
}
void SetStartFormPositionAndSize()
{
if (IsFullscreen || mp.VideoSize.Width == 0) return;
Screen screen = Screen.FromControl(this);
int height = Convert.ToInt32(screen.Bounds.Height * MpvAutofit);
int width = Convert.ToInt32(height * mp.VideoSize.Width / (double)mp.VideoSize.Height);
Point middlePos = new Point(Left + Width / 2, Top + Height / 2);
var rect = new Native.RECT(new Rectangle(screen.Bounds.X, screen.Bounds.Y, width, height));
NativeHelp.AddWindowBorders(Handle, ref rect);
int left = middlePos.X - rect.Width / 2;
int top = middlePos.Y - rect.Height / 2;
Native.SetWindowPos(Handle, IntPtr.Zero /* HWND_TOP */, left, top, rect.Width, rect.Height, 4 /* SWP_NOZORDER */);
}
void SetFormPositionAndSizeKeepHeight()
{
if (IsFullscreen || mp.VideoSize.Width == 0) return;
Screen screen = Screen.FromControl(this);
int height = ClientSize.Height;
if (height > screen.Bounds.Height * 0.9) height = Convert.ToInt32(screen.Bounds.Height * MpvAutofit);
int width = Convert.ToInt32(height * mp.VideoSize.Width / (double)mp.VideoSize.Height);
Point middlePos = new Point(Left + Width / 2, Top + Height / 2);
var rect = new Native.RECT(new Rectangle(screen.Bounds.X, screen.Bounds.Y, width, height));
NativeHelp.AddWindowBorders(Handle, ref rect);
int left = middlePos.X - rect.Width / 2;
int top = middlePos.Y - rect.Height / 2;
Screen[] screens = Screen.AllScreens;
if (left < screens[0].Bounds.Left) left = screens[0].Bounds.Left;
int maxLeft = screens[0].Bounds.Left + screens.Select((sc) => sc.Bounds.Width).Sum() - rect.Width - SystemInformation.CaptionHeight;
if (left > maxLeft) left = maxLeft;
Native.SetWindowPos(Handle, IntPtr.Zero /* HWND_TOP */, left, top, rect.Width, rect.Height, 4 /* SWP_NOZORDER */);
}
protected void ProcessCommandLineEarly()
{
var args = Environment.GetCommandLineArgs().Skip(1);
foreach (string i in args)
if (mp.Fullscreen)
{
if (i.StartsWith("--"))
{
if (i.Contains("="))
{
string left = i.Substring(2, i.IndexOf("=") - 2);
string right = i.Substring(left.Length + 3);
ProcessMpvProperty(left, right);
ProcessMpvNetProperty(left, right);
}
else
{
string switchName = i.Substring(2);
CycleFullscreen(true);
return;
}
switch (switchName)
{
case "fs":
case "fullscreen":
MpvFullscreen = true;
break;
}
}
Screen screen = Screen.FromControl(this);
int autoFitHeight = Convert.ToInt32(screen.WorkingArea.Height * mp.Autofit);
if (mp.VideoSize.Height == 0 || mp.VideoSize.Width == 0 ||
mp.VideoSize.Width / (float)mp.VideoSize.Height < 1.3)
mp.VideoSize = new Size((int)(autoFitHeight * (16 / 9.0)), autoFitHeight);
Size size = mp.VideoSize;
int height = size.Height;
if (App.RememberHeight)
{
if (WasInitialSizeSet)
height = ClientSize.Height;
else
{
height = autoFitHeight;
WasInitialSizeSet = true;
}
}
}
void ProcessMpvProperty(string name, string value)
{
switch (name)
int width = Convert.ToInt32(height * size.Width / (double)size.Height);
if (height > screen.WorkingArea.Height * 0.9)
{
case "autofit":
if (value.Length == 3 && value.EndsWith("%"))
if (int.TryParse(value.Substring(0, 2), out int result))
MpvAutofit = result / 100f;
break;
case "fs":
case "fullscreen":
MpvFullscreen = value == "yes";
break;
case "screen":
MpvScreen = Convert.ToInt32(value);
break;
height = Convert.ToInt32(screen.WorkingArea.Height * 0.9);
width = Convert.ToInt32(height * size.Width / (double)size.Height);
}
if (width > screen.WorkingArea.Width * 0.9)
{
width = Convert.ToInt32(screen.WorkingArea.Width * 0.9);
height = Convert.ToInt32(width * size.Height / (double)size.Width);
}
if (height < screen.WorkingArea.Height * mp.AutofitSmaller)
{
height = Convert.ToInt32(screen.WorkingArea.Height * mp.AutofitSmaller);
width = Convert.ToInt32(height * size.Width / (double)size.Height);
}
if (height > screen.WorkingArea.Height * mp.AutofitLarger)
{
height = Convert.ToInt32(screen.WorkingArea.Height * mp.AutofitLarger);
width = Convert.ToInt32(height * size.Width / (double)size.Height);
}
Point middlePos = new Point(Left + Width / 2, Top + Height / 2);
var rect = new Native.RECT(new Rectangle(screen.Bounds.X, screen.Bounds.Y, width, height));
NativeHelp.AddWindowBorders(Handle, ref rect);
int left = middlePos.X - rect.Width / 2;
int top = middlePos.Y - rect.Height / 2;
Screen[] screens = Screen.AllScreens;
int minLeft = screens.Select(val => val.WorkingArea.X).Min();
int maxRight = screens.Select(val => val.WorkingArea.Right).Max();
int minTop = screens.Select(val => val.WorkingArea.Y).Min();
int maxBottom = screens.Select(val => val.WorkingArea.Bottom).Max();
if (left < minLeft) left = minLeft;
if (left + rect.Width > maxRight) left = maxRight - rect.Width;
if (top < minTop) top = minTop;
if (top + rect.Height > maxBottom) top = maxBottom - rect.Height;
Native.SetWindowPos(Handle, IntPtr.Zero /* HWND_TOP */, left, top, rect.Width, rect.Height, 4 /* SWP_NOZORDER */);
}
void ProcessMpvNetProperty(string name, string value)
public void CycleFullscreen(bool enabled)
{
switch (name)
mp.Fullscreen = enabled;
if (enabled)
{
case "dark-mode":
MpvNetDarkMode = value;
break;
if (WindowState != FormWindowState.Maximized)
{
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
}
}
else
{
if (WindowState == FormWindowState.Maximized)
{
WindowState = FormWindowState.Normal;
if (mp.Border)
FormBorderStyle = FormBorderStyle.Sizable;
else
FormBorderStyle = FormBorderStyle.None;
SetFormPosAndSize();
}
}
}
@@ -321,8 +349,7 @@ namespace mpvnet
foreach (CommandItem item in items)
{
if (string.IsNullOrEmpty(item.Path))
continue;
if (string.IsNullOrEmpty(item.Path)) continue;
string path = item.Path.Replace("&", "&&");
MenuItem menuItem = ContextMenu.Add(path, () => {
try {
@@ -331,18 +358,15 @@ namespace mpvnet
Msg.ShowException(ex);
}
});
if (menuItem != null)
menuItem.ShortcutKeyDisplayString = item.Input + " ";
if (menuItem != null) menuItem.ShortcutKeyDisplayString = item.Input + " ";
}
}
void ContextMenu_Opened(object sender, EventArgs e) => CursorHelp.Show();
private void Mp_FileLoaded()
private void FileLoaded()
{
string path = mp.get_property_string("path");
BeginInvoke(new Action(() => {
if (File.Exists(path) || path.StartsWith("http"))
if (File.Exists(path) || path.Contains("://"))
Text = Path.GetFileName(path) + " - mpv.net " + Application.ProductVersion;
else
Text = "mpv.net " + Application.ProductVersion;
@@ -352,52 +376,11 @@ namespace mpvnet
if (RecentFiles.Count > 15) RecentFiles.RemoveAt(15);
}
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);
}
void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Msg.ShowError(e.ExceptionObject.ToString());
}
void mp_VideoSizeChanged()
{
BeginInvoke(new Action(() => SetFormPositionAndSizeKeepHeight()));
}
void mp_Shutdown()
{
BeginInvoke(new Action(() => Close()));
}
public bool IsFullscreen => WindowState == FormWindowState.Maximized;
void mpPropChangeFullscreen(bool value)
{
BeginInvoke(new Action(() => ChangeFullscreen(value)));
}
void ChangeFullscreen(bool value)
{
if (value)
{
if (FormBorderStyle != FormBorderStyle.None)
{
FormBorderStyle = FormBorderStyle.None;
WindowState = FormWindowState.Maximized;
}
}
else
{
WindowState = FormWindowState.Normal;
FormBorderStyle = FormBorderStyle.Sizable;
SetFormPositionAndSizeKeepHeight();
protected override CreateParams CreateParams {
get {
CreateParams cp = base.CreateParams;
cp.Style |= 0x00020000 /* WS_MINIMIZEBOX */;
return cp;
}
}
@@ -409,21 +392,34 @@ namespace mpvnet
{
case 0x0201: // WM_LBUTTONDOWN
case 0x0202: // WM_LBUTTONUP
case 0x0207: // WM_MBUTTONDOWN
case 0x0208: // WM_MBUTTONUP
case 0x020b: // WM_XBUTTONDOWN
case 0x020c: // WM_XBUTTONUP
case 0x020A: // WM_MOUSEWHEEL
case 0x0100: // WM_KEYDOWN
case 0x0101: // WM_KEYUP
case 0x0104: // WM_SYSKEYDOWN
case 0x0105: // WM_SYSKEYUP
case 0x020A: // WM_MOUSEWHEEL
if (mp.MpvWindowHandle != IntPtr.Zero)
Native.SendMessage(mp.MpvWindowHandle, m.Msg, m.WParam, m.LParam);
case 0x319: // WM_APPCOMMAND
if (mp.WindowHandle != IntPtr.Zero)
Native.SendMessage(mp.WindowHandle, m.Msg, m.WParam, m.LParam);
break;
case 0x319: // WM_APPCOMMAND
if (mp.MpvWindowHandle != IntPtr.Zero)
Native.PostMessage(mp.MpvWindowHandle, m.Msg, m.WParam, m.LParam);
case 0x0200: // WM_MOUSEMOVE
{
Point pos = PointToClient(Cursor.Position);
mp.command_string($"mouse {pos.X} {pos.Y}");
if (CursorHelp.IsPosDifferent(LastCursorPosChanged)) CursorHelp.Show();
}
break;
case 0x2a3: // WM_MOUSELEAVE
mp.command_string("mouse 1 1"); // osc won't always auto hide
break;
case 0x203: // Native.WM.LBUTTONDBLCLK
if (!IsMouseInOSC())
mp.command_string("cycle fullscreen");
{
Point pos = PointToClient(Cursor.Position);
mp.command_string($"mouse {pos.X} {pos.Y} 0 double");
}
break;
case 0x02E0: // WM_DPICHANGED
if (IgnoreDpiChanged) break;
@@ -435,20 +431,39 @@ namespace mpvnet
var r = rc;
NativeHelp.SubtractWindowBorders(Handle, ref r);
int c_w = r.Right - r.Left, c_h = r.Bottom - r.Top;
float aspect = mp.VideoSize.Width / (float)mp.VideoSize.Height;
Size s = mp.VideoSize;
if (s == Size.Empty) s = new Size(16, 9);
float aspect = s.Width / (float)s.Height;
int d_w = Convert.ToInt32(c_h * aspect - c_w);
int d_h = Convert.ToInt32(c_w / aspect - c_h);
int[] d_corners = { d_w, d_h, -d_w, -d_h };
int[] corners = { rc.Left, rc.Top, rc.Right, rc.Bottom };
int corner = NativeHelp.GetResizeBorder(m.WParam.ToInt32());
if (corner >= 0)
corners[corner] -= d_corners[corner];
if (corner >= 0) corners[corner] -= d_corners[corner];
Marshal.StructureToPtr<Native.RECT>(new Native.RECT(corners[0], corners[1], corners[2], corners[3]), m.LParam, false);
m.Result = new IntPtr(1);
return;
case 0x004A: // WM_COPYDATA
var copyData = (Native.COPYDATASTRUCT)m.GetLParam(typeof(Native.COPYDATASTRUCT));
string[] files = copyData.lpData.Split('\n');
string mode = files[0];
files = files.Skip(1).ToArray();
switch (mode)
{
case "single":
mp.Load(files, true, Control.ModifierKeys.HasFlag(Keys.Control));
break;
case "queue":
foreach (string file in files)
mp.commandv("loadfile", file, "append");
break;
}
Activate();
return;
}
base.WndProc(ref m);
}
@@ -463,9 +478,91 @@ namespace mpvnet
{
base.OnDragDrop(e);
if (e.Data.GetDataPresent(DataFormats.FileDrop))
mp.LoadFiles(e.Data.GetData(DataFormats.FileDrop) as String[]);
mp.Load(e.Data.GetData(DataFormats.FileDrop) as String[], true, Control.ModifierKeys.HasFlag(Keys.Control));
if (e.Data.GetDataPresent(DataFormats.Text))
mp.LoadFiles(e.Data.GetData(DataFormats.Text).ToString());
mp.Load(new[] { e.Data.GetData(DataFormats.Text).ToString() }, true, Control.ModifierKeys.HasFlag(Keys.Control));
}
void Timer_Tick(object sender, EventArgs e)
{
if (CursorHelp.IsPosDifferent(LastCursorPosChanged))
{
LastCursorPosChanged = Control.MousePosition;
LastCursorChangedTickCount = Environment.TickCount;
}
else if (Environment.TickCount - LastCursorChangedTickCount > 1500 &&
!IsMouseInOSC() && ClientRectangle.Contains(PointToClient(MousePosition)) &&
Form.ActiveForm == this && !ContextMenu.Visible)
CursorHelp.Hide();
}
void PropChangeOnTop(bool value) => BeginInvoke(new Action(() => TopMost = value));
void PropChangeAid(string value) => mp.Aid = value;
void PropChangeSid(string value) => mp.Sid = value;
void PropChangeVid(string value) => mp.Vid = value;
void PropChangeEdition(int value) => mp.Edition = value;
void PropChangeBorder(bool enabled) {
mp.Border = enabled;
BeginInvoke(new Action(() => {
if (!IsFullscreen)
{
if (mp.Border && FormBorderStyle == FormBorderStyle.None)
FormBorderStyle = FormBorderStyle.Sizable;
if (!mp.Border && FormBorderStyle == FormBorderStyle.Sizable)
FormBorderStyle = FormBorderStyle.None;
}
}));
}
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
var wpfColor = WPF.WPF.ThemeColor;
Color color = Color.FromArgb(wpfColor.A, wpfColor.R, wpfColor.G, wpfColor.B);
ToolStripRendererEx.InitColors(color, App.IsDarkMode);
BuildMenu();
ContextMenuStrip = ContextMenu;
WPF.WPF.Init();
System.Windows.Application.Current.ShutdownMode = System.Windows.ShutdownMode.OnExplicitShutdown;
IgnoreDpiChanged = false;
CheckClipboardForURL();
Cursor.Position = new Point(Cursor.Position.X + 1, Cursor.Position.Y);
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
CheckClipboardForURL();
Message m = new Message() { Msg = 0x0202 }; // WM_LBUTTONUP
Native.SendMessage(Handle, m.Msg, m.WParam, m.LParam);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
if (mp.IsLogoVisible) mp.ShowLogo();
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
if (WindowState == FormWindowState.Normal)
{
RegHelp.SetObject(App.RegPath, "PosX", Left + Width / 2);
RegHelp.SetObject(App.RegPath, "PosY", Top + Height / 2);
}
RegHelp.SetObject(App.RegPath, "Recent", RecentFiles.ToArray());
mp.commandv("quit");
mp.ShutdownAutoResetEvent.WaitOne(3000);
}
protected override void OnMouseDown(MouseEventArgs e)
@@ -485,102 +582,27 @@ namespace mpvnet
mp.commandv("quit");
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
mp.command_string($"mouse {e.X} {e.Y}");
if (CursorHelp.IsPosDifferent(LastCursorPosChanged))
CursorHelp.Show();
}
bool IsMouseInOSC() => PointToClient(Control.MousePosition).Y > ClientSize.Height * 0.9;
void Timer_Tick(object sender, EventArgs e)
{
if (CursorHelp.IsPosDifferent(LastCursorPosChanged))
{
LastCursorPosChanged = Control.MousePosition;
LastCursorChangedTickCount = Environment.TickCount;
}
else if (Environment.TickCount - LastCursorChangedTickCount > 1500 &&
!IsMouseInOSC() && ClientRectangle.Contains(PointToClient(MousePosition)) &&
Form.ActiveForm == this && !ContextMenu.Visible)
{
CursorHelp.Hide();
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
mp.Init();
mp.observe_property_bool("fullscreen", mpPropChangeFullscreen);
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.VideoSizeChanged += mp_VideoSizeChanged;
mp.FileLoaded += Mp_FileLoaded;
mp.Idle += Mp_Idle;
}
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)
{
base.OnShown(e);
if ((MpvNetDarkMode == "system" && Sys.IsDarkTheme) || MpvNetDarkMode == "always")
ToolStripRendererEx.ColorTheme = Color.Black;
ContextMenu = new ContextMenuStripEx(components);
ContextMenu.Opened += ContextMenu_Opened;
ContextMenu.Opening += ContextMenu_Opening;
BuildMenu();
ContextMenuStrip = ContextMenu;
IgnoreDpiChanged = false;
CheckURL();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
base.OnFormClosed(e);
RegistryHelp.SetObject("HKCU\\Software\\" + Application.ProductName, "Recent", RecentFiles.ToArray());
mp.commandv("quit");
mp.AutoResetEvent.WaitOne(3000);
}
protected override void OnLostFocus(EventArgs e)
{
base.OnLostFocus(e);
CursorHelp.Show();
}
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
CheckURL();
}
void CheckURL()
void CheckClipboardForURL()
{
string clipboard = Clipboard.GetText();
if (clipboard.StartsWith("http") && RegistryHelp.GetString("HKCU\\Software\\" + Application.ProductName, "LastURL") != clipboard && Visible)
foreach (string url in App.UrlWhitelist)
{
RegistryHelp.SetObject("HKCU\\Software\\" + Application.ProductName, "LastURL", clipboard);
if (clipboard.Contains("://") && ! clipboard.Contains("\n") &&
! clipboard.Contains(" ") && clipboard.Contains(url.ToLower()) &&
RegHelp.GetString(App.RegPath, "LastURL") != clipboard && Visible)
{
RegHelp.SetObject(App.RegPath, "LastURL", clipboard);
if (Msg.ShowQuestion("Play URL?", clipboard) == MsgResult.OK)
mp.LoadFiles(clipboard);
if (Msg.ShowQuestion("Play URL?", clipboard) == MsgResult.OK)
mp.Load(new[] { clipboard }, true, Control.ModifierKeys.HasFlag(Keys.Control));
}
}
}
}

View File

@@ -131,8 +131,10 @@ public class MenuItem : ToolStripMenuItem
public class ToolStripRendererEx : ToolStripSystemRenderer
{
public static bool IsDarkMode { get; set; }
public static Color ColorForeground { get; set; } = Color.Black;
public static Color ColorTheme { get; set; } = Color.Empty;
public static Color ColorTheme { get; set; }
public static Color ColorChecked { get; set; }
public static Color ColorBorder { get; set; }
public static Color ColorTop { get; set; }
@@ -146,19 +148,7 @@ public class ToolStripRendererEx : ToolStripSystemRenderer
int TextOffset;
public ToolStripRendererEx()
{
var argb = Convert.ToInt32(Registry.GetValue(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\DWM", "ColorizationColor", 0));
if (argb == 0)
argb = Color.LightBlue.ToArgb();
if (ColorTheme == Color.Empty)
InitColors(Color.FromArgb(argb));
else
InitColors(ColorTheme);
}
public static void InitColors(Color c)
public static void InitColors(Color c, bool darkMode)
{
ColorBorder = HSLColor.Convert(c).ToColorSetLuminosity(100);
ColorChecked = HSLColor.Convert(c).ToColorSetLuminosity(160);
@@ -166,7 +156,7 @@ public class ToolStripRendererEx : ToolStripSystemRenderer
ColorBackground = HSLColor.Convert(c).ToColorSetLuminosity(210);
ColorTop = HSLColor.Convert(c).ToColorSetLuminosity(240);
if (ColorTheme == Color.Black)
if (darkMode)
{
ColorBorder = Color.White;
ColorBackground = Color.FromArgb(50, 50, 50);

View File

@@ -137,20 +137,20 @@
<Reference Include="WindowsBase" />
</ItemGroup>
<ItemGroup>
<Compile Include="Addon.cs" />
<Compile Include="Misc\Addon.cs" />
<Page Include="Controls\SearchTextBoxUserControl.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Windows\AboutWindow.xaml">
<Page Include="WPF\AboutWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Windows\EverythingWindow.xaml">
<Page Include="WPF\EverythingWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Windows\CommandPaletteWindow.xaml">
<Page Include="WPF\CommandPaletteWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
@@ -169,53 +169,52 @@
<DependentUpon>StringSettingControl.xaml</DependentUpon>
</Compile>
<Compile Include="DynamicGUI\Tommy.cs" />
<Compile Include="MediaInfo.cs" />
<Compile Include="Menu.cs">
<Compile Include="Native\MediaInfo.cs" />
<Compile Include="WinForms\Menu.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="PowerShellScript.cs" />
<Compile Include="Scripting\PowerShellScript.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="PythonScript.cs" />
<Compile Include="libmpv.cs" />
<Compile Include="MainForm.cs">
<Compile Include="Scripting\PythonScript.cs" />
<Compile Include="mpv\libmpv.cs" />
<Compile Include="WinForms\MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<Compile Include="WinForms\MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="Misc.cs" />
<Compile Include="mp.cs" />
<Compile Include="Command.cs" />
<Compile Include="Native.cs" />
<Compile Include="NativeHelp.cs" />
<Compile Include="Program.cs" />
<Compile Include="Misc\Misc.cs" />
<Compile Include="mpv\mp.cs" />
<Compile Include="Misc\Command.cs" />
<Compile Include="Native\Native.cs" />
<Compile Include="Native\NativeHelp.cs" />
<Compile Include="Misc\Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TaskDialog.cs" />
<Compile Include="Windows\EverythingWindow.xaml.cs">
<Compile Include="Native\TaskDialog.cs" />
<Compile Include="WPF\EverythingWindow.xaml.cs">
<DependentUpon>EverythingWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\CommandPaletteWindow.xaml.cs">
<Compile Include="WPF\CommandPaletteWindow.xaml.cs">
<DependentUpon>CommandPaletteWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\ConfWindow.xaml.cs">
<Compile Include="WPF\ConfWindow.xaml.cs">
<DependentUpon>ConfWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\AboutWindow.xaml.cs">
<Compile Include="WPF\AboutWindow.xaml.cs">
<DependentUpon>AboutWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\LearnWindow.xaml.cs">
<Compile Include="WPF\LearnWindow.xaml.cs">
<DependentUpon>LearnWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Windows\InputWindow.xaml.cs">
<Compile Include="WPF\InputWindow.xaml.cs">
<DependentUpon>InputWindow.xaml</DependentUpon>
</Compile>
<Compile Include="trash.cs" />
<Compile Include="WPF\WPF.cs" />
<EmbeddedResource Include="MainForm.resx">
<EmbeddedResource Include="WinForms\MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
@@ -224,8 +223,17 @@
<SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="..\Changelog.md">
<Link>Changelog.md</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\Manual.md">
<Link>Manual.md</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\README.md">
<Link>README.md</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="app.manifest" />
<None Include="packages.config" />
@@ -245,13 +253,13 @@
<Content Include="mpvnet.ico" />
<Content Include="Resources\mpvConf.txt" />
<Content Include="Resources\mpvConfToml.txt" />
<None Include="Resources\mpvnet.png" />
<Content Include="Resources\mpvNetConfToml.txt" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
<ItemGroup>
<Content Include="Resources\inputConfHeader.txt" />
<Content Include="Resources\inputConf.txt" />
</ItemGroup>
<ItemGroup>
@@ -263,19 +271,20 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Windows\ConfWindow.xaml">
<Page Include="WPF\ConfWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Windows\LearnWindow.xaml">
<Page Include="WPF\LearnWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
<Page Include="Windows\InputWindow.xaml">
<Page Include="WPF\InputWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.

View File

@@ -144,7 +144,7 @@ namespace mpvnet
MPV_END_FILE_REASON_REDIRECT = 5
}
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Sequential)]
public struct mpv_event_log_message
{
public string prefix;

View File

@@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -11,6 +13,8 @@ using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using WinForms = System.Windows.Forms;
using static mpvnet.libmpv;
using static mpvnet.Native;
@@ -50,138 +54,178 @@ namespace mpvnet
public static event Action QueueOverflow; // MPV_EVENT_QUEUE_OVERFLOW
public static event Action Hook; // MPV_EVENT_HOOK
public static IntPtr MpvHandle { get; set; }
public static IntPtr MpvWindowHandle { get; set; }
public static IntPtr Handle { get; set; }
public static IntPtr WindowHandle { get; set; }
public static Addon Addon { get; set; }
public static bool IsLogoVisible { set; get; }
public static List<KeyValuePair<string, Action<bool>>> BoolPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<bool>>>();
public static List<KeyValuePair<string, Action<int>>> IntPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<int>>>();
public static List<KeyValuePair<string, Action<string>>> StringPropChangeActions { get; set; } = new List<KeyValuePair<string, Action<string>>>();
public static Size VideoSize { get; set; } = new Size(1920, 1080);
public static Size VideoSize { get; set; }
public static List<PythonScript> PythonScripts { get; set; } = new List<PythonScript>();
public static AutoResetEvent AutoResetEvent { get; set; } = new AutoResetEvent(false);
public static AutoResetEvent ShutdownAutoResetEvent { get; set; } = new AutoResetEvent(false);
public static AutoResetEvent VideoSizeAutoResetEvent { 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>>();
public static string InputConfPath { get; } = MpvConfFolder + "\\input.conf";
public static string MpvConfPath { get; } = MpvConfFolder + "\\mpv.conf";
public static string MpvNetConfPath { get; } = MpvConfFolder + "\\mpvnet.conf";
public static string InputConfPath { get; } = ConfigFolder + "\\input.conf";
public static string ConfPath { get; } = ConfigFolder + "\\mpv.conf";
public static string Sid { get; set; } = "";
public static string Aid { get; set; } = "";
public static string Vid { get; set; } = "";
static string _MpvConfFolder;
public static bool Fullscreen { get; set; }
public static bool Border { get; set; } = true;
public static string MpvConfFolder {
get {
if (_MpvConfFolder == null)
{
string portableFolder = Application.StartupPath + "\\portable_config\\";
string appdataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\";
string startupFolder = Application.StartupPath + "\\";
public static int Screen { get; set; } = -1;
public static int Edition { get; set; }
if (!Directory.Exists(appdataFolder) && !Directory.Exists(portableFolder) &&
Sys.IsDirectoryWritable(Application.StartupPath) &&
!File.Exists(startupFolder + "mpv.conf"))
{
using (TaskDialog<string> td = new TaskDialog<string>())
{
td.MainInstruction = "Choose a settings folder.";
td.Content = "[MPV documentation about files on Windows.](https://mpv.io/manual/master/#files-on-windows)";
td.AddCommandLink("appdata", appdataFolder, appdataFolder);
td.AddCommandLink("portable", portableFolder, portableFolder);
td.AddCommandLink("startup", startupFolder, startupFolder);
td.AllowCancel = false;
_MpvConfFolder = td.Show();
}
}
else if (Directory.Exists(portableFolder))
_MpvConfFolder = portableFolder;
else if (Directory.Exists(appdataFolder))
_MpvConfFolder = appdataFolder;
else if (File.Exists(Application.StartupPath + "\\mpv.conf"))
_MpvConfFolder = Application.StartupPath + "\\";
if (string.IsNullOrEmpty(_MpvConfFolder)) _MpvConfFolder = appdataFolder;
if (!Directory.Exists(_MpvConfFolder)) Directory.CreateDirectory(_MpvConfFolder);
if (!File.Exists(_MpvConfFolder + "\\input.conf"))
File.WriteAllText(_MpvConfFolder + "\\input.conf", Properties.Resources.inputConf);
if (!File.Exists(_MpvConfFolder + "\\mpv.conf"))
File.WriteAllText(_MpvConfFolder + "\\mpv.conf", Properties.Resources.mpvConf);
}
return _MpvConfFolder;
}
}
static Dictionary<string, string> _mpvConf;
public static Dictionary<string, string> mpvConf {
get {
if (_mpvConf == null)
{
_mpvConf = new Dictionary<string, string>();
if (File.Exists(MpvConfPath))
foreach (var i in File.ReadAllLines(MpvConfPath))
if (i.Contains("=") && ! i.StartsWith("#"))
_mpvConf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
}
return _mpvConf;
}
}
static Dictionary<string, string> _mpvNetConf;
public static Dictionary<string, string> mpvNetConf {
get {
if (_mpvNetConf == null)
{
_mpvNetConf = new Dictionary<string, string>();
if (File.Exists(MpvNetConfPath))
foreach (string i in File.ReadAllLines(MpvNetConfPath))
if (i.Contains("=") && !i.StartsWith("#"))
_mpvNetConf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
}
return _mpvNetConf;
}
}
public static float Autofit { get; set; } = 0.5f;
public static float AutofitSmaller { get; set; } = 0.4f;
public static float AutofitLarger { get; set; } = 0.75f;
public static void Init()
{
string dummy = MpvConfFolder;
LoadLibrary("mpv-1.dll");
MpvHandle = mpv_create();
set_property_string("input-default-bindings", "yes");
Handle = mpv_create();
set_property_string("config-dir", ConfigFolder);
set_property_string("osc", "yes");
set_property_string("config", "yes");
set_property_string("wid", MainForm.Hwnd.ToString());
set_property_string("force-window", "yes");
set_property_string("input-media-keys", "yes");
mpv_initialize(MpvHandle);
mpv_initialize(Handle);
ShowLogo();
ProcessCommandLine();
Task.Run(() => { LoadScripts(); });
Task.Run(() => { Addon = new Addon(); });
Task.Run(() => { EventLoop(); });
}
public static void ProcessProperty(string name, string value)
{
switch (name)
{
case "autofit":
if (int.TryParse(value.Trim('%'), out int result))
Autofit = result / 100f;
break;
case "autofit-smaller":
if (int.TryParse(value.Trim('%'), out int result2))
AutofitSmaller = result2 / 100f;
break;
case "autofit-larger":
if (int.TryParse(value.Trim('%'), out int result3))
AutofitLarger = result3 / 100f;
break;
case "fs":
case "fullscreen": Fullscreen = value == "yes"; break;
case "border": Border = value == "yes"; break;
case "screen": Screen = Convert.ToInt32(value); break;
}
}
static string _ConfigFolder;
public static string ConfigFolder {
get {
if (_ConfigFolder == null)
{
_ConfigFolder = Application.StartupPath + "\\portable_config\\";
if (!Directory.Exists(_ConfigFolder))
_ConfigFolder = RegHelp.GetString(App.RegPath, "ConfigFolder");
if (!Directory.Exists(_ConfigFolder))
{
string portableFolder = Application.StartupPath + "\\portable_config\\";
string appdataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv.net\\";
string appdataFolderMpv = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\mpv\\";
string startupFolder = Application.StartupPath + "\\";
using (TaskDialog<string> td = new TaskDialog<string>())
{
td.MainInstruction = "Choose a settings folder.";
td.AddCommandLink(@"AppData\Roaming\mpv.net", appdataFolder, appdataFolder);
td.AddCommandLink(@"AppData\Roaming\mpv", appdataFolderMpv, appdataFolderMpv);
td.AddCommandLink("<startup>\\portable_config", portableFolder, portableFolder);
td.AddCommandLink("<startup>", startupFolder, startupFolder);
td.AddCommandLink("Choose custom folder", "custom");
td.AllowCancel = false;
_ConfigFolder = td.Show();
}
if (_ConfigFolder == "custom")
{
using (var d = new WinForms.FolderBrowserDialog())
{
d.Description = "Choose a folder.";
if (d.ShowDialog() == WinForms.DialogResult.OK)
_ConfigFolder = d.SelectedPath + "\\";
else
_ConfigFolder = appdataFolder;
}
}
}
if (!Directory.Exists(_ConfigFolder))
Directory.CreateDirectory(_ConfigFolder);
if (!_ConfigFolder.Contains("portable_config"))
RegHelp.SetObject(App.RegPath, "ConfigFolder", _ConfigFolder);
if (!File.Exists(_ConfigFolder + "\\input.conf"))
File.WriteAllText(_ConfigFolder + "\\input.conf", Properties.Resources.inputConf);
if (!File.Exists(_ConfigFolder + "\\mpv.conf"))
File.WriteAllText(_ConfigFolder + "\\mpv.conf", Properties.Resources.mpvConf);
}
return _ConfigFolder;
}
}
static Dictionary<string, string> _Conf;
public static Dictionary<string, string> Conf {
get {
if (_Conf == null)
{
_Conf = new Dictionary<string, string>();
if (File.Exists(ConfPath))
foreach (var i in File.ReadAllLines(ConfPath))
if (i.Contains("=") && ! i.StartsWith("#"))
_Conf[i.Substring(0, i.IndexOf("=")).Trim()] = i.Substring(i.IndexOf("=") + 1).Trim();
foreach (var i in Conf)
ProcessProperty(i.Key, i.Value);
}
return _Conf;
}
}
public static void LoadScripts()
{
string[] jsLua = { ".lua", ".js" };
string[] startupScripts = Directory.GetFiles(Application.StartupPath + "\\Scripts");
foreach (var scriptPath in startupScripts)
if (jsLua.Contains(Path.GetExtension(scriptPath).ToLower()))
mp.commandv("load-script", $"{scriptPath}");
if (Directory.Exists(Application.StartupPath + "\\Scripts"))
{
string[] startupScripts = Directory.GetFiles(Application.StartupPath + "\\Scripts");
foreach (var scriptPath in startupScripts)
if (Path.GetExtension(scriptPath) == ".py")
PythonScripts.Add(new PythonScript(File.ReadAllText(scriptPath)));
foreach (string scriptPath in startupScripts)
if (scriptPath.EndsWith(".lua") || scriptPath.EndsWith(".js"))
commandv("load-script", $"{scriptPath}");
foreach (var scriptPath in startupScripts)
if (Path.GetExtension(scriptPath) == ".ps1")
PowerShellScript.Init(scriptPath);
foreach (string scriptPath in startupScripts)
if (Path.GetExtension(scriptPath) == ".py")
PythonScripts.Add(new PythonScript(File.ReadAllText(scriptPath)));
if (Directory.Exists(mp.MpvConfFolder + "Scripts"))
foreach (var scriptPath in Directory.GetFiles(mp.MpvConfFolder + "Scripts"))
foreach (string scriptPath in startupScripts)
if (Path.GetExtension(scriptPath) == ".ps1")
PowerShellScript.Init(scriptPath);
}
if (Directory.Exists(ConfigFolder + "Scripts"))
foreach (string scriptPath in Directory.GetFiles(ConfigFolder + "Scripts"))
if (Path.GetExtension(scriptPath) == ".py")
PythonScripts.Add(new PythonScript(File.ReadAllText(scriptPath)));
else if (Path.GetExtension(scriptPath) == ".ps1")
@@ -192,22 +236,24 @@ namespace mpvnet
{
while (true)
{
IntPtr ptr = mpv_wait_event(MpvHandle, -1);
IntPtr ptr = mpv_wait_event(Handle, -1);
mpv_event evt = (mpv_event)Marshal.PtrToStructure(ptr, typeof(mpv_event));
if (MpvWindowHandle == IntPtr.Zero)
MpvWindowHandle = FindWindowEx(MainForm.Hwnd, IntPtr.Zero, "mpv", null);
if (WindowHandle == IntPtr.Zero)
WindowHandle = FindWindowEx(MainForm.Hwnd, IntPtr.Zero, "mpv", null);
//Debug.WriteLine(evt.event_id.ToString());
// Debug.WriteLine(evt.event_id.ToString());
try
{
switch (evt.event_id)
{
case mpv_event_id.MPV_EVENT_SHUTDOWN:
if (App.DebugMode) Trace.WriteLine("before Shutdown.Invoke");
Shutdown?.Invoke();
if (App.DebugMode) Trace.WriteLine("after Shutdown.Invoke");
WriteHistory(null);
AutoResetEvent.Set();
ShutdownAutoResetEvent.Set();
return;
case mpv_event_id.MPV_EVENT_LOG_MESSAGE:
LogMessage?.Invoke();
@@ -226,12 +272,23 @@ namespace mpvnet
break;
case mpv_event_id.MPV_EVENT_END_FILE:
var end_fileData = (mpv_event_end_file)Marshal.PtrToStructure(evt.data, typeof(mpv_event_end_file));
EndFile?.Invoke((EndFileEventMode)end_fileData.reason);
EndFileEventMode reason = (EndFileEventMode)end_fileData.reason;
EndFile?.Invoke(reason);
break;
case mpv_event_id.MPV_EVENT_FILE_LOADED:
HideLogo();
FileLoaded?.Invoke();
LoadFolder();
WriteHistory(mp.get_property_string("path"));
Size vidSize = new Size(get_property_int("width"), get_property_int("height"));
if (vidSize.Width == 0 || vidSize.Height == 0)
vidSize = new Size(1, 1);
if (VideoSize != vidSize)
{
VideoSize = vidSize;
VideoSizeChanged?.Invoke();
}
VideoSizeAutoResetEvent.Set();
Task.Run(new Action(() => ReadMetaData()));
WriteHistory(get_property_string("path"));
break;
case mpv_event_id.MPV_EVENT_TRACKS_CHANGED:
TracksChanged?.Invoke();
@@ -241,6 +298,7 @@ namespace mpvnet
break;
case mpv_event_id.MPV_EVENT_IDLE:
Idle?.Invoke();
if (get_property_int("playlist-count") == 0) ShowLogo();
break;
case mpv_event_id.MPV_EVENT_PAUSE:
Pause?.Invoke();
@@ -257,31 +315,10 @@ namespace mpvnet
case mpv_event_id.MPV_EVENT_CLIENT_MESSAGE:
var client_messageData = (mpv_event_client_message)Marshal.PtrToStructure(evt.data, typeof(mpv_event_client_message));
string[] args = NativeUtf8StrArray2ManagedStrArray(client_messageData.args, client_messageData.num_args);
if (args != null && args.Length > 1 && args[0] == "mpv.net")
{
bool found = false;
foreach (var i in mpvnet.Command.Commands)
{
if (args[1] == i.Name)
{
found = true;
i.Action.Invoke(args.Skip(2).ToArray());
MainForm.Instance.BeginInvoke(new Action(() => {
Message m = new Message() { Msg = 0x0202 }; // WM_LBUTTONUP
Native.SendMessage(MainForm.Instance.Handle, m.Msg, m.WParam, m.LParam);
}));
}
}
if (!found)
{
List<string> names = mpvnet.Command.Commands.Select((item) => item.Name).ToList();
names.Sort();
Msg.ShowError($"No command '{args[1]}' found.", $"Available commands are:\n\n{string.Join("\n", names)}\n\nHow to bind these commands can be seen in the [default input bindings and menu definition](https://github.com/stax76/mpv.net/blob/master/mpv.net/Resources/inputConf.txt).");
}
}
ClientMessage?.Invoke(args);
if (args.Length > 1 && args[0] == "mpv.net")
Command.Execute(args[1], args.Skip(2).ToArray());
else if (args.Length > 0)
ClientMessage?.Invoke(args);
break;
case mpv_event_id.MPV_EVENT_VIDEO_RECONFIG:
VideoReconfig?.Invoke();
@@ -315,15 +352,6 @@ namespace mpvnet
break;
case mpv_event_id.MPV_EVENT_PLAYBACK_RESTART:
PlaybackRestart?.Invoke();
Size vidSize = new Size(get_property_int("dwidth"), get_property_int("dheight"));
if (VideoSize != vidSize && vidSize != Size.Empty)
{
VideoSize = vidSize;
VideoSizeChanged?.Invoke();
}
Task.Run(new Action(() => ReadMetaData()));
break;
case mpv_event_id.MPV_EVENT_CHAPTER_CHANGE:
ChapterChange?.Invoke();
@@ -343,6 +371,15 @@ namespace mpvnet
}
}
static void HideLogo()
{
if (IsLogoVisible)
{
commandv("overlay-remove", "0");
IsLogoVisible = false;
}
}
static List<PythonEventObject> PythonEventObjects = new List<PythonEventObject>();
public static void register_event(string name, IronPython.Runtime.PythonFunction pyFunc)
@@ -357,17 +394,11 @@ namespace mpvnet
MethodInfo mi;
if (eventInfo.EventHandlerType == typeof(Action))
{
mi = eventObject.GetType().GetMethod(nameof(PythonEventObject.Invoke));
}
else if (eventInfo.EventHandlerType == typeof(Action<EndFileEventMode>))
{
mi = eventObject.GetType().GetMethod(nameof(PythonEventObject.InvokeEndFileEventMode));
}
else if (eventInfo.EventHandlerType == typeof(Action<string[]>))
{
mi = eventObject.GetType().GetMethod(nameof(PythonEventObject.InvokeStrings));
}
else
throw new Exception();
@@ -389,86 +420,82 @@ namespace mpvnet
public static void commandv(params string[] args)
{
if (MpvHandle == IntPtr.Zero)
return;
if (Handle == IntPtr.Zero) return;
IntPtr mainPtr = AllocateUtf8IntPtrArrayWithSentinel(args, out IntPtr[] byteArrayPointers);
int err = mpv_command(MpvHandle, mainPtr);
if (err < 0)
throw new Exception($"{(mpv_error)err}");
int err = mpv_command(Handle, mainPtr);
if (err < 0) throw new Exception($"{(mpv_error)err}");
foreach (var ptr in byteArrayPointers)
Marshal.FreeHGlobal(ptr);
Marshal.FreeHGlobal(mainPtr);
}
public static void command_string(string command, bool throwException = false)
{
if (MpvHandle == IntPtr.Zero)
return;
int err = mpv_command_string(MpvHandle, command);
if (err < 0 && throwException)
throw new Exception($"{(mpv_error)err}\r\n\r\n" + command);
if (Handle == IntPtr.Zero) return;
int err = mpv_command_string(Handle, command);
if (err < 0 && throwException) throw new Exception($"{(mpv_error)err}\n\n" + command);
}
public static void set_property_string(string name, string value, bool throwOnException = false)
{
byte[] bytes = GetUtf8Bytes(value);
int err = mpv_set_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, ref bytes);
if (err < 0 && throwOnException)
throw new Exception($"{name}: {(mpv_error)err}");
int err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, ref bytes);
if (err < 0 && throwOnException) throw new Exception($"{name}: {(mpv_error)err}");
}
public static string get_property_string(string name, bool throwOnException = false)
{
try
{
int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, out IntPtr lpBuffer);
int err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_STRING, out IntPtr lpBuffer);
if (err < 0 && throwOnException)
throw new Exception($"{name}: {(mpv_error)err}");
if (err < 0)
{
if (throwOnException) throw new Exception($"{name}: {(mpv_error)err}");
return "";
}
string ret = StringFromNativeUtf8(lpBuffer);
mpv_free(lpBuffer);
return ret;
}
catch (Exception ex)
catch (Exception e)
{
if (throwOnException) throw ex;
if (throwOnException) throw e;
return "";
}
}
public static int get_property_int(string name, bool throwOnException = false)
{
int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, out IntPtr lpBuffer);
int err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, out IntPtr lpBuffer);
if (err < 0 && throwOnException)
throw new Exception($"{name}: {(mpv_error)err}");
else
return lpBuffer.ToInt32();
if (err < 0)
{
if (throwOnException) throw new Exception($"{name}: {(mpv_error)err}");
return 0;
}
return lpBuffer.ToInt32();
}
public static double get_property_number(string name, bool throwOnException = false)
{
double val = 0;
int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_DOUBLE, ref val);
int err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_DOUBLE, ref val);
if (err < 0 && throwOnException)
throw new Exception($"{name}: {(mpv_error)err}");
else
return val;
if (err < 0)
{
if (throwOnException) throw new Exception($"{name}: {(mpv_error)err}");
return 0;
}
return val;
}
public static bool get_property_bool(string name, bool throwOnException = false)
{
int err = mpv_get_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_FLAG, out IntPtr lpBuffer);
int err = mpv_get_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_FLAG, out IntPtr lpBuffer);
if (err < 0 && throwOnException)
throw new Exception($"{name}: {(mpv_error)err}");
@@ -479,15 +506,13 @@ namespace mpvnet
public static void set_property_int(string name, int value, bool throwOnException = false)
{
Int64 val = value;
int err = mpv_set_property(MpvHandle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, ref val);
if (err < 0 && throwOnException)
throw new Exception($"{name}: {(mpv_error)err}");
int err = mpv_set_property(Handle, GetUtf8Bytes(name), mpv_format.MPV_FORMAT_INT64, ref val);
if (err < 0 && throwOnException) 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);
int err = mpv_observe_property(Handle, (ulong)action.GetHashCode(), name, mpv_format.MPV_FORMAT_INT64);
if (err < 0)
throw new Exception($"{name}: {(mpv_error)err}");
@@ -497,7 +522,7 @@ namespace mpvnet
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(Handle, (ulong)action.GetHashCode(), name, mpv_format.MPV_FORMAT_FLAG);
if (err < 0)
throw new Exception($"{name}: {(mpv_error)err}");
@@ -507,7 +532,7 @@ namespace mpvnet
public static void observe_property_string(string name, Action<string> action)
{
int err = mpv_observe_property(MpvHandle, (ulong)action.GetHashCode(), name, mpv_format.MPV_FORMAT_STRING);
int err = mpv_observe_property(Handle, (ulong)action.GetHashCode(), name, mpv_format.MPV_FORMAT_STRING);
if (err < 0)
throw new Exception($"{name}: {(mpv_error)err}");
@@ -518,67 +543,91 @@ namespace mpvnet
protected static void ProcessCommandLine()
{
var args = Environment.GetCommandLineArgs().Skip(1);
List<string> files = new List<string>();
foreach (string i in args)
if (!i.StartsWith("--") && File.Exists(i))
mp.commandv("loadfile", i, "append");
{
if (!i.StartsWith("--") && (i == "-" || i.Contains("://") || File.Exists(i)))
{
files.Add(i);
if (i.Contains("://")) RegHelp.SetObject(App.RegPath, "LastURL", i);
}
}
mp.set_property_string("playlist-pos", "0");
Load(files.ToArray(), App.ProcessInstance != "queue", Control.ModifierKeys.HasFlag(Keys.Control));
if (files.Count == 0 || files[0].Contains("://"))
{
VideoSizeAutoResetEvent.Set();
VideoSizeChanged?.Invoke();
}
foreach (string i in args)
{
if (i.StartsWith("--"))
{
if (i.Contains("="))
try
{
string left = i.Substring(2, i.IndexOf("=") - 2);
string right = i.Substring(left.Length + 3);
mp.set_property_string(left, right);
if (i.Contains("="))
{
string left = i.Substring(2, i.IndexOf("=") - 2);
string right = i.Substring(left.Length + 3);
set_property_string(left, right, true);
}
else
set_property_string(i.Substring(2), "yes", true);
}
catch (Exception e)
{
Msg.ShowException(e);
}
else
mp.set_property_string(i.Substring(2), "yes");
}
}
}
public static void LoadFiles(params string[] files)
public static DateTime LastLoad;
public static void Load(string[] files, bool loadFolder, bool append)
{
int count = mp.get_property_int("playlist-count");
if (files is null || files.Length == 0) return;
HideLogo();
foreach (string file in files)
mp.commandv("loadfile", file, "append");
if ((DateTime.Now - LastLoad).TotalMilliseconds < 500)
append = true;
mp.set_property_int("playlist-pos", count);
LastLoad = DateTime.Now;
for (int i = 0; i < count; i++)
mp.commandv("playlist-remove", "0");
for (int i = 0; i < files.Length; i++)
if (App.SubtitleTypes.Contains(Path.GetExtension(files[i]).TrimStart('.').ToLower()))
commandv("sub-add", files[i]);
else
if (i == 0 && !append)
commandv("loadfile", files[i]);
else
commandv("loadfile", files[i], "append");
mp.LoadFolder();
if (string.IsNullOrEmpty(get_property_string("path")))
set_property_int("playlist-pos", 0);
if (loadFolder && !append) Task.Run(() => LoadFolder()); // user reported race condition
}
static bool WasFolderLoaded;
static void LoadFolder()
public static void LoadFolder()
{
if (WasFolderLoaded) return;
if (get_property_int("playlist-count") == 1)
{
string path = get_property_string("path");
if (!File.Exists(path)) return;
List<string> files = Directory.GetFiles(Path.GetDirectoryName(path)).ToList();
files = files.Where((file) => App.VideoTypes.Contains(Path.GetExtension(file).TrimStart('.').ToLower()) ||
App.AudioTypes.Contains(Path.GetExtension(file).TrimStart('.').ToLower())).ToList();
files.Sort(new StringLogicalComparer());
int index = files.IndexOf(path);
files.Remove(path);
foreach (string i in files)
commandv("loadfile", i, "append");
if (index > 0)
commandv("playlist-move", "0", (index + 1).ToString());
}
WasFolderLoaded = true;
Thread.Sleep(200); // user reported race condition
string path = get_property_string("path");
if (!File.Exists(path) || get_property_int("playlist-count") != 1) return;
List<string> files = Directory.GetFiles(Path.GetDirectoryName(path)).ToList();
files = files.Where((file) =>
App.VideoTypes.Contains(Path.GetExtension(file).TrimStart('.').ToLower()) ||
App.AudioTypes.Contains(Path.GetExtension(file).TrimStart('.').ToLower()) ||
App.ImageTypes.Contains(Path.GetExtension(file).TrimStart('.').ToLower())).ToList();
files.Sort(new StringLogicalComparer());
int index = files.IndexOf(path);
files.Remove(path);
foreach (string i in files)
commandv("loadfile", i, "append");
if (index > 0) commandv("playlist-move", "0", (index + 1).ToString());
}
static IntPtr AllocateUtf8IntPtrArrayWithSentinel(string[] arr, out IntPtr[] byteArrayPointers)
@@ -599,16 +648,16 @@ namespace mpvnet
return rootPointer;
}
static string[] NativeUtf8StrArray2ManagedStrArray(IntPtr pUnmanagedStringArray, int StringCount)
static string[] NativeUtf8StrArray2ManagedStrArray(IntPtr unmanagedStringArray, int StringCount)
{
IntPtr[] pIntPtrArray = new IntPtr[StringCount];
string[] ManagedStringArray = new string[StringCount];
Marshal.Copy(pUnmanagedStringArray, pIntPtrArray, 0, StringCount);
IntPtr[] intPtrArray = new IntPtr[StringCount];
string[] stringArray = new string[StringCount];
Marshal.Copy(unmanagedStringArray, intPtrArray, 0, StringCount);
for (int i = 0; i < StringCount; i++)
ManagedStringArray[i] = StringFromNativeUtf8(pIntPtrArray[i]);
stringArray[i] = StringFromNativeUtf8(intPtrArray[i]);
return ManagedStringArray;
return stringArray;
}
static string StringFromNativeUtf8(IntPtr nativeUtf8)
@@ -625,102 +674,132 @@ namespace mpvnet
static string LastHistoryPath;
static DateTime LastHistoryStartDateTime;
static void WriteHistory(string filePath)
static void WriteHistory(string path)
{
if (!File.Exists(path)) return;
int totalMinutes = Convert.ToInt32((DateTime.Now - LastHistoryStartDateTime).TotalMinutes);
if (File.Exists(LastHistoryPath) && totalMinutes > 1)
{
string historyFilepath = mp.MpvConfFolder + "history.txt";
string historyFilepath = ConfigFolder + "history.txt";
File.AppendAllText(historyFilepath, DateTime.Now.ToString().Substring(0, 16) +
" " + totalMinutes.ToString().PadLeft(3) + " " +
Path.GetFileNameWithoutExtension(LastHistoryPath) + "\r\n");
}
LastHistoryPath = filePath;
LastHistoryPath = path;
LastHistoryStartDateTime = DateTime.Now;
}
public static void ShowLogo()
{
if (MainForm.Instance is null) return;
Rectangle cr = MainForm.Instance.ClientRectangle;
int len = cr.Height / 5;
if (len == 0) return;
using (Bitmap b = new Bitmap(len, len))
{
using (Graphics g = Graphics.FromImage(b))
{
g.InterpolationMode = InterpolationMode.HighQualityBicubic;
g.Clear(Color.Black);
Rectangle rect = new Rectangle(0, 0, len, len);
g.DrawImage(Properties.Resources.mpvnet, rect);
BitmapData bd = b.LockBits(rect, ImageLockMode.ReadOnly, PixelFormat.Format32bppPArgb);
int x = Convert.ToInt32((cr.Width - len) / 2.0);
int y = Convert.ToInt32(((cr.Height - len) / 2.0) * 0.9);
commandv("overlay-add", "0", $"{x}", $"{y}", "&" + bd.Scan0.ToInt64().ToString(), "0", "bgra", bd.Width.ToString(), bd.Height.ToString(), bd.Stride.ToString());
b.UnlockBits(bd);
IsLogoVisible = true;
}
}
}
static void ReadMetaData()
{
lock (MediaTracks)
{
MediaTracks.Clear();
string path = get_property_string("path");
using (MediaInfo mi = new MediaInfo(mp.get_property_string("path")))
if (File.Exists(path))
{
int count = mi.GetCount(MediaInfoStreamKind.Video);
for (int i = 0; i < count; i++)
using (MediaInfo mi = new MediaInfo(path))
{
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(' ', ',');
track.Type = "v";
track.ID = i + 1;
MediaTracks.Add(track);
}
int count = mi.GetCount(MediaInfoStreamKind.Video);
count = mi.GetCount(MediaInfoStreamKind.Audio);
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(' ', ',');
track.Type = "v";
track.ID = i + 1;
MediaTracks.Add(track);
}
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(' ', ',');
track.Type = "a";
track.ID = i + 1;
MediaTracks.Add(track);
}
count = mi.GetCount(MediaInfoStreamKind.Audio);
count = mi.GetCount(MediaInfoStreamKind.Text);
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(' ', ',');
track.Type = "a";
track.ID = i + 1;
MediaTracks.Add(track);
}
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(' ', ',');
track.Type = "s";
track.ID = i + 1;
MediaTracks.Add(track);
}
count = mi.GetCount(MediaInfoStreamKind.Text);
count = get_property_int("edition-list/count");
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(' ', ',');
track.Type = "s";
track.ID = i + 1;
MediaTracks.Add(track);
}
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);
}
count = get_property_int("edition-list/count");
void Add(MediaTrack track, string val)
{
if (!string.IsNullOrEmpty(val) && !(track.Text != null && track.Text.Contains(val)))
track.Text += " " + val + ",";
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 + ",";
}
}
}
}

View File

@@ -1,22 +0,0 @@
//public static string GetAssociatedApplication(string ext)
//{
// uint returnValue = 0U;
// // ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE
// if (1 == Native.AssocQueryString(0x40, 2, ext, null, null, ref returnValue))
// {
// if (returnValue > 0)
// {
// StringBuilder sb = new StringBuilder(Convert.ToInt32(returnValue));
// // ASSOCF_VERIFY, ASSOCSTR_EXECUTABLE
// if (0 == Native.AssocQueryString(0x40, 2, ext, null, sb, ref returnValue))
// {
// var ret = sb.ToString();
// if (File.Exists(ret)) return ret;
// }
// }
// }
// return "";
//}
//[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
//public static extern uint AssocQueryString(uint flags, uint str, string pszAssoc, string pszExtra, StringBuilder pszOut, ref uint pcchOut);

View File

@@ -39,6 +39,7 @@ Source: "{#MyAppSourceDir}\*"; DestDir: "{app}"; Flags: ignoreversion recursesub
[Run]
Filename: "{app}\{#MyAppExeName}"; Description: "Associate video file extensions?"; Flags: postinstall unchecked runascurrentuser runhidden nowait; Parameters: "--reg-file-assoc video"
Filename: "{app}\{#MyAppExeName}"; Description: "Associate audio file extensions?"; Flags: postinstall unchecked runascurrentuser runhidden nowait; Parameters: "--reg-file-assoc audio"
Filename: "{app}\{#MyAppExeName}"; Description: "Associate image file extensions?"; Flags: postinstall unchecked runascurrentuser runhidden nowait; Parameters: "--reg-file-assoc image"
[UninstallRun]
Filename: "{app}\{#MyAppExeName}"; Flags: runascurrentuser runhidden; Parameters: "--reg-file-assoc unreg"

View File

@@ -1,51 +0,0 @@
function CheckExitCode {
if ($LastExitCode -gt 0) {
Write-Host "`nExit code $LastExitCode was returned.`n" -ForegroundColor Red
exit
}
}
$msbuild = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\MSBuild\Current\Bin\MSBuild.exe"
& $msbuild mpv.net.sln /p:Configuration=Debug /p:Platform=x64
CheckExitCode
& $msbuild mpv.net.sln /p:Configuration=Debug /p:Platform=x86
CheckExitCode
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /Darch="x64" setup.iss
CheckExitCode
& "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /Darch="x86" setup.iss
CheckExitCode
$scriptDir = Split-Path -Path $PSCommandPath -Parent
$desktopDir = [Environment]::GetFolderPath("Desktop")
$exePath = $scriptDir + "\mpv.net\bin\x64\mpvnet.exe"
$version = [Diagnostics.FileVersionInfo]::GetVersionInfo($exePath).FileVersion
$targetDir = $desktopDir + "\mpv.net-portable-x64-" + $version
Copy-Item $scriptDir\mpv.net\bin\x64 $targetDir -Recurse -Exclude System.Management.Automation.xml -Force
& "C:\Program Files\7-Zip\7z.exe" a -t7z -mx9 "$targetDir.7z" -r "$targetDir\*"
CheckExitCode
& "C:\Program Files\7-Zip\7z.exe" a -tzip -mx9 "$targetDir.zip" -r "$targetDir\*"
CheckExitCode
$exePath = $scriptDir + "\mpv.net\bin\x86\mpvnet.exe"
$version = [Diagnostics.FileVersionInfo]::GetVersionInfo($exePath).FileVersion
$targetDir = $desktopDir + "\mpv.net-portable-x86-" + $version
Copy-Item $scriptDir\mpv.net\bin\x86 $targetDir -Recurse -Exclude System.Management.Automation.xml -Force
& "C:\Program Files\7-Zip\7z.exe" a -t7z -mx9 "$targetDir.7z" -r "$targetDir\*"
CheckExitCode
& "C:\Program Files\7-Zip\7z.exe" a -tzip -mx9 "$targetDir.zip" -r "$targetDir\*"
CheckExitCode