ektoplayer 0.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 +7 -0
- data/README.md +49 -0
- data/bin/ektoplayer +7 -0
- data/lib/ektoplayer.rb +10 -0
- data/lib/ektoplayer/application.rb +148 -0
- data/lib/ektoplayer/bindings.rb +230 -0
- data/lib/ektoplayer/browsepage.rb +138 -0
- data/lib/ektoplayer/client.rb +18 -0
- data/lib/ektoplayer/common.rb +91 -0
- data/lib/ektoplayer/config.rb +247 -0
- data/lib/ektoplayer/controllers/browser.rb +47 -0
- data/lib/ektoplayer/controllers/controller.rb +9 -0
- data/lib/ektoplayer/controllers/help.rb +21 -0
- data/lib/ektoplayer/controllers/info.rb +22 -0
- data/lib/ektoplayer/controllers/mainwindow.rb +40 -0
- data/lib/ektoplayer/controllers/playlist.rb +60 -0
- data/lib/ektoplayer/database.rb +199 -0
- data/lib/ektoplayer/events.rb +56 -0
- data/lib/ektoplayer/models/browser.rb +127 -0
- data/lib/ektoplayer/models/database.rb +49 -0
- data/lib/ektoplayer/models/model.rb +15 -0
- data/lib/ektoplayer/models/player.rb +28 -0
- data/lib/ektoplayer/models/playlist.rb +72 -0
- data/lib/ektoplayer/models/search.rb +42 -0
- data/lib/ektoplayer/models/trackloader.rb +17 -0
- data/lib/ektoplayer/mp3player.rb +151 -0
- data/lib/ektoplayer/operations/browser.rb +19 -0
- data/lib/ektoplayer/operations/operations.rb +26 -0
- data/lib/ektoplayer/operations/player.rb +11 -0
- data/lib/ektoplayer/operations/playlist.rb +67 -0
- data/lib/ektoplayer/theme.rb +102 -0
- data/lib/ektoplayer/trackloader.rb +146 -0
- data/lib/ektoplayer/ui.rb +404 -0
- data/lib/ektoplayer/ui/colors.rb +105 -0
- data/lib/ektoplayer/ui/widgets.rb +195 -0
- data/lib/ektoplayer/ui/widgets/container.rb +125 -0
- data/lib/ektoplayer/ui/widgets/labelwidget.rb +43 -0
- data/lib/ektoplayer/ui/widgets/listwidget.rb +332 -0
- data/lib/ektoplayer/ui/widgets/tabbedcontainer.rb +110 -0
- data/lib/ektoplayer/updater.rb +77 -0
- data/lib/ektoplayer/views/browser.rb +25 -0
- data/lib/ektoplayer/views/help.rb +46 -0
- data/lib/ektoplayer/views/info.rb +208 -0
- data/lib/ektoplayer/views/mainwindow.rb +64 -0
- data/lib/ektoplayer/views/playinginfo.rb +135 -0
- data/lib/ektoplayer/views/playlist.rb +39 -0
- data/lib/ektoplayer/views/progressbar.rb +51 -0
- data/lib/ektoplayer/views/splash.rb +99 -0
- data/lib/ektoplayer/views/trackrenderer.rb +137 -0
- data/lib/ektoplayer/views/volumemeter.rb +74 -0
- metadata +164 -0
@@ -0,0 +1,332 @@
|
|
1
|
+
require_relative '../widgets'
|
2
|
+
|
3
|
+
module UI
|
4
|
+
class ListItemRenderer
|
5
|
+
def initialize(width: nil)
|
6
|
+
@width = width
|
7
|
+
end
|
8
|
+
|
9
|
+
def width=(new)
|
10
|
+
@width != new and (@width = new; layout)
|
11
|
+
end
|
12
|
+
|
13
|
+
def layout; end
|
14
|
+
|
15
|
+
def render(scr, item, selected: false, marked: false)
|
16
|
+
scr << (selected ? ?> : ' ')
|
17
|
+
scr << item_to.s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ListSearch
|
22
|
+
attr_accessor :direction, :source
|
23
|
+
|
24
|
+
def initialize(search: '', source: [], direction: :down)
|
25
|
+
@source, @result, @current = source, [], 0
|
26
|
+
@direction = direction
|
27
|
+
self.search=(search)
|
28
|
+
end
|
29
|
+
|
30
|
+
def comp(item, search)
|
31
|
+
if item.is_a?String
|
32
|
+
return item.downcase =~ Regexp.new(search.downcase)
|
33
|
+
elsif item.is_a?Hash
|
34
|
+
%w(title artist album).each do |key|
|
35
|
+
return true if self.comp(item[key], search)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
false
|
40
|
+
end
|
41
|
+
|
42
|
+
def search=(search)
|
43
|
+
fail unless search
|
44
|
+
@search = search
|
45
|
+
@current = 0
|
46
|
+
@result = @source.size.times.select {|i| self.comp(@source[i], search) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def current; @result[@current] or 0 end
|
50
|
+
def next; @direction == :up ? search_up : search_down end
|
51
|
+
def prev; @direction == :up ? search_down : search_up end
|
52
|
+
|
53
|
+
def search_up
|
54
|
+
@current -= 1
|
55
|
+
@current = @result.size - 1 if @current < 0
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def search_down
|
60
|
+
@current = 0 if (@current += 1) >= @result.size
|
61
|
+
self
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ListWidget < Window
|
66
|
+
attr_reader :list, :selected, :cursor
|
67
|
+
attr_accessor :item_renderer
|
68
|
+
|
69
|
+
def initialize(list: [], item_renderer: nil, **opts)
|
70
|
+
super(**opts)
|
71
|
+
self.list=(list)
|
72
|
+
@item_renderer = (item_renderer or ListItemRenderer.new)
|
73
|
+
@cursor = @selected = 0
|
74
|
+
@search = ListSearch.new
|
75
|
+
end
|
76
|
+
|
77
|
+
def search_next; self.selected=(@search.next.current) end
|
78
|
+
def search_prev; self.selected=(@search.prev.current) end
|
79
|
+
def search_up; self.search_start(:up) end
|
80
|
+
def search_down; self.search_start(:down) end
|
81
|
+
def search_start(direction=:down)
|
82
|
+
UI::Input.readline(@pos, @size.update(height: 1), prompt: '> ', add_hist: true) do |result|
|
83
|
+
if result
|
84
|
+
@search.source=(@list)
|
85
|
+
@search.direction=(direction)
|
86
|
+
@search.search=(result)
|
87
|
+
search_next
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def render(index, **opts)
|
93
|
+
return unless @item_renderer
|
94
|
+
return unless @list[index] # TODO...?
|
95
|
+
@item_renderer.render(@win, @list[index], index, **opts)
|
96
|
+
end
|
97
|
+
|
98
|
+
def layout
|
99
|
+
@item_renderer.width = @size.width if @item_renderer
|
100
|
+
super
|
101
|
+
end
|
102
|
+
|
103
|
+
def top; self.selected=(0) end
|
104
|
+
def bottom; self.selected=(index_last) end
|
105
|
+
def page_up; self.scroll_up(size.height) end
|
106
|
+
def page_down; self.scroll_down(size.height) end
|
107
|
+
def up; self.selected=(selected - 1) end
|
108
|
+
def down; self.selected=(selected + 1) end
|
109
|
+
def center; self.force_cursorpos(@size.height / 2) end
|
110
|
+
|
111
|
+
def list=(list)
|
112
|
+
with_lock do
|
113
|
+
@list = list
|
114
|
+
@cursor = @selected = 0
|
115
|
+
self.selected=(0)
|
116
|
+
want_redraw
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def selected=(new_index)
|
121
|
+
fail ArgumentError unless new_index
|
122
|
+
new_index = new_index.clamp(0, index_last)
|
123
|
+
return if @selected == new_index or @list.empty?
|
124
|
+
|
125
|
+
self.lock
|
126
|
+
|
127
|
+
new_cursor = @cursor + new_index - @selected
|
128
|
+
if new_cursor.between?(0, @size.height - 1)
|
129
|
+
# new selected item resides in current screen,
|
130
|
+
# just want_redraw the old line and the newly selected one
|
131
|
+
write_at(@cursor); render(@selected)
|
132
|
+
write_at(new_cursor); render(new_index, selected: true)
|
133
|
+
@selected, @cursor = new_index, new_cursor
|
134
|
+
_check
|
135
|
+
want_refresh
|
136
|
+
elsif (new_cursor.between?(-(@size.height - 1), (2 * @size.height - 1)))
|
137
|
+
# new selected item is max a half screen size away
|
138
|
+
if new_index < @selected
|
139
|
+
if lines_after_cursor > (@selected - new_index)
|
140
|
+
write_at(@cursor); render(@selected)
|
141
|
+
end
|
142
|
+
|
143
|
+
(index_top - 1).downto(new_index + 1).each do |index|
|
144
|
+
@win.insert_top; render(index)
|
145
|
+
end
|
146
|
+
|
147
|
+
@win.insert_top; render(new_index, selected: true)
|
148
|
+
@selected, @cursor = new_index, 0
|
149
|
+
_check
|
150
|
+
else
|
151
|
+
if lines_before_cursor > (new_index - @selected)
|
152
|
+
write_at(@cursor); render(@selected)
|
153
|
+
end
|
154
|
+
|
155
|
+
(index_bottom + 1).upto(new_index - 1).each do |index|
|
156
|
+
@win.append_bottom; render(index)
|
157
|
+
end
|
158
|
+
|
159
|
+
@win.append_bottom; render(new_index, selected: true)
|
160
|
+
@selected, @cursor = new_index, cursor_max
|
161
|
+
_check
|
162
|
+
end
|
163
|
+
|
164
|
+
want_refresh
|
165
|
+
else
|
166
|
+
@selected = new_index
|
167
|
+
@cursor = new_index.clamp(0, cursor_max)
|
168
|
+
_check
|
169
|
+
want_redraw
|
170
|
+
end
|
171
|
+
|
172
|
+
self.unlock
|
173
|
+
end
|
174
|
+
|
175
|
+
# select an item by its current cursor pos
|
176
|
+
def select_from_cursorpos(new_cursor)
|
177
|
+
fail unless new_cursor.between?(0, cursor_max)
|
178
|
+
# FIXME: clamp with @list.size ????
|
179
|
+
return if (new_cursor == @cursor) or @list.empty?
|
180
|
+
|
181
|
+
with_lock do
|
182
|
+
new_index = (@selected - (@cursor - new_cursor)).clamp(0, index_last)
|
183
|
+
write_at(@cursor); render(@selected)
|
184
|
+
write_at(new_cursor); render(new_index, selected: true)
|
185
|
+
@selected, @cursor = new_index, new_cursor
|
186
|
+
_check
|
187
|
+
want_refresh
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def force_cursorpos(new_cursor)
|
192
|
+
self.lock
|
193
|
+
if @selected <= cursor_max
|
194
|
+
@cursor = @selected
|
195
|
+
elsif (diff = (index_last - @selected)) < cursor_max
|
196
|
+
@cursor = @size.height - diff - 1 #cursor_max.clamp(0, index_last - @selected)
|
197
|
+
else
|
198
|
+
@cursor = new_cursor.clamp(0, cursor_max)
|
199
|
+
end
|
200
|
+
want_redraw
|
201
|
+
self.unlock
|
202
|
+
end
|
203
|
+
|
204
|
+
def scroll_up(n=1)
|
205
|
+
fail ArgumentError unless n
|
206
|
+
n = n.clamp(0, items_before_cursor)
|
207
|
+
return if n == 0 or @list.empty?
|
208
|
+
self.lock
|
209
|
+
|
210
|
+
if index_top == 0
|
211
|
+
# list is already on top
|
212
|
+
select_from_cursorpos((@cursor - n).clamp(0, cursor_max))
|
213
|
+
elsif n < @size.height
|
214
|
+
if lines_after_cursor > n
|
215
|
+
write_at(@cursor); render(@selected)
|
216
|
+
end
|
217
|
+
|
218
|
+
(index_top - 1).downto(index_top - n).each do |index|
|
219
|
+
@win.insert_top; render(index)
|
220
|
+
end
|
221
|
+
|
222
|
+
@selected -= n
|
223
|
+
write_at(@cursor); render(@selected, selected: true)
|
224
|
+
|
225
|
+
_check
|
226
|
+
want_refresh
|
227
|
+
else
|
228
|
+
@selected -= n
|
229
|
+
force_cursorpos(@cursor)
|
230
|
+
_check
|
231
|
+
want_redraw
|
232
|
+
end
|
233
|
+
|
234
|
+
self.unlock
|
235
|
+
end
|
236
|
+
|
237
|
+
def scroll_down(n=1)
|
238
|
+
fail ArgumentError unless n
|
239
|
+
n = n.clamp(0, items_after_cursor)
|
240
|
+
return if n == 0 or @list.empty?
|
241
|
+
self.lock
|
242
|
+
|
243
|
+
if index_bottom == index_last
|
244
|
+
select_from_cursorpos((@cursor + n).clamp(0, cursor_max))
|
245
|
+
_check
|
246
|
+
elsif n < @size.height
|
247
|
+
if lines_before_cursor > n
|
248
|
+
write_at(@cursor); render(@selected)
|
249
|
+
end
|
250
|
+
|
251
|
+
(index_bottom + 1).upto(index_bottom + n).each do |index|
|
252
|
+
@win.append_bottom; render(index)
|
253
|
+
end
|
254
|
+
|
255
|
+
@selected += n
|
256
|
+
write_at(@cursor); render(@selected, selected: true)
|
257
|
+
|
258
|
+
_check
|
259
|
+
want_refresh
|
260
|
+
else
|
261
|
+
@selected += n
|
262
|
+
force_cursorpos(@cursor)
|
263
|
+
_check
|
264
|
+
want_redraw
|
265
|
+
end
|
266
|
+
|
267
|
+
self.unlock
|
268
|
+
_check
|
269
|
+
end
|
270
|
+
|
271
|
+
def draw
|
272
|
+
@win.erase
|
273
|
+
return if @list.empty?
|
274
|
+
@selected = @selected.clamp(0, index_last)
|
275
|
+
_check
|
276
|
+
|
277
|
+
@cursor.times do |i|
|
278
|
+
unless row = @list[@selected - (@cursor - i)]
|
279
|
+
@cursor = i
|
280
|
+
break
|
281
|
+
end
|
282
|
+
|
283
|
+
write_at(i); render(@selected - (@cursor - i))
|
284
|
+
end
|
285
|
+
|
286
|
+
_check
|
287
|
+
write_at(@cursor); render(@selected, selected: true)
|
288
|
+
|
289
|
+
(@cursor + 1).upto(@size.height - 1).each_with_index do |c, i|
|
290
|
+
break unless row = @list[@selected + i + 1]
|
291
|
+
write_at(c); render(@selected + i + 1)
|
292
|
+
end
|
293
|
+
|
294
|
+
_check
|
295
|
+
end
|
296
|
+
|
297
|
+
def on_mouse_click(mevent, mevent_transformed)
|
298
|
+
if new_mouse = mouse_event_transform(mevent)
|
299
|
+
select_from_cursorpos(new_mouse.y)
|
300
|
+
end
|
301
|
+
super(mevent)
|
302
|
+
end
|
303
|
+
|
304
|
+
protected
|
305
|
+
|
306
|
+
def write_at(pos) @win.line_start(pos).clrtoeol end
|
307
|
+
|
308
|
+
def index_first; 0 end
|
309
|
+
def index_last; [@list.size, 1].max - 1 end
|
310
|
+
def index_top; @selected - @cursor end
|
311
|
+
def index_bottom
|
312
|
+
[@selected + @size.height - @cursor, @list.size].min - 1
|
313
|
+
end
|
314
|
+
|
315
|
+
def lines_before_cursor; @cursor end
|
316
|
+
def lines_after_cursor; @size.height - cursor - 1 end
|
317
|
+
def items_before_cursor; @selected; end
|
318
|
+
def items_after_cursor; @list.size - @selected - 1 end
|
319
|
+
def cursor_min; 0 end
|
320
|
+
def cursor_max; [@size.height, @list.size].min - 1 end
|
321
|
+
|
322
|
+
private def _check # debug method
|
323
|
+
return
|
324
|
+
fail "@selected = nil" unless @selected
|
325
|
+
fail "@selected = #{@selected}" unless @selected >= 0
|
326
|
+
fail "@selected > @list.size" if @selected >= @list.size
|
327
|
+
fail "@cursor = nil" unless @cursor
|
328
|
+
fail "@cursor = #{@cursor}" unless @cursor >= 0
|
329
|
+
fail "@cursor > max" if @cursor > @win.maxy
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require_relative 'container'
|
2
|
+
require_relative 'labelwidget'
|
3
|
+
|
4
|
+
module UI
|
5
|
+
class TabbedContainer < GenericContainer
|
6
|
+
attr_reader :show_tabbar, :attributes
|
7
|
+
|
8
|
+
def initialize(**opts)
|
9
|
+
super(**opts)
|
10
|
+
@show_tabbar = true
|
11
|
+
@tabbar = sub(HorizontalContainer)
|
12
|
+
@attributes = Hash.new { 0 }
|
13
|
+
end
|
14
|
+
|
15
|
+
def show_tabbar=(new)
|
16
|
+
return if @show_tabbar == new
|
17
|
+
with_lock { @show_tabbar = new; want_refresh }
|
18
|
+
end
|
19
|
+
|
20
|
+
def layout
|
21
|
+
if @show_tabbar
|
22
|
+
@tabbar.with_lock do
|
23
|
+
@tabbar.visible!
|
24
|
+
@tabbar.pos=(@pos)
|
25
|
+
@tabbar.size=(@size.update(height: 1))
|
26
|
+
end
|
27
|
+
|
28
|
+
if @selected
|
29
|
+
@selected.with_lock do
|
30
|
+
@selected.size=(@size.calc(height: -1))
|
31
|
+
@selected.pos=(@pos.calc(y: 1))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else
|
35
|
+
if @selected
|
36
|
+
@selected.with_lock do
|
37
|
+
@selected.size=(@size)
|
38
|
+
@selected.pos=(@pos)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
46
|
+
def attributes=(new)
|
47
|
+
return if @attributes == new
|
48
|
+
with_lock { @attributes.update(new); update_tabbar }
|
49
|
+
end
|
50
|
+
|
51
|
+
def visible_widgets
|
52
|
+
return [@tabbar, @selected] if @show_tabbar and @selected
|
53
|
+
return [@selected] if @selected
|
54
|
+
return [@tabbar] if @show_tabbar
|
55
|
+
return []
|
56
|
+
end
|
57
|
+
|
58
|
+
def add(widget, title)
|
59
|
+
with_lock do
|
60
|
+
super(widget)
|
61
|
+
tab = @tabbar.sub(LabelWidget, text: title, pad: {left: 1})
|
62
|
+
tab.fit
|
63
|
+
tab.mouse.on_all { self.selected=(widget) }
|
64
|
+
@tabbar.add(tab)
|
65
|
+
update_tabbar
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def remove(widget)
|
70
|
+
with_lock do
|
71
|
+
index = @widgets.index(widget) or fail KeyError
|
72
|
+
@tabbar.remove(@tabbar.widgets[index])
|
73
|
+
super(widget)
|
74
|
+
update_tabbar
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def selected=(widget)
|
79
|
+
with_lock do
|
80
|
+
(@selected.invisible!) if @selected
|
81
|
+
super(widget)
|
82
|
+
(@selected.visible!) if @selected
|
83
|
+
update_tabbar
|
84
|
+
want_layout
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def selected_index=(index)
|
89
|
+
with_lock do
|
90
|
+
(@selected.invisible!) if @selected
|
91
|
+
super(index)
|
92
|
+
update_tabbar
|
93
|
+
(@selected.visible!) if @selected
|
94
|
+
want_layout
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private def update_tabbar
|
99
|
+
with_lock do
|
100
|
+
@tabbar.widgets.each_with_index do |tab, i|
|
101
|
+
if @widgets[i].equal?(@selected)
|
102
|
+
tab.attributes=(@attributes[:'tab_selected'])
|
103
|
+
else
|
104
|
+
tab.attributes=(@attributes[:'tabs'])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'thread'
|
3
|
+
require 'open-uri'
|
4
|
+
|
5
|
+
require_relative 'browsepage'
|
6
|
+
|
7
|
+
module Ektoplayer
|
8
|
+
MAIN_URL = 'http://www.ektoplazm.com'.freeze
|
9
|
+
FREE_MUSIC_URL = "#{MAIN_URL}/section/free-music".freeze
|
10
|
+
|
11
|
+
class DatabaseUpdater
|
12
|
+
ALBUM_STR_TAGS = Set.new(%w(url title date category cover_url
|
13
|
+
description download_count rating votes
|
14
|
+
released_by released_by_url posted_by posted_by_url).map(&:to_sym)).freeze
|
15
|
+
|
16
|
+
TRACK_STR_TAGS = Set.new(%w(url number title remix artist bpm).map(&:to_sym)).freeze
|
17
|
+
|
18
|
+
def initialize(db)
|
19
|
+
@db = db
|
20
|
+
end
|
21
|
+
|
22
|
+
def update(start_url: FREE_MUSIC_URL, pages: 0, parallel: 10)
|
23
|
+
queue = parallel > 0 ? SizedQueue.new(parallel) : Queue.new
|
24
|
+
insert_browserpage(bp = BrowsePage.new(start_url))
|
25
|
+
|
26
|
+
if pages > 0
|
27
|
+
bp.page_urls[(bp.current_page_index + 1)..(bp.current_page_index + pages + 1)]
|
28
|
+
else
|
29
|
+
bp.page_urls[(bp.current_page_index + 1)..-1]
|
30
|
+
end.
|
31
|
+
each do |url|
|
32
|
+
queue << Thread.new do
|
33
|
+
insert_browserpage(BrowsePage.new(url))
|
34
|
+
queue.pop # unregister our thread
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
sleep 1 while not queue.empty?
|
39
|
+
rescue Application.log(self, $!)
|
40
|
+
end
|
41
|
+
|
42
|
+
private def insert_browserpage(browserpage)
|
43
|
+
browserpage.styles.each do |style, url|
|
44
|
+
@db.replace_into(:styles, { style: style, url: url })
|
45
|
+
end
|
46
|
+
|
47
|
+
browserpage.albums.each { |album| insert_album album }
|
48
|
+
rescue Application.log(self, $!)
|
49
|
+
end
|
50
|
+
|
51
|
+
private def insert_album(album)
|
52
|
+
album_r = ALBUM_STR_TAGS.map { |tag| [tag, album[tag]] }.to_h
|
53
|
+
@db.replace_into(:albums, album_r)
|
54
|
+
|
55
|
+
album[:styles].each do |style|
|
56
|
+
@db.replace_into(:albums_styles, {
|
57
|
+
album_url: album[:url],
|
58
|
+
style: style
|
59
|
+
})
|
60
|
+
end
|
61
|
+
|
62
|
+
album[:archive_urls].each do |type, url|
|
63
|
+
@db.replace_into(:archive_urls, {
|
64
|
+
album_url: album[:url],
|
65
|
+
archive_type: type,
|
66
|
+
archive_url: url
|
67
|
+
})
|
68
|
+
end
|
69
|
+
|
70
|
+
album[:tracks].each do |track|
|
71
|
+
track_r = TRACK_STR_TAGS.map { |tag| [tag, track[tag]] }.to_h
|
72
|
+
track_r[:album_url] = album[:url]
|
73
|
+
@db.replace_into(:tracks, track_r)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|