easy_mplayer 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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