mpv-ipc 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +140 -0
- data/lib/mpv_ass/color.rb +111 -0
- data/lib/mpv_ass/span.rb +121 -0
- data/lib/mpv_ass/text.rb +91 -0
- data/lib/mpv_ass.rb +3 -0
- data/lib/mpv_ipc/client.rb +529 -0
- data/lib/mpv_ipc/exceptions.rb +55 -0
- data/lib/mpv_ipc/multi_queue.rb +76 -0
- data/lib/mpv_ipc/server.rb +122 -0
- data/lib/mpv_ipc/session.rb +170 -0
- data/lib/mpv_ipc/utils.rb +19 -0
- data/lib/mpv_ipc/version.rb +6 -0
- data/lib/mpv_ipc.rb +4 -0
- metadata +81 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require "concurrent"
|
|
5
|
+
|
|
6
|
+
require_relative "utils"
|
|
7
|
+
require_relative "exceptions"
|
|
8
|
+
|
|
9
|
+
module MPV
|
|
10
|
+
# Represents an active `mpv` process started with an IPC socket.
|
|
11
|
+
# @note Resources are released by calling `#wait` after termination.
|
|
12
|
+
# At exit, all running `mpv` processes are automatically stopped.
|
|
13
|
+
class Server
|
|
14
|
+
# @return [Array<String>] command-line options used when spawning `mpv`
|
|
15
|
+
attr_reader :options
|
|
16
|
+
|
|
17
|
+
# @return [String] the path to the currently used `mpv`
|
|
18
|
+
attr_reader :mpv_path
|
|
19
|
+
|
|
20
|
+
# @return [String] the path to the IPC socket used by this `mpv` process
|
|
21
|
+
attr_reader :ipc_path
|
|
22
|
+
|
|
23
|
+
@@existing_servers = Concurrent::Array.new
|
|
24
|
+
at_exit do
|
|
25
|
+
existing_servers = @@existing_servers.to_a
|
|
26
|
+
existing_servers.each{ |server| server.kill! }
|
|
27
|
+
existing_servers.each{ |server| server.wait }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Creates a new MPV::Server instance and starts the `mpv` process.
|
|
31
|
+
# @param bind [String] the path of the IPC socket to be used
|
|
32
|
+
# for the communication (defaults to a temporary path in `/tmp`)
|
|
33
|
+
# @param mpv [String] the path of the `mpv` executable
|
|
34
|
+
# (defaults to searching in system `$PATH`)
|
|
35
|
+
# @param options [Array<String>] additional initial options to pass
|
|
36
|
+
# to the `mpv` process
|
|
37
|
+
# @param chdir [String] custom working directory for `mpv`
|
|
38
|
+
# @raise [MPV::StartupError] if the `mpv` process fails to start
|
|
39
|
+
# @raise [MPV::NotAvailableError] if no `mpv` executable is available
|
|
40
|
+
# @raise [MPV::UnsupportedOptionError] if a given `mpv` option is not supported
|
|
41
|
+
def initialize(bind: nil, mpv: nil, options: [], chdir: nil)
|
|
42
|
+
@mpv_path = mpv || Utils.which("mpv")
|
|
43
|
+
raise NotAvailableError unless @mpv_path
|
|
44
|
+
|
|
45
|
+
@ipc_path = bind || File.join("/tmp", Utils.tmpsock)
|
|
46
|
+
File.delete(@ipc_path) if File.exist?(@ipc_path)
|
|
47
|
+
|
|
48
|
+
@options = options.map{ |option| option.sub(/\A-{1,2}/, "") }
|
|
49
|
+
@options << "terminal=no" << "input-ipc-server=#{@ipc_path}" << "idle=yes"
|
|
50
|
+
|
|
51
|
+
stdout, status = Open3.capture2(@mpv_path, "--list-options") rescue nil
|
|
52
|
+
raise NotAvailableError unless status&.success?
|
|
53
|
+
available_options = stdout.scan(/^\s*--(\S+)/).map(&:first)
|
|
54
|
+
@options.each{ |option| ensure_option!(option, available_options) }
|
|
55
|
+
|
|
56
|
+
args = @options.map{ |option| "--#{option}" }
|
|
57
|
+
params = {pgroup: true}.merge(({chdir: chdir} if chdir).to_h)
|
|
58
|
+
@pid = Process.spawn(@mpv_path, *args, **params)
|
|
59
|
+
|
|
60
|
+
timeout = Process.clock_gettime(Process::CLOCK_MONOTONIC) + STARTUP_TIMEOUT
|
|
61
|
+
until File.exist?(@ipc_path)
|
|
62
|
+
if status = Process.wait2(@pid, Process::WNOHANG)&.last
|
|
63
|
+
raise StartupError, "unexpected termination with " \
|
|
64
|
+
"#{status.to_s.delete_prefix("pid #{status.pid} ")}"
|
|
65
|
+
end
|
|
66
|
+
if Process.clock_gettime(Process::CLOCK_MONOTONIC) > timeout
|
|
67
|
+
Process.kill(:KILL, -@pid)
|
|
68
|
+
Process.wait(@pid)
|
|
69
|
+
raise StartupError, "timed out"
|
|
70
|
+
end
|
|
71
|
+
sleep 0.02
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
@@existing_servers << self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Checks whether the `mpv` process is still alive.
|
|
78
|
+
# @return [Boolean] true if the `mpv` process is alive
|
|
79
|
+
def alive?
|
|
80
|
+
@pid = nil if wait_pid(@pid, false)
|
|
81
|
+
!!@pid
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Blocks until the `mpv` process is terminated, then releases all resources.
|
|
85
|
+
# @return [void]
|
|
86
|
+
def wait
|
|
87
|
+
wait_pid(@pid, true)
|
|
88
|
+
File.delete(@ipc_path) rescue nil if @ipc_path
|
|
89
|
+
@pid = @ipc_path = nil
|
|
90
|
+
@@existing_servers.delete(self)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Sends a signal to the `mpv` process.
|
|
94
|
+
# @param signal [Object] the name (String/Symbol) or code (Integer)
|
|
95
|
+
# of the signal to send
|
|
96
|
+
# @return [Boolean] true if the signal was sent successfully
|
|
97
|
+
def kill!(signal=:TERM)
|
|
98
|
+
pid = @pid
|
|
99
|
+
Process.kill(signal, -pid) if pid
|
|
100
|
+
!!pid
|
|
101
|
+
rescue Errno::ESRCH
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
STARTUP_TIMEOUT = 6 # sec
|
|
107
|
+
|
|
108
|
+
def ensure_option!(option, available_options)
|
|
109
|
+
# Options can be prefixed with `no-` (equivalent to a `=no` suffix),
|
|
110
|
+
# but these variations aren’t included in the list of supported options,
|
|
111
|
+
# so these need to be removed before checking.
|
|
112
|
+
opt = option.sub(/\A(?:no-)?(.*?)(?:=.*)?\z/, "\\1")
|
|
113
|
+
raise UnsupportedOptionError, opt unless available_options.include?(opt)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def wait_pid(pid, blocking)
|
|
117
|
+
!pid || !!Process.wait(pid, blocking ? 0 : Process::WNOHANG)
|
|
118
|
+
rescue Errno::ECHILD
|
|
119
|
+
true
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
require_relative "client"
|
|
6
|
+
require_relative "server"
|
|
7
|
+
|
|
8
|
+
module MPV
|
|
9
|
+
# Combines an MPV::Server and MPV::Client, connecting them to each other.
|
|
10
|
+
# @note Automatically releases resources after termination.
|
|
11
|
+
class Session
|
|
12
|
+
extend Forwardable
|
|
13
|
+
|
|
14
|
+
# @return [MPV::Server] the server object responsible for the mpv process
|
|
15
|
+
attr_reader :server
|
|
16
|
+
|
|
17
|
+
# @return [MPV::Client] the client communicating with mpv
|
|
18
|
+
attr_reader :client
|
|
19
|
+
|
|
20
|
+
# Creates a new MPV::Session instance containing an MPV::Server and an
|
|
21
|
+
# MPV::Client that are connected to each other.
|
|
22
|
+
# All parameters are passed to MPV::Server.
|
|
23
|
+
# @see MPV::Server::new
|
|
24
|
+
def initialize(**params)
|
|
25
|
+
@server = Server.new(**params)
|
|
26
|
+
@client = Client.new(@server.ipc_path)
|
|
27
|
+
|
|
28
|
+
@shutdown_callback = nil
|
|
29
|
+
@client.register_shutdown_callback do |eof|
|
|
30
|
+
@shutdown_callback.call(eof) if @shutdown_callback
|
|
31
|
+
ensure
|
|
32
|
+
@server.wait if Thread.main.alive?
|
|
33
|
+
end
|
|
34
|
+
rescue
|
|
35
|
+
if @server
|
|
36
|
+
@server.kill!
|
|
37
|
+
@server.wait
|
|
38
|
+
end
|
|
39
|
+
raise
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Registers a shutdown callback for notification or cleanup.
|
|
43
|
+
# @yield [Boolean] called with a boolean indicating whether
|
|
44
|
+
# the server closed the connection properly
|
|
45
|
+
# @return [void]
|
|
46
|
+
def register_shutdown_callback(&block)
|
|
47
|
+
@shutdown_callback = block
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Unregisters the current shutdown callback.
|
|
51
|
+
# @return [void]
|
|
52
|
+
def unregister_shutdown_callback
|
|
53
|
+
@shutdown_callback = nil
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Terminates the mpv process and waits for it.
|
|
57
|
+
# It releases all the resources.
|
|
58
|
+
# @return [Boolean] false if it was already terminated
|
|
59
|
+
def quit!
|
|
60
|
+
result = @client.quit! || @server.kill!
|
|
61
|
+
@server.wait
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @!method alive?
|
|
66
|
+
# @return (see MPV::Client#alive?)
|
|
67
|
+
# @see MPV::Client#alive?
|
|
68
|
+
def_delegators :@client, :alive?
|
|
69
|
+
|
|
70
|
+
# @!method wait
|
|
71
|
+
# @return (see MPV::Client#wait)
|
|
72
|
+
# @see MPV::Client#wait
|
|
73
|
+
def_delegators :@client, :wait
|
|
74
|
+
|
|
75
|
+
# @!method command
|
|
76
|
+
# @return (see MPV::Client#command)
|
|
77
|
+
# @see MPV::Client#command
|
|
78
|
+
def_delegators :@client, :command
|
|
79
|
+
|
|
80
|
+
# @!method command!
|
|
81
|
+
# @return (see MPV::Client#command!)
|
|
82
|
+
# @see MPV::Client#command!
|
|
83
|
+
def_delegators :@client, :command!
|
|
84
|
+
|
|
85
|
+
# @!method get_property
|
|
86
|
+
# @return (see MPV::Client#get_property)
|
|
87
|
+
# @see MPV::Client#get_property
|
|
88
|
+
def_delegators :@client, :get_property
|
|
89
|
+
|
|
90
|
+
# @!method get_property!
|
|
91
|
+
# @return (see MPV::Client#get_property!)
|
|
92
|
+
# @see MPV::Client#get_property!
|
|
93
|
+
def_delegators :@client, :get_property!
|
|
94
|
+
|
|
95
|
+
# @!method set_property
|
|
96
|
+
# @return (see MPV::Client#set_property)
|
|
97
|
+
# @see MPV::Client#set_property
|
|
98
|
+
def_delegators :@client, :set_property
|
|
99
|
+
|
|
100
|
+
# @!method set_property!
|
|
101
|
+
# @return (see MPV::Client#set_property!)
|
|
102
|
+
# @see MPV::Client#set_property!
|
|
103
|
+
def_delegators :@client, :set_property!
|
|
104
|
+
|
|
105
|
+
# @!method observe_property
|
|
106
|
+
# @return (see MPV::Client#observe_property)
|
|
107
|
+
# @see MPV::Client#observe_property
|
|
108
|
+
def_delegators :@client, :observe_property
|
|
109
|
+
|
|
110
|
+
# @!method unobserve_property
|
|
111
|
+
# @return (see MPV::Client#unobserve_property)
|
|
112
|
+
# @see MPV::Client#unobserve_property
|
|
113
|
+
def_delegators :@client, :unobserve_property
|
|
114
|
+
|
|
115
|
+
# @!method register_event_listener
|
|
116
|
+
# @return (see MPV::Client#register_event_listener)
|
|
117
|
+
# @see MPV::Client#register_event_listener
|
|
118
|
+
def_delegators :@client, :register_event_listener
|
|
119
|
+
|
|
120
|
+
# @!method unregister_event_listener
|
|
121
|
+
# @return (see MPV::Client#unregister_event_listener)
|
|
122
|
+
# @see MPV::Client#unregister_event_listener
|
|
123
|
+
def_delegators :@client, :unregister_event_listener
|
|
124
|
+
|
|
125
|
+
# @!method register_message_handler
|
|
126
|
+
# @return (see MPV::Client#register_message_handler)
|
|
127
|
+
# @see MPV::Client#register_message_handler
|
|
128
|
+
def_delegators :@client, :register_message_handler
|
|
129
|
+
|
|
130
|
+
# @!method unregister_message_handler
|
|
131
|
+
# @return (see MPV::Client#unregister_message_handler)
|
|
132
|
+
# @see MPV::Client#unregister_message_handler
|
|
133
|
+
def_delegators :@client, :unregister_message_handler
|
|
134
|
+
|
|
135
|
+
# @!method register_keybindings
|
|
136
|
+
# @return (see MPV::Client#register_keybindings)
|
|
137
|
+
# @see MPV::Client#register_keybindings
|
|
138
|
+
def_delegators :@client, :register_keybindings
|
|
139
|
+
|
|
140
|
+
# @!method unregister_keybindings
|
|
141
|
+
# @return (see MPV::Client#unregister_keybindings)
|
|
142
|
+
# @see MPV::Client#unregister_keybindings
|
|
143
|
+
def_delegators :@client, :unregister_keybindings
|
|
144
|
+
|
|
145
|
+
# @!method create_osd_message
|
|
146
|
+
# @return (see MPV::Client#create_osd_message)
|
|
147
|
+
# @see MPV::Client#create_osd_message
|
|
148
|
+
def_delegators :@client, :create_osd_message
|
|
149
|
+
|
|
150
|
+
# @!method edit_osd_message
|
|
151
|
+
# @return (see MPV::Client#edit_osd_message)
|
|
152
|
+
# @see MPV::Client#edit_osd_message
|
|
153
|
+
def_delegators :@client, :edit_osd_message
|
|
154
|
+
|
|
155
|
+
# @!method delete_osd_message
|
|
156
|
+
# @return (see MPV::Client#delete_osd_message)
|
|
157
|
+
# @see MPV::Client#delete_osd_message
|
|
158
|
+
def_delegators :@client, :delete_osd_message
|
|
159
|
+
|
|
160
|
+
# @!method clear_osd_messages
|
|
161
|
+
# @return (see MPV::Client#clear_osd_messages)
|
|
162
|
+
# @see MPV::Client#clear_osd_messages
|
|
163
|
+
def_delegators :@client, :clear_osd_messages
|
|
164
|
+
|
|
165
|
+
# @!method enter_modal_mode
|
|
166
|
+
# @return (see MPV::Client#enter_modal_mode)
|
|
167
|
+
# @see MPV::Client#enter_modal_mode
|
|
168
|
+
def_delegators :@client, :enter_modal_mode
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MPV
|
|
4
|
+
# Various utility methods for mpv-ipc.
|
|
5
|
+
module Utils
|
|
6
|
+
# Finds where the given utility is located in the system paths.
|
|
7
|
+
# @param name [String] name of the utility to find
|
|
8
|
+
# @return [String] full path of the utility
|
|
9
|
+
# @api private
|
|
10
|
+
def self.which(name)
|
|
11
|
+
paths = ENV["PATH"].split(File::PATH_SEPARATOR).map{ |dir| File.join(dir, name) }
|
|
12
|
+
paths.find{ |path| File.file?(path) && File.executable?(path) }
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.tmpsock
|
|
16
|
+
"mpv-ipc-#{$$}-#{Time.now.strftime("%Y%m%d%N")}-#{rand(36**8).to_s(36)}.sock"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/mpv_ipc.rb
ADDED
metadata
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mpv-ipc
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 5.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Péter Bakonyi
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-05-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: concurrent-ruby
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.1'
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 1.1.6
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - "~>"
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.1'
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 1.1.6
|
|
33
|
+
description: |-
|
|
34
|
+
A Ruby interface to the mpv media player via its JSON IPC protocol.
|
|
35
|
+
It can start and manage mpv instances, connect to and control existing players, or be used to write Ruby scripts that run embedded within mpv.
|
|
36
|
+
email: bakonyi.peter@gmail.com
|
|
37
|
+
executables: []
|
|
38
|
+
extensions: []
|
|
39
|
+
extra_rdoc_files: []
|
|
40
|
+
files:
|
|
41
|
+
- LICENSE
|
|
42
|
+
- README.md
|
|
43
|
+
- lib/mpv_ass.rb
|
|
44
|
+
- lib/mpv_ass/color.rb
|
|
45
|
+
- lib/mpv_ass/span.rb
|
|
46
|
+
- lib/mpv_ass/text.rb
|
|
47
|
+
- lib/mpv_ipc.rb
|
|
48
|
+
- lib/mpv_ipc/client.rb
|
|
49
|
+
- lib/mpv_ipc/exceptions.rb
|
|
50
|
+
- lib/mpv_ipc/multi_queue.rb
|
|
51
|
+
- lib/mpv_ipc/server.rb
|
|
52
|
+
- lib/mpv_ipc/session.rb
|
|
53
|
+
- lib/mpv_ipc/utils.rb
|
|
54
|
+
- lib/mpv_ipc/version.rb
|
|
55
|
+
homepage: https://github.com/BPeti/mpv-ipc
|
|
56
|
+
licenses:
|
|
57
|
+
- MIT
|
|
58
|
+
metadata:
|
|
59
|
+
source_code_uri: https://github.com/BPeti/mpv-ipc
|
|
60
|
+
documentation_uri: https://www.rubydoc.info/gems/mpv-ipc
|
|
61
|
+
rubygems_mfa_required: 'true'
|
|
62
|
+
post_install_message:
|
|
63
|
+
rdoc_options: []
|
|
64
|
+
require_paths:
|
|
65
|
+
- lib
|
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
67
|
+
requirements:
|
|
68
|
+
- - ">="
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: 2.7.0
|
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0'
|
|
76
|
+
requirements: []
|
|
77
|
+
rubygems_version: 3.1.2
|
|
78
|
+
signing_key:
|
|
79
|
+
specification_version: 4
|
|
80
|
+
summary: A simple interface to mpv via JSON IPC.
|
|
81
|
+
test_files: []
|