artbase 0.2.1 → 0.3.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.
@@ -1,306 +0,0 @@
1
-
2
- module Artbase
3
- class Base ## "abstract" Base collection - check -use a different name - why? why not?
4
-
5
-
6
- def convert_images( overwrite: )
7
- image_dir = "./#{slug}/token-i"
8
- Image.convert( image_dir, from: 'jpg', to: 'png', overwrite: overwrite )
9
- Image.convert( image_dir, from: 'gif', to: 'png', overwrite: overwrite )
10
- Image.convert( image_dir, from: 'svg', to: 'png', overwrite: overwrite )
11
- end
12
-
13
-
14
-
15
- def make_strip
16
- composite_count = @count - @excludes.size
17
-
18
- composite = ImageComposite.new( 9, 1,
19
- width: @width,
20
- height: @height )
21
-
22
- i = 0
23
- each_image do |img, id|
24
- puts "==> [#{i+1}/9] #{id}"
25
- composite << img
26
-
27
- i += 1
28
- break if i >= 9
29
- end
30
-
31
-
32
- composite.save( "./#{@slug}/tmp/#{@slug}-strip.png" )
33
- end
34
-
35
-
36
-
37
- def make_composite
38
- ### use well-known / pre-defined (default) grids
39
- ## (cols x rows) for now - why? why not?
40
-
41
- composite_count = @count - @excludes.size
42
- cols, rows = case composite_count
43
- when 99 then [10, 10]
44
- when 100 then [10, 10]
45
- when 150 then [15, 10]
46
- when 314 then [15, 21]
47
- when 500 then [25, 20]
48
- when 1000 then [25, 40]
49
- when 2200 then [50, 44]
50
- when 2222 then [50, 45]
51
- when 2469 then [50, 50]
52
- when 3000 then [100, 30] ## or use 50*60 - why? why not?
53
- when 3500 then [100, 35] ## or use 50*x ??
54
- when 3979 then [100, 40]
55
- when 4000 then [100, 40] ## or use 50x80 - why? why not?
56
- when 4444 then [100, 45] ## or use 50x??
57
- when 5000 then [100, 50] ## or use 50x100 - why? why not?
58
- when 5555 then [100, 56] # 5600 (45 left empty)
59
- when 6666 then [100, 67] # 6700 (34 left empty)
60
- when 6688 then [100, 67] # 6700 (12 left empty)
61
- when 6969 then [100, 70] # 7000 (31 left empty)
62
- when 8888 then [100, 89]
63
- when 9969 then [100,100]
64
- when 10000 then [100,100]
65
- else
66
- raise ArgumentError, "sorry - unknown composite count #{composite_count}/#{@count} for now"
67
- end
68
-
69
- composite = ImageComposite.new( cols, rows,
70
- width: @width,
71
- height: @height )
72
-
73
- each_image do |img, id|
74
- puts "==> #{id}"
75
- composite << img
76
- end
77
-
78
-
79
-
80
- composite.save( "./#{@slug}/tmp/#{@slug}-#{@width}x#{@height}.png" )
81
-
82
- if composite_count < 1000
83
- composite.zoom(2).save( "./#{@slug}/tmp/#{@slug}-#{@width}x#{@height}@2x.png" )
84
- end
85
- end
86
-
87
-
88
-
89
-
90
- def calc_attribute_counters ## todo/check: use a different name _counts/_stats etc - why? why not?
91
-
92
- attributes_by_count = { count: 0,
93
- by_count: Hash.new(0)
94
- }
95
- counter = {}
96
-
97
-
98
- each_meta do |meta, id| ## todo/fix: change id to index
99
- traits = meta.traits
100
- # print "#{traits.size} - "
101
- # pp traits
102
-
103
- print "#{id}.." if id % 100 == 0 ## print progress report
104
-
105
- attributes_by_count[ :count ] +=1
106
- attributes_by_count[ :by_count ][ traits.size ] += 1
107
-
108
- traits.each do |trait_type, trait_value|
109
- trait_type = _normalize_trait_type( trait_type )
110
- trait_value = _normalize_trait_value( trait_value )
111
-
112
-
113
- rec = counter[ trait_type ] ||= { count: 0,
114
- by_type: Hash.new(0)
115
- }
116
- rec[ :count ] +=1
117
- rec[ :by_type ][ trait_value ] += 1
118
- end
119
- end
120
-
121
- print "\n"
122
- puts
123
-
124
- ## return all-in-one hash
125
- {
126
- total: attributes_by_count,
127
- traits: counter,
128
- }
129
- end
130
-
131
-
132
- def dump_attributes
133
- stats = calc_attribute_counters
134
-
135
- total = stats[:total]
136
- counter = stats[:traits]
137
-
138
- puts
139
- puts "attribute usage / counts:"
140
- pp total
141
- puts
142
-
143
- puts "#{counter.size} attribute(s):"
144
- counter.each do |trait_name, trait_rec|
145
- puts " #{trait_name} #{trait_rec[:count]} (#{trait_rec[:by_type].size} uniques)"
146
- end
147
-
148
- puts
149
- pp counter
150
- end
151
-
152
-
153
-
154
-
155
- ## order - allow "custom" attribute order export
156
- ## renames - allow renames of attributes
157
- def export_attributes(
158
- order: [],
159
- renames: {}
160
- )
161
-
162
- ## step 1: get counters
163
- stats = calc_attribute_counters
164
-
165
- total = stats[:total]
166
- counter = stats[:traits]
167
-
168
- puts
169
- puts "attribute usage / counts:"
170
- pp total
171
- puts
172
-
173
- puts "#{counter.size} attribute(s):"
174
- counter.each do |trait_name, trait_rec|
175
- puts " #{trait_name} #{trait_rec[:count]} (#{trait_rec[:by_type].size} uniques)"
176
- end
177
-
178
-
179
- trait_names = []
180
- trait_names += order ## get attributes if any in pre-defined order
181
- counter.each do |trait_name, _|
182
- if trait_names.include?( trait_name )
183
- next ## skip already included
184
- else
185
- trait_names << trait_name
186
- end
187
- end
188
-
189
-
190
- recs = []
191
-
192
-
193
- ## step 2: get tabular data
194
- each_meta do |meta, id| ## todo/fix: change id to index
195
-
196
- traits = meta.traits
197
- # print "#{traits.size} - "
198
- # pp traits
199
-
200
- print "#{id}.." if id % 100 == 0 ## print progress report
201
-
202
- ## setup empty hash table (with all attributes)
203
- rec = {}
204
-
205
- ## note: use __Slug__& __Name__
206
- ## to avoid conflict with attribute names
207
- ## e.g. attribute with "Name" will overwrite built-in and so on
208
-
209
- rec['__Slug__'] = if respond_to?( :_meta_slugify )
210
- _meta_slugify( meta, id )
211
- else
212
- ## default to id (six digits) as string with leading zeros
213
- ## for easy sorting using strings
214
- ## e.g. 1 => '000001'
215
- ## 2 => '000002'
216
- '%06d' % id
217
- end
218
-
219
- rec['__Name__'] = meta.name
220
-
221
- ## add all attributes/traits names/keys
222
- trait_names.reduce( rec ) { |h,value| h[value] = []; h }
223
- ## pp rec
224
-
225
- ## note: use an array (to allow multiple values for attributes)
226
- traits.each do |trait_type, trait_value|
227
- trait_type = _normalize_trait_type( trait_type )
228
- trait_value = _normalize_trait_value( trait_value )
229
-
230
- values = rec[ trait_type ]
231
- values << trait_value
232
- end
233
- recs << rec
234
- end
235
- print "\n"
236
-
237
- ## pp recs
238
-
239
- ## flatten recs
240
- data = []
241
- recs.each do |rec|
242
- row = rec.values.map do |value|
243
- if value.is_a?( Array )
244
- value.join( ' / ' )
245
- else
246
- value
247
- end
248
- end
249
- data << row
250
- end
251
-
252
-
253
- ## sort by slug
254
- data = data.sort {|l,r| l[0] <=> r[0] }
255
- pp data
256
-
257
- ### save dataset
258
- ## note: change first colum Slug to ID - only used for "internal" sort etc.
259
- headers = ['ID', 'Name']
260
- headers += trait_names.map do |trait_name| ## check for renames
261
- renames[trait_name] || trait_name
262
- end
263
-
264
-
265
- path = "./#{@slug}/tmp/#{@slug}.csv"
266
- dirname = File.dirname( path )
267
- FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
268
-
269
- File.open( path, 'w:utf-8' ) do |f|
270
- f.write( headers.join( ', ' ))
271
- f.write( "\n" )
272
- ## note: replace ID with our own internal running (zero-based) counter
273
- data.each_with_index do |row,i|
274
- f.write( ([i]+row[1..-1]).join( ', '))
275
- f.write( "\n" )
276
- end
277
- end
278
- end
279
-
280
-
281
-
282
-
283
- #############
284
- # "private" helpers
285
-
286
- def _normalize_trait_type( trait_type )
287
- if @patch && @patch[:trait_types]
288
- @patch[:trait_types][ trait_type ] || trait_type
289
- else
290
- trait_type
291
- end
292
- end
293
-
294
- def _normalize_trait_value( trait_value )
295
- if @patch && @patch[:trait_values]
296
- @patch[:trait_values][ trait_value ] || trait_value
297
- else
298
- trait_value
299
- end
300
- end
301
-
302
-
303
-
304
-
305
- end # class Base
306
- end # module Artbase
@@ -1,39 +0,0 @@
1
-
2
-
3
- class ImageCollection
4
-
5
- attr_reader :slug, :count
6
-
7
- def initialize( slug, count,
8
- image_base: ) # check: rename count to items or such - why? why not?
9
- @slug = slug
10
- @count = count
11
- @image_base = image_base
12
- end
13
-
14
- def download_images( range=(0...@count) )
15
- start = Time.now
16
- delay_in_s = 0.3
17
-
18
- range.each do |offset|
19
- image_src = @image_base.sub( '{id}', offset.to_s )
20
-
21
- puts "==> #{offset} - #{@slug}..."
22
-
23
- ## note: will auto-add format file extension (e.g. .png, .jpg)
24
- ## depending on http content type!!!!!
25
- copy_image( image_src, "./#{@slug}/image-i/#{offset}" )
26
-
27
- stop = Time.now
28
- diff = stop - start
29
-
30
- mins = diff / 60 ## todo - use floor or such?
31
- secs = diff % 60
32
- puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
33
-
34
- puts "sleeping #{delay_in_s}s..."
35
- sleep( delay_in_s )
36
- end
37
- end
38
- end # class ImageCollection
39
-
@@ -1,297 +0,0 @@
1
-
2
-
3
- class Collection ## todo/check - change to OpenseaCollection or such - why? why not?
4
-
5
- attr_reader :slug, :count
6
-
7
- # check: rename count to items or such - why? why not?
8
- # default format to '24x24' - why? why not?
9
- def initialize( slug, count,
10
- meta_slugify: nil,
11
- image_pixelate: nil,
12
- patch: nil,
13
- exclude: [],
14
- format:,
15
- source: )
16
- @slug = slug
17
- @count = count
18
-
19
- @meta_slugify = meta_slugify
20
- @image_pixelate = image_pixelate
21
-
22
- @patch = patch
23
-
24
- @exclude = exclude
25
-
26
- @width, @height = _parse_dimension( format )
27
-
28
-
29
- ## note: allow multiple source formats / dimensions
30
- ### e.g. convert 512x512 into [ [512,512] ]
31
- ##
32
- source = [source] unless source.is_a?( Array )
33
- @sources = source.map { |dimension| _parse_dimension( dimension ) }
34
- end
35
-
36
- ## e.g. convert dimension (width x height) "24x24" or "24 x 24" to [24,24]
37
- def _parse_dimension( str )
38
- str.split( /x/i ).map { |str| str.strip.to_i }
39
- end
40
-
41
-
42
- def _image_pixelate( img )
43
- if @image_pixelate
44
- @image_pixelate.call( img )
45
- else
46
- @sources.each do |source_width, source_height|
47
- if img.width == source_width && img.height == source_height
48
- from = "#{source_width}x#{source_height}"
49
- to = "#{@width}x#{@height}"
50
- steps = (Image::DOwNSAMPLING_STEPS[ to ] || {})[ from ]
51
- if steps.nil?
52
- puts "!! ERROR - no sampling steps defined for #{from} to #{to}; sorry"
53
- exit 1
54
- end
55
-
56
- return img.pixelate( steps )
57
- end
58
- end
59
-
60
- puts "!! ERROR - unknown image dimension #{img.width}x#{img.height}; sorry"
61
- puts " supported source dimensions include: #{@sources.inspect}"
62
- exit 1
63
- end
64
- end
65
-
66
-
67
-
68
-
69
- def download_meta( range=(0...@count) )
70
- self.class.download_meta( range, @slug )
71
- end
72
-
73
- def download_images( range=(0...@count) )
74
- self.class.download_images( range, @slug )
75
- end
76
-
77
- def download( range=(0...@count) )
78
- download_meta( range )
79
- download_images( range )
80
- end
81
-
82
-
83
-
84
-
85
-
86
- def _meta_slugify_match( regex, meta, index )
87
- if m=regex.match( meta.name )
88
- captures = m.named_captures ## get named captures in match data as hash (keys as strings)
89
- # e.g.
90
- #=> {"num"=>"3"}
91
- #=> {"num"=>"498", "name"=>"Doge"}
92
- pp captures
93
-
94
- num = captures['num'] ? captures['num'].to_i( 10 ) : nil ## note: add base 10 (e.g. 015=>15)
95
- name = captures['name'] ? captures['name'].strip : nil
96
-
97
- slug = ''
98
- if num
99
- slug << "%06d" % num ## todo/check: always fill/zero-pad with six 000000's - why? why not?
100
- end
101
-
102
- if name
103
- slug << "-" if num ## add separator
104
- slug << slugify( name )
105
- end
106
- slug
107
- else
108
- nil ## note: return nil if no match / slug
109
- end
110
- end
111
-
112
- def _do_meta_slugify( meta_slugify, meta, index )
113
- if meta_slugify.is_a?( Regexp )
114
- _meta_slugify_match( meta_slugify, meta, index )
115
- elsif meta_slugify.is_a?( Proc )
116
- meta_slugify.call( meta, index )
117
- else
118
- raise ArgumentError, "meta_slugify - unsupported type: #{meta_slugify.class.name}"
119
- end
120
- end
121
-
122
-
123
- def _meta_slugify( meta, index )
124
- slug = nil
125
-
126
- if @meta_slugify.is_a?( Array )
127
- @meta_slugify.each do |meta_slugify|
128
- slug = _do_meta_slugify( meta_slugify, meta, index )
129
- return slug if slug ## note: short-circuit on first match
130
- ## use break instead of return - why? why not?
131
- end
132
- else ## assume object e.g. Regexp, Proc, etc.
133
- slug = _do_meta_slugify( @meta_slugify, meta, index )
134
- end
135
-
136
- ## do nothing
137
- if slug.nil?
138
- puts "!! ERROR - cannot find id in >#{meta.name}<:"
139
- pp meta
140
- exit 1
141
- end
142
-
143
- slug
144
- end
145
-
146
-
147
-
148
- def each_meta( range=(0...@count),
149
- exclude: true, &blk )
150
- range.each do |id| ## todo/fix: change id to index
151
- meta = OpenSea::Meta.read( "./#{@slug}/meta/#{id}.json" )
152
-
153
- ####
154
- # filter out/skip
155
- if exclude && @exclude.include?( meta.name )
156
- puts " skipping / exclude #{id} >#{meta.name}<..."
157
- next
158
- end
159
-
160
- blk.call( meta, id )
161
- end
162
- end
163
-
164
-
165
-
166
-
167
- def pixelate( range=(0...@count) )
168
-
169
- meta_slugs = Hash.new( 0 ) ## deduplicate (auto-add counter if duplicate)
170
-
171
- ### todo/fix: must read slugs starting at 0
172
- ### to work for deduplicate!!!!!!
173
-
174
-
175
- range.each do |id|
176
- meta = OpenSea::Meta.read( "./#{@slug}/meta/#{id}.json" )
177
-
178
- ####
179
- # filter out/skip
180
- if @exclude.include?( meta.name )
181
- puts " skipping / exclude #{id} >#{meta.name}<..."
182
- next
183
- end
184
-
185
- puts meta.name
186
-
187
-
188
- meta_slug = _meta_slugify( meta, id )
189
- count = meta_slugs[ meta_slug ] += 1
190
-
191
- meta_slug = "#{meta_slug}_(#{count})" if count > 1
192
-
193
-
194
- img = Image.read( "./#{@slug}/i/#{id}.png" )
195
-
196
- pix = _image_pixelate( img )
197
-
198
- path = "./#{@slug}/ii/#{meta_slug}.png"
199
- puts " saving to >#{path}<..."
200
- pix.save( path )
201
- end
202
- end
203
-
204
-
205
-
206
- ################################
207
- # private (static) helpers
208
- #
209
-
210
- def self.download_images( range, collection,
211
- original: false )
212
- start = Time.now
213
- delay_in_s = 0.3
214
-
215
- range.each do |offset|
216
- meta = OpenSea::Meta.read( "./#{collection}/meta/#{offset}.json" )
217
-
218
- puts "==> #{offset}.json - #{meta.name}"
219
-
220
- image_src = if original
221
- meta.image_original_url
222
- else
223
- meta.image_url
224
- end
225
-
226
- puts " >#{image_src}<"
227
- if image_src.nil?
228
- puts "!! ERROR - no image url found (use original: #{original}):"
229
- pp meta
230
- exit 1
231
- end
232
-
233
- ## note: use a different directory to avoid size confusion!!!
234
- img_slug = if original
235
- 'i_org'
236
- else
237
- 'i'
238
- end
239
-
240
- ## note: will auto-add format file extension (e.g. .png, .jpg)
241
- ## depending on http content type!!!!!
242
- copy_image( image_src, "./#{collection}/#{img_slug}/#{offset}" )
243
-
244
- stop = Time.now
245
- diff = stop - start
246
-
247
- mins = diff / 60 ## todo - use floor or such?
248
- secs = diff % 60
249
- puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
250
-
251
- puts "sleeping #{delay_in_s}s..."
252
- sleep( delay_in_s )
253
- end
254
- end
255
-
256
-
257
- def self.download_meta( range, collection )
258
- start = Time.now
259
- delay_in_s = 0.3
260
-
261
- range.each do |offset|
262
-
263
- dest = "./#{collection}/meta/#{offset}.json"
264
- meta = nil
265
-
266
- puts "==> #{offset} / #{collection} (#{dest})..."
267
-
268
- data = OpenSea.assets( collection: collection,
269
- offset: offset )
270
- meta = OpenSea::Meta.new( data )
271
- puts " name: >#{meta.name}<"
272
- puts " image_url: >#{meta.image_url}<"
273
-
274
-
275
- ## make sure path exists
276
- dirname = File.dirname( dest )
277
- FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
278
-
279
- File.open( dest, "w:utf-8" ) do |f|
280
- f.write( JSON.pretty_generate( data ) )
281
- end
282
-
283
-
284
- stop = Time.now
285
- diff = stop - start
286
-
287
- mins = diff / 60 ## todo - use floor or such?
288
- secs = diff % 60
289
- puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
290
-
291
- puts " sleeping #{delay_in_s}s..."
292
- sleep( delay_in_s )
293
- end
294
- end
295
-
296
-
297
- end # class Collection