class Collection

Attributes

count[R]
slug[R]

Public Class Methods

download_images( range, collection, original: false ) click to toggle source

private (static) helpers

# File lib/artbase/collection/opensea.rb, line 397
def self.download_images( range, collection,
                            original: false )
  start = Time.now
  delay_in_s = 0.3

  range.each do |offset|
    meta = OpenSea::Meta.read( "./#{collection}/meta/#{offset}.json" )

    puts "==> #{offset}.json  -  #{meta.name}"

    image_src =  if original
                   meta.image_original_url
                 else
                   meta.image_url
                 end

    puts "    >#{image_src}<"
    if image_src.nil?
       puts "!! ERROR - no image url found (use original: #{original}):"
       pp meta
       exit 1
    end

    ## note: use a different directory to avoid size confusion!!!
    img_slug =  if original
                   'i_org'
                else
                   'i'
                end

    ## note: will auto-add format file extension (e.g. .png, .jpg)
    ##        depending on http content type!!!!!
    copy_image( image_src, "./#{collection}/#{img_slug}/#{offset}" )

    stop = Time.now
    diff = stop - start

    mins = diff / 60  ## todo - use floor or such?
    secs = diff % 60
    puts "up #{mins} mins #{secs} secs (total #{diff} secs)"

    puts "sleeping #{delay_in_s}s..."
    sleep( delay_in_s )
  end
