musicbox 0.1
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/.gitignore +3 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +21 -0
- data/README.md +211 -0
- data/Rakefile +9 -0
- data/TODO.txt +145 -0
- data/bin/musicbox +118 -0
- data/lib/extensions/string.rb +35 -0
- data/lib/musicbox.rb +268 -0
- data/lib/musicbox/catalog.rb +205 -0
- data/lib/musicbox/catalog/album.rb +179 -0
- data/lib/musicbox/catalog/album_track.rb +66 -0
- data/lib/musicbox/catalog/albums.rb +19 -0
- data/lib/musicbox/catalog/artist.rb +52 -0
- data/lib/musicbox/catalog/artists.rb +15 -0
- data/lib/musicbox/catalog/collection.rb +15 -0
- data/lib/musicbox/catalog/collection_item.rb +35 -0
- data/lib/musicbox/catalog/format.rb +52 -0
- data/lib/musicbox/catalog/release.rb +243 -0
- data/lib/musicbox/catalog/release_artist.rb +59 -0
- data/lib/musicbox/catalog/releases.rb +15 -0
- data/lib/musicbox/catalog/tags.rb +116 -0
- data/lib/musicbox/catalog/track.rb +43 -0
- data/lib/musicbox/cover_maker.rb +49 -0
- data/lib/musicbox/discogs.rb +53 -0
- data/lib/musicbox/equalizer.rb +86 -0
- data/lib/musicbox/error.rb +5 -0
- data/lib/musicbox/exporter.rb +76 -0
- data/lib/musicbox/extractor.rb +35 -0
- data/lib/musicbox/group.rb +169 -0
- data/lib/musicbox/importer.rb +133 -0
- data/lib/musicbox/info_to_s.rb +18 -0
- data/lib/musicbox/label_maker.rb +61 -0
- data/lib/musicbox/player.rb +289 -0
- data/lib/musicbox/version.rb +5 -0
- data/musicbox.gemspec +38 -0
- metadata +303 -0
@@ -0,0 +1,179 @@
|
|
1
|
+
class MusicBox
|
2
|
+
|
3
|
+
class Catalog
|
4
|
+
|
5
|
+
class Album < Group::Item
|
6
|
+
|
7
|
+
attr_accessor :title
|
8
|
+
attr_accessor :artist
|
9
|
+
attr_accessor :year
|
10
|
+
attr_accessor :discs
|
11
|
+
attr_accessor :tracks
|
12
|
+
|
13
|
+
def initialize(params={})
|
14
|
+
@tracks = []
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def tracks=(tracks)
|
19
|
+
@tracks = tracks.map { |h| AlbumTrack.new(h.merge(album: self)) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def date=(date)
|
23
|
+
@year = case date
|
24
|
+
when Date
|
25
|
+
date.year
|
26
|
+
when String
|
27
|
+
date.to_i
|
28
|
+
else
|
29
|
+
date
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def release_id=(id)
|
34
|
+
@id = id
|
35
|
+
end
|
36
|
+
|
37
|
+
def log_files=(*); end
|
38
|
+
|
39
|
+
def artist
|
40
|
+
@artist || @tracks&.first&.artist
|
41
|
+
end
|
42
|
+
|
43
|
+
def cover_file
|
44
|
+
files = @dir.glob('cover.{jpg,png}')
|
45
|
+
raise Error, "Multiple cover files" if files.length > 1
|
46
|
+
files.first
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_cover?
|
50
|
+
!cover_file.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
def show_cover(width: nil, height: nil, preserve_aspect_ratio: nil)
|
54
|
+
# see https://iterm2.com/documentation-images.html
|
55
|
+
file = cover_file
|
56
|
+
if file && file.exist?
|
57
|
+
data = Base64.strict_encode64(file.read)
|
58
|
+
args = {
|
59
|
+
name: Base64.strict_encode64(file.to_s),
|
60
|
+
size: data.length,
|
61
|
+
width: width,
|
62
|
+
height: height,
|
63
|
+
preserveAspectRatio: preserve_aspect_ratio,
|
64
|
+
inline: 1,
|
65
|
+
}.compact
|
66
|
+
puts "\033]1337;File=%s:%s\a" % [
|
67
|
+
args.map { |a| a.join('=') }.join(';'),
|
68
|
+
data,
|
69
|
+
]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def validate_logs
|
74
|
+
log_files = @dir.glob('*.log')
|
75
|
+
raise Error, "No rip logs" if log_files.empty?
|
76
|
+
state = :initial
|
77
|
+
log_files.each do |log_file|
|
78
|
+
log_file.readlines.map(&:chomp).each do |line|
|
79
|
+
case state
|
80
|
+
when :initial
|
81
|
+
if line =~ /^AccurateRip Summary/
|
82
|
+
state = :accuraterip_summary
|
83
|
+
end
|
84
|
+
when :accuraterip_summary
|
85
|
+
if line =~ /^\s+Track \d+ : (\S+)/
|
86
|
+
raise Error, "Not accurately ripped" unless $1 == 'OK'
|
87
|
+
else
|
88
|
+
break
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def update_tags(force: false)
|
96
|
+
changes = []
|
97
|
+
@tracks.each do |track|
|
98
|
+
track.update_tags
|
99
|
+
changes << track if track.tags.changed?
|
100
|
+
end
|
101
|
+
unless changes.empty?
|
102
|
+
puts
|
103
|
+
puts "#{@title} [#{@dir}]"
|
104
|
+
changes.each do |track|
|
105
|
+
puts "\t" + track.file.to_s
|
106
|
+
track.tags.changes.each do |change|
|
107
|
+
puts "\t\t" + change.inspect
|
108
|
+
end
|
109
|
+
end
|
110
|
+
if force || TTY::Prompt.new.yes?('Update track files?')
|
111
|
+
changes.each do |track|
|
112
|
+
track.save_tags
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
if has_cover?
|
117
|
+
# --replace apparently doesn't work, so must do --remove, then --add
|
118
|
+
@tracks.each do |track|
|
119
|
+
begin
|
120
|
+
run_command('mp4art',
|
121
|
+
'--quiet',
|
122
|
+
'--remove',
|
123
|
+
track.path)
|
124
|
+
rescue RunCommandFailed => e
|
125
|
+
# ignore
|
126
|
+
end
|
127
|
+
run_command('mp4art',
|
128
|
+
'--quiet',
|
129
|
+
'--add',
|
130
|
+
cover_file,
|
131
|
+
track.path)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def extract_cover
|
137
|
+
if has_cover?
|
138
|
+
puts "#{@id}: already has cover"
|
139
|
+
return
|
140
|
+
end
|
141
|
+
file = @dir / @tracks.first.file
|
142
|
+
begin
|
143
|
+
run_command('mp4art',
|
144
|
+
'--extract',
|
145
|
+
'--art-index', 0,
|
146
|
+
'--overwrite',
|
147
|
+
'--quiet',
|
148
|
+
file)
|
149
|
+
rescue RunCommandFailed => e
|
150
|
+
# ignore
|
151
|
+
end
|
152
|
+
# cover is in FILE.art[0].TYPE
|
153
|
+
files = @dir.glob('*.art*.*').reject { |f| f.extname.downcase == '.gif' }
|
154
|
+
if files.length == 0
|
155
|
+
puts "#{@id}: no cover to extract"
|
156
|
+
elsif files.length > 1
|
157
|
+
raise Error, "Multiple covers found"
|
158
|
+
else
|
159
|
+
file = files.first
|
160
|
+
new_cover_file = (@dir / 'cover').add_extension(file.extname)
|
161
|
+
puts "#{@id}: extracted cover: #{new_cover_file.basename}"
|
162
|
+
file.rename(new_cover_file)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def serialize
|
167
|
+
super(
|
168
|
+
title: @title,
|
169
|
+
artist: @artist,
|
170
|
+
year: @year,
|
171
|
+
discs: @discs,
|
172
|
+
tracks: @tracks.map(&:to_h))
|
173
|
+
end
|
174
|
+
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class MusicBox
|
2
|
+
|
3
|
+
class Catalog
|
4
|
+
|
5
|
+
class AlbumTrack
|
6
|
+
|
7
|
+
attr_accessor :title
|
8
|
+
attr_accessor :artist
|
9
|
+
attr_accessor :track
|
10
|
+
attr_accessor :disc
|
11
|
+
attr_accessor :album
|
12
|
+
attr_accessor :file
|
13
|
+
attr_accessor :tags
|
14
|
+
|
15
|
+
def initialize(params={})
|
16
|
+
params.each { |k, v| send("#{k}=", v) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def file=(file)
|
20
|
+
@file = Path.new(file)
|
21
|
+
end
|
22
|
+
|
23
|
+
def path
|
24
|
+
@album.dir / @file
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_tags
|
28
|
+
@tags ||= Tags.load(path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def save_tags
|
32
|
+
@tags.save(path)
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_tags
|
36
|
+
load_tags
|
37
|
+
@tags.update(
|
38
|
+
{
|
39
|
+
title: @title,
|
40
|
+
album: @album.title,
|
41
|
+
track: @track,
|
42
|
+
disc: @disc,
|
43
|
+
discs: @album.discs,
|
44
|
+
artist: @artist || @album.artist,
|
45
|
+
album_artist: @album.artist,
|
46
|
+
grouping: @album.title,
|
47
|
+
year: @album.year,
|
48
|
+
}.reject { |k, v| v.to_s.empty? }
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_h
|
53
|
+
{
|
54
|
+
title: @title,
|
55
|
+
artist: @artist,
|
56
|
+
track: @track,
|
57
|
+
disc: @disc,
|
58
|
+
file: @file.to_s,
|
59
|
+
}.compact
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class MusicBox
|
2
|
+
|
3
|
+
class Catalog
|
4
|
+
|
5
|
+
class Artist < Group::Item
|
6
|
+
|
7
|
+
attr_accessor :aliases
|
8
|
+
attr_accessor :data_quality
|
9
|
+
attr_accessor :groups
|
10
|
+
attr_accessor :images #FIXME: make Image class?
|
11
|
+
attr_accessor :members
|
12
|
+
attr_accessor :name
|
13
|
+
attr_accessor :namevariations
|
14
|
+
attr_accessor :profile
|
15
|
+
attr_accessor :realname
|
16
|
+
attr_accessor :releases_url
|
17
|
+
attr_accessor :resource_url
|
18
|
+
attr_accessor :uri
|
19
|
+
attr_accessor :urls
|
20
|
+
|
21
|
+
def aliases=(aliases)
|
22
|
+
@aliases = aliases.map { |a| ReleaseArtist.new(a) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def groups=(groups)
|
26
|
+
@groups = groups.map { |a| ReleaseArtist.new(a) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def members=(members)
|
30
|
+
@members = members.map { |a| ReleaseArtist.new(a) }
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
@name
|
35
|
+
end
|
36
|
+
|
37
|
+
def summary_to_s
|
38
|
+
'%-8s | %s' % [
|
39
|
+
@id,
|
40
|
+
@name,
|
41
|
+
]
|
42
|
+
end
|
43
|
+
|
44
|
+
def <=>(other)
|
45
|
+
@name <=> other.name
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class MusicBox
|
2
|
+
|
3
|
+
class Catalog
|
4
|
+
|
5
|
+
class CollectionItem < Group::Item
|
6
|
+
|
7
|
+
attr_accessor :basic_information
|
8
|
+
attr_accessor :date_added
|
9
|
+
attr_accessor :folder_id
|
10
|
+
attr_accessor :instance_id
|
11
|
+
attr_accessor :notes
|
12
|
+
attr_accessor :rating
|
13
|
+
attr_accessor :resource_url
|
14
|
+
attr_accessor :release # linked on load
|
15
|
+
|
16
|
+
def date_added=(date)
|
17
|
+
@date_added = DateTime.parse(date.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
def serialize(args={})
|
21
|
+
super(
|
22
|
+
basic_information: @basic_information,
|
23
|
+
date_added: @date_added,
|
24
|
+
folder_id: @folder_id,
|
25
|
+
instance_id: @instance_id,
|
26
|
+
notes: @notes,
|
27
|
+
rating: @rating,
|
28
|
+
resource_url: @resource_url)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class MusicBox
|
2
|
+
|
3
|
+
class Catalog
|
4
|
+
|
5
|
+
class Format
|
6
|
+
|
7
|
+
attr_accessor :descriptions
|
8
|
+
attr_accessor :name
|
9
|
+
attr_accessor :qty
|
10
|
+
attr_accessor :text
|
11
|
+
|
12
|
+
def self.to_s(formats)
|
13
|
+
formats.map(&:short_to_s).join(', ')
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(params={})
|
17
|
+
params.each { |k, v| send("#{k}=", v) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def qty=(n)
|
21
|
+
@qty = n.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
@name + descriptions_to_s + qty_to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
def short_to_s
|
29
|
+
@name + qty_to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def descriptions_to_s
|
33
|
+
@descriptions ? " (#{@descriptions.join(', ')})" : ''
|
34
|
+
end
|
35
|
+
|
36
|
+
def qty_to_s
|
37
|
+
(@qty && @qty > 1) ? " [#{@qty}]" : ''
|
38
|
+
end
|
39
|
+
|
40
|
+
def cd?
|
41
|
+
@name == 'CD'
|
42
|
+
end
|
43
|
+
|
44
|
+
def vinyl?
|
45
|
+
@name == 'Vinyl'
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,243 @@
|
|
1
|
+
class MusicBox
|
2
|
+
|
3
|
+
class Catalog
|
4
|
+
|
5
|
+
class Release < Group::Item
|
6
|
+
|
7
|
+
attr_accessor :artists
|
8
|
+
attr_accessor :artists_sort
|
9
|
+
attr_accessor :blocked_from_sale
|
10
|
+
attr_accessor :community
|
11
|
+
attr_accessor :companies
|
12
|
+
attr_accessor :country
|
13
|
+
attr_accessor :data_quality
|
14
|
+
attr_accessor :date_added
|
15
|
+
attr_accessor :date_changed
|
16
|
+
attr_accessor :estimated_weight
|
17
|
+
attr_accessor :extraartists
|
18
|
+
attr_accessor :format_quantity
|
19
|
+
attr_accessor :formats
|
20
|
+
attr_accessor :genres
|
21
|
+
attr_accessor :identifiers
|
22
|
+
attr_accessor :images
|
23
|
+
attr_accessor :labels
|
24
|
+
attr_accessor :lowest_price
|
25
|
+
attr_accessor :main_release
|
26
|
+
attr_accessor :main_release_url
|
27
|
+
attr_accessor :master_id
|
28
|
+
attr_accessor :master_url
|
29
|
+
attr_accessor :most_recent_release
|
30
|
+
attr_accessor :most_recent_release_url
|
31
|
+
attr_accessor :notes
|
32
|
+
attr_accessor :num_for_sale
|
33
|
+
attr_accessor :released
|
34
|
+
attr_accessor :released_formatted
|
35
|
+
attr_accessor :resource_url
|
36
|
+
attr_accessor :series
|
37
|
+
attr_accessor :status
|
38
|
+
attr_accessor :styles
|
39
|
+
attr_accessor :thumb
|
40
|
+
attr_accessor :tracklist
|
41
|
+
attr_accessor :title
|
42
|
+
attr_accessor :uri
|
43
|
+
attr_accessor :versions_url
|
44
|
+
attr_accessor :videos
|
45
|
+
attr_accessor :year
|
46
|
+
attr_accessor :master # linked on load
|
47
|
+
attr_accessor :album # linked on load
|
48
|
+
|
49
|
+
def self.csv_header
|
50
|
+
%w[ID year artist title].to_csv
|
51
|
+
end
|
52
|
+
|
53
|
+
def artists=(artists)
|
54
|
+
@artists = artists.map { |a| ReleaseArtist.new(a) }
|
55
|
+
end
|
56
|
+
|
57
|
+
def extraartists=(artists)
|
58
|
+
@extraartists = artists.map { |a| ReleaseArtist.new(a) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def date_added=(date)
|
62
|
+
@date_added = DateTime.parse(date.to_s)
|
63
|
+
end
|
64
|
+
|
65
|
+
def date_changed=(date)
|
66
|
+
@date_changed = DateTime.parse(date.to_s)
|
67
|
+
end
|
68
|
+
|
69
|
+
def formats=(formats)
|
70
|
+
@formats = formats.map { |f| Format.new(f) }
|
71
|
+
end
|
72
|
+
|
73
|
+
def tracklist=(tracklist)
|
74
|
+
@tracklist = tracklist.map { |t| Track.new(t) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def release_year
|
78
|
+
if @year && @year != 0
|
79
|
+
@year
|
80
|
+
elsif @released
|
81
|
+
@released.to_s.split('-').first&.to_i
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def original_release_year
|
86
|
+
@master&.release_year || release_year
|
87
|
+
end
|
88
|
+
|
89
|
+
def cd?
|
90
|
+
@formats.find(&:cd?) != nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def artist
|
94
|
+
ReleaseArtist.artists_to_s(@artists)
|
95
|
+
end
|
96
|
+
|
97
|
+
def artist_key
|
98
|
+
@artists.first.key
|
99
|
+
end
|
100
|
+
|
101
|
+
def <=>(other)
|
102
|
+
sort_tuple <=> other.sort_tuple
|
103
|
+
end
|
104
|
+
|
105
|
+
def sort_tuple
|
106
|
+
[artist_key, original_release_year || 0, @title]
|
107
|
+
end
|
108
|
+
|
109
|
+
def to_csv
|
110
|
+
[@id, original_release_year, artist, @title].to_csv
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_s
|
114
|
+
summary_to_s
|
115
|
+
end
|
116
|
+
|
117
|
+
def summary_to_s
|
118
|
+
'%-8s | %1s%1s | %-4s %4s | %-50.50s | %-60.60s | %-6s' % [
|
119
|
+
@id,
|
120
|
+
@album ? 'A' : '',
|
121
|
+
@album&.has_cover? ? 'C' : '',
|
122
|
+
artist_key,
|
123
|
+
original_release_year || '-',
|
124
|
+
artist,
|
125
|
+
@title,
|
126
|
+
Format.to_s(@formats),
|
127
|
+
]
|
128
|
+
end
|
129
|
+
|
130
|
+
def details_to_s
|
131
|
+
info = [
|
132
|
+
['ID', @id],
|
133
|
+
['Master ID', @master_id || '-'],
|
134
|
+
['Artist', ReleaseArtist.artists_to_s(@artists)],
|
135
|
+
['Title', @title],
|
136
|
+
['Formats', Format.to_s(@formats)],
|
137
|
+
['Released', release_year || '-'],
|
138
|
+
['Originally released', original_release_year || '-'],
|
139
|
+
['Discogs URI', @uri || '-'],
|
140
|
+
['Album dir', @album&.dir || '-'],
|
141
|
+
['Tracks', nil, tracklist_to_info],
|
142
|
+
]
|
143
|
+
MusicBox.info_to_s(info)
|
144
|
+
end
|
145
|
+
|
146
|
+
def tracklist_flattened(tracklist=nil)
|
147
|
+
tracklist ||= @tracklist
|
148
|
+
tracks = []
|
149
|
+
tracklist.each do |track|
|
150
|
+
tracks << track
|
151
|
+
tracks += tracklist_flattened(track.sub_tracks) if track.type == 'index'
|
152
|
+
end
|
153
|
+
tracks
|
154
|
+
end
|
155
|
+
|
156
|
+
def tracklist_to_info(tracklist=nil)
|
157
|
+
tracklist ||= @tracklist
|
158
|
+
max_position_length = tracklist.select(&:position).map { |t| t.position.to_s.length }.max
|
159
|
+
tracklist.map do |track|
|
160
|
+
[
|
161
|
+
track.type,
|
162
|
+
[
|
163
|
+
!track.position.to_s.empty? ? ('%*s:' % [max_position_length, track.position]) : nil,
|
164
|
+
track.title || '-',
|
165
|
+
track.artists ? "(#{ReleaseArtist.artists_to_s(track.artists)})" : nil,
|
166
|
+
!track.duration.to_s.empty? ? "[#{track.duration}]" : nil,
|
167
|
+
].compact.join(' '),
|
168
|
+
track.sub_tracks ? tracklist_to_info(track.sub_tracks) : nil,
|
169
|
+
]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_label
|
174
|
+
{
|
175
|
+
artist: artist,
|
176
|
+
title: title,
|
177
|
+
key: artist_key,
|
178
|
+
year: original_release_year,
|
179
|
+
format: Format.to_s(@formats),
|
180
|
+
id: id,
|
181
|
+
}
|
182
|
+
end
|
183
|
+
|
184
|
+
def link_images(images_dir)
|
185
|
+
if @images
|
186
|
+
@images.each do |image|
|
187
|
+
uri = URI.parse(image['uri'])
|
188
|
+
image['file'] = images_dir / Path.new(uri.path).basename
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def download_images
|
194
|
+
image = @images.find { |image| image['type'] == 'primary' }
|
195
|
+
if image
|
196
|
+
download_image(uri: image['uri'], file: image['file'])
|
197
|
+
else
|
198
|
+
@images.each do |image|
|
199
|
+
download_image(uri: image['uri'], file: image['file'])
|
200
|
+
end
|
201
|
+
end
|
202
|
+
@master.download_images if @master
|
203
|
+
end
|
204
|
+
|
205
|
+
def download_image(uri:, file:)
|
206
|
+
if uri && file
|
207
|
+
unless file.exist?
|
208
|
+
puts "#{@id}: downloading #{uri}"
|
209
|
+
file.dirname.mkpath unless file.dirname.exist?
|
210
|
+
file.write(HTTP.get(uri))
|
211
|
+
sleep(1)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def select_cover
|
217
|
+
if @album.has_cover?
|
218
|
+
puts "#{@id}: cover already exists"
|
219
|
+
return
|
220
|
+
end
|
221
|
+
download_images
|
222
|
+
@album.extract_cover
|
223
|
+
choices = [
|
224
|
+
@master&.images&.map { |i| i['file'] },
|
225
|
+
@images&.map { |i| i['file'] },
|
226
|
+
@album.cover_file,
|
227
|
+
].flatten.compact.uniq.select(&:exist?)
|
228
|
+
if choices.empty?
|
229
|
+
puts "#{@id}: no covers exist"
|
230
|
+
else
|
231
|
+
choices.each { |f| run_command('open', f) }
|
232
|
+
choice = TTY::Prompt.new.select('Cover?', choices)
|
233
|
+
cover_file = (album.dir / 'cover').add_extension(choice.extname)
|
234
|
+
choice.cp(cover_file)
|
235
|
+
@album.update_tags
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
240
|
+
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|