NodeJS: salvataggio di un'immagine codificata in base64 su disco


162

La mia app Express sta ricevendo un PNG con codifica base64 dal browser (generato da canvas con toDataURL ()) e lo sta scrivendo su un file. Ma il file non è un file di immagine valido e l'utilità "file" lo identifica semplicemente come "dati".

var body = req.rawBody,
  base64Data = body.replace(/^data:image\/png;base64,/,""),
  binaryData = new Buffer(base64Data, 'base64').toString('binary');

require("fs").writeFile("out.png", binaryData, "binary", function(err) {
  console.log(err); // writes out file without error, but it's not a valid image
});

1
Ho aggiornato la risposta che penso sia ciò di cui avevi bisogno in primo luogo;)
Alfred

Ovviamente questo non è quello che hai chiesto, ma (nel mio caso) mi sono reso conto che l'approccio migliore era solo quello di memorizzare l'intera stringa codificata nel mio database (puoi sempre caricarlo usando <img src="data:image/png;base64,..." />). Solo un'opzione da considerare per gli altri che usano questa discussione come riferimento.
JSideris,

Risposte:


325

Penso che tu stia convertendo i dati un po 'più del necessario. Una volta creato il buffer con la codifica corretta, è sufficiente scrivere il buffer nel file.

var base64Data = req.rawBody.replace(/^data:image\/png;base64,/, "");

require("fs").writeFile("out.png", base64Data, 'base64', function(err) {
  console.log(err);
});

new Buffer (..., 'base64') convertirà la stringa di input in un Buffer, che è solo un array di byte, interpretando l'input come una stringa codificata in base64. Quindi puoi semplicemente scrivere quell'array di byte nel file.

Aggiornare

Come menzionato nei commenti, req.rawBodynon è più una cosa. Se stai usando express/ connectallora dovresti usare il bodyParser()middleware e usare req.body, e se lo stai facendo usando il Nodo standard allora devi aggregare gli oggetti dataevento in arrivo Buffered eseguire questi dati di immagine analizzando nel endcallback.


2
Inoltre, c'è un leggero errore di battitura nell'argomento writeFile nell'esempio: "bufferData" -> "dataBuffer".
mahemoff,

@RJ. req.rawBodycontiene i dati della richiesta codificati come URL dei dati: developer.mozilla.org/en-US/docs/data_URIs . Quindi devi rimuovere la parte iniziale per ottenere solo i dati base64 da salvare.
loganfsmyth,

2
Questa è roba eccellente, grazie! Per quelli che lo troveranno in futuro, rawBody non è più una proprietà di req. È necessario utilizzare il middleware parser del corpo espresso per ottenere i dati.
DigitalDesignDj,

10
var base64Data = req.rawBody.split (',') [1];
Anja Ishmukhametova,

@notgiorgi È meglio porre una nuova domanda con dettagli sufficienti per riprodurre il problema e collegarsi a questo dicendo che non è possibile farlo funzionare.
loganfsmyth,

22

questa è la mia soluzione completa che potrebbe leggere qualsiasi formato di immagine base64 e salvarlo nel formato corretto nel database:

    // Save base64 image to disk
    try
    {
        // Decoding base-64 image
        // Source: http://stackoverflow.com/questions/20267939/nodejs-write-base64-image-file
        function decodeBase64Image(dataString) 
        {
          var matches = dataString.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
          var response = {};

          if (matches.length !== 3) 
          {
            return new Error('Invalid input string');
          }

          response.type = matches[1];
          response.data = new Buffer(matches[2], 'base64');

          return response;
        }

        // Regular expression for image type:
        // This regular image extracts the "jpeg" from "image/jpeg"
        var imageTypeRegularExpression      = /\/(.*?)$/;      

        // Generate random string
        var crypto                          = require('crypto');
        var seed                            = crypto.randomBytes(20);
        var uniqueSHA1String                = crypto
                                               .createHash('sha1')
                                                .update(seed)
                                                 .digest('hex');

        var base64Data = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAZABkAAD/4Q3zaHR0cDovL25zLmFkb2JlLmN...';

        var imageBuffer                      = decodeBase64Image(base64Data);
        var userUploadedFeedMessagesLocation = '../img/upload/feed/';

        var uniqueRandomImageName            = 'image-' + uniqueSHA1String;
        // This variable is actually an array which has 5 values,
        // The [1] value is the real image extension
        var imageTypeDetected                = imageBuffer
                                                .type
                                                 .match(imageTypeRegularExpression);

        var userUploadedImagePath            = userUploadedFeedMessagesLocation + 
                                               uniqueRandomImageName +
                                               '.' + 
                                               imageTypeDetected[1];

        // Save decoded binary image to disk
        try
        {
        require('fs').writeFile(userUploadedImagePath, imageBuffer.data,  
                                function() 
                                {
                                  console.log('DEBUG - feed:message: Saved to disk image attached by user:', userUploadedImagePath);
                                });
        }
        catch(error)
        {
            console.log('ERROR:', error);
        }

    }
    catch(error)
    {
        console.log('ERROR:', error);
    }

