ampv 1.0.3 → 1.1.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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OTU4NTMxOTlmYWI1Mzc3ODkxYTIwNDc2YmViYzhjZGYxNzY4MWUzZg==
4
+ OTI1MjM3YjlmNDJhZTdlMTdjOTY4MWU5YjMwZTdkZDZmYmJlNjMwYg==
5
5
  data.tar.gz: !binary |-
6
- NTgzOTFhM2Q3MzZjNmE5YjdlMTVhMDliYzlmYjIyODEwZDNhODUzOA==
6
+ ZGFjZTg2ZGRiZDI1NDY0MTAwNmRjY2Y1M2MzODY5YmIxZTJkYmY4Yg==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- MDJmODI2YmZiMWMzNmFiMmVkY2Y1N2U1OTQwNjQxMWViNTc2YmE1NTM3ZmIy
10
- MjQyYWQ1MWNhNDNmNGQzNmZkMzBhOTJmNzhhNDZlZWFjMzNkMjQ4ZDlkNjIz
11
- YTI1MjllMmFiMzIwMWIzNDA3MWQ4Y2VhODk1ODZkOGVkODQwYzY=
9
+ ZTA5ZjMxMTEzNjVhMWY2NGFkZWUwNjJiZmQ4ZjBkZDVjOTg5ZTdjZGMwZDBl
10
+ MWZmZmVlYTIzY2M0ZGM4ZjhiZGU2NTdkZTE2ZWYxNzc3ZDQyMDdmOGFjNzQ1
11
+ ODU5ZTJjYjMwMTYyMTBmNmQ2Yzk2ZWJkMzJiNTk5ZDBhZmI5Yjk=
12
12
  data.tar.gz: !binary |-
13
- YzU4MDcwMjg1MDg4N2UyMjQ0YjU4ODI5ODA4YTJjZjkzZTY5ZDk1ZGZlZjdm
14
- MzkwMzlkZTRiYzM1ZjgyMTg4MmVkZmY5NDk3ZTJmNzM2ZmQxYmVhYzE2ZGM0
15
- MGU1ZGM5NjQyOTQ1NjJjYjFmZDY1M2ZjYTBjODVkMmQ4OWEyZWM=
13
+ YWIzYWM1YWI2ZDU2OGQ2NGZiYjE3MzUyNzQ1YmQwZDYzZjQ5MzE4MjQ4Njcx
14
+ MzA2MmQyMGM3NDUyMmM3YzdlY2E0OWYyNzYwOTQ3YzhkODU1YTVjZDg3ZjNl
15
+ YTk4MzMxNjA2YzI3N2IzMWZiMTBkNjgyODg0NjU4YmI4NzZiMmI=
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2013 ahoka
3
+ Copyright (c) 2013-2014 ahoka
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/bin/ampv CHANGED
@@ -1,5 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
+ unless $:.include?(lib = File.expand_path("../../lib", __FILE__))
4
+ $:.unshift(lib)
5
+ end
6
+
3
7
  require "ampv"
4
8
 
5
9
  Ampv::MainWindow.new
data/input.conf CHANGED
@@ -6,23 +6,24 @@ MOUSE_BTN7 playlist_prev
6
6
  MOUSE_BTN8 playlist_next
7
7
  MOUSE_BTN9 cycle pause
8
8
 
9
- RIGHT seek 10
10
- LEFT seek -10
11
- UP add volume 1
12
- DOWN add volume -1
9
+ RIGHT seek 10
10
+ LEFT seek -10
11
+ UP add volume 1
12
+ DOWN add volume -1
13
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
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
20
 
21
- f cycle fullscreen
22
- SPACE cycle pause
23
- ESC quit_watch_later
21
+ f cycle fullscreen
22
+ s cycle sub
23
+ SPACE cycle pause
24
+ ESC quit_watch_later
24
25
 
25
26
  # ampv commands
26
- l cycle playlist
27
- o open_file_chooser
28
- p cycle progress_bar
27
+ l cycle playlist
28
+ o open_file_chooser
29
+ p cycle progress_bar
@@ -1,31 +1,17 @@
1
-
2
1
  require "gtk2"
2
+ require "json"
3
+ require "uri"
4
+ require "ampv/config"
3
5
  require "ampv/mpvwidget"
4
6
  require "ampv/playlist"
5
7
  require "ampv/progressbarwidget"
6
8
  require "ampv/version"
7
- require "uri"
8
9
 
9
10
  module Ampv
10
11
  class MainWindow < Gtk::Window
11
12
 
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
- }
13
+ INPUT_CONF = "#{ENV["HOME"]}/.mpv/input.conf"
14
+ VIDEO_EXTS = [ ".avi", ".mkv", ".mp4", ".mpeg", ".mpg", ".ogm", ".ogv", ".rm", ".ts", ".wmv" ]
29
15
  WHEEL_BUTTONS = {
30
16
  Gdk::EventScroll::UP => 4,
31
17
  Gdk::EventScroll::DOWN => 5,
@@ -37,208 +23,128 @@ module Ampv
37
23
  BLANK_CURSOR = Gdk::Cursor.new(Gdk::Cursor::BLANK_CURSOR)
38
24
 
39
25
  def initialize
40
- load_config
26
+ unless defined?(MpvWidget::PATH)
27
+ dlg = Gtk::MessageDialog.new(nil,
28
+ Gtk::Dialog::DESTROY_WITH_PARENT,
29
+ Gtk::MessageDialog::ERROR,
30
+ Gtk::MessageDialog::BUTTONS_CLOSE,
31
+ "Unable to find mpv executable")
32
+ dlg.set_secondary_text("Please ensure you have mpv installed in your PATH.")
33
+ dlg.run
34
+ dlg.destroy
35
+ exit
36
+ end
37
+
38
+ args = ARGV.reject { |x| x[0] != "-" }
39
+ files = ARGV - args
40
+
41
+ print_version if args.include?("--version")
42
+ Config.load
41
43
  super
44
+
42
45
  set_title(PACKAGE)
43
- set_default_size(@config["width"], @config["height"])
46
+ set_default_size(Config["width"], Config["height"])
44
47
  set_window_position(Gtk::Window::POS_CENTER)
45
- move(@config["x"], @config["y"]) unless @config["x"] == -1 and @config["y"] == -1
48
+ move(Config["x"], Config["y"]) unless Config["x"] == -1 and Config["y"] == -1
46
49
 
50
+ add_events(Gdk::Event::POINTER_MOTION_MASK)
47
51
  Gtk::Drag.dest_set(self, Gtk::Drag::DEST_DEFAULT_ALL,
48
52
  [ [ "text/uri-list", 0, 0 ] ],
49
- Gdk::DragContext::ACTION_LINK)
50
- add_events(Gdk::Event::POINTER_MOTION_MASK)
53
+ Gdk::DragContext::ACTION_COPY)
51
54
 
