artbase 0.1.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,72 +1,400 @@
1
-
2
- class TokenCollection
3
-
4
- attr_reader :slug, :count
5
-
6
- def initialize( slug, count,
7
- token_base: ) # check: rename count to items or such - why? why not?
8
- @slug = slug
9
- @count = count
10
- @token_base = token_base
11
- end
12
-
13
-
14
- def download_meta( range=(0...@count) )
15
- start = Time.now
16
- delay_in_s = 0.3
17
-
18
- range.each do |offset|
19
- token_src = @token_base.sub( '{id}', offset.to_s )
20
-
21
- puts "==> #{offset} - #{@slug}..."
22
-
23
- copy_json( token_src, "./#{@slug}/token-meta/#{offset}.json" )
24
-
25
- stop = Time.now
26
- diff = stop - start
27
-
28
- mins = diff / 60 ## todo - use floor or such?
29
- secs = diff % 60
30
- puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
31
-
32
- puts "sleeping #{delay_in_s}s..."
33
- sleep( delay_in_s )
34
- end
35
- end
36
-
37
-
38
- def download_images( range=(0...@count) )
39
- start = Time.now
40
- delay_in_s = 0.3
41
-
42
- range.each do |offset|
43
- txt = File.open( "./#{@slug}/token-meta/#{offset}.json", 'r:utf-8') { |f| f.read }
44
- data = JSON.parse( txt )
45
-
46
- meta_name = data['name']
47
- meta_image = data['image']
48
-
49
- puts "==> #{offset} - #{@slug}..."
50
- puts " name: #{meta_name}"
51
- puts " image: #{meta_image}"
52
-
53
- ## note: will auto-add format file extension (e.g. .png, .jpg)
54
- ## depending on http content type!!!!!
55
- start_copy = Time.now
56
- copy_image( meta_image, "./#{@slug}/token-i/#{offset}" )
57
-
58
- stop = Time.now
59
-
60
- diff = stop -start_copy
61
- puts " download image in #{diff} sec(s)"
62
-
63
- diff = stop - start
64
- mins = diff / 60 ## todo - use floor or such?
65
- secs = diff % 60
66
- puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
67
-
68
- puts "sleeping #{delay_in_s}s..."
69
- sleep( delay_in_s )
70
- end
71
- end
72
- end # class TokenCollection
1
+
2
+ class TokenCollection < Artbase::Base
3
+
4
+
5
+ #############
6
+ # (nested) Meta classes
7
+ # read meta data into struct
8
+ class Meta
9
+ def self.read( path )
10
+ new( read_json( path ))
11
+ end
12
+
13
+
14
+ def initialize( data )
15
+ @data = data
16
+ end
17
+
18
+
19
+ def name
20
+ ## note: name might be an integer number e.g. 0/1/2 etc.
21
+ ## e.g. see crypto pudgy punks and others?
22
+ ## always auto-convert to string
23
+ @name ||= _normalize( @data['name'].to_s )
24
+ end
25
+
26
+
27
+ def description
28
+ @description ||= _normalize( @data['description'] )
29
+ end
30
+
31
+ ## note: auto-convert "" (empty string) to nil
32
+ def image() _blank( @data['image'] ); end
33
+ alias_method :image_url, :image ## add image_url alias - why? why not?
34
+
35
+
36
+ def attributes
37
+ @attributes ||= begin
38
+ traits = []
39
+ ## keep traits as (simple)
40
+ ## ordered array of pairs for now
41
+ ##
42
+ ## in a step two make lookup via hash table
43
+ ## or such easier / "automagic"
44
+
45
+ @data[ 'attributes' ].each do |t|
46
+ trait_type = t['trait_type'].strip
47
+ trait_value = t['value'].strip
48
+ traits << [trait_type, trait_value]
49
+ end
50
+
51
+ traits
52
+ end
53
+ end
54
+ alias_method :traits, :attributes ## keep traits alias - why? why not?
55
+
56
+ ### "private" convenience / helper methods
57
+ def _normalize( str )
58
+ return if str.nil? ## check: check for nil - why? why not?
59
+
60
+ ## normalize string
61
+ ## remove leading and trailing spaces
62
+ ## collapse two and more spaces into one
63
+ ## change unicode space to ascii
64
+ str = str.gsub( "\u{00a0}", ' ' )
65
+ str = str.strip.gsub( /[ ]{2,}/, ' ' )
66
+ str
67
+ end
68
+
69
+ def _blank( o ) ## auto-convert "" (empty string) into nil
70
+ if o && o.strip.empty?
71
+ nil
72
+ else
73
+ o
74
+ end
75
+ end
76
+ end # (nested) class Meta
77
+
78
+
79
+
80
+
81
+ attr_reader :slug, :count
82
+
83
+ def initialize( slug, count,
84
+ token_base:,
85
+ image_base: nil,
86
+ image_base_id_format: nil,
87
+ format:,
88
+ source:,
89
+ top_x: 0,
90
+ top_y: 0,
91
+ center_x: true,
92
+ center_y: true,
93
+ excludes: [],
94
+ offset: 0 ) # check: rename count to items or such - why? why not?
95
+ @slug = slug
96
+ @count = count
97
+ @offset = offset ## starting by default at 0 (NOT 1 or such)
98
+
99
+ @token_base = token_base
100
+ @image_base = image_base
101
+ @image_base_id_format = image_base_id_format
102
+
103
+ @width, @height = _parse_dimension( format )
104
+
105
+
106
+ ## note: allow multiple source formats / dimensions
107
+ ### e.g. convert 512x512 into [ [512,512] ]
108
+ ##
109
+ source = [source] unless source.is_a?( Array )
110
+ @sources = source.map { |dimension| _parse_dimension( dimension ) }
111
+
112
+ @top_x = top_x ## more (down)sampling / pixelate options
113
+ @top_y = top_y
114
+ @center_x = center_x
115
+ @center_y = center_y
116
+
117
+ @excludes = excludes
118
+ end
119
+
120
+
121
+ ## e.g. convert dimension (width x height) "24x24" or "24 x 24" to [24,24]
122
+ def _parse_dimension( str )
123
+ str.split( /x/i ).map { |str| str.strip.to_i }
124
+ end
125
+
126
+
127
+ def _range( offset: 0 ) ## return "default" range - make "private" helper public - why? why not?
128
+ ## note: range uses three dots (...) exclusive (NOT inclusive) range
129
+ ## e.g. 0...100 => [0,..,99]
130
+ ## 1...101 => [1,..,100]
131
+ ##
132
+ ## note: allow offset argument
133
+ ## (to start with different offset - note: in addition to builtin 0/1 offset)
134
+
135
+ (0+@offset+offset...@count+@offset)
136
+ end
137
+
138
+
139
+
140
+ def each_image( range=_range,
141
+ exclude: true, &blk )
142
+ range.each do |id|
143
+ ####
144
+ # filter out/skip
145
+ # if exclude && @excludes.include?( id )
146
+ # puts " skipping / exclude #{id}..."
147
+ # next
148
+ # end
149
+
150
+ puts "==> #{id}"
151
+ img = Image.read( "./#{@slug}/#{@width}x#{@height}/#{id}.png" )
152
+ blk.call( img, id )
153
+ end
154
+ end
155
+
156
+
157
+ def each_meta( range=_range,
158
+ exclude: true, &blk )
159
+ range.each do |id| ## check: change/rename id to index - why? why not?
160
+ meta = Meta.read( "./#{@slug}/token/#{id}.json" )
161
+
162
+ ####
163
+ # filter out/skip
164
+ # if exclude && @excludes.include?( meta.name )
165
+ # puts " skipping / exclude #{id} >#{meta.name}<..."
166
+ # next
167
+ # end
168
+
169
+ blk.call( meta, id )
170
+ end
171
+ end
172
+
173
+
174
+
175
+
176
+
177
+ def pixelate( range=_range, exclude: true,
178
+ force: false,
179
+ debug: false,
180
+ zoom: nil,
181
+ faster: false )
182
+
183
+ range.each do |id|
184
+
185
+ if exclude && @excludes.include?( id )
186
+ puts " skipping #{id}; listed in excludes #{@excludes.inspect}"
187
+ next
188
+ end
189
+
190
+ outpath = "./#{@slug}/#{@width}x#{@height}/#{id}.png"
191
+ if !force && File.exist?( outpath )
192
+ next ## note: skip if file already exists
193
+ end
194
+
195
+ center_x = if @center_x.is_a?( Proc ) then @center_x.call( id ); else @center_x; end
196
+ center_y = if @center_y.is_a?( Proc ) then @center_y.call( id ); else @center_y; end
197
+
198
+
199
+
200
+ puts "==> #{id} - reading / decoding #{id} ..."
201
+
202
+
203
+ if faster
204
+ ## note: faster for now only supports
205
+ ## single /one source format
206
+ ## always will use first source format from array for now
207
+ cmd = "./pixelator "
208
+ cmd << "./#{@slug}/token-i/#{id}.png"
209
+ cmd << " " + @sources[0][0].to_s
210
+ cmd << " " + @sources[0][1].to_s
211
+ cmd << " " + outpath
212
+ cmd << " " + @width.to_s
213
+ cmd << " " + @height.to_s
214
+ puts "==> #{cmd}..."
215
+ ret = system( cmd )
216
+ if ret
217
+ puts "OK"
218
+ else
219
+ puts "!! FAIL"
220
+ if ret.nil?
221
+ puts " command not found"
222
+ else
223
+ puts " exit code: #{$?}"
224
+ end
225
+ end
226
+ else
227
+ start = Time.now
228
+
229
+ img = Image.read( "./#{@slug}/token-i/#{id}.png" )
230
+
231
+ stop = Time.now
232
+ diff = stop - start
233
+
234
+ puts " in #{diff} sec(s)\n"
235
+
236
+
237
+ source = nil
238
+ @sources.each do |source_width, source_height|
239
+ if img.width == source_width && img.height == source_height
240
+ source = [source_width, source_height]
241
+ break
242
+ end
243
+ end
244
+
245
+
246
+ if source
247
+ source_width = source[0]
248
+ source_height = source[1]
249
+
250
+ steps_x = Image.calc_sample_steps( source_width-@top_x, @width, center: center_x )
251
+ steps_y = Image.calc_sample_steps( source_height-@top_y, @height, center: center_y )
252
+
253
+ pix = if debug
254
+ img.pixelate_debug( steps_x, steps_y,
255
+ top_x: @top_x,
256
+ top_y: @top_y )
257
+ else
258
+ img.pixelate( steps_x, steps_y,
259
+ top_x: @top_x,
260
+ top_y: @top_y )
261
+ end
262
+ ## todo/check: keep usingu slug e.g. 0001.png or "plain" 1.png - why? why not?
263
+ ## slug = "%04d" % id
264
+ pix.save( outpath )
265
+
266
+ if zoom
267
+ outpath = "./#{@slug}/#{@width}x#{@height}/#{id}@#{zoom}x.png"
268
+ pix.zoom( zoom ).save( outpath )
269
+ end
270
+ else
271
+ puts "!! ERROR - unknown/unsupported dimension - #{img.width}x#{img.height}; sorry - tried:"
272
+ pp @sources
273
+ exit 1
274
+ end
275
+ end
276
+ end
277
+ end
278
+
279
+
280
+
281
+ def meta_url( id: )
282
+ src = @token_base.gsub( '{id}', id.to_s )
283
+
284
+ ## quick ipfs (interplanetary file system) hack - make more reusabele!!!
285
+ src = handle_ipfs( src )
286
+ src
287
+ end
288
+ alias_method :token_url, :meta_url
289
+
290
+
291
+ def image_url( id:,
292
+ direct: @image_base ? true : false )
293
+ src = if direct && @image_base
294
+ ###
295
+ ## todo/fix:
296
+ ## change image_base_id_format
297
+ ## to image_base proc with para id and call proc!!!!
298
+ if @image_base_id_format
299
+ @image_base.gsub( '{id}', @image_base_id_format % id )
300
+ else
301
+ @image_base.gsub( '{id}', id.to_s )
302
+ end
303
+ else
304
+ ## todo/check - change/rename data to meta - why? why not?
305
+ data = Meta.read( "./#{@slug}/token/#{id}.json" )
306
+
307
+ meta_name = data.name
308
+ meta_image = data.image
309
+
310
+ puts "==> #{id} - #{@slug}..."
311
+ puts " name: #{meta_name}"
312
+ puts " image: #{meta_image}"
313
+ meta_image
314
+ end
315
+ src
316
+
317
+ ## quick ipfs (interplanetary file system) hack - make more reusabele!!!
318
+ src = handle_ipfs( src )
319
+ src
320
+ end
321
+
322
+
323
+
324
+ def download_meta( range=_range, force: false )
325
+ start = Time.now
326
+ delay_in_s = 0.3
327
+
328
+ range.each do |id|
329
+ outpath = "./#{@slug}/token/#{id}.json"
330
+ if !force && File.exist?( outpath )
331
+ next ## note: skip if file already exists
332
+ end
333
+
334
+ dirname = File.dirname( outpath )
335
+ FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
336
+
337
+ puts "==> #{id} - #{@slug}..."
338
+
339
+ token_src = meta_url( id: id )
340
+ copy_json( token_src, outpath )
341
+
342
+ stop = Time.now
343
+ diff = stop - start
344
+ puts " download token metadata in #{diff} sec(s)"
345
+
346
+ mins = diff / 60 ## todo - use floor or such?
347
+ secs = diff % 60
348
+ puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
349
+
350
+ puts "sleeping #{delay_in_s}s..."
351
+ sleep( delay_in_s )
352
+ end
353
+ end
354
+
355
+
356
+ ## note: default to direct true if image_base present/availabe
357
+ ## otherwise to false
358
+ ## todo/check: change/rename force para to overwrite - why? why not?
359
+ def download_images( range=_range, force: false,
360
+ direct: @image_base ? true : false )
361
+ start = Time.now
362
+ delay_in_s = 0.3
363
+
364
+ range.each do |id|
365
+
366
+ ## note: skip if (downloaded) file already exists
367
+ skip = false
368
+ if !force
369
+ ['png', 'gif', 'jgp', 'svg'].each do |format|
370
+ if File.exist?( "./#{@slug}/token-i/#{id}.#{format}" )
371
+ skip = true
372
+ break
373
+ end
374
+ end
375
+ end
376
+ next if skip
377
+
378
+ image_src = image_url( id: id, direct: direct )
379
+
380
+ ## note: will auto-add format file extension (e.g. .png, .jpg)
381
+ ## depending on http content type!!!!!
382
+ start_copy = Time.now
383
+ copy_image( image_src, "./#{@slug}/token-i/#{id}" )
384
+
385
+ stop = Time.now
386
+
387
+ diff = stop - start_copy
388
+ puts " download image in #{diff} sec(s)"
389
+
390
+ diff = stop - start
391
+ mins = diff / 60 ## todo - use floor or such?
392
+ secs = diff % 60
393
+ puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
394
+
395
+ puts "sleeping #{delay_in_s}s..."
396
+ sleep( delay_in_s )
397
+ end
398
+ end
399
+
400
+ end # class TokenCollection
@@ -1,12 +1,12 @@
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
- require_relative 'collection/token'
10
- require_relative 'collection/image'
11
- require_relative 'collection/opensea'
12
-
1
+
2
+
3
+
4
+
5
+
6
+
7
+
8
+ require_relative 'collection/base'
9
+ require_relative 'collection/token'
10
+ require_relative 'collection/image'
11
+ require_relative 'collection/opensea'
12
+