qualcuno qui per rispondermi ?? riguardo a questo ??
iam

ho appena modificato il tuo codice. fs.writeFile ("test.jpg", imageBuffer.data, function (err) {json_response ['success'] = true; res.json (json_response);}); l'immagine è caricata ma il risultato non mi piace .. errore: 502 Gateway non valido in realtà in res.json, perché questo non sta stampando ...
iam

18

AGGIORNARE

Ho trovato questo link interessante su come risolvere il tuo problema in PHP . Credo che ti sei dimenticato di sostituire spaceda+ come indicato nel link.

Ho preso questa cerchia da http://images-mediawiki-sites.thefullwiki.org/04/1/7/5/6204600836255205.png come esempio che assomiglia a:

http://images-mediawiki-sites.thefullwiki.org/04/1/7/5/6204600836255205.png

Successivamente l'ho messo su http://www.greywyvern.com/code/php/binary2base64 che mi ha restituito:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAAAAACPAi4CAAAAB3RJTUUH1QEHDxEhOnxCRgAAAAlwSFlzAAAK8AAACvABQqw0mAAAAXBJREFUeNrtV0FywzAIxJ3+K/pZyctKXqamji0htEik9qEHc3JkWC2LRPCS6Zh9HIy/AP4FwKf75iHEr6eU6Mt1WzIOFjFL7IFkYBx3zWBVkkeXAUCXwl1tvz2qdBLfJrzK7ixNUmVdTIAB8PMtxHgAsFNNkoExRKA+HocriOQAiC+1kShhACwSRGAEwPP96zYIoE8Pmph9qEWWKcCWRAfA/mkfJ0F6dSoA8KW3CRhn3ZHcW2is9VOsAgoqHblncAsyaCgcbqpUZQnWoGTcp/AnuwCoOUjhIvCvN59UBeoPZ/AYyLm3cWVAjxhpqREVaP0974iVwH51d4AVNaSC8TRNNYDQEFdlzDW9ob10YlvGQm0mQ+elSpcCCBtDgQD7cDFojdx7NIeHJkqi96cOGNkfZOroZsHtlPYoR7TOp3Vmfa5+49uoSSRyjfvc0A1kLx4KC6sNSeDieD1AWhrJLe0y+uy7b9GjP83l+m68AJ72AwSRPN5g7uwUAAAAAElFTkSuQmCC

salvato questa stringa da base64cui ho letto nel mio codice.

var fs      = require('fs'),
data        = fs.readFileSync('base64', 'utf8'),
base64Data,
binaryData;

base64Data  =   data.replace(/^data:image\/png;base64,/, "");
base64Data  +=  base64Data.replace('+', ' ');
binaryData  =   new Buffer(base64Data, 'base64').toString('binary');

fs.writeFile("out.png", binaryData, "binary", function (err) {
    console.log(err); // writes out file without error, but it's not a valid image
});

Ricevo un cerchio, ma la cosa divertente è che la dimensione del file è cambiata:) ...

FINE

Quando rileggi l'immagine, penso che devi impostare le intestazioni

Prendi ad esempio imagepng dalla pagina PHP:

<?php
$im = imagecreatefrompng("test.png");

header('Content-Type: image/png');

imagepng($im);
imagedestroy($im);
?>

Penso che la seconda riga header('Content-Type: image/png');sia importante, altrimenti la tua immagine non verrà visualizzata nel browser, ma solo un mucchio di dati binari viene mostrato al browser.