52
55
  signal_connect("delete_event") { quit }
53
56
  signal_connect("scroll_event") { |w, e| handle_mouse_event(e) }
54
57
  signal_connect("button_press_event") { |w, e| handle_mouse_event(e) }
55
58
  signal_connect("key_press_event") { |w, e| handle_keyboard_event(e) }
56
59
  signal_connect("drag_data_received") { |w, dc, x, y, sd, type, time|
57
- handle_drop_event(sd.data, dc, time, false, true)
58
- }
59
- signal_connect("motion_notify_event") {
60
- window.set_cursor(LEFT_PTR)
61
- GLib::Source.remove(@cursor_timeout) if @cursor_timeout
62
- @cursor_timeout = GLib::Timeout.add(1000) {
63
- window.set_cursor(BLANK_CURSOR)
64
- } unless @mpv.is_paused
60
+ handle_drop_event(sd.uris, false, true)
61
+ Gtk::Drag.finish(dc, true, false, time)
65
62
  }
63
+ signal_connect("motion_notify_event") { mouse_cursor_timeout }
66
64
 
67
- vbox = Gtk::VBox.new
68
- add(vbox)
69
-
70
- args = process_args
71
- print_version if args.include?("--version")
72
- load_bindings
65
+ vbox = Gtk::VBox.new
66
+ @mpv = MpvWidget.new(args)
67
+ @progress_bar = ProgressBarWidget.new
68
+ @playlist = Playlist.new
69
+ @ignore_stop = false
73
70
 
74
- @mpv = MpvWidget.new(args, @config["scrobbler"])
75
71
  @mpv.signal_connect("file_changed") { |w, file|
76
- @playing = file
77
- @mpv.send("show_text ${media-title} 1500") if window.state.fullscreen?
72
+ @playing = URI.decode(file).sub(/^file:\/\/[^\/]*/, "")
73
+ # hacky work around when mpv catches drag and drop events.
74
+ unless @playlist.include?(@playing)
75
+ @playlist.clear(true)
76
+ if (file = create_playlist(@playing)) != @playing and File.directory?(@playing)
77
+ @playing = file
78
+ GLib::Idle.add { @mpv.load_file(@playing); false }
79
+ # mpv will fail to play the directory and then stop
80
+ # ignore the next stop event so the playlist does not advance
81
+ @ignore_stop = true
82
+ end
83
+ end
84
+ @mpv.send("show_text ${media-title} 1500") if window.state.fullscreen? and !@ignore_stop
78
85
  @playlist.set_selected(@playing)
79
86
  set_title(File.basename(@playing))
80
87
  }
81
- @mpv.signal_connect("length_changed") { |w, len|
82
- @length = len
83
- @playlist.update_length(@length)
84
- }
88
+ @mpv.signal_connect("playing_watched") { @playlist.on_playing_watched }
89
+ @mpv.signal_connect("length_changed") { |w, len| @playlist.update_length(@length = len) }
85
90
  @mpv.signal_connect("time_pos_changed") { |w, pos| @progress_bar.value = pos / @length.to_f }
86
91
  @mpv.signal_connect("stopped") {
87
92
  @progress_bar.value = 0
88
- next_file = @playlist.get_next
89
- @really_stop = next_file.nil? or @really_stop
90
93
  set_title(PACKAGE)
91
- if @really_stop and window.state.fullscreen?
92
- toggle_fullscreen
93
- elsif not @really_stop
94
- load_file(next_file, false)
94
+ unless @ignore_stop
95
+ next_file = @playlist.get_next
96
+ @really_stop ||= next_file.nil?
97
+ if !@really_stop
98
+ load_file(next_file, false)
99
+ elsif window.state.fullscreen?
100
+ toggle_fullscreen
101
+ end
95
102
  end
96
- @really_stop = false
103
+ @really_stop = @ignore_stop = false
97
104
  }
98
105
 
99
- vbox.pack_start(@mpv)
100
-
101
- @playlist = Playlist.new(@config["playlist_x"],
102
- @config["playlist_y"],
103
- @config["playlist_width"],
104
- @config["playlist_height"],
105
- @config["playlist_visible"])
106
-
107
- Gtk::Drag.dest_set(@playlist, Gtk::Drag::DEST_DEFAULT_ALL,
108
- [ [ "text/uri-list", 0, 0 ] ],
109
- Gdk::DragContext::ACTION_LINK)
110
-
111
106
  @playlist.signal_connect("open_file_chooser") { open_file_chooser }
112
107
  @playlist.signal_connect("drag_data_received") { |w, dc, x, y, sd, type, time|
113
- handle_drop_event(sd.data, dc, time, true, false)
108
+ handle_drop_event(sd.uris, true, false)
109
+ Gtk::Drag.finish(dc, true, false, time)
114
110
  }
115
111
  @playlist.signal_connect("play_entry") { |w, file| load_file(file, false, false, false, true) }
116
- @playlist.signal_connect("playing_removed") { @mpv.send("stop"); @really_stop = true }
112
+ @playlist.signal_connect("playing_removed") { @mpv.stop; @really_stop = true }
113
+
114
+ Gtk::Drag.dest_set(@playlist, Gtk::Drag::DEST_DEFAULT_ALL,
115
+ [ [ "text/uri-list", 0, 0 ] ],
116
+ Gdk::DragContext::ACTION_COPY)
117
117
 
118
- @progress_bar = ProgressBarWidget.new(@config["bar_color"],
119
- @config["head_color"],
120
- @config["progress_bar_height"])
121
118
  @progress_bar.add_events(Gdk::Event::BUTTON_PRESS_MASK)
122
119
  @progress_bar.signal_connect("button_press_event") { |w, e| handle_seek_event(e) }
123
- vbox.pack_start(@progress_bar, false)
124
120
 
121
+ vbox.pack_start(@mpv)
122
+ vbox.pack_start(@progress_bar, false)
123
+ add(vbox)
125
124
  show_all
