MPV support for linux first imlementation.

This commit is contained in:
var1ap 2016-11-11 14:45:37 +04:00
parent a091491c35
commit 13d870bdd3
9 changed files with 485 additions and 24 deletions

1
.gitignore vendored
View File

@ -32,3 +32,4 @@ SubtitleEdit-*-setup.exe
/src/SubtitleEdit.VC.opendb
/src/SubtitleEdit.VC.VC.opendb
/src/SubtitleEdit.VC.db
/src/SubtitleEdit.userprefs

View File

@ -668,7 +668,7 @@ namespace Nikse.SubtitleEdit.Core
OpenSubtitleExtraExtensions = "*.mp4;*.m4v;*.mkv;*.ts"; // matroska/mp4/m4v files (can contain subtitles)
ListViewColumnsRememberSize = true;
VlcWaveTranscodeSettings = "acodec=s16l"; // "acodec=s16l,channels=1,ab=64,samplerate=8000";
MpvVideoOutput = "direct3d_shaders"; // mpv "vo" option
MpvVideoOutput = "vaapi"; // mpv "vo" option
UseTimeFormatHHMMSSFF = false;
ClearStatusBarAfterSeconds = 10;
MoveVideo100Or500MsPlaySmallSample = false;

View File

@ -2327,7 +2327,10 @@ namespace Nikse.SubtitleEdit.Forms
private void RefreshMpvSettings()
{
radioButtonVideoPlayerMPV.Enabled = LibMpvDynamic.IsInstalled;
if (!Configuration.IsRunningOnLinux())
labelMpvSettings.Text = "--vo=" + Configuration.Settings.General.MpvVideoOutput;
else
labelMpvSettings.Text = "--vo=vaapi";
}
private void linkLabelBingSubscribe_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)

View File