In Express useresti semplicemente qualcosa come di seguito. Visualizzerò il tuo gravatar che si trova su http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG ed è un file jpeg quando lo fai curl --head http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG. Richiedo solo le intestazioni perché altrimenti curl visualizzerà un sacco di cose binarie (Google Chrome va immediatamente al download) sulla console:

curl --head "http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG"
HTTP/1.1 200 OK
Server: nginx
Date: Wed, 03 Aug 2011 12:11:25 GMT
Content-Type: image/jpeg
Connection: keep-alive
Last-Modified: Mon, 04 Oct 2010 11:54:22 GMT
Content-Disposition: inline; filename="cabf735ce7b8b4471ef46ea54f71832d.jpeg"
Access-Control-Allow-Origin: *
Content-Length: 1258
X-Varnish: 2356636561 2352219240
Via: 1.1 varnish
Expires: Wed, 03 Aug 2011 12:16:25 GMT
Cache-Control: max-age=300
Source-Age: 1482

$ mkdir -p ~/tmp/6922728
$ cd ~/tmp/6922728/
$ touch app.js

app.js

var app = require('express').createServer();

app.get('/', function (req, res) {
    res.contentType('image/jpeg');
    res.sendfile('cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG');
});

app.get('/binary', function (req, res) {
    res.sendfile('cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG');
});

app.listen(3000);

$ wget "http://www.gravatar.com/avatar/cabf735ce7b8b4471ef46ea54f71832d?s=32&d=identicon&r=PG"
$ node app.js

Grazie Alfred, ma in questo caso di test minimo, non rispedirò nulla dal server. Sto semplicemente scrivendo il file su disco sul server e sembra che il file stesso non sia un'immagine valida. Sono abbastanza sicuro che la base64 sia corretta, ma sembra che ci sia un problema a scriverla come binaria.
mahemoff,

1
Scusa, ho frainteso la domanda: $. Ci riproverò.
Alfred

1
Grazie per l'aggiornamento, ma la sostituzione dello spazio non ha funzionato per me e in realtà non era necessaria quando ho applicato la soluzione di Logan. Per riferimento, la tela è molto semplice nel mio caso di test: var context = canvas.getContext ('2d'); context.fillStyle = "# f89"; context.fillRect (50,50,100,100);
mahemoff,

Va bene perché ho ripreso l'immagine quando l'ho fatto, ma almeno il tuo problema è stato risolto: P
Alfred

Interessante, non so perché il toString ("binario") non abbia incasinato il caso. In ogni caso, gli spazi non dovrebbero apparire naturalmente in base64, quindi la sostituzione dovrebbe essere discutibile. È con l'esempio che ho fornito comunque. (Ho provato una variante con newline inserite manualmente, dopo aver letto le specifiche MIME richiede righe non più grandi di 72 caratteri, per lo più fuori dalla paranoia ... risulta funzionare con o senza le newline, purché toString ("binario" ) viene rilasciato.)
mahemoff il

6

Ho anche dovuto salvare immagini con codifica Base64 che fanno parte degli URL dei dati, quindi ho finito per creare un piccolo modulo npm per farlo nel caso in cui io (o qualcun altro) dovessi farlo di nuovo in futuro. Si chiama ba64 .

In poche parole, prende un URL di dati con un'immagine codificata Base64 e salva l'immagine nel tuo file system. Può salvare in modo sincrono o asincrono. Ha anche due funzioni di supporto, una per ottenere l'estensione del file dell'immagine e l'altra per separare la codifica Base64 dalladata: prefisso schema.

Ecco un esempio:

var ba64 = require("ba64"),
    data_url = "data:image/jpeg;base64,[Base64 encoded image goes here]";

// Save the image synchronously.
ba64.writeImageSync("myimage", data_url); // Saves myimage.jpeg.

// Or save the image asynchronously.
ba64.writeImage("myimage", data_url, function(err){
    if (err) throw err;

    console.log("Image saved successfully");

    // do stuff
});

Installarlo: npm i ba64 -S. Repo è su GitHub: https://github.com/HarryStevens/ba64 .

PS Più tardi mi è venuto in mente che ba64 è probabilmente un brutto nome per il modulo poiché le persone possono supporre che esegua la codifica e la decodifica Base64, cosa che non accade (ci sono molti moduli che già lo fanno). Oh bene.


