artbase 0.1.0 → 0.2.0

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