artbase 0.1.0 → 0.2.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,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
+