2

Questo è stato per me semplice e perfetto.

Ottima spiegazione di Scott Robinson

Dall'immagine alla stringa base64

let buff = fs.readFileSync('stack-abuse-logo.png');
let base64data = buff.toString('base64');

Dalla stringa base64 all'immagine

let buff = new Buffer(data, 'base64');
fs.writeFileSync('stack-abuse-logo-out.png', buff);

1

Modo semplice per convertire l' immagine base64 in file e salvarlo come ID o nome casuale.

// to create some random id or name for your image name
const imgname = new Date().getTime().toString();

// to declare some path to store your converted image
const path = yourpath.png    

// image takes from body which you uploaded
const imgdata = req.body.image;    

// to convert base64 format into random filename
const base64Data = imgdata.replace(/^data:([A-Za-z-+/]+);base64,/, '');
fs.writeFile(path, base64Data, 'base64', (err) => {
    console.log(err);
});

// assigning converted image into your database
req.body.coverImage = imgname

1

Conversione da file con stringa base64 in immagine png.

4 varianti che funzionano.

var {promisify} = require('util');
var fs = require("fs");

var readFile = promisify(fs.readFile)
var writeFile = promisify(fs.writeFile)

async function run () {

  // variant 1
  var d = await readFile('./1.txt', 'utf8')
  await writeFile("./1.png", d, 'base64')

  // variant 2
  var d = await readFile('./2.txt', 'utf8')
  var dd = new Buffer(d, 'base64')
  await writeFile("./2.png", dd)

  // variant 3
  var d = await readFile('./3.txt')
  await writeFile("./3.png", d.toString('utf8'), 'base64')

  // variant 4
  var d = await readFile('./4.txt')
  var dd = new Buffer(d.toString('utf8'), 'base64')
  await writeFile("./4.png", dd)

}

run();

1

Sotto la funzione per salvare i file, basta passare il file base64, restituire il nome file salvarlo nel DB.

import fs from 'fs';
 const uuid = require('uuid/v1');

/*Download the base64 image in the server and returns the filename and path of image.*/
function saveImage(baseImage) {
    /*path of the folder where your project is saved. (In my case i got it from config file, root path of project).*/
    const uploadPath = "/home/documents/project";
    //path of folder where you want to save the image.
    const localPath = `${uploadPath}/uploads/images/`;
    //Find extension of file
    const ext = baseImage.substring(baseImage.indexOf("/")+1, baseImage.indexOf(";base64"));
    const fileType = baseImage.substring("data:".length,baseImage.indexOf("/"));
    //Forming regex to extract base64 data of file.
    const regex = new RegExp(`^data:${fileType}\/${ext};base64,`, 'gi');
    //Extract base64 data.
    const base64Data = baseImage.replace(regex, "");
    const filename = `${uuid()}.${ext}`;

    //Check that if directory is present or not.
    if(!fs.existsSync(`${uploadPath}/uploads/`)) {
        fs.mkdirSync(`${uploadPath}/uploads/`);
    }
    if (!fs.existsSync(localPath)) {
        fs.mkdirSync(localPath);
    }
    fs.writeFileSync(localPath+filename, base64Data, 'base64');
    return filename;
}

1
Ha funzionato per me. E può essere utilizzato per qualsiasi conversione base64. Tratta ogni file in modo generico. Grazie!
Guilherme Sampaio,

1

È possibile utilizzare una libreria di terze parti come base64-img o base64-to-image .

  1. Base64-img
const base64Img = require('base64-img');

const data = 'data:image/png;base64,...';
const destpath = 'dir/to/save/image';
const filename = 'some-filename';

base64Img.img(data, destpath, filename, (err, filepath) => {}); // Asynchronous using

const filepath = base64Img.imgSync(data, destpath, filename); // Synchronous using
  1. Base64-to-immagine
const base64ToImage = require('base64-to-image');

const base64Str = 'data:image/png;base64,...';
const path = 'dir/to/save/image/'; // Add trailing slash
const optionalObj = { fileName: 'some-filename', type: 'png' };

const { imageType, fileName } = base64ToImage(base64Str, path, optionalObj); // Only synchronous using
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.