easy_mplayer 1.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.
@@ -0,0 +1,354 @@
1
+ class MPlayer
2
+ class Worker # :nodoc:all
3
+ include ColorDebugMessages
4
+
5
+ class Stream
6
+ include ColorDebugMessages
7
+
8
+ MATCH_STDOUT = { # :nodoc:
9
+ :version => {
10
+ :re => /^MPlayer\s(\S+)\s\(C\)/,
11
+ :stat => [:version]
12
+ },
13
+ :server => {
14
+ :re => /^Connecting to server (\S+)\[(\d+\.\d+\.\d+\.\d+)\]:/,
15
+ :stat => [:server, :server_ip]
16
+ },
17
+ :stream_info => {
18
+ :re => /^ICY Info: StreamTitle='(.*?)';StreamUrl='(.*?)';/,
19
+ :stat => [:stream_title, :stream_url]
20
+ },
21
+ :update_position => {
22
+ :re => /^A:\s+(\d+\.\d+)\s+\(\S+\)\s+of\s+(\d+\.\d+)/,
23
+ :stat => [:played_time, :total_time],
24
+ },
25
+ :audio_info => {
26
+ :re => /^AUDIO: (\d+) Hz, (\d+) ch, (\S+), ([0-9.]+) kbit/,
27
+ :stat => [:audio_sample_rate, :audio_channels,
28
+ :audio_format, :audio_data_rate],
29
+ :call => :audio_stats
30
+ },
31
+ :video_info => {
32
+ :re => /^VIDEO:\s+\[(\S{4})\]\s+(\d+)x(\d+)\s+(\d+)bpp\s+(\d+\.\d+)\s+fps/,
33
+ :stat => [:video_fourcc, :video_x_size, :video_y_size,
34
+ :video_bpp, :video_fps],
35
+ :call => :video_stats
36
+ },
37
+ :video_decoder => {
38
+ :re => /^Opening video decoder: \[(\S+)\]/,
39
+ :stat => [:video_decoder]
40
+ },
41
+ :audio_decoder => {
42
+ :re => /^Opening audio decoder: \[(\S+)\]/,
43
+ :stat => [:audio_decoder]
44
+ },
45
+ :video_codec => {
46
+ :re => /^Selected video codec: \[(\S+)\]/,
47
+ :stat => [:video_codec]
48
+ },
49
+ :audio_codec => {
50
+ :re => /^Selected audio codec: \[(\S+)\]/,
51
+ :stat => [:audio_codec]
52
+ }
53
+ }
54
+
55
+ MATCH_STDERR = { # :nodoc:
56
+ :file_not_found => {
57
+ :re => /^File not found: /,
58
+ :call => :file_error
59
+ }
60
+ }
61
+
62
+ attr_reader :parent, :type, :io
63
+
64
+ def initialize(p, w, stream_type, stream_io)
65
+ @parent = p
66
+ @worker = w
67
+ @type = stream_type
68
+ @io = stream_io
69
+ @line = ''
70
+ @outlist = Array.new
71
+ @stats = Hash.new
72
+ @select_wait_time = p.opts[:select_wait_time]
73
+ @sent_update_position = false
74
+ end
75
+
76
+ def prefix(msg)
77
+ "STREAM [#{@type}] #{msg}"
78
+ end
79
+
80
+ def debug(msg); super prefix(msg); end
81
+ def info(msg); super prefix(msg); end
82
+ def warn(msg); super prefix(msg); end
83
+
84
+ def stream_error(type)
85
+ @worker.flag_stream_error(type)
86
+ end
87
+
88
+ def callback!(name, *args)
89
+ case name
90
+ when :update_stat
91
+ stat = args[0]
92
+ val = args[1]
93
+ if @stats[stat] == val
94
+ return # only propagate changes
95
+ else
96
+ @stats[stat] = val
97
+ end
98
+ end
99
+ @worker.queue_callback [name, args]
100
+ end
101
+
102
+ def check_line(patterns, line)
103
+ patterns.each_pair do |name, pat|
104
+ if md = pat[:re].match(line)
105
+ args = md.captures.map do |x|
106
+ case x
107
+ when /^\d+$/ then Integer(x)
108
+ when /^\d+\.\d+$/ then Float(x)
109
+ else x
110
+ end
111
+ end
112
+
113
+ (pat[:stat] || []).each do |field|
114
+ callback! :update_stat, field, args.shift
115
+ end
116
+
117
+ callback! pat[:call] if pat[:call]
118
+ return name
119
+ end
120
+ end
121
+ nil
122
+ end
123
+
124
+ def process_stdout(line)
125
+ check_line(MATCH_STDOUT, line)
126
+ end
127
+
128
+ def process_stderr(line)
129
+ if check_line(MATCH_STDERR, line)
130
+ stream_error(:stderr)
131
+ end
132
+ end
133
+
134
+ def process_line
135
+ debug "LINE> \"#{@line}\""
136
+ send "process_#{@type}", @line
137
+ # callback! @type, @line
138
+ @line = ''
139
+ end
140
+
141
+ def process_stream
142
+ result = IO.select([@io], nil, nil, @select_wait_time)
143
+ return if result.nil? or result.empty?
144
+
145
+ c = @io.read(1)
146
+ return stream_error(:eof) if c.nil?
147
+
148
+ @line << c
149
+ process_line if c == "\n" or c == "\r"
150
+ end
151
+
152
+ def run
153
+ @thread = Thread.new do
154
+ @alive = true
155
+ begin
156
+ debug "start"
157
+ process_stream while @alive
158
+ debug "clean end!"
159
+ rescue IOError => e
160
+ if e.to_s =~ /stream closed/
161
+ debug "stream closed!"
162
+ else
163
+ raise BadStream, e.to_s
164
+ end
165
+ rescue => e
166
+ warn "Unexpected error when parsing MPlayer's IO stream!"
167
+ warn "error was: #{e}"
168
+ stream_error(:exception)
169
+ ensure
170
+ cleanup
171
+ end
172
+ end
173
+ end
174
+
175
+ def cleanup
176
+ @io.close unless @io.closed?
177
+ end
178
+
179
+ def kill
180
+ @alive = false
181
+ cleanup
182
+ end
183
+
184
+ def join
185
+ @thread.join if @thread
186
+ @thread = nil
187
+ end
188
+ end
189
+
190
+ attr_reader :parent, :io
191
+
192
+ def initialize(p)
193
+ @parent = p
194
+ @pid = nil
195
+ @streams = Array.new
196
+ @pending = Array.new
197
+ @mutex = Mutex.new
198
+ @failed = nil
199
+
200
+ @thread_safe_callbacks = @parent.opts[:thread_safe_callbacks]
201
+ @shutdown_in_progress = false
202
+
203
+ begin
204
+ info "running mplayer >>> #{cmdline}"
205
+ @io_stdin, @io_stdout, @io_stderr = Open3.popen3(cmdline)
206
+
207
+ create_stream(:stdout, @io_stdout)
208
+ create_stream(:stderr, @io_stderr)
209
+ send_each_stream :run
210
+ rescue
211
+ raise BadStream, "couldn't create streams to mplayer: #{$!}"
212
+ end
213
+
214
+ debug "mplayer threads created!"
215
+ end
216
+
217
+ def cmdline(target = parent.opts[:path])
218
+ cmd = "#{parent.opts[:program]} -slave "
219
+ cmd += "-playlist " if target=~ /\.m3u$/
220
+ cmd += target.to_s
221
+ end
222
+
223
+ def lock!
224
+ @mutex.synchronize do
225
+ yield
226
+ end
227
+ end
228
+
229
+ def queue_callback(args)
230
+ if @thread_safe_callbacks
231
+ lock! do
232
+ @pending.push(args)
233
+ end
234
+ else
235
+ @parent.callback! args.first, *(args.last)
236
+ end
237
+ end
238
+
239
+ def dispatch_callbacks
240
+ return unless @thread_safe_callbacks
241
+ list = nil
242
+ lock! do
243
+ list = @pending
244
+ @pending = Array.new
245
+ end
246
+ list.each do |args|
247
+ @parent.callback! args.first, *(args.last)
248
+ end
249
+ end
250
+
251
+ def send_to_stdin(str)
252
+ begin
253
+ @io_stdin.puts str
254
+ rescue => e
255
+ warn "Couldn't write to mplayer's stdin!"
256
+ warn "error was: #{e}"
257
+ shutdown!
258
+ end
259
+ end
260
+
261
+ def send_command(*args)
262
+ cmd = args.join(' ')
263
+ if @io_stdin.nil?
264
+ debug "cannot send \"#{cmd}\" - stdin closed"
265
+ else
266
+ Command.validate! args
267
+ send_to_stdin cmd
268
+ end
269
+ end
270
+
271
+ def create_stream(type, io)
272
+ returning Stream.new(parent, self, type, io) do |stream|
273
+ @streams.push(stream)
274
+ end
275
+ end
276
+
277
+ def send_each_stream(*args)
278
+ cmd = args.shift
279
+ cmd_str = "#{cmd.to_s}(#{args.join(', ')})"
280
+ if @streams.length < 1
281
+ warn "No streams available for \"cmd_str\""
282
+ else
283
+ debug "Sending each stream: #{cmd_str}"
284
+ @streams.each do |stream|
285
+ if stream.respond_to? cmd
286
+ stream.send(cmd, *args)
287
+ else
288
+ raise BadStream, "stream command not valid: #{cmd_str}"
289
+ end
290
+ end
291
+ end
292
+ end
293
+
294
+ def flag_stream_error(type)
295
+ lock! do
296
+ @failed = type if @failed.nil?
297
+ end
298
+ end
299
+
300
+ def ok?
301
+ dispatch_callbacks
302
+ err = nil
303
+ lock! do
304
+ err = @failed
305
+ end
306
+ return true if err.nil? and @streams.length > 0
307
+
308
+ case err
309
+ when :eof
310
+ info "MPlayer process shut itself down!"
311
+ close_stdin
312
+ when :stderr
313
+ warn "Caugh error message on MPlayer's STDERR"
314
+ when :exception
315
+ warn "Unexpected IO stream failure!"
316
+ end
317
+ shutdown!
318
+ end
319
+
320
+ def close_stdin
321
+ @io_stdin.close if @io_stdin and !@io_stdin.closed?
322
+ @io_stdin = nil
323
+ end
324
+
325
+ def startup!
326
+ @parent.callback! :startup
327
+ end
328
+
329
+ def shutdown!
330
+ if @shutdown_in_progress
331
+ debug "shutdown already in progress, skipping shutdown call..."
332
+ return
333
+ end
334
+
335
+ @parent.callback! :pre_shutdown
336
+
337
+ # give mplayer it's close signal
338
+ debug "Sending QUIT to mplayer..."
339
+ @shutdown_in_progress = true
340
+ send_command :quit
341
+
342
+ # close our side of the IO
343
+ close_stdin
344
+
345
+ # then wait for the threads to cleanup after themselves
346
+ info "Waiting for worker thread to exit..."
347
+ send_each_stream :kill
348
+ send_each_stream :join
349
+ @streams = Array.new
350
+ info "MPlayer process cleaned up!"
351
+ @parent.callback! :shutdown
352
+ end
353
+ end
354
+ end
metadata ADDED
@@ -0,0 +1,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easy_mplayer
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Brent Sanders
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-05 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: color_debug_messages
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.1.2
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: facets
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 2.8.0
34
+ version:
35
+ description: A wrapper to manage mplayer, that supports callbacks to easyily support event-driven GUIs
36
+ email: gem-mplayer@thoughtnoise.net
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.rdoc
49
+ - Rakefile
50
+ - VERSION
51
+ - easy_mplayer.gemspec
52
+ - examples/basic.rb
53
+ - examples/callbacks.rb
54
+ - examples/inherit_class.rb
55
+ - examples/minimal.rb
56
+ - lib/easy_mplayer.rb
57
+ - lib/easy_mplayer/callback.rb
58
+ - lib/easy_mplayer/commands.rb
59
+ - lib/easy_mplayer/errors.rb
60
+ - lib/easy_mplayer/helpers.rb
61
+ - lib/easy_mplayer/main.rb
62
+ - lib/easy_mplayer/worker.rb
63
+ has_rdoc: true
64
+ homepage: http://github.com/pdkl95/easy_mplayer
65
+ licenses: []
66
+
67
+ post_install_message:
68
+ rdoc_options:
69
+ - --charset=UTF-8
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: "0"
77
+ version:
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ requirements: []
85
+
86
+ rubyforge_project:
87
+ rubygems_version: 1.3.5
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: Wrapper to launch and control MPlayer
91
+ test_files:
92
+ - examples/minimal.rb
93
+ - examples/basic.rb
94
+ - examples/inherit_class.rb
95
+ - examples/callbacks.rb