artbase 0.1.0 → 0.2.2

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,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
+