artbase 0.2.1 → 0.3.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,372 +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
-
182
- range.each do |id|
183
-
184
- if exclude && @excludes.include?( id )
185
- puts " skipping #{id}; listed in excludes #{@excludes.inspect}"
186
- next
187
- end
188
-
189
- outpath = "./#{@slug}/#{@width}x#{@height}/#{id}.png"
190
- if !force && File.exist?( outpath )
191
- next ## note: skip if file already exists
192
- end
193
-
194
- center_x = if @center_x.is_a?( Proc ) then @center_x.call( id ); else @center_x; end
195
- center_y = if @center_y.is_a?( Proc ) then @center_y.call( id ); else @center_y; end
196
-
197
-
198
-
199
- puts "==> #{id} - reading / decoding #{id} ..."
200
- start = Time.now
201
-
202
- img = Image.read( "./#{@slug}/token-i/#{id}.png" )
203
-
204
- stop = Time.now
205
- diff = stop - start
206
-
207
- puts " in #{diff} sec(s)\n"
208
-
209
-
210
- source = nil
211
- @sources.each do |source_width, source_height|
212
- if img.width == source_width && img.height == source_height
213
- source = [source_width, source_height]
214
- break
215
- end
216
- end
217
-
218
-
219
- if source
220
- source_width = source[0]
221
- source_height = source[1]
222
-
223
- steps_x = Image.calc_sample_steps( source_width-@top_x, @width, center: center_x )
224
- steps_y = Image.calc_sample_steps( source_height-@top_y, @height, center: center_y )
225
-
226
- pix = if debug
227
- img.pixelate_debug( steps_x, steps_y,
228
- top_x: @top_x,
229
- top_y: @top_y )
230
- else
231
- img.pixelate( steps_x, steps_y,
232
- top_x: @top_x,
233
- top_y: @top_y )
234
- end
235
- ## todo/check: keep usingu slug e.g. 0001.png or "plain" 1.png - why? why not?
236
- ## slug = "%04d" % id
237
- pix.save( outpath )
238
-
239
- if zoom
240
- outpath = "./#{@slug}/#{@width}x#{@height}/#{id}@#{zoom}x.png"
241
- pix.zoom( zoom ).save( outpath )
242
- end
243
- else
244
- puts "!! ERROR - unknown/unsupported dimension - #{img.width}x#{img.height}; sorry - tried:"
245
- pp @sources
246
- exit 1
247
- end
248
- end
249
- end
250
-
251
-
252
-
253
- def meta_url( id: )
254
- src = @token_base.gsub( '{id}', id.to_s )
255
-
256
- ## quick ipfs (interplanetary file system) hack - make more reusabele!!!
257
- src = handle_ipfs( src )
258
- src
259
- end
260
- alias_method :token_url, :meta_url
261
-
262
-
263
- def image_url( id:,
264
- direct: @image_base ? true : false )
265
- src = if direct && @image_base
266
- ###
267
- ## todo/fix:
268
- ## change image_base_id_format
269
- ## to image_base proc with para id and call proc!!!!
270
- if @image_base_id_format
271
- @image_base.gsub( '{id}', @image_base_id_format % id )
272
- else
273
- @image_base.gsub( '{id}', id.to_s )
274
- end
275
- else
276
- ## todo/check - change/rename data to meta - why? why not?
277
- data = Meta.read( "./#{@slug}/token/#{id}.json" )
278
-
279
- meta_name = data.name
280
- meta_image = data.image
281
-
282
- puts "==> #{id} - #{@slug}..."
283
- puts " name: #{meta_name}"
284
- puts " image: #{meta_image}"
285
- meta_image
286
- end
287
- src
288
-
289
- ## quick ipfs (interplanetary file system) hack - make more reusabele!!!
290
- src = handle_ipfs( src )
291
- src
292
- end
293
-
294
-
295
-
296
- def download_meta( range=_range, force: false )
297
- start = Time.now
298
- delay_in_s = 0.3
299
-
300
- range.each do |id|
301
- outpath = "./#{@slug}/token/#{id}.json"
302
- if !force && File.exist?( outpath )
303
- next ## note: skip if file already exists
304
- end
305
-
306
- dirname = File.dirname( outpath )
307
- FileUtils.mkdir_p( dirname ) unless Dir.exist?( dirname )
308
-
309
- puts "==> #{id} - #{@slug}..."
310
-
311
- token_src = meta_url( id: id )
312
- copy_json( token_src, outpath )
313
-
314
- stop = Time.now
315
- diff = stop - start
316
- puts " download token metadata in #{diff} sec(s)"
317
-
318
- mins = diff / 60 ## todo - use floor or such?
319
- secs = diff % 60
320
- puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
321
-
322
- puts "sleeping #{delay_in_s}s..."
323
- sleep( delay_in_s )
324
- end
325
- end
326
-
327
-
328
- ## note: default to direct true if image_base present/availabe
329
- ## otherwise to false
330
- ## todo/check: change/rename force para to overwrite - why? why not?
331
- def download_images( range=_range, force: false,
332
- direct: @image_base ? true : false )
333
- start = Time.now
334
- delay_in_s = 0.3
335
-
336
- range.each do |id|
337
-
338
- ## note: skip if (downloaded) file already exists
339
- skip = false
340
- if !force
341
- ['png', 'gif', 'jgp', 'svg'].each do |format|
342
- if File.exist?( "./#{@slug}/token-i/#{id}.#{format}" )
343
- skip = true
344
- break
345
- end
346
- end
347
- end
348
- next if skip
349
-
350
- image_src = image_url( id: id, direct: direct )
351
-
352
- ## note: will auto-add format file extension (e.g. .png, .jpg)
353
- ## depending on http content type!!!!!
354
- start_copy = Time.now
355
- copy_image( image_src, "./#{@slug}/token-i/#{id}" )
356
-
357
- stop = Time.now
358
-
359
- diff = stop - start_copy
360
- puts " download image in #{diff} sec(s)"
361
-
362
- diff = stop - start
363
- mins = diff / 60 ## todo - use floor or such?
364
- secs = diff % 60
365
- puts "up #{mins} mins #{secs} secs (total #{diff} secs)"
366
-
367
- puts "sleeping #{delay_in_s}s..."
368
- sleep( delay_in_s )
369
- end
370
- end
371
-
372
- 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