end
download_meta( range, collection ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 444
def self.download_meta( range, collection )
  start = Time.now
  delay_in_s = 0.3

  range.each do |offset|

    dest = "./#{collection}/meta/#{offset}.json"
    meta = nil

    puts "==> #{offset} / #{collection} (#{dest})..."

    data = OpenSea.assets( collection: collection,
                           offset:     offset )
    meta = OpenSea::Meta.new( data )
    puts "  name:      >#{meta.name}<"
    puts "  image_url: >#{meta.image_url}<"


    ## make sure path exists
    dirname = File.dirname( dest )
    FileUtils.mkdir_p( dirname )  unless Dir.exist?( dirname )

    File.open( dest, "w:utf-8" ) do |f|
      f.write( JSON.pretty_generate( data ) )
    end


    stop = Time.now
    diff = stop - start

    mins = diff / 60  ## todo - use floor or such?
    secs = diff % 60
    puts "up #{mins} mins #{secs} secs (total #{diff} secs)"

    puts "  sleeping #{delay_in_s}s..."
    sleep( delay_in_s )
  end
end
new( slug, count, meta_slugify: nil, image_pixelate: nil, patch: nil, exclude: [], format:, source: ) click to toggle source

check: rename count to items or such - why? why not?

default format to '24x24' - why? why not?
# File lib/artbase/collection/opensea.rb, line 9
def initialize( slug, count,
                meta_slugify: nil,
                image_pixelate: nil,
                patch: nil,
                exclude: [],
                format:,
                source:  )
  @slug = slug
  @count = count

  @meta_slugify   = meta_slugify
  @image_pixelate = image_pixelate

  @patch  = patch

  @exclude = exclude

  @width, @height = _parse_dimension( format )


  ## note: allow multiple source formats / dimensions
  ### e.g. convert   512x512 into  [ [512,512] ]
  ##
  source = [source]  unless source.is_a?( Array )
  @sources = source.map { |dimension| _parse_dimension( dimension ) }
end

Public Instance Methods

_do_meta_slugify( meta_slugify, meta, index ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 129
def _do_meta_slugify( meta_slugify, meta, index )
  if meta_slugify.is_a?( Regexp )
    _meta_slugify_match( meta_slugify, meta, index )
  elsif meta_slugify.is_a?( Proc )
    meta_slugify.call( meta, index )
  else
    raise ArgumentError, "meta_slugify - unsupported type: #{meta_slugify.class.name}"
  end
end
_image_pixelate( img ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 42
def _image_pixelate( img )
  if @image_pixelate
    @image_pixelate.call( img )
  else
    @sources.each do |source_width, source_height|
      if img.width == source_width && img.height == source_height
        return img.pixelate( from: "#{source_width}x#{source_height}",
                             to: "#{@width}x#{@height}" )
      end
    end

    puts "!! ERROR - unknown image dimension #{img.width}x#{img.height}; sorry"
    puts "           supported source dimensions include: #{@sources.inspect}"
    exit 1
  end
end
_meta_slugify( meta, index ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 140
def _meta_slugify( meta, index )
  slug = nil

  if @meta_slugify.is_a?( Array )
      @meta_slugify.each do |meta_slugify|
         slug = _do_meta_slugify( meta_slugify, meta, index )
         return slug   if slug     ## note: short-circuit on first match
                                   ##   use break instead of return - why? why not?
      end
   else  ## assume object e.g. Regexp, Proc, etc.
     slug = _do_meta_slugify( @meta_slugify, meta, index )
   end

   ## do nothing
   if slug.nil?
      puts "!! ERROR - cannot find id in >#{meta.name}<:"
      pp meta
      exit 1
   end

   slug
end
_meta_slugify_match( regex, meta, index ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 103
def _meta_slugify_match( regex, meta, index )
  if m=regex.match( meta.name )
    captures = m.named_captures   ## get named captures in match data as hash (keys as strings)
    # e.g.
    #=> {"num"=>"3"}
    #=> {"num"=>"498", "name"=>"Doge"}
    pp captures

    num  = captures['num']  ? captures['num'].to_i( 10 ) : nil   ## note: add base 10 (e.g. 015=>15)
    name = captures['name'] ? captures['name'].strip     : nil

    slug = ''
    if num
      slug << "%06d" % num    ## todo/check: always fill/zero-pad with six 000000's - why? why not?
    end

    if name
      slug << "-"   if num   ## add separator
      slug << slugify( name )
    end
    slug
  else
     nil  ## note: return nil if no match / slug
  end
end
_normalize_trait_type( trait_type ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 182
def _normalize_trait_type( trait_type )
  if @patch && @patch[:trait_types]
    @patch[:trait_types][ trait_type ] || trait_type
  else
     trait_type
  end
end
_normalize_trait_value( trait_value ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 190
def _normalize_trait_value( trait_value )
  if @patch && @patch[:trait_values]
    @patch[:trait_values][ trait_value ] || trait_value
  else
    trait_value
  end
end
_parse_dimension( str ) click to toggle source

e.g. convert dimension (width x height) “24x24” or “24 x 24” to [24,24]

# File lib/artbase/collection/opensea.rb, line 37
def _parse_dimension( str )
   str.split( /x/i ).map { |str| str.strip.to_i }
end
calc_attribute_counters() click to toggle source
# File lib/artbase/collection/opensea.rb, line 290
def calc_attribute_counters  ## todo/check: use a different name _counts/_stats etc - why? why not?

  attributes_by_count = { count: 0,
                          by_count: Hash.new(0)
                        }
  counter = {}


  each_meta do |meta, id|   ## todo/fix: change id to index
    traits = meta.traits
    # print "#{traits.size} - "
    # pp  traits

    print "#{id}.."   if id % 100 == 0  ## print progress report

    attributes_by_count[ :count ] +=1
    attributes_by_count[ :by_count ][ traits.size ] += 1

    traits.each do |trait_type, trait_value|
        trait_type  = _normalize_trait_type( trait_type )
        trait_value = _normalize_trait_value( trait_value )


        rec = counter[ trait_type ] ||= { count: 0,
                                          by_type: Hash.new(0)
                                        }
        rec[ :count ] +=1
        rec[ :by_type ][ trait_value ] += 1
    end
  end

  print "\n"
  puts

  ## return all-in-one hash
  {
    total:  attributes_by_count,
    traits: counter,
  }
end
download( range=(0...@count) ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 70
def download( range=(0...@count) )
  download_meta( range )
  download_images( range )
end
download_images( range=(0...@count) ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 66
def download_images( range=(0...@count) )
  self.class.download_images( range, @slug )
end
download_meta( range=(0...@count) ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 62
def download_meta( range=(0...@count) )
  self.class.download_meta( range, @slug )
end
dump_attributes() click to toggle source
# File lib/artbase/collection/opensea.rb, line 332
def dump_attributes
  stats = calc_attribute_counters

  total    = stats[:total]
  counter  = stats[:traits]

  puts
  puts "attribute usage / counts:"
  pp total
  puts

  puts "#{counter.size} attribute(s):"
  counter.each do |trait_name, trait_rec|
     puts "  #{trait_name}  #{trait_rec[:count]}  (#{trait_rec[:by_type].size} uniques)"
  end

  puts
  pp counter
end
each_meta( range=(0...@count), exclude: true, &blk ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 165
def each_meta( range=(0...@count),
               exclude: true,      &blk )
  range.each do |id|    ## todo/fix: change id to index
    meta = OpenSea::Meta.read( "./#{@slug}/meta/#{id}.json" )

    ####
    # filter out/skip
    if exclude && @exclude.include?( meta.name )
      puts "  skipping / exclude #{id} >#{meta.name}<..."
      next
    end

    blk.call( meta, id )
  end
end
export_attributes() click to toggle source
# File lib/artbase/collection/opensea.rb, line 200
def export_attributes
  ## step 1: get counters
  stats = calc_attribute_counters

  total    = stats[:total]
  counter  = stats[:traits]

  puts
  puts "attribute usage / counts:"
  pp total
  puts

  puts "#{counter.size} attribute(s):"
  counter.each do |trait_name, trait_rec|
     puts "  #{trait_name}  #{trait_rec[:count]}  (#{trait_rec[:by_type].size} uniques)"
  end

  recs = []


  ## step 2: get tabular data
  each_meta do |meta, id|   ## todo/fix: change id to index

    traits = meta.traits
    # print "#{traits.size} - "
    # pp  traits

    print "#{id}.."   if id % 100 == 0  ## print progress report

    ## setup empty hash table (with all attributes)
    rec = {
       'Slug'   => _meta_slugify( meta, id ),
       'Name' => meta.name,
    }
    ## add all attributes
    counter.keys.reduce( rec ) { |h,value| h[value] = []; h }
    ## pp rec

    ## note: use an array (to allow multiple values for attributes)
    traits.each do |trait_type, trait_value|
       trait_type  = _normalize_trait_type( trait_type )
       trait_value = _normalize_trait_value( trait_value )

       values = rec[ trait_type ]
       values << trait_value
    end
    recs << rec
  end
  print "\n"

  ## pp recs

  ## flatten recs
  data = []
  recs.each do |rec|
     row = rec.values.map do |value|
                  if value.is_a?( Array )
                     value.join( ' / ' )
                  else
                     value
                  end
             end
     data << row
  end


  ## sort by slug
  data = data.sort {|l,r| l[0] <=> r[0] }
  pp data

  ### save dataset
  ##  note: change first colum Slug to ID - only used for "internal" sort etc.
  headers = ['ID', 'Name'] + counter.keys    ## add header row

  path = "./#{@slug}/tmp/#{@slug}.csv"
  dirname = File.dirname( path )
  FileUtils.mkdir_p( dirname )  unless Dir.exist?( dirname )

  File.open( path, 'w:utf-8' ) do |f|
    f.write(  headers.join( ', ' ))
    f.write( "\n" )
    ## note: replace ID with our own internal running (zero-based) counter
    data.each_with_index do |row,i|
      f.write( ([i]+row[1..-1]).join( ', '))
      f.write( "\n" )
    end
  end
end
make_composite() click to toggle source
# File lib/artbase/collection/opensea.rb, line 79
def make_composite
  ### use well-known / pre-defined (default) grids (cols x rows) for now - why? why not?

  composite_count = @count - @exclude.size
  cols, rows = if      composite_count == 100    then   [10,  10]
               elsif   composite_count == 500    then   [25,  20]
               elsif   composite_count == 10000  then   [100, 100]
               else
                   raise ArgumentError, "sorry - unknown composite count #{composite_count}/#{@count} for now"
               end

  composite = ImageComposite.new( cols, rows,
                                  width:  @width,
                                  height: @height )

  ## note: for now as a convention (auto-)add all .pngs
  ##         in saved in (default) pixelate output dir
  composite.add_glob( "./#{@slug}/ii/*.png" )

  composite.save( "./#{@slug}/tmp/#{@slug}-#{@width}x#{@height}.png" )
end
pixelate( range=(0...@count) ) click to toggle source
# File lib/artbase/collection/opensea.rb, line 354
def pixelate( range=(0...@count) )

  meta_slugs = Hash.new( 0 )   ## deduplicate (auto-add counter if duplicate)

  ### todo/fix: must read slugs starting at 0
  ###               to work for deduplicate!!!!!!


  range.each do |id|
    meta = OpenSea::Meta.read( "./#{@slug}/meta/#{id}.json" )

    ####
    # filter out/skip
    if @exclude.include?( meta.name )
       puts "  skipping / exclude #{id} >#{meta.name}<..."
       next
    end

    puts meta.name


    meta_slug = _meta_slugify( meta, id )
    count = meta_slugs[ meta_slug ] += 1

    meta_slug = "#{meta_slug}_(#{count})"  if count > 1


    img = Image.read( "./#{@slug}/i/#{id}.png" )

    pix = _image_pixelate( img )

    path = "./#{@slug}/ii/#{meta_slug}.png"
    puts "   saving to >#{path}<..."
    pix.save( path )
  end
end