126
- @mpv.start
127
-
128
- argv = ARGV.join(" ")
129
- if not argv.empty?
130
- load_file(argv)
131
- elsif not @config["playlist"].empty?
132
- @config["playlist"].each { |x| load_file(x, true, true, false) }
133
- @playlist.set_selected(@config["playlist_selected"])
134
- end
135
-
136
- Gtk.main
137
- end
138
-
139
- def load_config
140
- @config = {
141
- "width" => Gdk::Screen.default.width > 1280 ? 1280 : 853,
142
- "height" => Gdk::Screen.default.width > 1280 ? 726 : 486,
143
- "x" => -1,
144
- "y" => -1,
145
- "fullscreen_progressbar" => false,
146
- "progress_bar_visible" => true,
147
- "progress_bar_height" => 6,
148
- "bar_color" => "#8f5b5b",
149
- "head_color" => "#c48181",
150
- "playlist_width" => 360,
151
- "playlist_height" => 550,
152
- "playlist_x" => 0,
153
- "playlist_y" => 0,
154
- "playlist_visible" => true,
155
- "always_save_position" => false,
156
- "scrobbler" => "",
157
- "playlist_selected" => "",
158
- "playlist" => [ ],
159
- }
160
125
 
161
- if File.exists?(MAIN_CONF)
162
- File.readlines(MAIN_CONF).each { |line|
163
- key, _, val = line.partition("=")
164
- key = key.strip
165
- val = val.strip
166
- next unless @config.has_key?(key) and not key.start_with?("#")
167
-
168
- if @config[key].is_a?(Integer)
169
- val = val.to_i
170
- elsif @config[key].is_a?(TrueClass) or @config[key].is_a?(FalseClass)
171
- val = val == "true"
172
- elsif @config[key].is_a?(Array)
173
- val = val.split("|")
174
- elsif val.start_with?("#")
175
- begin
176
- c = Gdk::Color.parse(val)
177
- rescue
178
- puts("Invalid hexidecimal color for setting `#{key}': `#{val}")
179
- c = Gdk::Color.parse(@config[key])
180
- end
181
- val = c
182
- end
183
-
184
- @config[key] = val
185
- }
186
- end
187
- end
188
-
189
- def process_args
190
- args = [ ]
191
- ARGV.dup.each { |arg|
192
- if arg.start_with?("-")
193
- args.push(arg)
194
- ARGV.delete(arg)
126
+ if !files.empty?
127
+ if files.length > 1
128
+ files.each_with_index { |x, i| load_file(x, true, i != 0, false) }
129
+ else
130
+ load_file(files[0])
195
131
  end
196
- }
197
- return args
198
- end
199
-
200
- def load_bindings
201
- @mouse_bindings = [ ]
202
- @key_bindings = [ ]
203
- if File.exists?(INPUT_CONF)
204
- File.readlines(INPUT_CONF).each { |line|
205
- line = line.strip
206
- if line.start_with?("MOUSE_BTN")
207
- # 4 = up, 5 = down, 6 = left, 7 = right
208
- button, cmd = line.match(/MOUSE_BTN(\d+)(?:_DBL)?\s+(.+)$/).captures
209
- button = button.to_i + 1
210
- type = (4..7).include?(button) ? Gdk::Event::SCROLL :
211
- line.include?("DBL") ? Gdk::Event::BUTTON2_PRESS : Gdk::Event::BUTTON_PRESS
212
- @mouse_bindings[type] = [ ] if @mouse_bindings[type].nil?
213
- @mouse_bindings[type][button] = cmd
214
- elsif not line.empty?
215
- key, cmd = line.match(/^([^\s]+)\s+(.+)$/).captures
216
- if name = KEY_NAMES[key.downcase]
217
- keyval = Gdk::Keyval.from_name(name)
218
- else
219
- keyval = Gdk::Keyval.from_name(key)
220
- end
221
-
222
- @key_bindings[keyval] = cmd if keyval > 0
223
- end
224
- }
132
+ elsif Config["playlist"].length > 0
133
+ Config["playlist"].each { |x| @playlist.add_file(x["file"], x["length"], x["watched"]) }
134
+ @playlist.set_selected(Config["playlist_selected"])
225
135
  end
136
+
137
+ Gtk.main
226
138
  end
227
139
 
228
- def load_file(file, add_to_playlist=true, do_not_play=false, auto_add=true, force_play=false)
229
- return if file.nil? or file.empty?
140
+ private
141
+ def load_file(file, add_to_playlist = true, do_not_play = false, auto_add = true, force_play = false)
230
142
  file = File.expand_path(file) if file[0] == "~"
143
+ return unless (File.directory?(file) or valid_video_file?(file))
231
144
 
232
145
  if add_to_playlist
233
146
  if @playlist.count == 0 and auto_add and file !~ /^https?:\/\//
234
- dir = File.directory?(file) ? file : File.dirname(file)
235
- entries = Dir.entries(dir).sort
236
- entries.delete_if { |x| x.start_with?(".") or not valid_video_file(x) }
237
- entries.map { |x|
238
- x = dir + "/" + x
239
- @playlist.add_file(x)
240
- file = x if file == dir
241
- }
147
+ file = create_playlist(file)
242
148
  else
243
149
  @playlist.add_file(file)
244
150
  end
@@ -247,19 +153,23 @@ module Ampv
247
153
  @mpv.load_file(file, force_play) unless do_not_play
248
154
  end
249
155
 
156
+ def create_playlist(file)
157
+ dir = File.directory?(file) ? file : File.dirname(file)
158
+ entries = Dir.entries(dir).sort.map { |x| "#{dir}/#{x}" }
159
+ entries.delete_if { |x| x[0] == "." || !valid_video_file?(x) }
160
+ entries.each { |x| @playlist.add_file(x) }
161
+ file == dir ? entries[0] : file
162
+ end
163
+
250
164
  def handle_mouse_event(e)
165
+ mouse_cursor_timeout
251
166
  button = e.event_type == Gdk::Event::SCROLL ? WHEEL_BUTTONS[e.direction] : e.button
252
- return if @mouse_bindings[e.event_type].nil?
253
-
254
- process_cmd(@mouse_bindings[e.event_type][button])
255
-
256
- return true
167
+ return if Config["mouse_bindings"][e.event_type].nil?
168
+ process_cmd(Config["mouse_bindings"][e.event_type][button])
257
169
  end
258
170
 
259
171
  def handle_keyboard_event(e)
260
- process_cmd(@key_bindings[e.keyval])
261
-
262
- return true
172
+ process_cmd(Config["key_bindings"][e.keyval])
263
173
  end
264
174
 
265
175
  def handle_seek_event(e)
@@ -267,19 +177,19 @@ module Ampv
267
177
  pos = e.x / allocation.width * @length.to_f
268
178
  seek("seek #{pos} absolute")
269
179
  end
270
-
271
- return true
272
180
  end
273
181
 
274
- def handle_drop_event(data, context, time, do_not_play, replace)
275
- files = URI.decode(data).gsub("file://", "").split("\r\n")
182
+ def handle_drop_event(files, do_not_play, replace)
276
183
  @playlist.clear if replace
