ampv 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/LICENSE +1 -1
- data/bin/ampv +4 -0
- data/input.conf +17 -16
- data/lib/ampv.rb +132 -221
- data/lib/ampv/config.rb +120 -0
- data/lib/ampv/mpvwidget.rb +66 -61
- data/lib/ampv/playlist.rb +87 -81
- data/lib/ampv/progressbarwidget.rb +17 -20
- data/lib/ampv/version.rb +1 -2
- metadata +19 -5
checksums.yaml
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
---
|
2
2
|
!binary "U0hBMQ==":
|
3
3
|
metadata.gz: !binary |-
|
4
|
-
|
4
|
+
OTI1MjM3YjlmNDJhZTdlMTdjOTY4MWU5YjMwZTdkZDZmYmJlNjMwYg==
|
5
5
|
data.tar.gz: !binary |-
|
6
|
-
|
6
|
+
ZGFjZTg2ZGRiZDI1NDY0MTAwNmRjY2Y1M2MzODY5YmIxZTJkYmY4Yg==
|
7
7
|
!binary "U0hBNTEy":
|
8
8
|
metadata.gz: !binary |-
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
ZTA5ZjMxMTEzNjVhMWY2NGFkZWUwNjJiZmQ4ZjBkZDVjOTg5ZTdjZGMwZDBl
|
10
|
+
MWZmZmVlYTIzY2M0ZGM4ZjhiZGU2NTdkZTE2ZWYxNzc3ZDQyMDdmOGFjNzQ1
|
11
|
+
ODU5ZTJjYjMwMTYyMTBmNmQ2Yzk2ZWJkMzJiNTk5ZDBhZmI5Yjk=
|
12
12
|
data.tar.gz: !binary |-
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
YWIzYWM1YWI2ZDU2OGQ2NGZiYjE3MzUyNzQ1YmQwZDYzZjQ5MzE4MjQ4Njcx
|
14
|
+
MzA2MmQyMGM3NDUyMmM3YzdlY2E0OWYyNzYwOTQ3YzhkODU1YTVjZDg3ZjNl
|
15
|
+
YTk4MzMxNjA2YzI3N2IzMWZiMTBkNjgyODg0NjU4YmI4NzZiMmI=
|
data/LICENSE
CHANGED
data/bin/ampv
CHANGED
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
|
10
|
-
LEFT
|
11
|
-
UP
|
12
|
-
DOWN
|
9
|
+
RIGHT seek 10
|
10
|
+
LEFT seek -10
|
11
|
+
UP add volume 1
|
12
|
+
DOWN add volume -1
|
13
13
|
|
14
|
-
PGUP
|
15
|
-
PGDWN
|
16
|
-
HOME
|
17
|
-
END
|
18
|
-
INS
|
19
|
-
DEL
|
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
|
22
|
-
|
23
|
-
|
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
|
27
|
-
o
|
28
|
-
p
|
27
|
+
l cycle playlist
|
28
|
+
o open_file_chooser
|
29
|
+
p cycle progress_bar
|
data/lib/ampv.rb
CHANGED
@@ -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
|
-
|
13
|
-
|
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
|
-
|
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(
|
46
|
+
set_default_size(Config["width"], Config["height"])
|
44
47
|
set_window_position(Gtk::Window::POS_CENTER)
|
45
|
-
move(
|
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::
|
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.
|
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
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
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("
|
82
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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.
|
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.
|
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
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
198
|
-
|
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
|
-
|
229
|
-
|
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
|
-
|
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
|
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(
|
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(
|
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
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
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
|
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))
|
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
|
-
|
375
|
-
|
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
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
@mpv.quit(
|
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
|
data/lib/ampv/config.rb
ADDED
@@ -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
|
+
|
data/lib/ampv/mpvwidget.rb
CHANGED
@@ -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
|
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
|
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 = "/
|
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
|
-
|
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)
|
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 =
|
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.
|
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
|
75
|
-
@
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
111
|
-
|
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
|
data/lib/ampv/playlist.rb
CHANGED
@@ -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
|
-
|
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(
|
22
|
+
set_default_size(Config["playlist_width"], Config["playlist_height"])
|
23
23
|
set_skip_taskbar_hint(true)
|
24
|
-
move(
|
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
|
35
|
-
|
36
|
-
|
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
|
-
|
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") { |
|
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") { |
|
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") { |
|
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 { |
|
60
|
+
["Name", "Length"].each_with_index { |x, i|
|
61
61
|
renderer = Gtk::CellRendererText.new
|
62
|
-
column = Gtk::TreeViewColumn.new(
|
63
|
-
|
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
|
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
|
-
|
103
|
+
i
|
107
104
|
end
|
108
105
|
|
109
|
-
def add_file(file)
|
110
|
-
|
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
|
124
|
-
@
|
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
|
132
|
-
@
|
133
|
-
|
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
|
126
|
+
return iter.next! ? iter[0] : nil if iter == @playing_iter
|
153
127
|
}
|
154
|
-
|
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
|
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|
|
168
|
-
|
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
|
-
|
190
|
-
|
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
|
-
|
5
|
-
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
super
|
6
6
|
modify_bg(Gtk::STATE_NORMAL, Gdk::Color.parse("#000"))
|
7
|
-
set_size_request(-1,
|
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
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
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)
|
data/lib/ampv/version.rb
CHANGED
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
|
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:
|
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
|
-
-
|
76
|
-
- mpv
|
90
|
+
- mpv v0.3.x
|
77
91
|
rubyforge_project:
|
78
92
|
rubygems_version: 2.0.3
|
79
93
|
signing_key:
|