ampv 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZTMwNzg3MDNhNjM2ZTk1Njk4MmYyNzllNTYzOTQ4MzQ1OTJiNmJkOQ==
5
+ data.tar.gz: !binary |-
6
+ MmFmYTY5YmQ4Zjc0OTQ4NzE3Y2IxNTBmNmZhNGQ5ZTc5M2E5NDBiMQ==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ YzFkNDdmOTYyOTg1NDdkNjEyZTgwN2NlY2Y0NzU3MjkzODE2OTQ1YWFjZDNm
10
+ ZTJlMWQyNTUwNTc5ZTVjZTFlZjEzZGZmMjMzY2E1MTRmZDZmOWQ2YzE3MjUx
11
+ NmU0ZTQyNzA2YWMzMjU2MGI5YzRiMmJjMjk5YjJhYTJmNDcxMDM=
12
+ data.tar.gz: !binary |-
13
+ MzE0ZjZhZGYyYzhkNWZmNjhiZDA1ZjAyNDBlMmVmMDQyYWVhNGZkOTk4YmQw
14
+ MWJiMDU0ZGNmYzBjMDIyNzg1OWI3NDE0MWM1Zjk3ZTgwMzRkY2VlOTE1ZWJj
15
+ OWNiMjZkOTYzY2YxOWEwMGZiMmZlN2RkYzdiNWQxODYxZTZkZjU=
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013 ahoka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/bin/ampv ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "ampv"
4
+
5
+ Ampv::MainWindow.new
6
+
data/input.conf ADDED
@@ -0,0 +1,28 @@
1
+ MOUSE_BTN0_DBL cycle fullscreen
2
+ MOUSE_BTN1 cycle pause
3
+ MOUSE_BTN3 add volume 1
4
+ MOUSE_BTN4 add volume -1
5
+ MOUSE_BTN7 playlist_prev
6
+ MOUSE_BTN8 playlist_next
7
+ MOUSE_BTN9 cycle pause
8
+
9
+ RIGHT seek 10
10
+ LEFT seek -10
11
+ UP add volume 1
12
+ DOWN add volume -1
13
+
14
+ PGUP seek -90 - exact
15
+ PGDWN seek 90 - exact
16
+ HOME add chapter -1
17
+ END add chapter 1
18
+ INS playlist_prev
19
+ DEL playlist_next
20
+
21
+ f cycle fullscreen
22
+ SPACE cycle pause
23
+ ESC quit_watch_later
24
+
25
+ # ampv commands
26
+ l cycle playlist
27
+ o open_file_chooser
28
+ p cycle progress_bar
data/lib/ampv.rb ADDED
@@ -0,0 +1,380 @@
1
+
2
+ require "gtk2"
3
+ require "ampv/mpvwidget"
4
+ require "ampv/playlist"
5
+ require "ampv/progressbarwidget"
6
+ require "ampv/version"
7
+ require "uri"
8
+
9
+ module Ampv
10
+ class MainWindow < Gtk::Window
11
+
12
+ MAIN_CONF = "#{ENV["HOME"]}/.config/ampv.conf"
13
+ INPUT_CONF = "#{ENV["HOME"]}/.mpv/input.conf"
14
+ VIDEO_EXTS = [ ".avi", ".mkv", ".mp4", ".mpeg", ".mpg", ".ogm", ".ogv" ]
15
+ KEY_NAMES = {
16
+ "esc" => "Escape",
17
+ "space" => "space",
18
+ "right" => "Right",
19
+ "left" => "Left",
20
+ "up" => "Up",
21
+ "down" => "Down",
22
+ "pgup" => "Page_Up",
23
+ "pgdwn" => "Page_Down",
24
+ "home" => "Home",
25
+ "end" => "End",
26
+ "ins" => "Insert",
27
+ "del" => "Delete",
28
+ }
29
+ WHEEL_BUTTONS = {
30
+ Gdk::EventScroll::UP => 4,
31
+ Gdk::EventScroll::DOWN => 5,
32
+ Gdk::EventScroll::LEFT => 6,
33
+ Gdk::EventScroll::RIGHT => 7
34
+ }
35
+
36
+ LEFT_PTR = Gdk::Cursor.new(Gdk::Cursor::LEFT_PTR)
37
+ BLANK_CURSOR = Gdk::Cursor.new(Gdk::Cursor::BLANK_CURSOR)
38
+
39
+ def initialize
40
+ load_config
41
+ super
42
+ set_title(PACKAGE)
43
+ set_default_size(@config["width"], @config["height"])
44
+ set_window_position(Gtk::Window::POS_CENTER)
45
+ move(@config["x"], @config["y"]) unless @config["x"] == -1 and @config["y"] == -1
46
+
47
+ Gtk::Drag.dest_set(self, Gtk::Drag::DEST_DEFAULT_ALL,
48
+ [ [ "text/uri-list", 0, 0 ] ],
49
+ Gdk::DragContext::ACTION_LINK)
50
+
51
+ signal_connect("delete_event") { quit }
52
+ signal_connect("scroll_event") { |w, e| handle_mouse_event(e) }
53
+ signal_connect("button_press_event") { |w, e| handle_mouse_event(e) }
54
+ signal_connect("key_press_event") { |w, e| handle_keyboard_event(e) }
55
+ signal_connect("drag_data_received") { |w, dc, x, y, sd, type, time|
56
+ handle_drop_event(sd.data, dc, time, false, true)
57
+ }
58
+
59
+ vbox = Gtk::VBox.new
60
+ add(vbox)
61
+
62
+ args = process_args
63
+ load_bindings
64
+
65
+ @mpv = MPvWidget.new(args, @config["scrobbler"])
66
+ @mpv.signal_connect("file_changed") { |w, file|
67
+ @playing = file
68
+ @mpv.send("show_text ${media-title} 1500") if window.state.fullscreen?
69
+ @playlist.set_selected(@playing)
70
+ set_title(File.basename(@playing))
71
+ }
72
+ @mpv.signal_connect("length_changed") { |w, len|
73
+ @length = len
74
+ @playlist.update_length(@length)
75
+ }
76
+ @mpv.signal_connect("time_pos_changed") { |w, pos| @progress_bar.value = pos / @length.to_f }
77
+ @mpv.signal_connect("stopped") {
78
+ @progress_bar.value = 0
79
+ next_file = @playlist.get_next
80
+ @really_stop = next_file.nil? or @really_stop
81
+ set_title(PACKAGE)
82
+ if @really_stop and window.state.fullscreen?
83
+ toggle_fullscreen
84
+ elsif not @really_stop
85
+ load_file(next_file, false)
86
+ end
87
+ @really_stop = false
88
+ }
89
+
90
+ vbox.pack_start(@mpv)
91
+
92
+ @playlist = Playlist.new(@config["playlist_x"],
93
+ @config["playlist_y"],
94
+ @config["playlist_width"],
95
+ @config["playlist_height"],
96
+ @config["playlist_visible"])
97
+ @playlist.signal_connect("open_file_chooser") { open_file_chooser }
98
+ @playlist.signal_connect("drag_data_received") { |w, dc, x, y, sd, type, time|
99
+ handle_drop_event(sd.data, dc, time, true, false)
100
+ }
101
+ @playlist.signal_connect("play_entry") { |w, file| load_file(file, false, false, false, true) }
102
+ @playlist.signal_connect("playing_removed") { @mpv.send("stop"); @really_stop = true }
103
+
104
+ @progress_bar = ProgressBarWidget.new(@config["bar_color"],
105
+ @config["head_color"],
106
+ @config["progress_bar_height"])
107
+ @progress_bar.add_events(Gdk::Event::BUTTON_PRESS_MASK)
108
+ @progress_bar.signal_connect("button_press_event") { |w, e| handle_seek_event(e) }
109
+ vbox.pack_start(@progress_bar, false, false)
110
+
111
+ show_all
112
+ @mpv.start
113
+
114
+ argv = ARGV.join(" ")
115
+ if not argv.empty?
116
+ load_file(argv)
117
+ elsif not @config["playlist"].empty?
118
+ @config["playlist"].each { |x| load_file(x, true, true, false) }
119
+ @playlist.set_selected(@config["playlist_selected"])
120
+ end
121
+
122
+ Gtk.main
123
+ end
124
+
125
+ def load_config
126
+ @config = {
127
+ "width" => Gdk::Screen.default.width > 1280 ? 1280 : 853,
128
+ "height" => Gdk::Screen.default.width > 1280 ? 726 : 486,
129
+ "x" => -1,
130
+ "y" => -1,
131
+ "fullscreen_progressbar" => false,
132
+ "progress_bar_visible" => true,
133
+ "progress_bar_height" => 6,
134
+ "bar_color" => "#8f5b5b",
135
+ "head_color" => "#c48181",
136
+ "playlist_width" => 360,
137
+ "playlist_height" => 550,
138
+ "playlist_x" => 0,
139
+ "playlist_y" => 0,
140
+ "playlist_visible" => true,
141
+ "always_save_position" => false,
142
+ "scrobbler" => "",
143
+ "playlist_selected" => "",
144
+ "playlist" => [ ],
145
+ }
146
+
147
+ if File.exists?(MAIN_CONF)
148
+ File.readlines(MAIN_CONF).each { |line|
149
+ key, _, val = line.partition("=")
150
+ key = key.strip
151
+ val = val.strip
152
+ next unless @config.has_key?(key) and not key.start_with?("#")
153
+
154
+ if @config[key].is_a?(Integer)
155
+ val = val.to_i
156
+ elsif @config[key].is_a?(TrueClass) or @config[key].is_a?(FalseClass)
157
+ val = val == "true"
158
+ elsif @config[key].is_a?(Array)
159
+ val = val.split("|")
160
+ elsif val.start_with?("#")
161
+ begin
162
+ c = Gdk::Color.parse(val)
163
+ rescue
164
+ puts("Invalid hexidecimal color for setting `#{key}': `#{val}")
165
+ c = Gdk::Color.parse(@config[key])
166
+ end
167
+ val = c
168
+ end
169
+
170
+ @config[key] = val
171
+ }
172
+ end
173
+ end
174
+
175
+ def process_args
176
+ args = [ ]
177
+ ARGV.dup.each { |arg|
178
+ if arg.start_with?("-")
179
+ args.push(arg)
180
+ ARGV.delete(arg)
181
+ end
182
+ }
183
+ return args
184
+ end
185
+
186
+ def load_bindings
187
+ @mouse_bindings = [ ]
188
+ @key_bindings = [ ]
189
+ if File.exists?(INPUT_CONF)
190
+ File.readlines(INPUT_CONF).each { |line|
191
+ line = line.strip
192
+ if line.start_with?("MOUSE_BTN")
193
+ # 4 = up, 5 = down, 6 = left, 7 = right
194
+ button, cmd = line.match(/MOUSE_BTN(\d+)(?:_DBL)?\s+(.+)$/).captures
195
+ button = button.to_i + 1
196
+ type = (4..7).include?(button) ? Gdk::Event::SCROLL :
197
+ line.include?("DBL") ? Gdk::Event::BUTTON2_PRESS : Gdk::Event::BUTTON_PRESS
198
+ @mouse_bindings[type] = [ ] if @mouse_bindings[type].nil?
199
+ @mouse_bindings[type][button] = cmd
200
+ elsif not line.empty?
201
+ key, cmd = line.match(/^([^\s]+)\s+(.+)$/).captures
202
+ if name = KEY_NAMES[key.downcase]
203
+ keyval = Gdk::Keyval.from_name(name)
204
+ else
205
+ keyval = Gdk::Keyval.from_name(key)
206
+ end
207
+
208
+ @key_bindings[keyval] = cmd if keyval > 0
209
+ end
210
+ }
211
+ end
212
+ end
213
+
214
+ def load_file(file, add_to_playlist=true, do_not_play=false, auto_add=true, force_play=false)
215
+ return if file.nil? or file.empty?
216
+ file = File.expand_path(file) if file[0] == "~"
217
+
218
+ if add_to_playlist
219
+ if @playlist.count == 0 and auto_add and file !~ /^https?:\/\//
220
+ dir = File.directory?(file) ? file : File.dirname(file)
221
+ entries = Dir.entries(dir).sort
222
+ entries.delete_if { |x| x.start_with?(".") or not valid_video_file(x) }
223
+ entries.map { |x|
224
+ x = dir + "/" + x
225
+ @playlist.add_file(x)
226
+ file = x if file == dir
227
+ }
228
+ else
229
+ @playlist.add_file(file)
230
+ end
231
+ end
232
+
233
+ @mpv.load_file(file, force_play) unless do_not_play
234
+ end
235
+
236
+ def handle_mouse_event(e)
237
+ button = e.event_type == Gdk::Event::SCROLL ? WHEEL_BUTTONS[e.direction] : e.button
238
+ return if @mouse_bindings[e.event_type].nil?
239
+
240
+ process_cmd(@mouse_bindings[e.event_type][button])
241
+
242
+ return true
243
+ end
244
+
245
+ def handle_keyboard_event(e)
246
+ process_cmd(@key_bindings[e.keyval])
247
+
248
+ return true
249
+ end
250
+
251
+ def handle_seek_event(e)
252
+ if e.event_type == Gdk::Event::BUTTON_PRESS and e.button == 1
253
+ pos = e.x / allocation.width * @length.to_f
254
+ seek("seek #{pos} absolute")
255
+ end
256
+
257
+ return true
258
+ end
259
+
260
+ def handle_drop_event(data, context, time, do_not_play, replace)
261
+ files = URI.decode(data).gsub("file://", "").split("\r\n")
262
+ @playlist.clear if replace
263
+
264
+ files.each { |x|
265
+ load_file(x, true, do_not_play) if valid_video_file(x)
266
+ }
267
+
268
+ Gtk::Drag.finish(context, true, true, time)
269
+ end
270
+
271
+ def process_cmd(cmd)
272
+ case cmd
273
+ when "cycle fullscreen"
274
+ toggle_fullscreen
275
+ when "cycle pause"
276
+ @mpv.play_pause
277
+ when "cycle playlist"
278
+ @playlist.visible? ? @playlist.hide : @playlist.show
279
+ when /seek /
280
+ seek(cmd)
281
+ when /add chapter/
282
+ seek(cmd)
283
+ when "playlist_next"
284
+ load_file(@playlist.get_next, false)
285
+ when "playlist_prev"
286
+ load_file(@playlist.get_prev, false)
287
+ when "open_file_chooser"
288
+ open_file_chooser
289
+ when "cycle progress_bar"
290
+ toggle_progress_bar
291
+ when "quit_watch_later"
292
+ quit(true)
293
+ when "quit"
294
+ quit
295
+ else
296
+ @mpv.send(cmd) if cmd
297
+ end
298
+ end
299
+
300
+ def seek(cmd)
301
+ cmd = "no-osd " + cmd unless cmd.start_with?("no-osd") or
302
+ not @progress_bar.visible?
303
+ @mpv.send(cmd)
304
+ @mpv.send("get_property time-pos")
305
+ end
306
+
307
+ def toggle_fullscreen
308
+ if window.state.fullscreen?
309
+ @progress_bar.show unless @progress_bar_user_hidden
310
+ unfullscreen
311
+ window.set_cursor(LEFT_PTR)
312
+ else
313
+ @progress_bar.hide unless @config["fullscreen_progressbar"]
314
+ fullscreen
315
+ window.set_cursor(BLANK_CURSOR)
316
+ end
317
+ end
318
+
319
+ def toggle_progress_bar
320
+ if @progress_bar.visible?
321
+ @progress_bar.hide
322
+ @progress_bar_user_hidden = true
323
+ else
324
+ @progress_bar.show
325
+ @progress_bar_user_hidden = false
326
+ end
327
+ end
328
+
329
+ def valid_video_file(x)
330
+ return (VIDEO_EXTS.include?(File.extname(x)) or
331
+ `file -b --mime-type "#{x}"`.start_with?("video"))
332
+ end
333
+
334
+
335
+ def open_file_chooser
336
+ dialog = Gtk::FileChooserDialog.new("Open File - #{PACKAGE}",
337
+ self, Gtk::FileChooser::ACTION_OPEN, nil,
338
+ [ Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL ],
339
+ [ Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT ])
340
+ dialog.select_multiple = true
341
+ do_not_play = @playlist.count > 0
342
+
343
+ filter = Gtk::FileFilter.new
344
+ filter.name = "Video Files"
345
+ VIDEO_EXTS.each { |x| filter.add_pattern("*#{x}") }
346
+ dialog.add_filter(filter)
347
+
348
+ filterAll = Gtk::FileFilter.new
349
+ filterAll.name = "All Files"
350
+ filterAll.add_pattern("*.*")
351
+ dialog.add_filter(filterAll)
352
+
353
+ dialog.filenames.each { |x|
354
+ load_file(x, true, do_not_play)
355
+ do_not_play = true
356
+ } if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
357
+ dialog.destroy
358
+ end
359
+
360
+ def quit(watch_later=false)
361
+ @config["x"],
362
+ @config["y"],
363
+ @config["width"],
364
+ @config["height"] = window.geometry unless window.state.fullscreen?
365
+ @config["playlist_x"],
366
+ @config["playlist_y"],
367
+ @config["playlist_width"],
368
+ @config["playlist_height"] = @playlist.window.geometry
369
+ @config["playlist_visible"] = @playlist.visible?
370
+ @config["playlist_selected"] = @playing
371
+ @config["playlist"] = @playlist.get_entries.join("|")
372
+ @config["progress_bar_visible"] = @progress_bar.visible?
373
+ File.open(MAIN_CONF, "w") { |file| @config.each { |k, v| file.puts("#{k}=#{v}") } }
374
+
375
+ @mpv.quit(@config["always_save_position"] ? true : watch_later)
376
+ Gtk.main_quit
377
+ end
378
+ end
379
+ end
380
+
@@ -0,0 +1,125 @@
1
+ require "fifo"
2
+
3
+ module Ampv
4
+ class MPvWidget < Gtk::EventBox
5
+
6
+ type_register
7
+ signal_new("file_changed", GLib::Signal::RUN_FIRST, nil, nil, String)
8
+ signal_new("length_changed", GLib::Signal::RUN_FIRST, nil, nil, Integer)
9
+ signal_new("time_pos_changed", GLib::Signal::RUN_FIRST, nil, nil, Float)
10
+ signal_new("stopped", GLib::Signal::RUN_FIRST, nil, nil)
11
+
12
+ def initialize(args, scrobbler)
13
+ if args.include?("--debug")
14
+ args.delete("--debug")
15
+ @debug = true
16
+ end
17
+
18
+ @scrobbler = scrobbler
19
+ @mpv_path = "/usr/bin/mpv"
20
+ @mpv_options = args.join(" ")
21
+ @mpv_fifo = "/tmp/mpv.fifo." + Process.pid.to_s
22
+
23
+ super()
24
+
25
+ @socket = Gtk::Socket.new
26
+ @socket.modify_bg(Gtk::STATE_NORMAL, Gdk::Color.parse("#000"))
27
+
28
+ @socket.signal_connect("plug_removed") { signal_emit("stopped"); true }
29
+ add(@socket)
30
+ end
31
+
32
+ def start
33
+ if @thread.nil?
34
+ @fifo = Fifo.new(@mpv_fifo, :w, :nowait)
35
+
36
+ cmd = "#{@mpv_path} \
37
+ --identify \
38
+ --idle \
39
+ --input-file=#{@mpv_fifo} \
40
+ --no-mouse-movements \
41
+ --cursor-autohide=no \
42
+ --msglevel=all=2 \
43
+ --msglevel=global=4 \
44
+ --wid=#{@socket.id} #{@mpv_options}"
45
+ @thread = Thread.new { slave_reader(cmd) }
46
+ end
47
+ end
48
+
49
+ def send(cmd)
50
+ @fifo.puts(cmd) unless @fifo.nil?
51
+ end
52
+
53
+ def load_file(file, force_play=false)
54
+ send("loadfile \"#{file}\"")
55
+ @force_play = force_play
56
+ end
57
+
58
+ def play_pause
59
+ send("cycle pause")
60
+ @is_paused = @is_paused ? false : true
61
+ end
62
+
63
+ def quit(watch_later)
64
+ send("quit" + (watch_later ? "_watch_later" : ""))
65
+ @thread.join unless @thread.nil? or not @thread.alive?
66
+ @fifo.close
67
+ File.delete(@mpv_fifo)
68
+ end
69
+
70
+ private
71
+ def slave_reader(cmd)
72
+ @pipe = IO.popen(cmd, "a+")
73
+
74
+ until @pipe.nil? or @pipe.closed? or @pipe.eof?
75
+ line = @pipe.readline.chomp
76
+ if line.include?("ID_FILENAME=")
77
+ signal_emit("file_changed", (@playing = line.partition("ID_FILENAME=").last))
78
+ send("get_property pause") # saved position also saves play state
79
+
80
+ @prog_thread.kill unless @prog_thread.nil? or not @prog_thread.alive?
81
+ @prog_thread = Thread.new { progress_update }
82
+ elsif line.start_with?("ID_LENGTH=")
83
+ signal_emit("length_changed", (@length = line.rpartition("=").last.to_i))
84
+ elsif line.start_with?("ANS_pause=")
85
+ @is_paused = line.rpartition("=").last == "yes"
86
+ play_pause if @force_play and @is_paused
87
+ elsif line.start_with?("ANS_time-pos=")
88
+ signal_emit("time_pos_changed", line.rpartition("=").last.to_f)
89
+ end
90
+
91
+ if @debug or line.start_with?("Error")
92
+ puts(line) unless line.start_with?("ANS_time-pos=") or
93
+ line.start_with?("ANS_ERROR") or
94
+ line.start_with?("Failed to get") or
95
+ line.start_with?("Command ")
96
+ end
97
+ end
98
+ end
99
+
100
+ def progress_update
101
+ scrobbled = false
102
+ watched = 0
103
+
104
+ while true
105
+ send("get_property time-pos") unless @is_paused
106
+
107
+ if not @scrobbler.nil? and not scrobbled and watched >= @length * 0.5
108
+ system("#{@scrobbler} \"#{@playing}\"")
109
+ scrobbled = true
110
+ end
111
+
112
+ sleep(1)
113
+ watched += 1 unless @is_paused
114
+ end
115
+ end
116
+
117
+ def signal_do_file_changed(file) end
118
+ def signal_do_length_changed(len) end
119
+ def signal_do_time_pos_changed(pos) end
120
+ def signal_do_stopped()
121
+ @prog_thread.kill
122
+ end
123
+ end
124
+ end
125
+
@@ -0,0 +1,207 @@
1
+ #require "gtk2"
2
+
3
+ module Ampv
4
+ class Playlist < Gtk::Window
5
+
6
+ type_register
7
+ signal_new("play_entry", GLib::Signal::RUN_FIRST, nil, nil, String)
8
+ signal_new("playing_removed", GLib::Signal::RUN_FIRST, nil, nil)
9
+ signal_new("open_file_chooser", GLib::Signal::RUN_FIRST, nil, nil)
10
+
11
+ def initialize(x, y, w, h, is_visible)
12
+ buttons = {
13
+ [ Gtk::Stock::OPEN, "Add to Playlist" ] => lambda { signal_emit("open_file_chooser") },
14
+ [ Gtk::Stock::GO_UP, "Move Up" ] => lambda { move_selected_up },
15
+ [ Gtk::Stock::GO_DOWN, "Move Down" ] => lambda { move_selected_down },
16
+ [ Gtk::Stock::REMOVE, "Remove Selected" ] => lambda { remove_selected },
17
+ [ Gtk::Stock::CLEAR, "Clear Playlist" ] => lambda { clear }
18
+ }
19
+
20
+ super()
21
+ set_title("Playlist - #{PACKAGE}")
22
+ set_default_size(w, h)
23
+ set_skip_taskbar_hint(true)
24
+ move(x, y)
25
+
26
+ Gtk::Drag.dest_set(self, Gtk::Drag::DEST_DEFAULT_ALL,
27
+ [ [ "text/uri-list", 0, 0 ] ],
28
+ Gdk::DragContext::ACTION_LINK)
29
+
30
+ signal_connect("show") { move(@pos[0], @pos[1]) unless @pos.nil? }
31
+ signal_connect("hide") { @pos = window.root_origin }
32
+ signal_connect("delete_event") { hide_on_delete }
33
+
34
+ signal_connect("key_press_event") { |_w, e|
35
+ hide_on_delete if e.keyval == Gdk::Keyval::GDK_Escape
36
+ }
37
+
38
+ vbox = Gtk::VBox.new(false, 10)
39
+ vbox.border_width = 10
40
+ add(vbox)
41
+
42
+ sw = Gtk::ScrolledWindow.new
43
+ sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
44
+ vbox.pack_start(sw)
45
+
46
+ @model = Gtk::ListStore.new(String, String)
47
+ @treeview = Gtk::TreeView.new(@model)
48
+ @treeview.enable_search = false
49
+ @treeview.rubber_banding = true
50
+ @treeview.reorderable = true
51
+ @treeview.selection.mode = Gtk::SELECTION_MULTIPLE
52
+
53
+ @treeview.signal_connect("row_activated") { |_w, p, c|
54
+ signal_emit("play_entry", @model.get_iter(p)[0])
55
+ }
56
+ @treeview.signal_connect("key_press_event") { |_w, e|
57
+ remove_selected if e.keyval == Gdk::Keyval::GDK_Delete
58
+ }
59
+ @treeview.signal_connect("button_press_event") { |_w, e|
60
+ @menu.popup(nil, nil, e.button, e.time) if
61
+ e.event_type == Gdk::Event::BUTTON_PRESS and e.button == 3
62
+ }
63
+
64
+ ["Name", "Length"].each_with_index { |_x, i|
65
+ renderer = Gtk::CellRendererText.new
66
+ column = Gtk::TreeViewColumn.new(_x,
67
+ renderer,
68
+ :text => i)
69
+ if _x == "Name"
70
+ renderer.ellipsize = Pango::ELLIPSIZE_MIDDLE
71
+ column.expand = true
72
+ column.set_cell_data_func(renderer) { |t, c, m, j|
73
+ c.text = File.basename(m.get_value(j, 0)) unless m.get_value(j, 0).nil?
74
+ }
75
+ end
76
+
77
+ @treeview.append_column(column)
78
+ }
79
+
80
+ sw.add(@treeview)
81
+
82
+ hbox = Gtk::HBox.new(true, 5)
83
+
84
+ buttons.each { |k, v|
85
+ button = Gtk::Button.new
86
+ button.image = Gtk::Image.new(k[0], Gtk::IconSize::BUTTON)
87
+ button.height_request = 36
88
+ button.set_tooltip_text(k[1])
89
+ button.signal_connect("clicked") { v.call }
90
+ hbox.pack_start(button)
91
+ }
92
+
93
+ vbox.pack_start(hbox, false)
94
+
95
+ @menu = Gtk::Menu.new
96
+ buttons.each { |k, v|
97
+ item = Gtk::ImageMenuItem.new(k[1])
98
+ item.image = Gtk::Image.new(k[0], Gtk::IconSize::MENU)
99
+ item.signal_connect("activate") { v.call }
100
+ @menu.append(item)
101
+ }
102
+ @menu.show_all
103
+
104
+ show_all if is_visible
105
+ end
106
+
107
+ def count
108
+ i = 0
109
+ @model.each { i += 1 }
110
+ return i
111
+ end
112
+
113
+ def add_file(file)
114
+ contains = false
115
+ @model.each { |m, p, iter|
116
+ if iter[0] == file
117
+ contains = true
118
+ break
119
+ end
120
+ }
121
+ unless contains
122
+ iter = @model.append
123
+ iter[0] = file
124
+ end
125
+ end
126
+
127
+ def move_selected_up
128
+ @treeview.selection.selected_rows.each { |path|
129
+ tmp = path.dup
130
+ break if not tmp.prev! or @treeview.selection.selected_rows.include?(tmp)
131
+ @model.move_before(@model.get_iter(path), @model.get_iter(tmp))
132
+ }
133
+ end
134
+
135
+ def move_selected_down
136
+ @treeview.selection.selected_rows.reverse.each { |path|
137
+ break if @treeview.selection.selected_rows.include?((tmp = path.dup.next!))
138
+ tmp = @model.get_iter(tmp)
139
+ @model.move_after(@model.get_iter(path), tmp) unless tmp.nil?
140
+ }
141
+ end
142
+
143
+ def remove_selected
144
+ to_remove = [ ]
145
+ @treeview.selection.selected_rows.each { |path|
146
+ to_remove.push(@model.get_iter(path))
147
+ }
148
+ to_remove.each { |iter|
149
+ signal_emit("playing_removed") if iter[0] == @playing
150
+ @model.remove(iter)
151
+ }
152
+ end
153
+
154
+ def get_next
155
+ @model.each { |m, p, iter|
156
+ return iter.next! ? iter[0] : nil if iter[0] == @playing
157
+ }
158
+ return nil
159
+ end
160
+
161
+ def get_prev
162
+ prev = nil
163
+ @model.each { |m, p, iter|
164
+ return prev if iter[0] == @playing
165
+ prev = iter[0]
166
+ }
167
+ end
168
+
169
+ def get_entries
170
+ entries = [ ]
171
+ @model.each { |m, p, iter| entries.push(iter[0]) }
172
+ return entries
173
+ end
174
+
175
+ def clear
176
+ @model.clear
177
+ signal_emit("playing_removed")
178
+ end
179
+
180
+ def set_selected(file)
181
+ @playing = file
182
+ i = 0
183
+ @model.each { |m, p, iter|
184
+ if iter[0] == @playing
185
+ @treeview.set_cursor(Gtk::TreePath.new(i), nil, false)
186
+ break
187
+ end
188
+ i += 1
189
+ }
190
+ end
191
+
192
+ def update_length(length)
193
+ @model.each { |m, p, iter|
194
+ if iter[0] == @playing
195
+ iter[1] = Time.at(length).utc.strftime("%H:%M:%S") unless length == 0
196
+ break
197
+ end
198
+ }
199
+ end
200
+
201
+ private
202
+ def signal_do_play_entry(file) end
203
+ def signal_do_playing_removed() end
204
+ def signal_do_open_file_chooser() end
205
+ end
206
+ end
207
+
@@ -0,0 +1,36 @@
1
+
2
+ module Ampv
3
+ class ProgressBarWidget < Gtk::DrawingArea
4
+ def initialize(bar_color, head_color, height)
5
+ super()
6
+ modify_bg(Gtk::STATE_NORMAL, Gdk::Color.parse("#000"))
7
+ set_height_request(height)
8
+
9
+ signal_connect("expose_event") {
10
+ @cx = window.create_cairo_context
11
+ draw_widget
12
+ }
13
+ @value = 0
14
+ @bar_color = bar_color
15
+ @head_color = head_color
16
+ end
17
+
18
+ def draw_widget
19
+ @cx.set_source_color(@bar_color)
20
+ @cx.rectangle(0, 0, allocation.width * @value.to_f, allocation.height)
21
+ @cx.fill
22
+
23
+ if @value > 0
24
+ @cx.set_source_color(@head_color)
25
+ @cx.rectangle(allocation.width * @value.to_f, 0, 2, allocation.height)
26
+ @cx.fill
27
+ end
28
+ end
29
+
30
+ def value=(v)
31
+ @value = v
32
+ queue_draw
33
+ end
34
+ end
35
+ end
36
+
@@ -0,0 +1,6 @@
1
+
2
+ module Ampv
3
+ PACKAGE = "ampv"
4
+ VERSION = "1.0.0"
5
+ end
6
+
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ampv
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - ahoka
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: gtk2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ! '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ! '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-fifo
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ! '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: A minimal GTK2 mpv frontend.
42
+ email:
43
+ executables:
44
+ - ampv
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE
49
+ - input.conf
50
+ - lib/ampv/version.rb
51
+ - lib/ampv/playlist.rb
52
+ - lib/ampv/progressbarwidget.rb
53
+ - lib/ampv/mpvwidget.rb
54
+ - lib/ampv.rb
55
+ - bin/ampv
56
+ homepage: https://github.com/ahodesuka/ampv
57
+ licenses:
58
+ - MIT
59
+ metadata: {}
60
+ post_install_message:
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.9.3
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements:
75
+ - gtk2
76
+ rubyforge_project:
77
+ rubygems_version: 2.0.3
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: ampv
81
+ test_files: []