184
+ files.each { |x| load_file(URI.decode(x).sub(/^file:\/\/[^\/]*/, ""), true, do_not_play) }
185
+ end
277
186
 
278
- files.each { |x|
279
- load_file(x, true, do_not_play) if valid_video_file(x)
280
- }
281
-
282
- Gtk::Drag.finish(context, true, true, time)
187
+ def mouse_cursor_timeout
188
+ window.set_cursor(LEFT_PTR)
189
+ GLib::Source.remove(@cursor_timeout) if @cursor_timeout
190
+ @cursor_timeout = GLib::Timeout.add(1000) {
191
+ window.set_cursor(BLANK_CURSOR)
192
+ } unless @mpv.is_paused
283
193
  end
284
194
 
285
195
  def process_cmd(cmd)
@@ -288,6 +198,8 @@ module Ampv
288
198
  toggle_fullscreen
289
199
  when "cycle pause"
290
200
  @mpv.play_pause
201
+ when "stop"
202
+ @mpv.stop
291
203
  when "cycle playlist"
292
204
  @playlist.visible? ? @playlist.hide : @playlist.show
293
205
  when /seek /
@@ -309,11 +221,11 @@ module Ampv
309
221
  else
310
222
  @mpv.send(cmd) if cmd
311
223
  end
224
+ true
312
225
  end
313
226
 
314
227
  def seek(cmd)
315
- cmd = "no-osd " + cmd unless cmd.start_with?("no-osd") or
316
- not @progress_bar.visible?
228
+ cmd = "no-osd " + cmd unless cmd.start_with?("no-osd") or !@progress_bar.visible?
317
229
  @mpv.send(cmd)
318
230
  @mpv.send("get_property time-pos")
319
231
  end
@@ -323,9 +235,10 @@ module Ampv
323
235
  @progress_bar.show unless @progress_bar_user_hidden
324
236
  unfullscreen
325
237
  else
326
- @progress_bar.hide unless @config["fullscreen_progressbar"]
238
+ @progress_bar.hide unless Config["fullscreen_progressbar"]
327
239
  fullscreen
328
240
  end
241
+ mouse_cursor_timeout
329
242
  end
330
243
 
331
244
  def toggle_progress_bar
@@ -338,9 +251,8 @@ module Ampv
338
251
  end
339
252
  end
340
253
 
341
- def valid_video_file(x)
342
- return (VIDEO_EXTS.include?(File.extname(x)) or
343
- `file -b --mime-type "#{x}"`.start_with?("video"))
254
+ def valid_video_file?(x)
255
+ return (x and File.exists?(x) and !File.directory?(x) and VIDEO_EXTS.include?(File.extname(x).downcase))
344
256
  end
345
257
 
346
258
 
@@ -350,7 +262,6 @@ module Ampv
350
262
  [ Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL ],
351
263
  [ Gtk::Stock::OPEN, Gtk::Dialog::RESPONSE_ACCEPT ])
352
264
  dialog.select_multiple = true
353
- do_not_play = @playlist.count > 0
354
265
 
355
266
  filter = Gtk::FileFilter.new
356
267
  filter.name = "Video Files"
@@ -363,35 +274,35 @@ module Ampv
363
274
  dialog.add_filter(filterAll)
364
275
 
365
276
  dialog.filenames.each { |x|
277
+ do_not_play = @playlist.count > 0
366
278
  load_file(x, true, do_not_play)
367
- do_not_play = true
368
279
  } if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
369
280
  dialog.destroy
370
281
  end
371
282
 
372
283
  def print_version
373
- puts("#{PACKAGE} - v#{VERSION}\n" +
374
- "ruby #{RUBY_VERSION}-p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE} revision #{RUBY_REVISION}) [#{RUBY_PLATFORM}]\n\n" +
375
- `#{MpvWidget::PATH} --version`)
284
+ puts("#{PACKAGE} - v#{VERSION} (C) 2013-2014 ahoka\n" +
285
+ `ruby --version` +
286
+ `#{MpvWidget::PATH} --version`)
376
287
  exit
377
288
  end
378
289
 
379
- def quit(watch_later=false)
380
- @config["x"],
381
- @config["y"],
382
- @config["width"],
383
- @config["height"] = window.geometry unless window.state.fullscreen?
384
- @config["playlist_x"],
385
- @config["playlist_y"],
386
- @config["playlist_width"],
387
- @config["playlist_height"] = @playlist.window.geometry
388
- @config["playlist_visible"] = @playlist.visible?
389
- @config["playlist_selected"] = @playing
390
- @config["playlist"] = @playlist.get_entries.join("|")
391
- @config["progress_bar_visible"] = @progress_bar.visible?
392
- File.open(MAIN_CONF, "w") { |file| @config.each { |k, v| file.puts("#{k}=#{v}") } }
393
-
394
- @mpv.quit(@config["always_save_position"] ? true : watch_later)
290
+ def quit(watch_later = false)
291
+ Config["x"],
292
+ Config["y"],
293
+ Config["width"],
294
+ Config["height"] = window.geometry unless window.state.fullscreen?
295
+ Config["playlist_x"],
296
+ Config["playlist_y"],
297
+ Config["playlist_width"],
298
+ Config["playlist_height"] = @playlist.window.geometry
299
+ Config["playlist_visible"] = @playlist.visible?
300
+ Config["playlist_selected"] = @playing
301
+ Config["playlist"] = @playlist.get_entries.to_json
302
+ Config["progress_bar_visible"] = @progress_bar.visible?
303
+ Config.save
304
+
305
+ @mpv.quit(Config["always_save_position"] ? true : watch_later)
395
306
  Gtk.main_quit
396
307
  end
397
308
  end
@@ -0,0 +1,120 @@
1
+ module Ampv
2
+ class Config
3
+
4
+ @@config_file = "#{(ENV["XDG_CONFIG_HOME"] || "#{Dir.home}/.config")}/ampv.conf"
5
+ @@input_config = File.exists?("#{Dir.home}/.mpv/input.conf") ?
6
+ "#{Dir.home}/.mpv/input.conf" : File.expand_path("../../../input.conf", __FILE__)
7
+ @@key_names = {
8
+ "esc" => "Escape",
9
+ "space" => "space",
10
+ "right" => "Right",
11
+ "left" => "Left",
12
+ "up" => "Up",
13
+ "down" => "Down",
14
+ "pgup" => "Page_Up",
15
+ "pgdwn" => "Page_Down",
16
+ "home" => "Home",
17
+ "end" => "End",
18
+ "ins" => "Insert",
19
+ "del" => "Delete",
20
+ }
21
+ @@config = { }
22
+ @@defaults = {
23
+ "width" => Gdk::Screen.default.width > 1280 ? 1280 : 853,
24
+ "height" => Gdk::Screen.default.width > 1280 ? 726 : 486,
25
+ "x" => -1,
26
+ "y" => -1,
27
+ "fullscreen_progressbar" => false,
28
+ "progress_bar_visible" => true,
29
+ "progress_bar_height" => 6,
30
+ "bar_color" => Gdk::Color.parse("#8f5b5b"),
31
+ "head_color" => Gdk::Color.parse("#c48181"),
32
+ "playlist_width" => 360,
33
+ "playlist_height" => 550,
34
+ "playlist_x" => 0,
35
+ "playlist_y" => 0,
36
+ "playlist_visible" => true,
37
+ "always_save_position" => false,
38
+ "scrobbler" => "",
39
+ "playlist_selected" => "",
40
+ "playlist" => [ ],
41
+ "key_bindings" => [ ],
42
+ "mouse_bindings" => [ ]
43
+ }
44
+
45
+ def self.load
46
+ if File.exists?(@@config_file)
47
+ File.readlines(@@config_file).each { |line|
48
+ key, _, val = line.partition("=")
49
+ key.strip!
50
+ val.strip!
51
+ next unless @@defaults.has_key?(key) and key[0] != "#"
52
+
53
+ if @@defaults[key].is_a?(Integer)
54
+ val = val.to_i
55
+ elsif @@defaults[key].is_a?(TrueClass) or @@defaults[key].is_a?(FalseClass)
56
+ val = val == "true"
57
+ elsif @@defaults[key].is_a?(Gdk::Color)
58
+ begin
59
+ val = Gdk::Color.parse(val)
60
+ rescue
61
+ puts("Invalid hexidecimal color for setting `#{key}': `#{val}'")
62
+ next
63
+ end
64
+ elsif key == "playlist"
65
+ begin
66
+ val = JSON.parse(val)
67
+ rescue
68
+ puts("Failed to parse playlist JSON array.")
69
+ next
70
+ end
71
+ end
72
+
73
+ @@config[key] = val
74
+ }
75
+ end
76
+
77
+ # load input config
78
+ self["mouse_bindings"] = [ ]
79
+ self["key_bindings"] = [ ]
80
+ if File.exists?(@@input_config)
81
+ File.readlines(@@input_config).each { |line|
82
+ line.strip!
83
+ if line.start_with?("MOUSE_BTN")
84
+ # 4 = up, 5 = down, 6 = left, 7 = right
85
+ button, cmd = line.match(/MOUSE_BTN(\d+)(?:_DBL)?\s+(.+)$/).captures
86
+ button = button.to_i + 1
87
+ type = (4..7).include?(button) ? Gdk::Event::SCROLL :
88
+ line.include?("DBL") ? Gdk::Event::BUTTON2_PRESS : Gdk::Event::BUTTON_PRESS
89
+ self["mouse_bindings"][type] = [ ] if self["mouse_bindings"][type].nil?
90
+ self["mouse_bindings"][type][button] = cmd
91
+ elsif !line.empty?
92
+ key, cmd = line.match(/^([^\s]+)\s+(.+)$/).captures
93
+ if name = @@key_names[key.downcase]
94
+ keyval = Gdk::Keyval.from_name(name)
95
+ else
96
+ keyval = Gdk::Keyval.from_name(key)
97
+ end
98
+
99
+ self["key_bindings"][keyval] = cmd if keyval > 0
100
+ end
101
+ }
102
+ end
103
+ end
104
+
105
+ def self.[](key)
106
+ @@config[key].nil? ? @@defaults[key] : @@config[key]
107
+ end
108
+
109
+ def self.[]=(key, value)
110
+ @@config[key] = value
111
+ end
112
+
113
+ def self.save
114
+ File.open(@@config_file, "w") { |file|
115
+ @@config.each { |k, v| file.puts("#{k}=#{v}") unless k == "key_bindings" or k == "mouse_bindings" }
116
+ }
117
+ end
118
+ end
119
+ end
120
+
@@ -1,4 +1,5 @@
1
1
  require "fifo"
2
+ require "tmpdir"
2
3
 
3
4
  module Ampv
4
5
  class MpvWidget < Gtk::EventBox
@@ -6,112 +7,115 @@ module Ampv
6
7
  type_register
7
8
  signal_new("file_changed", GLib::Signal::RUN_FIRST, nil, nil, String)
8
9
  signal_new("length_changed", GLib::Signal::RUN_FIRST, nil, nil, Integer)
10
+ signal_new("playing_watched", GLib::Signal::RUN_FIRST, nil, nil)
9
11
  signal_new("time_pos_changed", GLib::Signal::RUN_FIRST, nil, nil, Float)
10
12
  signal_new("stopped", GLib::Signal::RUN_FIRST, nil, nil)
11
13
 
12
- PATH = "/usr/bin/mpv"
14
+ ENV["PATH"].split(":").each { |x|
15
+ if File.executable?("#{x}/mpv")
16
+ PATH = "#{x}/mpv"
17
+ break
18
+ end
19
+ }
13
20
 
14
21
  attr_reader :is_paused
15
22
 
16
- def initialize(args, scrobbler)
23
+ def initialize(args)
17
24
  if args.include?("--debug")
18
25
  args.delete("--debug")
19
26
  @debug = true
20
27
  end
21
28
 
22
- @scrobbler = scrobbler
23
- @mpv_path = "/usr/bin/mpv"
24
29
  @mpv_options = args.join(" ")
25
- @mpv_fifo = "/tmp/mpv.fifo." + Process.pid.to_s
30
+ @mpv_fifo = "#{Dir.tmpdir}/mpv.fifo." + Process.pid.to_s
31
+ @is_paused = true
26
32
 
27
33
  super()
34
+ signal_connect("realize") { start }
28
35
 
29
36
  @socket = Gtk::Socket.new
30
37
  @socket.modify_bg(Gtk::STATE_NORMAL, Gdk::Color.parse("#000"))
31
-
32
38
  @socket.signal_connect("plug_removed") { signal_emit("stopped"); true }
33
- add(@socket)
34
- end
35
39
 
36
- def start
37
- if @thread.nil?
38
- @fifo = Fifo.new(@mpv_fifo, :w, :nowait)
39
-
40
- cmd = "#{@mpv_path} \
41
- --identify \
42
- --idle \
43
- --input-file=#{@mpv_fifo} \
44
- --no-mouse-movements \
45
- --cursor-autohide=no \
46
- --msglevel=all=info \
47
- --wid=#{@socket.id} #{@mpv_options}"
48
- @thread = Thread.new { slave_reader(cmd) }
49
- end
40
+ add(@socket)
50
41
  end
