top of page
  • doctorsmonsters

Generate NFT Images and Metadata in Bulk With Python

Updated: Dec 30, 2021



When I wrote the first article, I was just exploring the process of generating NFT images. As I learned more, I realized it’s not just about generating images, but also generating metadata, as well as storing the images in the cloud. You can think of this article as an improved version of the first article.

This code has some improvements compared to the previous one. The main ones are as follow:

  • The user can add as many layers as they want, the only thing they have to specify in the code is the name of the layers.

  • The number of variations in each layer doesn’t have to be the same, the code will just load a list of files in each folder representing a layer using them.

  • The code also generates a .json file containing the metadata of the image generated.

Once again, I am not a professional programmer and my code will likely not be the best way to accomplish a task but hey, it works!


Directory Structure:

The following image shows the directory structure:


The root folder contains subfolders, with each representing a layer. Each subfolder representing a layer has to be numbered in the sequence they want the layers to be picked and merged. So the lowest layer of the image, which is the background, will be named as “1”, the next layer “2”, and so on. In an image with 5 layers, the background would be 1 and the topmost layer would be 5.

Each layer subfolder contains image files representing each variation of that layer. They don’t have to be numbered as our updated code loads the filenames. However, you should name each file appropriately as this name will be used in the metadata of the image file generated.

Storing Images in The Cloud:

All the images in your NFT collection will have to be stored somewhere. You can choose to store it anywhere you want but keep in mind you have to make sure that it’s reliable storage so that your pictures are accessible at any time. A popular option is Pinata. Pinata stores your data in a peer-to-peer file system known as Inter-Planetary File System. Pinata is pretty straightforward. You can register for free storage for up to 1 GB. Once you are logged in, upload a folder. Any folder with a test file would do. The point at this stage is to get a URL address that you can use in your metadata file. Once you have uploaded the folder, go back to the home page, you will see your folder name there. Next to it, you will see it’s CID, which is a long sequence of alphabets and numbers. Copy that and save it at a location you can access easily as we will need it for our code.


Setting Parameters:

Let’s define some parameters.


#Name of your  project, using test for now.
project = ‘test’#The CID of the folder on Pinata.
url = ‘QtgdsX3fsfT45fsggrd’#The local folder which will have the layers' subfolders.
folder = ‘layers’#The number of images you want to generate.
count = ‘100’#List containing the names of the layers, in the sequence you want
#the layers to be picked by the code and merged.layers_names = [“background”, “face”, “shirt”, “accessory”]

The total count of the images that could possibly be generated can be calculated by multiplying the number of files in each layer’s subfolder. Let’s say we have 4 layers and each layer has 4 variations represented by files, the total number of possible unique images would be:

4x4x4x4=256

Adding more layers would increase the count exponentially. However, for testing, you may not want to generate that many images as it will take a lot of time so can set it to 100 or even 10.


Importing Necessary Libraries:

import random
import os
from itertools import product
from PIL import Image

Generating all possible combinations of the variation files:

Now we will load a list of files in each subfolder in our folder specified by the folder parameter. We will then combine them in all possible combinations to generate a list of tuples, each tuple containing a possible combination of a variation of each layer. This can be accomplished by one line of code.

combinations = list(product(*[os.listdir(f'{folder}/{x}') for x in os.listdir(folder)]))

Each item in this list represents a possible combination of variations in each layer. We will use it to load the files based on their filenames and merge them. In case you don’t want to go all the way and have limited the number of images to a smaller number it might be a good idea to shuffle this list.

random.shuffle(combinations)


Generating The Images and Metadata:

The following code iterates through each combination in the combinations list, loads an image from each layer folder and merges them into one image, then saves it and also generates the metadata for that image and saves that too.

c=0
metadata={}for combination in combinations:
    if c != count:
        if len(combination)>2:
            comp = image.alpha_composite(image.open(f'{folder}/1/{combination[1]}').convert('RGBA'),
                                         image.open(f'{folder}/2/{combination[2]}').convert('RGBA'))
            n=3
            for item in combination[2:]:
                if combination.index(item)!=-1:
                    comp = Image.alpha_composite(comp,
                                         image.open(f'{folder}/{n}/{item}').convert('RGBA'))
                    n+=1
        rgb_im = comp.convert('RGB')
        file_name = str(combinations.index(combination)) + ".png"
        rgb_im.save("./images/" + file_name)
        metadata['image']=f'{image_url}/{file_name}'
        metadata['tokenId']=str(combinations.index(combination))
        metadata['name']=project+' '+str(combinations.index(combination))
        metadata['attributes']=[]
        for item in layers_names:
            metadata['attributes'].append({item:combination[layers_names.index(item)][:-4]})
        with open(f"./metadata/{str(combinations.index(combination))}.json", "w") as outfile:
            json.dump(metadata, outfile, indent=4)
        c+=1
    else:
        break

The variable “c” keeps a count of how many files have been generated and is updated at the end of each iteration. For each item in the “combinations” list, the code first checks the value of “c”, if it is not equal to the “count” variable, it will proceed otherwise it will break the loop.

For the first two layers, I had to hard code their paths, I am sure there is a better way of doing this, but I could not figure it out. Beyond the first two layers, other layers are loaded one by one and merged with the previously merged layers.

The index of each item in the “combinations” list is used as a file name for both the image file and the json file. We need the “layers_names” list for the metadata. Each item in that list represents the attribute of the image, the value of which is the filename of the file representing that variation in its respective layer. Therefore, each variation in every layer’s folder should be named appropriately as it will go on the metadata. Images are saved in the “images” folder and metadata files are saved in the “metadata” folder.

The code will likely not be displayed with the appropriate indentation therefore feel free to check out the Jupyter notebook. As stated earlier, my code may be juvenile but I am here to learn and your feedback will certainly help me improve. Thank you for reading this far! As I go through my project, I will share what I learn.


425 views0 comments

Recent Posts

See All
Post: Blog2_Post
bottom of page