Create NFT collection and metadata

Prajwal Bati
6 min readMar 7, 2023

In this article, we will learn to create NFT metadata and use it in our NFT minting process. We will create a bunch of digital avatars as png images and create metadata files for each image and upload it in IPFS(InterPlanetary File System).

This is the 2nd part of NFT tutorial series. You can find 1st and 3rd part in links below:
1st part: Write and deploy NFT Smart Contracts
3rd part: Mint NFTs with web3.js

Data source for NFT images and metadata

We will use already existing script files and image files to create our list of avatars. We will combine unique traits of different parts of the face like hair, nose, eyes, ears, and mouth and, create a unique avatar for each nft metadata.

We will use data from the repository of `Substrapunks` by usetech-llc. You can clone the repository or download it from this link. Please feel free to look around the files especially inside scripts/face_parts because we will use image files from that specific folder.

Creating NFT Avatars

You need to have python installed in your system since we will write a python program to create avatar files and metadata collections. Create a file named create-avatars.py file in the root directory and copy and paste the below code in the file.

from PIL import Image
import random
import json
import os

# Each image is made up a series of traits
# The weightings for each trait drive the rarity and add up to 100%

face = ["White", "Black"]
face_weights = [60, 40]

ears = ["No Earring", "Left Earring", "Right Earring", "Two Earrings"]
ears_weights = [25, 30, 44, 1]

eyes = ["Regular", "Small", "Rayban", "Hipster", "Focused"]
eyes_weights = [70, 10, 5 , 1 , 14]

hair = ['Up Hair', 'Down Hair', 'Mohawk', 'Red Mohawk', 'Orange Hair', 'Bubble Hair', 'Emo Hair',
'Thin Hair',
'Bald',
'Blonde Hair',
'Caret Hair',
'Pony Tails']
hair_weights = [10 , 10 , 10 , 10 ,10, 10, 10 ,10 ,10, 7 , 1 , 2]

mouth = ['Black Lipstick', 'Red Lipstick', 'Big Smile', 'Smile', 'Teeth Smile', 'Purple Lipstick']
mouth_weights = [10, 10,50, 10,15, 5]

nose = ['Nose', 'Nose Ring']
nose_weights = [90, 10]

#Classify traits

face_files = {
"White": "face1",
"Black": "face2"
}

ears_files = {
"No Earring": "ears1",
"Left Earring": "ears2",
"Right Earring": "ears3",
"Two Earrings": "ears4"
}

eyes_files = {
"Regular": "eyes1",
"Small": "eyes2",
"Rayban": "eyes3",
"Hipster": "eyes4",
"Focused": "eyes5"
}

hair_files = {
"Up Hair": "hair1",
"Down Hair": "hair2",
"Mohawk": "hair3",
"Red Mohawk": "hair4",
"Orange Hair": "hair5",
"Bubble Hair": "hair6",
"Emo Hair": "hair7",
"Thin Hair": "hair8",
"Bald": "hair9",
"Blonde Hair": "hair10",
"Caret Hair": "hair11",
"Pony Tails": "hair12"
}

mouth_files = {
"Black Lipstick": "m1",
"Red Lipstick": "m2",
"Big Smile": "m3",
"Smile": "m4",
"Teeth Smile": "m5",
"Purple Lipstick": "m6"
}

nose_files = {
"Nose": "n1",
"Nose Ring": "n2"
}

## Generate Traits

TOTAL_IMAGES = 25 # Number of random unique images we want to generate

all_images = []

# A recursive function to generate unique image combinations
def create_new_image():
new_image = {}

# For each trait category, select a random trait based on the weightings
new_image ["Face"] = random.choices(face, face_weights)[0]
new_image ["Ears"] = random.choices(ears, ears_weights)[0]
new_image ["Eyes"] = random.choices(eyes, eyes_weights)[0]
new_image ["Hair"] = random.choices(hair, hair_weights)[0]
new_image ["Mouth"] = random.choices(mouth, mouth_weights)[0]
new_image ["Nose"] = random.choices(nose, nose_weights)[0]

if new_image in all_images:
return create_new_image()
else:
return new_image


# Generate the unique combinations based on trait weightings
for i in range(TOTAL_IMAGES):
new_trait_image = create_new_image()

all_images.append(new_trait_image)

# Returns true if all images are unique
def all_images_unique(all_images):
seen = list()
return not any(i in seen or seen.append(i) for i in all_images)

print("Are all images unique?", all_images_unique(all_images))

# Add token Id to each image
i = 0
for item in all_images:
item["tokenId"] = i
i = i + 1

# Get Trait Counts

face_count = {}
for item in face:
face_count[item] = 0

ears_count = {}
for item in ears:
ears_count[item] = 0

eyes_count = {}
for item in eyes:
eyes_count[item] = 0

hair_count = {}
for item in hair:
hair_count[item] = 0

mouth_count = {}
for item in mouth:
mouth_count[item] = 0

nose_count = {}
for item in nose:
nose_count[item] = 0

for image in all_images:
face_count[image["Face"]] += 1
ears_count[image["Ears"]] += 1
eyes_count[image["Eyes"]] += 1
hair_count[image["Hair"]] += 1
mouth_count[image["Mouth"]] += 1
nose_count[image["Nose"]] += 1


#### Generate Images

os.mkdir(f'./images')