51
42
 
52
43
  def send(cmd)
53
- @fifo.puts(cmd) unless @fifo.nil?
44
+ @fifo.puts(cmd) if @fifo
54
45
  end
55
46
 
56
- def load_file(file, force_play=false)
47
+ def load_file(file, force_play = false)
57
48
  send("loadfile \"#{file}\"")
58
49
  @force_play = force_play
59
50
  end
60
51
 
61
52
  def play_pause
62
53
  send("cycle pause")
63
- @is_paused = @is_paused ? false : true
54
+ @is_paused = !@is_paused
55
+ end
56
+
57
+ def stop
58
+ send("stop")
59
+ @is_paused = true
64
60
  end
65
61
 
66
62
  def quit(watch_later)
67
63
  send("quit" + (watch_later ? "_watch_later" : ""))
68
- @thread.join unless @thread.nil? or not @thread.alive?
64
+ @thread.kill if @thread
65
+ @prog_thread.kill if @prog_thread
69
66
  @fifo.close
70
- File.delete(@mpv_fifo)
71
67
  end
72
68
 
73
69
  private
74
- def slave_reader(cmd)
75
- @pipe = IO.popen(cmd, "a+")
76
-
77
- until @pipe.nil? or @pipe.closed? or @pipe.eof?
78
- line = @pipe.readline.chomp
79
- if line.include?("ID_FILENAME=")
80
- signal_emit("file_changed", (@playing = line.partition("ID_FILENAME=").last))
81
- send("get_property pause") # saved position also saves play state
82
-
83
- @prog_thread.kill unless @prog_thread.nil? or not @prog_thread.alive?
84
- @prog_thread = Thread.new { progress_update }
85
- elsif line.start_with?("ID_LENGTH=")
86
- signal_emit("length_changed", (@length = line.rpartition("=").last.to_i))
87
- elsif line.start_with?("ANS_pause=")
88
- @is_paused = line.rpartition("=").last == "yes"
89
- play_pause if @force_play and @is_paused
90
- elsif line.start_with?("ANS_time-pos=")
91
- signal_emit("time_pos_changed", line.rpartition("=").last.to_f)
92
- end
93
-
94
- if @debug or line.start_with?("Error")
95
- puts(line) unless line.start_with?("ANS_time-pos=") or
96
- line.start_with?("ANS_ERROR") or
97
- line.start_with?("Failed to get") or
98
- line.start_with?("Command ")
99
- end
100
- end
70
+ def start
71
+ return if @thread and @thread.alive?
72
+ @fifo = Fifo.new(@mpv_fifo, :w, :nowait)
73
+ ObjectSpace.define_finalizer(self, proc { File.delete(@mpv_fifo) })
74
+
75
+ cmd = "#{PATH} \
76
+ --idle \
77
+ --input-file=#{@mpv_fifo} \
78
+ --cursor-autohide=no \
79
+ --no-mouse-movements \
80
+ --msglevel=all=info \
81
+ --wid=#{@socket.id} #{@mpv_options}"
82
+
83
+ @thread = Thread.start {
84
+ IO.popen(cmd) { |io|
85
+ io.each { |line|
86
+ line.chomp!
87
+ if line.include?("Playing: ")
88
+ signal_emit("file_changed", (@playing = line.partition("Playing: ")[-1]))
89
+ send("get_property length")
90
+ send("get_property time-pos")
91
+ send("get_property pause")
92
+ elsif line.start_with?("ANS_length=")
93
+ signal_emit("length_changed", (@length = line.rpartition("=")[-1].to_i))
94
+ elsif line.start_with?("ANS_pause=")
95
+ @is_paused = line.rpartition("=")[-1] == "yes"
96
+ @prog_thread.kill if @prog_thread
97
+ @prog_thread = Thread.new { progress_update }
98
+ play_pause if @force_play and @is_paused
99
+ elsif line.start_with?("ANS_time-pos=")
100
+ signal_emit("time_pos_changed", line.rpartition("=")[-1].to_f)
101
+ end
102
+
103
+ puts(line) if @debug and !line.start_with?("ANS_")
104
+ }
105
+ }
106
+ }
101
107
  end
102
108
 
103
109
  def progress_update
104
110
  scrobbled = false
105
111
  watched = 0
106
-
107
112
  loop {
108
113
  send("get_property time-pos") unless @is_paused
109
-
110
- unless @scrobbler.nil? or scrobbled or watched < @length * 0.5
111
- system("#{@scrobbler} \"#{@playing}\"")
114
+ unless scrobbled or watched < @length * 0.5
115
+ system("#{Config["scrobbler"]} \"#{@playing}\"") if Config["scrobbler"]
116
+ signal_emit("playing_watched")
112
117
  scrobbled = true
113
118
  end
114
-
115
119
  sleep(1)
116
120
  watched += 1 unless @is_paused
117
121
  }
@@ -119,9 +123,10 @@ module Ampv
119
123
 
120
124
  def signal_do_file_changed(file) end
121
125
  def signal_do_length_changed(len) end
126
+ def signal_do_playing_watched; end
122
127
  def signal_do_time_pos_changed(pos) end
123
- def signal_do_stopped()
124
- @prog_thread.kill
128
+ def signal_do_stopped
129
+ @prog_thread.kill if @prog_thread
125
130
  end
126
131
  end
127
132
  end
@@ -1,5 +1,3 @@
1
- #require "gtk2"
2
-
3
1
  module Ampv
4
2
  class Playlist < Gtk::Window
5
3
 
@@ -8,7 +6,9 @@ module Ampv
8
6
  signal_new("playing_removed", GLib::Signal::RUN_FIRST, nil, nil)
9
7
  signal_new("open_file_chooser", GLib::Signal::RUN_FIRST, nil, nil)
10
8
 
11
- def initialize(x, y, w, h, is_visible)
9
+ WATCHED_PIXBUF = Gtk::Invisible.new.render_icon(Gtk::Stock::OK, Gtk::IconSize::MENU)
10
+
11
+ def initialize
12
12
  buttons = {
13
13
  [ Gtk::Stock::OPEN, "Add to Playlist" ] => lambda { signal_emit("open_file_chooser") },
14
14
  [ Gtk::Stock::GO_UP, "Move Up" ] => lambda { move_selected_up },
@@ -17,11 +17,11 @@ module Ampv
17
17
  [ Gtk::Stock::CLEAR, "Clear Playlist" ] => lambda { clear }
18
18
  }
19
19
 
20
- super()
20
+ super
21
21
  set_title("Playlist - #{PACKAGE}")
22
- set_default_size(w, h)
22
+ set_default_size(Config["playlist_width"], Config["playlist_height"])
23
23
  set_skip_taskbar_hint(true)
24
- move(x, y)
24
+ move(Config["playlist_x"], Config["playlist_y"])
25
25
 
26
26
  signal_connect("show") { move(@pos[0], @pos[1]) unless @pos.nil? }
27
27
  signal_connect("hide") { @pos = window.root_origin }
@@ -31,38 +31,36 @@ module Ampv
31
31
  hide_on_delete if e.keyval == Gdk::Keyval::GDK_Escape
32
32
  }
33
33
 
34
- vbox = Gtk::VBox.new(false, 10)
35
- vbox.border_width = 10
36
- add(vbox)
34
+ vbox = Gtk::VBox.new(false, 10)
35
+ sw = Gtk::ScrolledWindow.new
36
+ @model = Gtk::ListStore.new(String, String, Gdk::Pixbuf)
37
+ @treeview = Gtk::TreeView.new(@model)
38
+ hbox = Gtk::HBox.new(true, 5)
39
+ @menu = Gtk::Menu.new
37
40
 
38
- sw = Gtk::ScrolledWindow.new
41
+ vbox.border_width = 10
39
42
  sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
40
- vbox.pack_start(sw)
41
43
 
42
- @model = Gtk::ListStore.new(String, String)
43
- @treeview = Gtk::TreeView.new(@model)
44
44
  @treeview.enable_search = false
45
45
  @treeview.rubber_banding = true
46
46
  @treeview.reorderable = true
47
47
  @treeview.selection.mode = Gtk::SELECTION_MULTIPLE
48
+ @treeview.tooltip_column = 0
48
49
 
