artbase 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,400 +0,0 @@
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 +0,0 @@
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
-
@@ -1,169 +0,0 @@
1
-
2
-
3
-
4
- def slugify( name )
5
- name.downcase.gsub( /[^a-z0-9 ()$_-]/ ) do |_|
6
- puts " !! WARN: asciify - found (and removing) non-ascii char >#{Regexp.last_match}<"
7
- '' ## remove - use empty string
8
- end.gsub( ' ', '_')
9
- end
10
-
11
-
12
-
13
- =begin
14
- moved/ use Image.convert !!! remove here
15
- def convert_images( collection, from: 'jpg',
16
- to: 'png',
17
- dir: 'i',
18
- overwrite: true )
19
- files = Dir.glob( "./#{collection}/#{dir}/*.#{from}" )
20
- puts "==> converting #{files.size} image(s) from #{from} to #{to}"
21
-
22
- files.each_with_index do |file,i|
23
- dirname = File.dirname( file )
24
- extname = File.extname( file )
25
- basename = File.basename( file, extname )
26
-
27
- ## skip convert if target / dest file already exists
28
- next if overwrite == false && File.exist?( "#{dirname}/#{basename}.#{to}" )
29
-
30
-
31
- cmd = "magick convert #{dirname}/#{basename}.#{from} #{dirname}/#{basename}.#{to}"
32
-
33
- puts " [#{i+1}/#{files.size}] - #{cmd}"
34
- system( cmd )
35
-
36
- if from == 'gif'
37
- ## assume multi-images for gif
38
- ## save image-0.png to image.png
39
- path0 = "#{dirname}/#{basename}-0.#{to}"
40
- path = "#{dirname}/#{basename}.#{to}"
41
-
42
- puts " saving #{path0} to #{path}..."
43
-
44
- blob = File.open( path0, 'rb' ) { |f| f.read }
45
- File.open( path, 'wb' ) { |f| f.write( blob ) }
46
- end
47
- end
48
- end
49
- =end
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
- def copy_json( src, dest )
58
- uri = URI.parse( src )
59
-
60
- http = Net::HTTP.new( uri.host, uri.port )
61
-
62
- puts "[debug] GET #{uri.request_uri} uri=#{uri}"
63
-
64
- headers = { 'User-Agent' => "ruby v#{RUBY_VERSION}" }
65
-
66
-
67
- request = Net::HTTP::Get.new( uri.request_uri, headers )
68
- if uri.instance_of? URI::HTTPS
69
- http.use_ssl = true
70
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
71
- end
72
-
73
- response = http.request( request )
74
-
75
- if response.code == '200'
76
- puts "#{response.code} #{response.message}"
77
- puts " content_type: #{response.content_type}, content_length: #{response.content_length}"
78
-
79
- text = response.body.to_s
80
- text = text.force_encoding( Encoding::UTF_8 )
81
-
82
- data = JSON.parse( text )
83
-
84
- File.open( dest, "w:utf-8" ) do |f|
85
- f.write( JSON.pretty_generate( data ) )
86
- end
87
- else
88
- puts "!! error:"
89
- puts "#{response.code} #{response.message}"
90
- exit 1
91
- end
92
- end
93
-
94
-
95
- def copy_image( src, dest,
96
- dump_headers: false )
97
- uri = URI.parse( src )
98
-
99
- http = Net::HTTP.new( uri.host, uri.port )
100
-
101
- puts "[debug] GET #{uri.request_uri} uri=#{uri}"
102
-
103
- headers = { 'User-Agent' => "ruby v#{RUBY_VERSION}" }
104
-
105
- request = Net::HTTP::Get.new( uri.request_uri, headers )
106
- if uri.instance_of? URI::HTTPS
107
- http.use_ssl = true
108
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
109
- end
110
-
111
- response = http.request( request )
112
-
113
- if response.code == '200'
114
- puts "#{response.code} #{response.message}"
115
-
116
- content_type = response.content_type
117
- content_length = response.content_length
118
- puts " content_type: #{content_type}, content_length: #{content_length}"
119
-
120
- if dump_headers ## for debugging dump headers
121
- headers = response.each_header.to_h
122
- puts "htttp respone headers:"
123
- pp headers
124
- end
125
-
126
-
127
- format = if content_type =~ %r{image/jpeg}i
128
- 'jpg'
129
- elsif content_type =~ %r{image/png}i
130
- 'png'
131
- elsif content_type =~ %r{image/gif}i
132
- 'gif'
133
- elsif content_type =~ %r{image/svg}i
134
- 'svg'
135
- else
136
- puts "!! error:"
137
- puts " unknown image format content type: >#{content_type}<"
138
- exit 1
139
- end
140
-
141
- ## make sure path exits - autocreate dirs
142
- ## make sure path exists
143
- dirname = File.dirname( "#{dest}.#{format}" )
144
- FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
145
-
146
- if format == 'svg'
147
- ## save as text (note: assume utf-8 encoding for now)
148
- text = response.body.to_s
149
- text = text.force_encoding( Encoding::UTF_8 )
150
-
151
- File.open( "#{dest}.svg", 'w:utf-8' ) do |f|
152
- f.write( text )
153
- end
154
- else
155
- ## save as binary
156
- File.open( "#{dest}.#{format}", 'wb' ) do |f|
157
- f.write( response.body )
158
- end
159
- end
160
- else
161
- puts "!! error:"
162
- puts "#{response.code} #{response.message}"
163
- exit 1
164
- end
165
- end
166
-
167
-
168
-
169
-
@@ -1,31 +0,0 @@
1
- module Pixelart
2
-
3
- class Image
4
-
5
- ###
6
- # add common
7
- # pixel(ate) steps/offsets (for re/down/sampling)
8
- DOwNSAMPLING_STEPS = {
9
- '24x24' => {
10
- '269x269' => Image.calc_sample_steps( 269, 24 ), # width (269px), new_width (24px)
11
- '512x512' => Image.calc_sample_steps( 512, 24 ), # width (512px), new_width (24px)
12
- },
13
- '32x32' => {
14
- '320x320' => Image.calc_sample_steps( 320, 32 ),
15
- '512x512' => Image.calc_sample_steps( 512, 32 ),
16
- },
17
- '35x35' => {
18
- '512x512' => Image.calc_sample_steps( 512, 35 ),
19
- },
20
- '60x60' => {
21
- '512x512' => Image.calc_sample_steps( 512, 60 ),
22
- },
23
- '80x80' => {
24
- '512x512' => Image.calc_sample_steps( 512, 80 ),
25
- },
26
- }
27
-
28
- end # class Image
29
- end # module Pixelart
30
-
31
-
data/lib/artbase/image.rb DELETED
@@ -1,31 +0,0 @@
1
- ######################
2
- # pixelart image extensions
3
- # move upstream!!!!!
4
-
5
-
6
- module Pixelart
7
- class ImageComposite
8
- def add_glob( glob )
9
- files = Dir.glob( glob )
10
- puts "#{files.size} file(s) found matching >#{glob}<"
11
-
12
-
13
- files = files.sort
14
- ## puts files.inspect
15
-
16
- files.each_with_index do |file,i|
17
- puts "==> [#{i+1}/#{files.size}] - #{file}"
18
- img = Image.read( file )
19
-
20
- self << img ## todo/check: use add alias - why? why not?
21
- end
22
- end
23
- end # class ImageComposite
24
- end # module Pixelart
25
-
26
-
27
-
28
- require_relative 'image/sample' ## check - change to downsample/pixelate - why? why not?
29
-
30
-
31
-
data/lib/artbase/retry.rb DELETED
@@ -1,41 +0,0 @@
1
-
2
- ## for more ideas on retry
3
- ## see https://github.com/ooyala/retries
4
-
5
-
6
- ## todo/check: use a different name than retry - why? why not?
7
- def retry_on_error( max_tries: 3, &block )
8
- errors = []
9
- delay = 3 ## 3 secs
10
-
11
- begin
12
- block.call
13
-
14
- ## note: add more exception here (separated by comma) like
15
- ## rescue Errno::ETIMEDOUT, Errno::ECONNREFUSED => e
16
- rescue Net::ReadTimeout => e
17
- ## (re)raise (use raise with arguments or such - why? why not?)
18
- raise if errors.size >= max_tries
19
-
20
- ## ReadTimeout, a subclass of Timeout::Error, is raised if a chunk of the response cannot be read within the read_timeout.
21
- ## subclass of RuntimeError
22
- ## subclass of StandardError
23
- ## subclass of Exception
24
- puts "!! ERROR - #{e}:"
25
- pp e
26
-
27
- errors << e
28
-
29
- puts
30
- puts "==> retrying (count=#{errors.size}, max_tries=#{max_tries}) in #{delay} sec(s)..."
31
- sleep( delay )
32
- retry
33
- end
34
-
35
- if errors.size > 0
36
- puts " #{errors.size} retry attempt(s) on error(s):"
37
- pp errors
38
- end
39
-
40
- errors
41
- end