Jump to content

MPV - How-to remote control this media player


Recommended Posts

https://mpv.io/ is a free, open source, and cross-platform media player.

mpv can be controlled by external programs using the JSON-based IPC protocol. It can be enabled by specifying the path to a unix socket or a named pipe using the option --input-ipc-server. Clients can connect to this socket and send commands to the player or receive events from it.

https://mpv.io/manual/stable/#json-ipc

Find attached a proof of concept example on how to control MPV via Pipes by AutoIt.

MediaInfo.dll is included to auto-rotated movies with "Orientation" flag set.

Download MPV.exe from homepage and point to it with the var $s_Global_Path_MPV.

Set $s_Global_Path_MediaFile to a movie file to run the test-script on.

 

Have fun :)

 

MPV-Test_v0011.zip

Link to post
Share on other sites

hi @KaFu, and thanks for sharing this stuff
I'm doing some quick tests and I can't upload a new video while another video is being viewed.
I reduced your script a bit (to be able to manage it
more easily) and I put some buttons on the GUI to do some tests. The first button on the left should load a new video, but I get an error when I try to set the new video (see the lines related to the 'open file' button from 107 to 112)
do you know how to set the json string to upload a new video and send it running? How I tried to use it doesn't work
(reference: https://mpv.io/manual/stable/#command-interface-[%3Coptions%3E]])
Thanks for your help
P.S.
for those wishing to try the script, you also need to extract the mpv.exe file from this archive (https://sourceforge.net/projects/mpv-player-windows/files/32bit/) and save it in the same directory as the script renamed in AMT- mpv.exe

Global $s_Global_Path_MediaFile = FileGetShortName(FileOpenDialog("peek a file", @ScriptDir, "All (*.*)", 3))

; path of the mpv.exe
Global $s_Global_Path_MPV = @ScriptDir & '\AMT-mpv.exe' ; '\mpv.exe'
$s_Global_Path_MPV = '"' & $s_Global_Path_MPV & '"'

#include <NamedPipes.au3>
#include <WinAPIFiles.au3>
#include <WinAPIHObj.au3>
#include <WinAPI.au3>
#include <WinAPIProc.au3>
#include <Date.au3>
#include <GUIConstantsEx.au3>
; Opt("GUIOnEventMode", 1)

Global $s_Global_Pipe_Name = "\\.\pipe\AMT_mpvsocket"
Global $h_Global_MPV_Pipe, $i_Global_MPV_PID

Global $s_Screenshot_Directory = @ScriptDir & "\screen shot\"
DirCreate($s_Screenshot_Directory)

Global $h_DLL_kernel32 = DllOpen("kernel32.dll")

; gui with 16:9 aspect ratio
Global $iWidth = 800
Global $iheight = ($iWidth / 16) * 9
Global $hGUI = GUICreate("", $iWidth, $iheight + 40)

; video placeholder
; dimension and location of this label will be dimension and location of the video on the GUI
Global $c_Display_Label = GUICtrlCreateLabel("", 0, 0, $iWidth, $iheight)

Global $h_Slider = GUICtrlCreateSlider(0, $iheight, $iWidth, 20, 0x0010)
GUICtrlSetLimit(-1, 100, 0) ; change min/max value
Global $iBtnW = $iWidth / 6, $iX = 0
Global $hButton1 = GUICtrlCreateButton("Open file", $iX, $iheight + 20, $iBtnW, 20) ; open file
$iX += $iBtnW
Global $hButton2 = GUICtrlCreateButton("backward 10%", $iX, $iheight + 20, $iBtnW, 20) ; backward 10%
$iX += $iBtnW
Global $hButton3 = GUICtrlCreateButton("play/pause", $iX, $iheight + 20, $iBtnW, 20) ; toggle play/pause
$iX += $iBtnW
Global $hButton4 = GUICtrlCreateButton("forward 10%", $iX, $iheight + 20, $iBtnW, 20) ; forward 10%
$iX += $iBtnW
Global $hButton5 = GUICtrlCreateButton("snapshot", $iX, $iheight + 20, $iBtnW, 20) ; take a snapshot
$iX += $iBtnW
Global $hButton6 = GUICtrlCreateButton("audio on/off", $iX, $iheight + 20, $iBtnW, 20) ; toggle audio on/off
GUISetState()

; Global $h_DLL_MediaInfo = DllOpen(@ScriptDir & "\z_MediaInfo.dll")
Global $s_Global_MPV_Args = " --vo=direct3d, "
$s_Global_MPV_Args &= " --vf lavfi=[scale=w=320:h=-1] " ; to prescale output > huge performance improvment for HD+ files
$s_Global_MPV_Args &= " --vf-add=lavfi=[eq=1:0:1:1,hue=h=0] "

; https://mpv.io/manual/stable/#video
$s_Global_MPV_Args &= " --cache=yes "

$s_Global_MPV_Args &= " --screenshot-format=png "
$s_Global_MPV_Args &= " --no-keepaspect --no-config --no-quiet --terminal --no-msg-color --msg-level=ffmpeg/demuxer=error --no-fs "
$s_Global_MPV_Args &= " --vd-lavc-threads=4 "

; Specify the hardware video decoding API that should be used if possible.
$s_Global_MPV_Args &= " --hwdec=auto "

$s_Global_MPV_Args &= " --priority=abovenormal --no-input-default-bindings --input-vo-keyboard=no --no-input-cursor --cursor-autohide=no --wid=" & Number(GUICtrlGetHandle($c_Display_Label)) & " --monitorpixelaspect=1 "
$s_Global_MPV_Args &= " --osd-level=3 --osd-scale=1 --osd-bar-align-y=0.6 "

$s_Global_MPV_Args &= " --sub-auto=fuzzy "
$s_Global_MPV_Args &= " --sub-ass --embeddedfonts --sub-ass-line-spacing=0 --sub-scale=1 --sub-font=""Bodoni MT Poster Compressed"" --sub-color=#ffffffff --sub-shadow-color=#ff000000 --sub-border-color=#ff000000 --sub-border-size=0.75 --sub-shadow-offset=2.5 "
$s_Global_MPV_Args &= " --sub-font-size=35 --sub-bold=no --sub-italic=no "
$s_Global_MPV_Args &= " --sub-ass-force-style=PlayResX=512,PlayResY=320,Name=Default,Fontname=""Bodoni MT Poster Compressed"",Fontsize=14,PrimaryColour=#00ffffff,BackColour=#00000000,OutlineColour=#00000000,Bold=0,Italic=0,Alignment=2,BorderStyle=1,Outline=0.3,Shadow=1,MarginL=20,MarginR=20,MarginV=8 "
$s_Run_Commandline = $s_Global_Path_MPV & " " & $s_Global_MPV_Args & " " & $s_Global_Path_MediaFile & " --input-ipc-server=" & $s_Global_Pipe_Name

_MPV_CloseProcesses()

Global $i_Global_MPV_PID = Run($s_Run_Commandline, $s_Screenshot_Directory, @SW_SHOW, $STDERR_MERGED + $STDIN_CHILD)

OnAutoItExitRegister("_OnAutoItExitRegister")

Func _OnAutoItExitRegister()
    ; DllClose($h_DLL_MediaInfo)
    DllClose($h_DLL_kernel32)
    _MPV_CloseProcesses()
EndFunc   ;==>_OnAutoItExitRegister

$h_Global_MPV_Pipe = _MPV_GetHandle()
$sRes = _MPV_Wait_for_Event('{"event":"playback-restart"}') ; Movie started

; https://mpv.io/manual/stable/#property-list
ConsoleWrite(_MPV_Command_Send_and_Wait_for_Success('["set_property","mute","no"]') & @CRLF)
ConsoleWrite(_MPV_Command_Send_and_Wait_for_Success('["set_property","volume",50]') & @CRLF)

; ---------
; main loop
; ---------
; Loop until the user exits.
Global $iDelay = 3000, $iTimer = TimerInit(), $idMsg, $vResult
Do
    $idMsg = GUIGetMsg()
    Switch $idMsg
        Case $h_Slider
            ConsoleWrite(GUICtrlRead($h_Slider) & @CRLF)
            $sCommand = '{"command":["seek","' & GUICtrlRead($h_Slider) & '","absolute-percent+keyframes"]}' & @LF
            $sRes = _MPV_Command_Send_Request($sCommand)
            _MPV_Command_Read_Response() ; flush pipe

        Case $hButton1 ; file open
            $s_Global_Path_MediaFile = FileGetShortName(FileOpenDialog("load a file", @ScriptDir, "All (*.*)", 3))
            ; $s_Global_Path_MediaFile = StringMid($s_Global_Path_MediaFile, StringInStr($s_Global_Path_MediaFile, '\', 0, -1) + 1)
            $sCommand = '{"command":["loadfile", "' & $s_Global_Path_MediaFile & '", "replace"]}' & @LF
            $sRes = _MPV_Command_Send_Request($sCommand)
            MsgBox(262144, "Debug", $sCommand & @CRLF & _MPV_Wait_for_Event(''))

        Case $hButton2 ; step back
            $sCommand = '{"command":["seek","-10","relative-percent+keyframes"]}' & @LF
            $sRes = _MPV_Command_Send_Request($sCommand)
            _MPV_Wait_for_Event('') ; flush pipe
            _SetSlider()

        Case $hButton3 ; play/pause
            $sCommand = '{"command":["cycle","pause"]}' & @LF
            $sRes = _MPV_Command_Send_Request($sCommand)
            _MPV_Wait_for_Event('') ; flush pipe

        Case $hButton4 ; step forward
            $sCommand = '{"command":["seek","10","relative-percent+keyframes"]}' & @LF
            $sRes = _MPV_Command_Send_Request($sCommand)
            _MPV_Wait_for_Event('') ; flush pipe
            _SetSlider()

        Case $hButton5 ; take snapshot
            $s_File_Screenshot = 'screenshot_' & @YEAR & @MON & @MDAY & @HOUR & @MIN & @SEC & @MSEC & '.png'
            $sCommand = '{"command":["screenshot-to-file","' & $s_File_Screenshot & '","video"]}' & @LF
            $sRes = _MPV_Command_Send_Request($sCommand)
            _MPV_Wait_for_Event('') ; flush pipe

        Case $hButton6 ; audio on/off
            $sCommand = '{"command":["cycle","mute"]}' & @LF
            $sRes = _MPV_Command_Send_Request($sCommand)
            _MPV_Wait_for_Event('') ; flush pipe

        Case Else ; do noting
    EndSwitch

    If TimerDiff($iTimer) > $iDelay Then ; each "delay" period update the slider
        _SetSlider()
    EndIf

Until $idMsg = $GUI_EVENT_CLOSE

Func _SetSlider()
    $vResult = _MPV_Command_Send_and_Wait_for_Success('["get_property","percent-pos"]')
    $vResult = StringTrimLeft($vResult, StringInStr($vResult, ":"))
    $vResult = Int(Number(StringLeft($vResult, StringInStr($vResult, ",") - 1)))
    ConsoleWrite(" --> " & $vResult & @CRLF)
    GUICtrlSetData($h_Slider, $vResult) ; set cursor
    $iTimer = TimerInit()
EndFunc   ;==>_SetSlider

Exit

_MPV_CloseProcesses()

Func _MPV_Command_Send_and_Wait_for_Success($sCommand, $iTimeout = 1000)
    Local $i_Request_ID = TimerInit()
    Local $sCommand_Send = '{"command":' & $sCommand & ',"request_id": ' & $i_Request_ID & '}' & @LF
    Local $sCommand_Success_String = '"request_id":' & $i_Request_ID & ',"error":"success"}'
    Local $sRes = _MPV_Command_Send_Request($sCommand_Send)
    If Not @error Then
        $sRes = _MPV_Wait_for_Event($sCommand_Success_String, $iTimeout)
        If Not @error Then Return $sRes
    EndIf
    ConsoleWrite("Debug: _MPV_Command_Send_and_Wait_for_Success error:" & @error & @CRLF)
    Return SetError(@error, @extended, $sRes)
EndFunc   ;==>_MPV_Command_Send_and_Wait_for_Success


Func _MPV_Wait_for_Event($sEvent, $iTimeout = 1000)
    Local $iTimer_StartUp = TimerInit(), $sRes
    While Sleep(10)
        $sRes &= _MPV_Command_Read_Response($iTimeout)
        If @error Then Return SetError(@error, @extended, $sRes)
        If StringInStr($sRes, $sEvent) Then Return $sRes
        If TimerDiff($iTimer_StartUp) > 10000 Then Return SetError(1, "", "Playback not started-" & @ScriptLineNumber)
    WEnd
EndFunc   ;==>_MPV_Wait_for_Event

Func _MPV_Command_Send_Request($sCommand)
    ; https://mpv.io/manual/stable/#commands
    ; https://mpv.io/manual/stable/#list-of-input-commands

    If Not IsHWnd($h_Global_MPV_Pipe) Then Return SetError(1, "", "Invalid pipe handle")

    Local $tBuffer = DllStructCreate("char[" & StringLen($sCommand) & "]")
    DllStructSetData($tBuffer, 1, $sCommand)

    Local $nBytes = 0
    Local $iRes = _WinAPI_WriteFile($h_Global_MPV_Pipe, $tBuffer, DllStructGetSize($tBuffer), $nBytes)
    If $iRes = False Then Return SetError(2, _WinAPI_GetLastError(), _WinAPI_GetLastErrorMessage())
    Return $iRes

EndFunc   ;==>_MPV_Command_Send_Request

Func _MPV_Command_Read_Response($iTimeout = 1000)
    ; https://mpv.io/manual/stable/#property-list

    If Not IsHWnd($h_Global_MPV_Pipe) Then Return SetError(1, "", "Invalid pipe handle")
    Local $tBuffer = DllStructCreate("char[4096]")
    Local $nBytes = 0, $iRes, $iTimer = TimerInit(), $aRes

    While 1

        $iRes = _WinAPI_ReadFile($h_Global_MPV_Pipe, $tBuffer, DllStructGetSize($tBuffer), $nBytes)
        If $iRes = True Or _WinAPI_GetLastError() <> 232 Then ExitLoop ; 232 = NO_DATA
        If TimerDiff($iTimer) > 1000 Then ExitLoop
        Sleep(10)
    WEnd

    If $iRes = False Then Return SetError(3, _WinAPI_GetLastError(), _WinAPI_GetLastErrorMessage())
    Local $sRes = StringStripWS(_UTF8toUCS2(DllStructGetData($tBuffer, 1)), 3)

    Return $sRes
EndFunc   ;==>_MPV_Command_Read_Response

Func _MPV_CloseProcesses()
    If $h_Global_MPV_Pipe Then
        If IsHWnd($h_Global_MPV_Pipe) Then
            _WinAPI_CloseHandle($h_Global_MPV_Pipe)
        EndIf
        $h_Global_MPV_Pipe = 0
    EndIf
    Local $s_MPV_ProcessName = StringRight($s_Global_Path_MPV, StringLen($s_Global_Path_MPV) - StringInStr($s_Global_Path_MPV, "\", 0, -1))
    If ProcessExists($s_MPV_ProcessName) Then
        Local $iTimer = TimerInit()
        While ProcessExists($s_MPV_ProcessName)
            ProcessClose($s_MPV_ProcessName)
            If TimerDiff($iTimer) > 3000 Then Return SetError(1, "", "Timeout")
        WEnd
    EndIf
    StdioClose($i_Global_MPV_PID)
EndFunc   ;==>_MPV_CloseProcesses

Func _MPV_GetHandle()
    Local $iTimer = TimerInit()
    While 1
        ; Local $hPipe =  (_WinAPI_CreateFile($s_Global_Pipe_Name, 2, 6, 0)) ;_NamedPipes_CreateNamedPipe($s_Global_Pipe_Name);, 2,2,7) ;
        Local $hPipe = _WinAPI_CreateFile($s_Global_Pipe_Name, 2, 6, 0)
        If $hPipe = 0 Then
            If _WinAPI_GetLastError() <> 2 Then Return SetError(1, _WinAPI_GetLastError(), _WinAPI_GetLastErrorMessage())
        ElseIf IsHWnd($hPipe) Then
            _NamedPipes_SetNamedPipeHandleState($hPipe, 0, 1, 4096, 100)
            Return $hPipe
        EndIf
        If TimerDiff($iTimer) > 4000 Then
            ConsoleWrite("Debug: creat pipe Timeout" & @CRLF)
            Return SetError(2, "", "Timeout")
        EndIf
        Sleep(10)
    WEnd
EndFunc   ;==>_MPV_GetHandle

Func _UCS2toUTF8($sString = "", $iFlag = 1)
    ; Convert UTF8 to ANSI to insert into DB
    ; http://www.autoitscript.com/forum/index.php?showtopic=85496&view=findpost&p=614497
    ; ProgAndy
    ; Make ANSI-string representation out of UTF-8
    ; $iFlag
    #cs
        Local Const $SF_ANSI = 1
        Local Const $SF_UTF16_LE = 2
        Local Const $SF_UTF16_BE = 3
        Local Const $SF_UTF8 = 4
    #ce
    Return BinaryToString(StringToBinary($sString, 4), $iFlag)
EndFunc   ;==>_UCS2toUTF8

Func _UTF8toUCS2($sString = "", $iFlag = 4)
    ; Extract ANSI and convert to UTF8 to display
    ; http://www.autoitscript.com/forum/index.php?showtopic=85496&view=findpost&p=614497
    ; ProgAndy
    ; convert ANSI-UTF8 representation to ANSI/Unicode
    #cs
        Local Const $SF_ANSI = 1
        Local Const $SF_UTF16_LE = 2
        Local Const $SF_UTF16_BE = 3
        Local Const $SF_UTF8 = 4
    #ce
    Return BinaryToString(StringToBinary($sString, 1), $iFlag)
EndFunc   ;==>_UTF8toUCS2

 

small minds discuss people average minds discuss events great minds discuss ideas.... and use AutoIt....

Link to post
Share on other sites

URL is meant literally for that command:

$sCommand = '{"command":["loadfile", "file:///' & StringReplace($s_Global_Path_MediaFile, "\", "/") & '","replace"]}' & @LF

 

Link to post
Share on other sites
  • 1 year later...

how can I write a variable to stdout?

from the mpv manual:

print-text <text> Print text to stdout. The string can contain properties (see Property Expansion). Take care to put the argument in quotes.  

 

I tried 

$s = '{"command":["print-text","${=chapter-list/count}"]}'
    _MPV_Command_Send_Request($s & @LF)

 

but i don't get the number of chapters only

INFO_CHAPTERS_COUNT=${=chapter-list/count}

 

I have a little player attached

Add mpv.exe and mpv.com to the mpv folder

 

BasicMpvPlayer.zip

Edited by bladem2003
Link to post
Share on other sites
; https://github.com/mpv-player/mpv/issues/4026
$s = '{"command":["expand-properties","print-text","INFO_CHAPTERS_COUNT=${chapter-list/count}"]}'

 

Link to post
Share on other sites
  • 4 months later...

Hi,

I sometimes get an error from _MPV_GetHandle () "The system cannot find the file specified" and there is no connection to the pipe.

The error is not reproducible, it runs 100 times without errors and then the error 5-6 times in a row.

Link to post
Share on other sites
Posted (edited)

Hmm, doesn't ring a bell, did not happen to me as far as I can recall. Does mpv open up in that case?

In your script above you use

$s_Global_Pipe_Name = '\\.\pipe\mpv_' & $iPipeCount

where $iPipeCount starts with 1. Is it possible that you restart the script and have a hanging pipe handle still open?

In my scripts I use this to create a unique pipe name

Global $s_Global_Pipe_Name = "\\.\pipe\MPVEnhancer" & TimerInit()

 

Also I tweak the _MPV_GetHandle() code a little in my current player script (can't remember why 😉), maybe it helps too.

Func _MPV_GetHandle($iTimeout = 5000)
    Local $iTimer = TimerInit()
    While 1
        Local $hPipe = _WinAPI_CreateFile($s_Global_Pipe_Name, 2, 6, 0)
        If $hPipe = 0 And _WinAPI_GetLastError() > 0 Then
            If _WinAPI_GetLastError() <> 2 Then
                _ConsoleWrite("GetHandle-Error-1" & @CRLF)
                Return SetError(1, _WinAPI_GetLastError(), _WinAPI_GetLastErrorMessage())
            EndIf
        ElseIf IsHWnd($hPipe) Then
            Return $hPipe
        EndIf
        If TimerDiff($iTimer) > $iTimeout Then
            _ConsoleWrite("GetHandle-Error-2" & @CRLF)
            Return SetError(2, "", "_MPV_GetHandle-Timeout")
        EndIf
        Sleep(10)
    WEnd
EndFunc   ;==>_MPV_GetHandle

 

 

Edited by KaFu
Link to post
Share on other sites

Hi KaFu,

I changed the _MPV_GetHandle () with yours and change to $s_Global_Pipe_Name = "\\.\pipe\MPVEnhancer" & TimerInit()

I get sometimes a pipe timeout (set to 10000) and sometimes error 231 All pipe instaces are busy.

this happens when the pc is restarted and when it is called up for the first time

very strange

Edited by bladem2003
Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...