49
- @treeview.signal_connect("row_activated") { |_w, p, c|
50
+ @treeview.signal_connect("row_activated") { |w, p, c|
50
51
  signal_emit("play_entry", @model.get_iter(p)[0])
51
52
  }
52
- @treeview.signal_connect("key_press_event") { |_w, e|
53
+ @treeview.signal_connect("key_press_event") { |w, e|
53
54
  remove_selected if e.keyval == Gdk::Keyval::GDK_Delete
54
55
  }
55
- @treeview.signal_connect("button_press_event") { |_w, e|
56
- @menu.popup(nil, nil, e.button, e.time) if
57
- e.event_type == Gdk::Event::BUTTON_PRESS and e.button == 3
56
+ @treeview.signal_connect("button_press_event") { |w, e|
57
+ @menu.popup(nil, nil, e.button, e.time) if e.event_type == Gdk::Event::BUTTON_PRESS and e.button == 3
58
58
  }
59
59
 
60
- ["Name", "Length"].each_with_index { |_x, i|
60
+ ["Name", "Length"].each_with_index { |x, i|
61
61
  renderer = Gtk::CellRendererText.new
62
- column = Gtk::TreeViewColumn.new(_x,
63
- renderer,
64
- :text => i)
65
- if _x == "Name"
62
+ column = Gtk::TreeViewColumn.new(x, renderer, :text => i)
63
+ if x == "Name"
66
64
  renderer.ellipsize = Pango::ELLIPSIZE_MIDDLE
67
65
  column.expand = true
68
66
  column.set_cell_data_func(renderer) { |t, c, m, j|
@@ -72,105 +70,87 @@ module Ampv
72
70
 
73
71
  @treeview.append_column(column)
74
72
  }
75
-
76
- sw.add(@treeview)
77
-
78
- hbox = Gtk::HBox.new(true, 5)
73
+ @treeview.append_column(Gtk::TreeViewColumn.new("", Gtk::CellRendererPixbuf.new, :pixbuf => 2))
79
74
 
80
75
  buttons.each { |k, v|
81
76
  button = Gtk::Button.new
77
+ item = Gtk::ImageMenuItem.new(k[1])
78
+
82
79
  button.image = Gtk::Image.new(k[0], Gtk::IconSize::BUTTON)
83
80
  button.height_request = 36
84
81
  button.set_tooltip_text(k[1])
85
82
  button.signal_connect("clicked") { v.call }
86
- hbox.pack_start(button)
87
- }
88
-
89
- vbox.pack_start(hbox, false)
90
83
 
91
- @menu = Gtk::Menu.new
92
- buttons.each { |k, v|
93
- item = Gtk::ImageMenuItem.new(k[1])
94
84
  item.image = Gtk::Image.new(k[0], Gtk::IconSize::MENU)
95
85
  item.signal_connect("activate") { v.call }
86
+
87
+ hbox.pack_start(button)
96
88
  @menu.append(item)
97
89
  }
90
+
91
+ sw.add(@treeview)
92
+ vbox.pack_start(sw)
93
+ vbox.pack_start(hbox, false)
94
+ add(vbox)
98
95
  @menu.show_all
99
96
 
100
- show_all if is_visible
97
+ show_all if Config["playlist_visible"]
101
98
  end
102
99
 
103
100
  def count
104
101
  i = 0
105
102
  @model.each { i += 1 }
106
- return i
103
+ i
107
104
  end
108
105
 
109
- def add_file(file)
110
- contains = false
111
- @model.each { |m, p, iter|
112
- if iter[0] == file
113
- contains = true
114
- break
115
- end
116
- }
117
- unless contains
106
+ def add_file(file, length = nil, watched = false)
107
+ unless include?(file)
118
108
  iter = @model.append
119
109
  iter[0] = file
110
+ iter[1] = length if length
111
+ iter[2] = WATCHED_PIXBUF if watched
120
112
  end
121
113
  end
122
114
 
123
- def move_selected_up
124
- @treeview.selection.selected_rows.each { |path|
125
- tmp = path.dup
126
- break if not tmp.prev! or @treeview.selection.selected_rows.include?(tmp)
127
- @model.move_before(@model.get_iter(path), @model.get_iter(tmp))
128
- }
115
+ def on_playing_watched
116
+ @playing_iter[2] = WATCHED_PIXBUF
129
117
  end
130
118
 
131
- def move_selected_down
132
- @treeview.selection.selected_rows.reverse.each { |path|
133
- break if @treeview.selection.selected_rows.include?((tmp = path.dup.next!))
134
- tmp = @model.get_iter(tmp)
135
- @model.move_after(@model.get_iter(path), tmp) unless tmp.nil?
136
- }
137
- end
138
-
139
- def remove_selected
140
- to_remove = [ ]
141
- @treeview.selection.selected_rows.each { |path|
142
- to_remove.push(@model.get_iter(path))
143
- }
144
- to_remove.each { |iter|
145
- signal_emit("playing_removed") if iter[0] == @playing
146
- @model.remove(iter)
147
- }
119
+ def include?(file)
120
+ @model.each { |m, p, iter| return true if iter[0] == file }
121
+ false
148
122
  end
149
123
 
150
124
  def get_next
151
125
  @model.each { |m, p, iter|
152
- return iter.next! ? iter[0] : nil if iter[0] == @playing
126
+ return iter.next! ? iter[0] : nil if iter == @playing_iter
153
127
  }
154
- return nil
128
+ nil
155
129
  end
156
130
 
157
131
  def get_prev
158
132
  prev = nil
159
133
  @model.each { |m, p, iter|
160
- return prev if iter[0] == @playing
134
+ return prev if iter == @playing_iter
161
135
  prev = iter[0]
162
136
  }
163
137
  end
164
138
 
165
139
  def get_entries
166
140
  entries = [ ]
167
- @model.each { |m, p, iter| entries.push(iter[0]) }
168
- return entries
141
+ @model.each { |m, p, iter|
142
+ entries << {
143
+ "file" => iter[0],
144
+ "length" => iter[1] ? iter[1] : "",
145
+ "watched" => !iter[2].nil?
146
+ }
147
+ }
148
+ entries
169
149
  end
170
150
 
171
- def clear
151
+ def clear(quiet = false)
172
152
  @model.clear
173
- signal_emit("playing_removed")
153
+ signal_emit("playing_removed") unless quiet or @playing_iter.nil?
174
154
  end
175
155
 
176
156
  def set_selected(file)
@@ -179,6 +159,7 @@ module Ampv
179
159
  @model.each { |m, p, iter|
180
160
  if iter[0] == @playing
181
161
  @treeview.set_cursor(Gtk::TreePath.new(i), nil, false)
162
+ @playing_iter = iter
182
163
  break
183
164
  end
184
165
  i += 1
@@ -186,15 +167,40 @@ module Ampv
186
167
  end
187
168
 
188
169
  def update_length(length)
189
- @model.each { |m, p, iter|
190
- if iter[0] == @playing
191
- iter[1] = Time.at(length).utc.strftime("%H:%M:%S") unless length == 0
192
- break
193
- end
194
- }
170
+ return if length == 0
171
+ @playing_iter[1] = Time.at(length).utc.strftime("%H:%M:%S") if @playing_iter
195
172
  end
196
173
 
197
174
  private
175
+ def move_selected_up
176
+ @treeview.selection.selected_rows.each { |path|
177
+ tmp = path.dup
178
+ # prev!: Returns: true if path has a previous node, and the move was made.
179
+ break unless tmp.prev! or @treeview.selection.selected_rows.include?(tmp)
180
+ @model.move_before(@model.get_iter(path), @model.get_iter(tmp))
181
+ }
182
+ end
183
+
184
+ def move_selected_down
185
+ @treeview.selection.selected_rows.reverse.each { |path|
186
+ # next!: Moves the path to point to the next node at the current depth. Returns self
187
+ break if @treeview.selection.selected_rows.include?(tmp = path.dup.next!)
188
+ tmp = @model.get_iter(tmp)
189
+ @model.move_after(@model.get_iter(path), tmp) unless tmp.nil?
190
+ }
191
+ end
192
+
193
+ def remove_selected
194
+ to_remove = [ ]
195
+ @treeview.selection.selected_rows.each { |path|
196
+ to_remove << @model.get_iter(path)
197
+ }
198
+ to_remove.each { |iter|
199
+ signal_emit("playing_removed") if iter == @playing_iter
200
+ @model.remove(iter)
201
+ }
202
+ end
203
+
198
204
  def signal_do_play_entry(file) end
199
205
  def signal_do_playing_removed() end
200
206
  def signal_do_open_file_chooser() end
@@ -1,30 +1,27 @@
1
-
2
1
  module Ampv
3
2
  class ProgressBarWidget < Gtk::DrawingArea
4
- def initialize(bar_color, head_color, height)
5
- super()
3
+
4
+ def initialize
5
+ super
6
6
  modify_bg(Gtk::STATE_NORMAL, Gdk::Color.parse("#000"))
7
- set_size_request(-1, height)
7
+ set_size_request(-1, Config["progress_bar_height"])
8
8
 
9
- signal_connect("expose_event") {
10
- @cx = window.create_cairo_context
11
- draw_widget
12
- }
13
9
  @value = 0
14
- @bar_color = bar_color
15
- @head_color = head_color
16
- end
10
+ @bar_color = Config["bar_color"]
11
+ @head_color = Config["head_color"]
17
12
 
18
- def draw_widget
19
- @cx.set_source_color(@bar_color)
20
- @cx.rectangle(0, 0, (allocation.width * @value.to_f).round, 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).round, 0, 2, allocation.height)
13
+ signal_connect("expose_event") {
14
+ @cx = window.create_cairo_context
15
+ @cx.set_source_color(@bar_color)
16
+ @cx.rectangle(0, 0, (allocation.width * @value.to_f).round, allocation.height)
26
17
  @cx.fill
27
- end
18
+
19
+ if @value > 0
20
+ @cx.set_source_color(@head_color)
21
+ @cx.rectangle((allocation.width * @value.to_f).round, 0, 2, allocation.height)
22
+ @cx.fill
23
+ end
24
+ }
28
25
  end
29
26
 
30
27
  def value=(v)
@@ -1,6 +1,5 @@
1
-
2
1
  module Ampv
3
2
  PACKAGE = "ampv"
4
- VERSION = "1.0.3"
3
+ VERSION = "1.1.0"
5
4
  end
6
5
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ampv
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ahoka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-30 00:00:00.000000000 Z
11
+ date: 2014-01-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: gtk2
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ! '>='
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
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'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: ruby-fifo
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -47,10 +61,11 @@ extra_rdoc_files: []
47
61
  files:
48
62
  - LICENSE
49
63
  - input.conf
64
+ - lib/ampv/config.rb
50
65
  - lib/ampv/playlist.rb
51
- - lib/ampv/mpvwidget.rb
52
66
  - lib/ampv/version.rb
53
67
  - lib/ampv/progressbarwidget.rb
68
+ - lib/ampv/mpvwidget.rb
54
69
  - lib/ampv.rb
55
70
  - bin/ampv
56
71
  homepage: https://github.com/ahodesuka/ampv
@@ -72,8 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
87
  - !ruby/object:Gem::Version
73
88
  version: '0'
74
89
  requirements:
75
- - gtk2
76
- - mpv
90
+ - mpv v0.3.x
77
91
  rubyforge_project:
78
92
  rubygems_version: 2.0.3
79
93
  signing_key: