Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Not running in docker container #5

Open
anuj9196 opened this issue Oct 26, 2024 · 10 comments
Open

Not running in docker container #5

anuj9196 opened this issue Oct 26, 2024 · 10 comments
Assignees
Labels
bug Something isn't working docker image enhancement New feature or request

Comments

@anuj9196
Copy link

I have been trying to run the application to generate markers in a docker container running Python as base image and node 18 as additional dependency. But it stucks on

Module._createNftDataSet(heapSpace, imageData.dpi, imageData.sizeX, imageData.sizeY, imageData.nc, StrBuffer)
Screenshot 2024-10-26 at 2 29 24 PM

Full Dockerfile is like

FROM python:3.12-slim
ENV PYTHONUNBUFFERED=1

# Create a group and user to run our app
ARG APP_USER=tapxreality
RUN groupadd -r ${APP_USER} && useradd --no-log-init -r -m -g ${APP_USER} ${APP_USER}

RUN set -ex \
    && RUN_DEPS=" \
    libpcre3 \
    mime-support \
    libmagic1 \
    libcurl4-nss-dev libssl-dev \
    weasyprint \
    libpq-dev postgresql postgresql-contrib \
    vim \
    nodejs npm\                                                         # <-- Installing nodejs 18.x
    " \
    && seq 1 8 | xargs -I{} mkdir -p /usr/share/man/man{} \
    && apt-get update && apt-get install -y --no-install-recommends $RUN_DEPS \
    && rm -rf /var/lib/apt/lists/* \
    # Create directories
    && mkdir /app/ \
    && mkdir /config/ \
    && mkdir /scripts/ \
    && mkdir -p /static_cdn/static_root/ \
    && chown -R ${APP_USER} /static_cdn/


ADD requirements/ /

WORKDIR /app/

COPY nft-marker-creator /nft-marker-creator

# Install node dependency
RUN npm install --prefix /nft-marker-creator