@ -1,5 +1,7 @@
namespace Nikse.SubtitleEdit.Forms
{
using Nikse.SubtitleEdit.Core;
sealed partial class SettingsMpv
{
/// <summary>
@ -38,6 +40,8 @@
//
// buttonDownload
//
if (!Configuration.IsRunningOnLinux())
{
this.buttonDownload.Location = new System.Drawing.Point(12, 24);
this.buttonDownload.Name = "buttonDownload";
this.buttonDownload.Size = new System.Drawing.Size(186, 23);
@ -45,16 +49,28 @@
this.buttonDownload.Text = "Download mpv dll";
this.buttonDownload.UseVisualStyleBackColor = true;
this.buttonDownload.Click += new System.EventHandler(this.buttonDownload_Click_1);
}
//
// comboBoxVideoOutput
//
this.comboBoxVideoOutput.FormattingEnabled = true;
if (Configuration.IsRunningOnLinux())
{
this.comboBoxVideoOutput.Items.AddRange(new object[] {
"sdl",
"vaapi",
"vdpau"});
}
else
{
this.comboBoxVideoOutput.Items.AddRange(new object[] {
"direct3d_shaders",
"direct3d ",
"sdl",
"vaapi",
"vdpau"});
}
this.comboBoxVideoOutput.Location = new System.Drawing.Point(12, 109);
this.comboBoxVideoOutput.Name = "comboBoxVideoOutput";
this.comboBoxVideoOutput.Size = new System.Drawing.Size(186, 21);
@ -113,6 +129,7 @@
this.Controls.Add(this.buttonCancel);
this.Controls.Add(this.label1);
this.Controls.Add(this.comboBoxVideoOutput);
if (!Configuration.IsRunningOnLinux())
this.Controls.Add(this.buttonDownload);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.KeyPreview = true;

View File

@ -13,10 +13,14 @@ namespace Nikse.SubtitleEdit.Forms
{
InitializeComponent();
labelPleaseWait.Text = string.Empty;
if (!Configuration.IsRunningOnLinux())
comboBoxVideoOutput.Text = Configuration.Settings.General.MpvVideoOutput;
else
comboBoxVideoOutput.Text = "vaapi";
buttonCancel.Text = Configuration.Settings.Language.General.Cancel;
buttonOK.Text = Configuration.Settings.Language.General.Ok;
Text = Configuration.Settings.Language.SettingsMpv.Title;
if (!Configuration.IsRunningOnLinux())
buttonDownload.Text = Configuration.Settings.Language.SettingsMpv.DownloadMpv;
}
@ -27,6 +31,7 @@ namespace Nikse.SubtitleEdit.Forms
MessageBox.Show(Configuration.Settings.Language.SettingsMpv.DownloadMpvFailed);
labelPleaseWait.Text = string.Empty;
buttonOK.Enabled = true;
if (!Configuration.IsRunningOnLinux())
buttonDownload.Enabled = true;
Cursor = Cursors.Default;
return;
@ -53,6 +58,7 @@ namespace Nikse.SubtitleEdit.Forms
Cursor = Cursors.Default;
labelPleaseWait.Text = string.Empty;
buttonOK.Enabled = true;
if (!Configuration.IsRunningOnLinux())
buttonDownload.Enabled = true;
MessageBox.Show(Configuration.Settings.Language.SettingsMpv.DownloadMpvOk);
}
@ -63,6 +69,7 @@ namespace Nikse.SubtitleEdit.Forms
{
labelPleaseWait.Text = Configuration.Settings.Language.General.PleaseWait;
buttonOK.Enabled = false;
if (!Configuration.IsRunningOnLinux())
buttonDownload.Enabled = false;
Refresh();
Cursor = Cursors.WaitCursor;
@ -80,6 +87,7 @@ namespace Nikse.SubtitleEdit.Forms
{
labelPleaseWait.Text = string.Empty;
buttonOK.Enabled = true;
if (!Configuration.IsRunningOnLinux())
buttonDownload.Enabled = true;
Cursor = Cursors.Default;
MessageBox.Show(exception.Message + Environment.NewLine + Environment.NewLine + exception.StackTrace);

View File

@ -174,7 +174,7 @@ namespace Nikse.SubtitleEdit.Logic
GeneralSettings gs = Configuration.Settings.General;
if (Configuration.IsRunningOnLinux())
return new MPlayer();
return new LibMpvMono();
// Mono on OS X is 32 bit and thus requires 32 bit VLC. Place VLC in the same
// folder as Subtitle Edit and add this to the app.config inside the

View File

@ -0,0 +1,431 @@
using Nikse.SubtitleEdit.Core;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using Nikse.SubtitleEdit.Core.SubtitleFormats;
namespace Nikse.SubtitleEdit.Logic.VideoPlayers
{
public class LibMpvMono : VideoPlayer, IDisposable
{
#region mpv dll methods
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr mpv_create();
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_initialize(IntPtr mpvHandle);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_command(IntPtr mpvHandle, IntPtr utf8Strings);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_terminate_destroy(IntPtr mpvHandle);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr mpv_wait_event(IntPtr mpvHandle, double wait);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_set_option(IntPtr mpvHandle, byte[] name, int format, ref long data);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_set_option_string(IntPtr mpvHandle, byte[] name, byte[] value);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr mpv_get_property_string(IntPtr mpvHandle, byte[] name);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_get_property(IntPtr mpvHandle, byte[] name, int format, ref double data);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_set_property(IntPtr mpvHandle, byte[] name, int format, ref byte[] data);
[DllImport("mpv", CallingConvention = CallingConvention.Cdecl)]
private static extern int mpv_free(IntPtr data);
#endregion
private IntPtr _libMpvDll;
private IntPtr _mpvHandle;
private Timer _videoLoadedTimer;
// private Timer _videoEndedTimer;
public override event EventHandler OnVideoLoaded;
public override event EventHandler OnVideoEnded;
private const int MpvFormatString = 1;
private object GetDllType(Type type, string name)
{
IntPtr address = NativeMethods.GetProcAddress(_libMpvDll, name);
if (address != IntPtr.Zero)
{
return Marshal.GetDelegateForFunctionPointer(address, type);
}
return null;
}
private static byte[] GetUtf8Bytes(string s)
{
return Encoding.UTF8.GetBytes(s + "\0");
}
public static IntPtr AllocateUtf8IntPtrArrayWithSentinel(string[] arr, out IntPtr[] byteArrayPointers)
{
int numberOfStrings = arr.Length + 1; // add extra element for extra null pointer last (sentinel)
byteArrayPointers = new IntPtr[numberOfStrings];
IntPtr rootPointer = Marshal.AllocCoTaskMem(IntPtr.Size * numberOfStrings);
for (int index = 0; index < arr.Length; index++)
{
var bytes = GetUtf8Bytes(arr[index]);
IntPtr unmanagedPointer = Marshal.AllocHGlobal(bytes.Length);
Marshal.Copy(bytes, 0, unmanagedPointer, bytes.Length);
byteArrayPointers[index] = unmanagedPointer;
}
Marshal.Copy(byteArrayPointers, 0, rootPointer, numberOfStrings);
return rootPointer;
}
private void DoMpvCommand(params string[] args)
{
if (_mpvHandle == IntPtr.Zero)
return;
IntPtr[] byteArrayPointers;
var mainPtr = AllocateUtf8IntPtrArrayWithSentinel(args, out byteArrayPointers);
mpv_command(_mpvHandle, mainPtr);
foreach (var ptr in byteArrayPointers)
{
Marshal.FreeHGlobal(ptr);
}
Marshal.FreeHGlobal(mainPtr);
}
public override string PlayerName
{
get { return "MPV Player"; }
}
private int _volume = 75;
public override int Volume
{
get
{
return _volume;
}
set
{
DoMpvCommand("set", "volume", value.ToString());
_volume = value;
}
}
public override double Duration
{
get
{
if (_mpvHandle == IntPtr.Zero)
return 0;
int mpvFormatDouble = 5;
double d = 0;
mpv_get_property(_mpvHandle, GetUtf8Bytes("duration"), mpvFormatDouble, ref d);
return d;
}
}
public override double CurrentPosition
{
get
{
if (_mpvHandle == IntPtr.Zero)
return 0;
int mpvFormatDouble = 5;
double d = 0;
mpv_get_property(_mpvHandle, GetUtf8Bytes("time-pos"), mpvFormatDouble, ref d);
return d;
}
set
{
if (_mpvHandle == IntPtr.Zero)
return;
DoMpvCommand("seek", value.ToString(CultureInfo.InvariantCulture), "absolute");
}
}
private double _playRate = 2.0;
public override double PlayRate
{
get
{
return _playRate;
}
set
{
DoMpvCommand("set", "speed", value.ToString(CultureInfo.InvariantCulture));
_playRate = value;
}
}
public void GetNextFrame()
{
if (_mpvHandle == IntPtr.Zero)
return;
DoMpvCommand("frame-step");
}
public void GetPreviousFrame()
{
if (_mpvHandle == IntPtr.Zero)
return;
DoMpvCommand("frame-back-step");
}
public override void Play()
{
if (_mpvHandle == IntPtr.Zero)
return;
var bytes = GetUtf8Bytes("no");
mpv_set_property(_mpvHandle, GetUtf8Bytes("pause"), MpvFormatString, ref bytes);
}
public override void Pause()
{
if (_mpvHandle == IntPtr.Zero)
return;
var bytes = GetUtf8Bytes("yes");
mpv_set_property(_mpvHandle, GetUtf8Bytes("pause"), MpvFormatString, ref bytes);
}
public override void Stop()
{
Pause();
CurrentPosition = 0;
}
public override bool IsPaused
{
get
{
if (_mpvHandle == IntPtr.Zero)
return true;
var lpBuffer = mpv_get_property_string(_mpvHandle, GetUtf8Bytes("pause"));
string s = Marshal.PtrToStringAnsi(lpBuffer);
bool isPaused = s == "yes";
mpv_free(lpBuffer);
return isPaused;
}
}
public override bool IsPlaying
{
get { return !IsPaused; }
}
private List<string> _audioTrackIds;
public int AudioTrackCount
{
get
{
if (_audioTrackIds == null)
{
_audioTrackIds = new List<string>();
var lpBuffer = mpv_get_property_string(_mpvHandle, GetUtf8Bytes("track-list"));
string trackListJson = Marshal.PtrToStringAnsi(lpBuffer);
foreach (var json in Json.ReadObjectArray(trackListJson))
{
string trackType = Json.ReadTag(json, "type");
string id = Json.ReadTag(json, "id");
if (trackType == "audio")
{
_audioTrackIds.Add(id);
}
}
mpv_free(lpBuffer);
}
return _audioTrackIds.Count;
}
}
public int AudioTrackNumber
{
get
{
var lpBuffer = mpv_get_property_string(_mpvHandle, GetUtf8Bytes("aid"));
string str = Marshal.PtrToStringAnsi(lpBuffer);
int number = 0;
if (AudioTrackCount > 1 && _audioTrackIds.Contains(str))
{
number = _audioTrackIds.IndexOf(str);
}
mpv_free(lpBuffer);
return number;
}
set
{
string id = "1";
if (AudioTrackCount > 1 && value >= 0 && value < _audioTrackIds.Count)
{
id = _audioTrackIds[value];
}
DoMpvCommand("set", "aid", id);
}
}
public static bool IsInstalled
{
get
{
return true;
}
}
public static string GetMpvPath(string fileName)
{
var path = Path.Combine(Configuration.DataDirectory, fileName);
if (File.Exists(path))
return path;
return null;
}
public override void Initialize(Control ownerControl, string videoFileName, EventHandler onVideoLoaded, EventHandler onVideoEnded)
{
_mpvHandle = mpv_create();
OnVideoLoaded = onVideoLoaded;
OnVideoEnded = onVideoEnded;
if (!string.IsNullOrEmpty(videoFileName))
{
//Mono.Unix.
mpv_initialize(_mpvHandle);
string videoOutput = "vaapi";
if (!string.IsNullOrWhiteSpace(Configuration.Settings.General.MpvVideoOutput))
videoOutput = Configuration.Settings.General.MpvVideoOutput;
mpv_set_option_string(_mpvHandle, GetUtf8Bytes("vo"), GetUtf8Bytes(videoOutput)); // "direct3d_shaders" is default, "direct3d" could be used for compabality with old systems
mpv_set_option_string(_mpvHandle, GetUtf8Bytes("keep-open"), GetUtf8Bytes("always")); // don't auto close video
mpv_set_option_string(_mpvHandle, GetUtf8Bytes("no-sub"), GetUtf8Bytes("")); // don't load subtitles
if (ownerControl != null)
{
int mpvFormatInt64 = 4;
var windowId = ownerControl.Handle.ToInt64();
mpv_set_option(_mpvHandle, GetUtf8Bytes("wid"), mpvFormatInt64, ref windowId);
}
DoMpvCommand("loadfile", videoFileName);
_videoLoadedTimer = new Timer { Interval = 50 };
_videoLoadedTimer.Tick += VideoLoadedTimer_Tick;
_videoLoadedTimer.Start();
}
}
private void VideoLoadedTimer_Tick(object sender, EventArgs e)
{
_videoLoadedTimer.Stop();
const int mpvEventFileLoaded = 8;
int l = 0;
while (l < 10000)
{
Application.DoEvents();
try
{
if (_mpvHandle != IntPtr.Zero)
{
var eventIdHandle = mpv_wait_event(_mpvHandle, 0);
var eventId = Convert.ToInt64(Marshal.PtrToStructure(eventIdHandle, typeof(int)));
if (eventId == mpvEventFileLoaded)
{
break;
}
}
l++;
}
catch
{
return;
}
}
Application.DoEvents();
if (OnVideoLoaded != null)
OnVideoLoaded.Invoke(this, null);
Application.DoEvents();
Pause();
}
public override void DisposeVideoPlayer()
{
Dispose();
}
private void ReleaseUnmangedResources()
{
try
{
lock (this)
{
if (_mpvHandle != IntPtr.Zero)
{
mpv_terminate_destroy(_mpvHandle);
_mpvHandle = IntPtr.Zero;
}
if (_libMpvDll != IntPtr.Zero)
{
NativeMethods.FreeLibrary(_libMpvDll);
_libMpvDll = IntPtr.Zero;
}
}
}
catch
{
// ignored
}
}
~LibMpvMono()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (_mpvHandle != IntPtr.Zero)
DoMpvCommand("quit");
ReleaseUnmangedResources();
}
}
}

View File

@ -378,7 +378,7 @@ namespace Nikse.SubtitleEdit.Logic.VideoPlayers
{
_mpvInitialize.Invoke(_mpvHandle);
string videoOutput = "direct3d_shaders";
string videoOutput = "vaapi";
if (!string.IsNullOrWhiteSpace(Configuration.Settings.General.MpvVideoOutput))
videoOutput = Configuration.Settings.General.MpvVideoOutput;
_mpvSetOptionString(_mpvHandle, GetUtf8Bytes("vo"), GetUtf8Bytes(videoOutput)); // "direct3d_shaders" is default, "direct3d" could be used for compabality with old systems

View File

@ -866,6 +866,7 @@
<Compile Include="Logic\UiGetPacEncoding.cs" />
<Compile Include="Logic\UiGetYouTubeAnnotationStyles.cs" />
<Compile Include="Logic\UiUtil.cs" />
<Compile Include="Logic\VideoPlayers\LibMpvMono.cs" />
<Compile Include="Logic\VideoPlayers\LibMpvDynamic.cs" />
<Compile Include="Logic\VideoPlayers\LibVlcDynamic.cs" />
<Compile Include="Logic\VideoPlayers\LibVlcMono.cs" />