for item in all_images:

im1 = Image.open(f'./scripts/face_parts/face/{face_files[item["Face"]]}.png').convert('RGBA')
im2 = Image.open(f'./scripts/face_parts/eyes/{eyes_files[item["Eyes"]]}.png').convert('RGBA')
im3 = Image.open(f'./scripts/face_parts/ears/{ears_files[item["Ears"]]}.png').convert('RGBA')
im4 = Image.open(f'./scripts/face_parts/hair/{hair_files[item["Hair"]]}.png').convert('RGBA')
im5 = Image.open(f'./scripts/face_parts/mouth/{mouth_files[item["Mouth"]]}.png').convert('RGBA')
im6 = Image.open(f'./scripts/face_parts/nose/{nose_files[item["Nose"]]}.png').convert('RGBA')

#Create each composite
com1 = Image.alpha_composite(im1, im2)
com2 = Image.alpha_composite(com1, im3)
com3 = Image.alpha_composite(com2, im4)
com4 = Image.alpha_composite(com3, im5)
com5 = Image.alpha_composite(com4, im6)

#Convert to RGB
rgb_im = com5.convert('RGB')
file_name = str(item["tokenId"]) + ".png"
rgb_im.save("./images/" + file_name)


#### Generate Metadata for all Traits
os.mkdir(f'./metadata')

METADATA_FILE_NAME = './all-traits.json';
with open(METADATA_FILE_NAME, 'w') as outfile:
json.dump(all_images, outfile, indent=4)

The above script is extracted from the article by Edward Jones. Please feel free to look into the explanation for each of the code segments.

Run the above program in your terminal. You will find images of the avatar inside the images folder. Also, you will find all-traits.json file in your root directory with collections of objects with unique traits and token IDs like below.

[
{
"Face": "White",
"Ears": "No Earring",
"Eyes": "Rayban",
"Hair": "Bubble Hair",
"Mouth": "Purple Lipstick",
"Nose": "Nose",
"tokenId": 0
},
{
"Face": "Black",
"Ears": "Right Earring",
"Eyes": "Regular",
"Hair": "Mohawk",
"Mouth": "Big Smile",
"Nose": "Nose",
"tokenId": 1
}
]

Upload Avatars in Pinata

The next step is to upload all the images to IPFS. For test purposes, we will use Pinata as IPFS to host our images and metadata. Create an account in pinata if you do not have one already. Now click Add Files and upload the images folder there.

Pinata add image folder

On successful upload of the folder with the images file, you can see the list of images below.

NFT images collection in Pinata

Copy the URL from this page as it will be used to create metadata later. It will look like this: https://gateway.pinata.cloud/ipfs/{CID} . CID is Content identifier that you can get on the files list page of the pinata.

Create NFT metadata

Now let’s generate NFT metadata. Create a new file named create-metadata.py in the root directory and copy and paste the below code.

#### Generate Metadata for each Image
import json

f = open('./all-traits.json')
data = json.load(f)

# Changes this IMAGES_BASE_URL to yours
IMAGES_BASE_URL = "https://gateway.pinata.cloud/ipfs/{CID}/"
PROJECT_NAME = "My NFT"

def getAttribute(key, value):
return {
"trait_type": key,
"value": value
}
for i in data:
token_id = i['tokenId']
token = {
"image": IMAGES_BASE_URL + str(token_id) + '.png',
"tokenId": token_id,
"name": PROJECT_NAME + ' ' + str(token_id),
"attributes": []
}
token["attributes"].append(getAttribute("Face", i["Face"]))
token["attributes"].append(getAttribute("Ears", i["Ears"]))
token["attributes"].append(getAttribute("Eyes", i["Eyes"]))
token["attributes"].append(getAttribute("Hair", i["Hair"]))
token["attributes"].append(getAttribute("Mouth", i["Mouth"]))
token["attributes"].append(getAttribute("Nose", i["Nose"]))

with open('./metadata/' + str(token_id), 'w') as outfile:
json.dump(token, outfile, indent=4)
f.close()

You need to replace CID with a real id from your pinata folder. Also before running this file, make sure you have a metadata folder created from create-avatars.py file. Run this program file. It will create metadata files for each image that we created inside the metadata folder. It will contain trait information and image URL in JSON format like below.

{
"image": "https://gateway.pinata.cloud/ipfs/{CID}/0.png",
"tokenId": 0,
"name": "My NFT 0",
"attributes": [
{
"trait_type": "Face",
"value": "White"
},
{
"trait_type": "Ears",
"value": "No Earring"
},
{
"trait_type": "Eyes",
"value": "Regular"
},
{
"trait_type": "Hair",
"value": "Down Hair"
},
{
"trait_type": "Mouth",
"value": "Big Smile"
},
{
"trait_type": "Nose",
"value": "Nose"
}
]
}

Now upload the metadata folder with all files to pinata just like we upload images before. It will look like this.

NFT metadata collection in Pinata

Again copy the URL from this page. This will be our base URL which we will use when minting NFT.

In the next article, we will learn to mint the NFT and also view our NFT in metamask or opensea account.
3rd part: Mint NFTs with web3.js

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Prajwal Bati
Prajwal Bati

Written by Prajwal Bati

Full Stack | MERN | MEAN | Blockchain | Javascript

No responses yet

Write a response