RUN set -ex \
    && BUILD_DEPS=" \
    build-essential \
    libpcre3-dev \
    libpq-dev \
    " \
    && apt-get update && apt-get install -y --no-install-recommends $BUILD_DEPS \
    && pip install --no-cache-dir -r /dev-requirements.txt \
    && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $BUILD_DEPS \
    && rm -rf /var/lib/apt/lists/*

COPY ./src /app/
COPY scripts/ /scripts/
COPY library_data /library_data

# uWSGI will listen on this port
EXPOSE 8000
ENV UWSGI_WSGI_FILE=tapxreality/wsgi.py

# Change to a non-root user
USER ${APP_USER}:${APP_USER}

ENTRYPOINT ["/scripts/docker/entrypoint.sh"]

Then running the node application using `

command = ["node", "/nft-marker-creator/app.js", "-i", file_path, "-o", output_dir]

process = subprocess.Popen(
        command,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True  # Ensures text output instead of bytes
 )

NOTE: I have made some changes to the src/NFTMarkerCreator.js file to allow absolute path to the file and output directory

@kalwalt kalwalt self-assigned this Oct 26, 2024
@kalwalt kalwalt added bug Something isn't working enhancement New feature or request labels Oct 26, 2024
@kalwalt
Copy link
Member

kalwalt commented Oct 26, 2024

Hi @anuj9196 At the moment i have no idea what it could be the reason, at least i will try your dockerfile. Can you share the changes you made to src/NFTMarkerCreator.js?

@anuj9196
Copy link
Author

@kalwalt These are the changes

  1. First I moved out the NFTMarkerCreator.js from src to root (project directory), so that it can be directly accessed using node app.js, without specifying the src/ path.
  2. I renamed the file to app.js to make it shorter to type
  3. I updated the srcImage and outputPath to allow any path in flag -i and -o repsectively. The input and output directories can be any place if absolute path is passed.
node app.js -i /static/media/image.png -o /result/output

The app.js file content is

const path = require("path");
const fs = require('fs');
const sharp = require('sharp');
const { prompt } = require('enquirer');
var Module = require('./build/NftMarkerCreator_wasm.js');

// GLOBAL VARs
var params = [
];

var validImageExt = [".jpg", ".jpeg", ".png"];

var srcImage;

var outputPath = 'output/';

var buffer;

var foundInputPath = {
    b: false,
    i: -1
}

var foundOutputPath = {
    b: false,
    i: -1
}

var noConf = false;
var withDemo = false;
var onlyConfidence = false;
var isZFT = false;

var imageData = {
    sizeX: 0,
    sizeY: 0,
    nc: 0,
    dpi: 0,
    array: []
}

Module.onRuntimeInitialized = async function () {
    console.log('arguments...: ', process.argv);

    for (let j = 2; j < process.argv.length; j++) {
        if (process.argv[j].indexOf('-i') !== -1 || process.argv[j].indexOf('-I') !== -1) {
            foundInputPath.b = true;
            foundInputPath.i = j + 1;
            j++;
        } else if (process.argv[j] === "-NoConf") {
            noConf = true;
        } else if (process.argv[j] === "-Demo") {
            withDemo = true;
        } else if (process.argv[j] === "-zft") {
            isZFT = true;
        } else if (process.argv[j] === "-onlyConfidence") {
            onlyConfidence = true;
        } else if (process.argv[j].indexOf('-o') !== -1 || process.argv[j].indexOf('-O') !== -1) {
            foundOutputPath.b = true;
            foundOutputPath.i = j + 1;
            j++;
        } else {
            console.log(process.argv[j]);
            params.push(process.argv[j]);
        }
    }

    if (!foundInputPath.b) {
        const response = await prompt({
            type: 'input',
            name: 'inputPath',
            message: 'Image path not present, to continue provide a path to image:'
          });
        srcImage = response.inputPath;
    } else {
        srcImage = process.argv[foundInputPath.i];
    }

    if (!srcImage) {
        console.log("\nERROR: No image in INPUT command!\n e.g:(-i /PATH/TO/IMAGE)\n");
        process.exit(1);
    } else {
        console.log('checking file path...: ', srcImage);
        if (!srcImage.startsWith('/')) {
            // Relative path
            srcImage = path.join(__dirname, srcImage);
        }

        console.log('image path is: ', srcImage);
        // srcImage = path.join(__dirname, process.argv[foundInputPath.i]);
    }

    if (foundOutputPath.b) {
        outputPath = process.argv[foundOutputPath.i];

        if (!outputPath.startsWith('/')) {
            // relative path
            outputPath = path.join(__dirname, outputPath);
            // outputPath = '/' + outputPath;
        }

        if (!outputPath.endsWith('/')) {
            outputPath += '/';
        }

        console.log('Set output path: ' + outputPath);
    }

    let fileNameWithExt = path.basename(srcImage);
    let fileName = path.parse(fileNameWithExt).name;
    let extName = path.parse(fileNameWithExt).ext;

    let foundExt = false;
    for (let ext in validImageExt) {
        if (extName.toLowerCase() === validImageExt[ext]) {
            foundExt = true;
            break;
        }
    }

    if (!foundExt) {
        console.log("\nERROR: Invalid image TYPE!\n Valid types:(jpg,JPG,jpeg,JPEG,png,PNG)\n");
        process.exit(1);
    }

    if (!fs.existsSync(srcImage)) {
        console.log("\nERROR: Not possible to read image, probably invalid image PATH!\n");
        process.exit(1);
    } else {
        buffer = fs.readFileSync(srcImage);
    }

    console.log('Check output path: ' + outputPath);
    if (!fs.existsSync(outputPath)) {
        fs.mkdirSync(outputPath);
    }

    if (extName.toLowerCase() === ".jpg" || extName.toLowerCase() === ".jpeg" || extName.toLowerCase() === ".png") {
        await processImage(buffer);
    }

    let confidence = calculateQuality();

    let txt = " - - - - - ";
    if (confidence.l != 0) {
        let str = txt.split(" ");
        str.pop();
        str.shift();
        for (let i = 0; i < parseInt(confidence.l); i++) {
            str[i] = " *";
        }
        str.push(" ");
        txt = str.join("");
    }

    if (onlyConfidence) {
        console.log("%f", confidence.l);
        process.exit(0);
    }

    console.log("\nConfidence level: [" + txt + "] %f/5 || Entropy: %f || Current max: 5.17 min: 4.6\n", confidence.l, confidence.e)

    if (noConf) {
        await askToContinue();
    }

    let paramStr = params.join(' ');

    let StrBuffer = Module._malloc(paramStr.length + 1);
    Module.stringToUTF8(paramStr, StrBuffer);

    console.log('Write Success');
    let heapSpace = Module._malloc(imageData.array.length * imageData.array.BYTES_PER_ELEMENT);
    Module.HEAPU8.set(imageData.array, heapSpace);

    console.log('Setting Heap Success.. Continue to Create ImageSet..');
    Module._createNftDataSet(heapSpace, imageData.dpi, imageData.sizeX, imageData.sizeY, imageData.nc, StrBuffer)
    console.log('Create NFT Dataset complete...')

    Module._free(heapSpace);
    Module._free(StrBuffer);

    let filenameIset = "tempFilename.iset";
    let filenameFset = "tempFilename.fset";
    let filenameFset3 = "tempFilename.fset3";

    let ext = ".iset";
    let ext2 = ".fset";
    let ext3 = ".fset3";

    let content = Module.FS.readFile(filenameIset);
    let contentFset = Module.FS.readFile(filenameFset);
    let contentFset3 = Module.FS.readFile(filenameFset3);

    if (isZFT) {
        console.log("CREATING ZFT FILE");
        let iset = Buffer.from(content.buffer);
        let fset = Buffer.from(contentFset.buffer);
        let fset3 = Buffer.from(contentFset3.buffer);

        let obj = {
            iset: iset.toString('hex'),
            fset: fset.toString('hex'),
            fset3: fset3.toString('hex')
        }

        let strObj = JSON.stringify(obj);

        let StrBufferZip = Module._malloc(strObj.length + 1);
        Module.stringToUTF8(strObj, StrBufferZip);

        Module._compressZip(StrBufferZip, strObj.length);

        let contentBin = Module.FS.readFile("tempBinFile.bin");

        // fs.writeFileSync(path.join(__dirname, '/output/') + fileName + ".zft", contentBin);
        fs.writeFileSync(outputPath + fileName + ".zft", contentBin);

        Module._free(StrBufferZip);

        if (withDemo) {
            console.log("\nFinished marker creation!\nNow configuring demo! \n")

            const markerDir = path.join(__dirname, './../demo/public/marker/');

            if (!fs.existsSync(markerDir)) {
                fs.mkdirSync(markerDir);
            }

            let demoHTML = fs.readFileSync("./demo/nft.html").toString('utf8').split("\n");
            addNewMarker(demoHTML, fileName);
            let newHTML = demoHTML.join('\n');

            fs.writeFileSync("./demo/nft.html", newHTML, { encoding: 'utf8', flag: 'w' });

            const files = fs.readdirSync(markerDir);
            for (const file of files) {
                fs.unlink(path.join(markerDir, file), err => {
                    if (err) throw err;
                });
            }

            fs.writeFileSync(markerDir + fileName + ".zft", contentBin);

            console.log("Finished!\nTo run demo use: 'npm run demo'");
        }

    } else {
        console.log("CREATING ISET, FSET AND FSET3 FILES");
        // fs.writeFileSync(path.join(__dirname, outputPath) + fileName + ext, content);
        // fs.writeFileSync(path.join(__dirname, outputPath) + fileName + ext2, contentFset);
        // fs.writeFileSync(path.join(__dirname, outputPath) + fileName + ext3, contentFset3);
        fs.writeFileSync(outputPath + fileName + ext, content);
        fs.writeFileSync(outputPath + fileName + ext2, contentFset);
        fs.writeFileSync(outputPath + fileName + ext3, contentFset3);

        if (withDemo) {
            console.log("\nFinished marker creation!\nNow configuring demo! \n")

            const markerDir = path.join(__dirname, './../demo/public/marker/');

            if (!fs.existsSync(markerDir)) {
                fs.mkdirSync(markerDir);
            }

            let demoHTML = fs.readFileSync("./../demo/nft.html").toString('utf8').split("\n");
            addNewMarker(demoHTML, fileName);
            let newHTML = demoHTML.join('\n');

            fs.writeFileSync("./../demo/nft.html", newHTML, { encoding: 'utf8', flag: 'w' });

            const files = fs.readdirSync(markerDir);
            for (const file of files) {
                fs.unlink(path.join(markerDir, file), err => {
                    if (err) throw err;
                });
            }

            fs.writeFileSync(markerDir + fileName + ext, content);
            fs.writeFileSync(markerDir + fileName + ext2, contentFset);
            fs.writeFileSync(markerDir + fileName + ext3, contentFset3);

            console.log("Finished!\nTo run demo use: 'npm run demo'");
        }
    }

    process.exit(0);
}

async function processImage(buf) {
    const image = sharp(buf);
    await image.metadata()
        .then(async metadata => {
            if (metadata.density) {
                imageData.dpi = metadata.density;
            } else {
                console.log("\nWARNING: No DPI value found! Using 150 as default value!\n");
                imageData.dpi = 150;
            }
            if(metadata.width){
                imageData.sizeX = metadata.width;
            } else {
                await metadataWidth();
            }
            if(metadata.height){
                imageData.sizeY = metadata.height;
            } else {
                await metadataHeigth();
            }
            if(metadata.channels){
                imageData.nc = metadata.channels;
            } else {
                await metadataChannels();
            }
            return image
            .raw()
            .toBuffer()
        })   
        .then(data => {
            let dt = data.buffer

            let verifyColorSpace = detectColorSpace(dt);

            if (verifyColorSpace === 1) {
                console.log("Color Space is 1 channel!");
            } else if (verifyColorSpace === 3) {
                console.log("Color Space is 3 channels!");
            }

            let uint = new Uint8Array(dt);
            if(imageData.nc === verifyColorSpace){
                console.log("Color Space is equal to metadata.channels!");
            }else{
                console.log("Color Space is not equal to metadata.channels!");
                //process.exit(1);
            }
            imageData.nc = verifyColorSpace;
            imageData.array = uint;
        })
        .catch(function (err) {
            console.error("Error extracting metadata: " + err);
            process.exit(1);
        });
}

async function extractMetadata(buf) {
    return await sharp(buf).metadata()
        .then(function (metadata) {
            if (metadata.density) {
                imageData.dpi = metadata.density;
            } else {
                console.log("\nWARNING: No DPI value found! Using 150 as default value!\n");
                imageData.dpi = 150;
            }
            imageData.sizeX = metadata.width;
            imageData.sizeY = metadata.height;
            imageData.nc = metadata.channels;
        }).catch(function (err) {
            console.error("Error extracting metadata: " + err);
            process.exit(1);
        });
}

function detectColorSpace(arr) {

    let target = parseInt(arr.length / 4);

    let counter = 0;

    for (let j = 0; j < arr.length; j += 4) {
        let r = arr[j];
        let g = arr[j + 1];
        let b = arr[j + 2];

        if (r === g && r === b) {
            counter++;
        }
    }

    if (target === counter) {
        return 1;
    } else {
        return 3;
    }
}

function rgbaToRgb(arr) {
    let newArr = [];
    let BGColor = {
        R: 255,
        G: 255,
        B: 255
    }

    for (let i = 0; i < arr.length; i += 4) {

        let r = parseInt(255 * (((1 - arr[i + 3]) * BGColor.R) + (arr[i + 3] * arr[i])));
        let g = parseInt(255 * (((1 - arr[i + 3]) * BGColor.G) + (arr[i + 3] * arr[i + 1])));
        let b = parseInt(255 * (((1 - arr[i + 3]) * BGColor.B) + (arr[i + 3] * arr[i + 2])));

        newArr.push(r);
        newArr.push(g);
        newArr.push(b);
    }
    return newArr;
}

function calculateQuality() {
    let gray = toGrayscale(imageData.array);
    let hist = getHistogram(gray);
    let ent = 0;
    let totSize = imageData.sizeX * imageData.sizeY;
    for (let i = 0; i < 255; i++) {
        if (hist[i] > 0) {
            let temp = (hist[i] / totSize) * (Math.log(hist[i] / totSize));
            ent += temp;
        }
    }

    let entropy = (-1 * ent).toFixed(2);
    let oldRange = (5.17 - 4.6);
    let newRange = (5 - 0);
    let level = (((entropy - 4.6) * newRange) / oldRange);

    if (level > 5) {
        level = 5;
    } else if (level < 0) {
        level = 0;
    }
    return { l: level.toFixed(2), e: entropy };
}

function toGrayscale(arr) {
    let gray = [];
    for (let i = 0; i < arr.length; i += 3) {
        let avg = (arr[i] + arr[i + 1] + arr[i + 2]) / 3;
        gray.push(parseInt(avg));
    }
    return gray;
}

function getHistogram(arr) {
    let hist = [256];
    for (let i = 0; i < arr.length; i++) {
        hist[i] = 0;
    }
    for (let i = 0; i < arr.length; i++) {
        hist[arr[i]]++;
    }
    return hist;
}

function addNewMarker(text, name) {
    for (let i = 0; i < text.length; i++) {
        if (text[i].trim().includes("<script>MARKER_NAME =")) {
            text[i] = "<script>MARKER_NAME = '" + name + "'</script>"
            break;
        }
    }
}

async function askToContinue() {
    const response = await prompt({
        type: 'input',
        name: 'answer',
        message: 'Do you want to continue? (Y/N)\n'
      });

    if (response.answer == "n") {
        console.log("\nProcess finished by the user! \n");
        process.exit(1);
    }
}

async function metadataWidth() {
    const responseToProceed = await prompt({
        type: 'input',
        name: 'answer',
        message: 'Metadata width not present do you want to inform it? (Y/N)\n'
      });

    if (responseToProceed.answer == "n") {
        console.log("\nProcess finished by the user! \n");
        process.exit(1);
    } else {
        const responseAfterEnquiry = await prompt({
            type: 'input',
            name: 'width',
            message: 'Inform the width: e.g 200\n'
          });
        if (responseAfterEnquiry.width) {
            imageData.sizeX = responseAfterEnquiry.width;
        }
    }
}

async function metadataHeigth() {
    const responseToProceed = await prompt({
        type: 'input',
        name: 'answer',
        message: 'Metadata height not present do you want to inform it? (Y/N)\n'
      });

    if (responseToProceed.answer == "n") {
        console.log("\nProcess finished by the user! \n");
        process.exit(1);
    } else {
        const responseAfterEnquiry = await prompt({
            type: 'input',
            name: 'height',
            message: 'Inform the height: e.g 400\n'
          });
        if (responseAfterEnquiry.height) {
            imageData.sizeY = responseAfterEnquiry.height;
        }
    }
}

async function metadataChannels() {
    const responseToProceed = await prompt({
        type: 'input',
        name: 'answer',
        message: 'Metadata channels not present do you want to inform it? (Y/N)\n'
      });

    if (responseToProceed.answer == "n") {
        console.log("\nProcess finished by the user! \n");
        process.exit(1);
    } else {
        const responseAfterEnquiry = await prompt({
            type: 'input',
            name: 'channels',
            message: 'Inform the number of channels: e.g 3\n'
          });
        if (responseAfterEnquiry.channels) {
            imageData.nc = responseAfterEnquiry.channels;
        }
    }
}

NOTE: Path of demo and build are not updated.
NOTE: If you feel these changes are good to merged in this repo, I can create a PR for the same.

@anuj9196
Copy link
Author

anuj9196 commented Oct 27, 2024

I think the issue is not with the library itself but with the dependencies required to run C++ executables. I tried using the python base image instead of the slim version.

FROM python:3.12
#FROM python:3.12-slim

And the command is executing. However, the stdout of the C++ command is not streaming live and it takes approx 15 minutes to complete the execution.

Screenshot 2024-10-27 at 11 03 09 AM

@kalwalt
Copy link
Member

kalwalt commented Oct 27, 2024

First I moved out the NFTMarkerCreator.js from src to root (project directory), so that it can be directly accessed using node app.js, without specifying the src/ path.
I renamed the file to app.js to make it shorter to type

Maybe we can setup a script to add the NFTMarkerCreator.js to the system envirnonment vars.
I will try as i have a bit of time.

And the command is executing. However, the stdout of the C++ command is not streaming live and it takes approx 15 minutes to complete the execution.

Probably it depends on dpi and width, height of the image you are providing for the NFT creations.

NOTE: If you feel these changes are good to merged in this repo, I can create a PR for the same.

I will think about, maybe i can create another repository and add the NFT-Marker-Creator-app as git submodule. I planned to do in the past but never had the time. @ThorstenBux in the past made an app to create NFT markers in a server, but i don't think it is working anymore. The repository is this https://github.com/webarkit/NFT-Creator-WS

@kalwalt
Copy link
Member

kalwalt commented Oct 27, 2024

I'm trying to build the image but it fails at the end because missed dev-requirements.txt file, i think required for python. @anuj9196 Can you share also this? Thank you.

P.S. and maybe /scripts/docker/entrypoint.sh ?

@anuj9196
Copy link
Author

@kalwalt Please check the content of requested files

dev-requirements.txt

django==5.0.6
djangorestframework==3.15.1
django-allauth==0.63.3
dj-rest-auth==6.0.0
django-oauth-toolkit==2.4.0
celery[sqs]==5.4.0
django-celery-beat==2.6.0
django-celery-results==2.5.1
django-filter==24.2
django-hosts==6.0
whitenoise==6.6.0
sentry-sdk==2.5.1
django-cors-headers==4.3.1
pre-commit==3.7.1
flake8==7.0.0
django-admin-search==0.3.15
boto3==1.34.125
django-storages==1.14.3
dj-database-url
psycopg==3.1.12
#django-postgres-extra==2.0.8  # No Django 5 support
#mysqlclient
django-safedelete==1.4.0
django-json-widget
jsonschema==4.23.0
jschon==0.11.0
django-ordered-model==3.7.4
django-silk
channels==4.1.0
channels_redis==4.2.0
daphne==4.1.2
uWSGI==2.0.26
pandas==2.2.2
openpyxl==3.1.5
django-import-export==4.0.8
django-libsass==0.9
opencv-python-headless==4.10.0.84
numpy==2.0.0
jet-django==1.9.11
dnspython==2.6.1
tld==0.13
django-countries==7.6.1
django-sequences==3.0
swapper==1.3.0
django-two-factor-auth==1.16.0
pyotp
geoip2==4.8.0
user-agents==2.2.0
css-inline==0.14.1
stripe
weasyprint==62.2
django-weasyprint==2.3.0
plotly==5.22.0
kaleido
markdown==3.6
newrelic
django-htmx

entrypoint.sh

#!/bin/sh
#set -e

# Accept other commands
exec "$@"

@kalwalt
Copy link
Member

kalwalt commented Oct 28, 2024

I was able to build the docker image with some modifications, creating a container and running a command to build a nft markers. But i avoided to use python as main command, instead i used the classical command node NFTMarkerCreator.js -i pinball.jpg for testing. I haven't tested yet your modifications to the js script but i will do as a next step.
I had to do this because probably in your setup you have other scripts, and was a nonsense to me try with them. I would develop a basic docker image so anyone may fork the repository and modify in base of the needs. Probably will switch to a ubuntu base layer instead of python, infact i saw it enlarge a lot in size.
In regards of nfts creation times i don't see they are increased, probably it depends on dpi, image size.

@kalwalt
Copy link
Member

kalwalt commented Nov 2, 2024

@anuj9196 tested your mod to NFTMarkerCreator.js, it works perfectly in my local docker image and
also when running the old style app (without docker i mean). Those changes are necessary for the docker image, many thanks for this! Soon i will upload on a branch this code, referencing this issue.

kalwalt added a commit that referenced this issue Nov 2, 2024
kalwalt added a commit that referenced this issue Nov 3, 2024
@kalwalt
Copy link
Member

kalwalt commented Dec 11, 2024

@anuj9196 did you solved this issue?

@anuj9196
Copy link
Author

@kalwalt Yes, I managed to run it in the docker. Here is how I'm doing it

  1. Added the following to the Dockerfile
FROM python:3.12
ENV PYTHONUNBUFFERED=1

# Create a group and user to run our app
ARG APP_USER=myapp
RUN groupadd -r ${APP_USER} && useradd --no-log-init -r -m -g ${APP_USER} ${APP_USER}

# Install packages needed to run your application (not build deps):
RUN set -ex \
    && RUN_DEPS=" \
    ...
    nodejs npm\
    " \
    && seq 1 8 | xargs -I{} mkdir -p /usr/share/man/man{} \
    && apt-get update && apt-get install -y --no-install-recommends $RUN_DEPS \
    && rm -rf /var/lib/apt/lists/* \
    # Create directories
    && mkdir /app/ 

# Copy in your requirements file
ADD requirements/ /

WORKDIR /app/

COPY nft-marker-creator /nft-marker-creator

# Install node dependency
RUN npm install --prefix /nft-marker-creator

RUN set -ex \
    && BUILD_DEPS=" \
    build-essential \
    libpcre3-dev \
    libpq-dev \
    " \
    && apt-get update && apt-get install -y --no-install-recommends $BUILD_DEPS \
    && pip install --no-cache-dir -r /dev-requirements.txt \
    \
    && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $BUILD_DEPS \
    && rm -rf /var/lib/apt/lists/*

COPY ./src /app/

# uWSGI will listen on this port
EXPOSE 8000

...

# Change to a non-root user
USER ${APP_USER}:${APP_USER}

ENTRYPOINT ["/scripts/docker/entrypoint.sh"]

Installing nodejs and npm using apt then installing dependencies from the nft-marker-creator directory which contains all files from this repository.

Then I have created this python module, that accepts the files and downloads it, generates marker and uploads to S3.

import os
import tempfile
from urllib.parse import urlparse

import requests
import logging
import subprocess

from django.core.files.base import ContentFile
from django.core.files.storage import default_storage

log = logging.getLogger('app')

def run_command(command):
    log.info(f'Running command: {" ".join(command)}')

    # Using Popen to capture real-time output
    process = subprocess.Popen(
        command,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE,
        universal_newlines=True  # Ensures text output instead of bytes
    )

    # Reading output in real-time
    for stdout_line in iter(process.stdout.readline, ""):
        log.info(f"stdout: {stdout_line.strip()}")  # Print or log real-time stdout

    for stderr_line in iter(process.stderr.readline, ""):
        log.info(f"stderr: {stderr_line.strip()}")  # Print or log real-time stderr

    process.stdout.close()
    process.stderr.close()

    return_code = process.wait()  # Wait for the process to finish
    if return_code:
        log.info(f"Process finished with code {return_code}")

        return return_code

    return False


def download_file_to_temp(url):
    # Create a temporary file path
    temp_dir = tempfile.gettempdir()
    file_name = os.path.basename(url)
    temp_file_path = os.path.join(temp_dir, file_name)

    log.info(f'temp dir: {temp_dir}')
    log.info(f'file name: {file_name}')
    log.info(f'temp file path: {temp_file_path}')

    # Download the file from the URL
    try:
        log.info(f'downloading file using request: {url}')
        response = requests.get(url, stream=True)
        response.raise_for_status()  # Check for HTTP errors

        log.info('Writing response to file')
        # Write the file to the temporary path
        with open(temp_file_path, 'wb') as temp_file:
            for chunk in response.iter_content(chunk_size=8192):
                temp_file.write(chunk)

        log.info(f'File downloaded to: {temp_file_path}')
    except Exception as e:
        log.error(f'Error downloading file: {e}')
        return None

    return temp_file_path


def _upload_directory_to_s3(local_dir, s3__dir_key):
    """
    Uploads all files from a local directory to an S3 bucket directory.

    :param local_dir: The path to the local directory.
    :param s3__dir_key: The directory path within the S3 bucket (optional).
    """
    # Traverse through all files in the directory
    for root, _, files in os.walk(local_dir):
        for file_name in files:
            # Construct a full local file path
            local_file_path = os.path.join(root, file_name)

            # Construct S3 file path (preserving directory structure)
            relative_path = os.path.relpath(local_file_path, local_dir)
            s3_file_path = os.path.join(s3__dir_key, relative_path).replace("\\", "/")  # for Windows compatibility

            # Open the file and save it to S3
            with open(local_file_path, 'rb') as file_data:
                file_content = ContentFile(file_data.read())
                default_storage.save(s3_file_path, file_content)

            log.info(f"Uploaded {file_name} to S3 at {s3_file_path}")


def get_output_dir():
    temp_dir = tempfile.gettempdir()
    temp_file_path = os.path.join(temp_dir, 'output/')

    return temp_file_path


def create_marker(file_url):
    s3_url = file_url

    parsed_url = urlparse(s3_url)
    # The path after the bucket name, excluding the filename
    path_parts = parsed_url.path.strip('/').split('/')
    s3_dir_key = '/'.join(path_parts[:-1]) + '/'

    file_path = download_file_to_temp(url=s3_url)
    output_dir = get_output_dir()

    log.info(f'file path is: {file_path}')
    log.info(f'output dir is: {output_dir}')

    # Check node version
    run_command(["node", "--version"])

    # Node.js command to run app.js with the -i flag and image path
    command = ["node", "/nft-marker-creator/app.js", "-i", file_path, "-o", output_dir]

    log.info(f'Executing marker command')

    run_command(command)

    log.info('Command execution complete')

    log.info(f'Listing files of output directory: {output_dir}')
    run_command(["ls", "-la", output_dir])

    log.info('All execution complete')
    log.info(f'Uploading output file to S3 path: {s3_dir_key}')
    _upload_directory_to_s3(output_dir, s3_dir_key)

usages of python module

from nft import create_marker

create_marker('https://bucket.s3.amazonaws.com/path/to/image.png')

The python module can be modified to support downloading of maker, or return marker files instead of uploading to s3.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working docker image enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants