artbase 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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