Save to Local File from Blob
Asked Answered
G

5

51

I have a difficult question to you, which i'm struggling on for some time now.

I'm looking for a solution, where i can save a file to the users computer, without the local storage, because local storage has 5MB limit. I want the "Save to file"-dialog, but the data i want to save is only available in javascript and i would like to prevent sending the data back to the server and then send it again.

The use-case is, that the service im working on is saving compressed and encrypted chunks of the users data, so the server has no knowledge whats in those chunks and by sending the data back to the server, this would cause 4 times traffic and the server is receiving the unencrypted data, which would render the whole encryption useless.

I found a javascript function to save the data to the users computer with the "Save to file"-dialog, but the work on this has been discontinued and isnt fully supported. It's this: http://www.w3.org/TR/file-writer-api/

So since i have no window.saveAs, what is the way to save data from a Blob-object without sending everything to the server?

Would be great if i could get a hint, what to search for.

I know that this works, because MEGA is doing it, but i want my own solution :)

Gamber answered 28/8, 2014 at 11:15 Comment(1)
similar questions posted here - #13405629 and here - #18690950Gradual
I
73

Your best option is to use a blob url (which is a special url that points to an object in the browser's memory) :

var myBlob = ...;
var blobUrl = URL.createObjectURL(myBlob);

Now you have the choice to simply redirect to this url (window.location.replace(blobUrl)), or to create a link to it. The second solution allows you to specify a default file name :

var link = document.createElement("a"); // Or maybe get it from the current document
link.href = blobUrl;
link.download = "aDefaultFileName.txt";
link.innerText = "Click here to download the file";
document.body.appendChild(link); // Or append it whereever you want
Illness answered 18/9, 2014 at 11:15 Comment(1)
dont forget to release memory by calling URL.revokeObjectURL()Loud
T
12

FileSaver.js implements saveAs for certain browsers that don't have it

https://github.com/eligrey/FileSaver.js

Tested with FileSaver.js 1.3.8 tested on Chromium 75 and Firefox 68, neither of which have saveAs.

The working principle seems to be to just create an <a element and click it with JavaScript oh the horrors of the web.

Here is a demo that save a blob generated with canvas.toBlob to your download folder with the chosen name mypng.png:

var canvas = document.getElementById("my-canvas");
var ctx = canvas.getContext("2d");
var pixel_size = 1;

function draw() {
    console.log("draw");
    for (x = 0; x < canvas.width; x += pixel_size) {
        for (y = 0; y < canvas.height; y += pixel_size) {
            var b = 0.5;
            ctx.fillStyle =
                "rgba(" +
                (x / canvas.width) * 255 + "," +
                (y / canvas.height) * 255 + "," +
                b * 255 +
                ",255)"
            ;
            ctx.fillRect(x, y, pixel_size, pixel_size);
        }
    }
    canvas.toBlob(function(blob) {
      saveAs(blob, 'mypng.png');
    });
}
window.requestAnimationFrame(draw);
<canvas id="my-canvas" width="512" height="512" style="border:1px solid black;"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.8/FileSaver.min.js"></script>

Here is an animated version that downloads multiple images: Convert HTML5 Canvas Sequence to a Video File

See also:

Tripetalous answered 28/7, 2019 at 8:15 Comment(3)
Is there some way to avoid being prompted before each save?Jussive
Also, how do I know when the save is complete? Right now my code continues to generate new frames even before I hit enter to save, but I need each frame to be saved.Jussive
@Jussive hi, I have addressed those points somewhat in the linked answer: #19235786 Summary: prompt before save: haven't been able to avoid the first one. Wait for frame to save: appears impossible, I just lower FPS and hope for the best, which is generally unacceptable.Tripetalous
T
5

HERE is the direct way.


canvas.toBlob(function(blob){

    console.log(typeof(blob)) //let you have 'blob' here

    var blobUrl = URL.createObjectURL(blob);
    
    var link = document.createElement("a"); // Or maybe get it from the current document
    link.href = blobUrl;
    link.download = "image.jpg";
    link.innerText = "Click here to download the file";

    document.body.appendChild(link); // Or append it whereever you want
    document.querySelector('a').click() //can add an id to be specific if multiple anchor tag, and use #id

  }, 'image/jpeg', 1); // JPEG at 100% quality

spent a while to come upto this solution, comment if this helps. Thanks to Sebastien C's answer.

Teran answered 8/8, 2021 at 7:55 Comment(1)
Couldn't you call link.click() instead of document.querySelector('a').click()? If that works, then you are not required to look up the corresponding a tag, but instead use the reference you already have, and this way you don't even need an ID for that tag.Larval
C
0

this node dependence was more utils fs-web;

npm i fs-web

Usage

import * as fs from 'fs-web';

async processFetch(url, file_path = 'cache-web') {
    const fileName = `${file_path}/${url.split('/').reverse()[0]}`;
    let cache_blob: Blob;
    await fs.readString(fileName).then((blob) => {
      cache_blob = blob;
    }).catch(() => { });
    if (!!cache_blob) {
      this.prepareBlob(cache_blob);
      console.log('FROM CACHE');
    } else {
      await fetch(url, {
        headers: {},
      }).then((response: any) => {
        return response.blob();
      }).then((blob: Blob) => {
        fs.writeFile(fileName, blob).then(() => {
          return fs.readString(fileName);
        });
        this.prepareBlob(blob);
      });
 }

}

Cabalist answered 25/10, 2020 at 5:1 Comment(0)
M
-3

From a file picker or input type=file file chooser, save the filename to local storage:

HTML:

<audio id="player1">Your browser does not support the audio element</audio>

JavaScript:

function picksinglefile() {
    var fop = new Windows.Storage.Pickers.FileOpenPicker();
    fop.suggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.musicLibrary;
    fop.fileTypeFilter.replaceAll([".mp3", ".wav"]);
    fop.pickSingleFileAsync().then(function (file) {
        if (file) {
            // save the file name to local storage
            localStorage.setItem("alarmname$", file.name.toString());
        } else {
            alert("Operation Cancelled");
        }
    });
}

Then later in your code, when you want to play the file you selected, use the following, which gets the file using only it's name from the music library. (In the UWP package manifest, set your 'Capabilites' to include 'Music Library'.)

        var l = Windows.Storage.KnownFolders.musicLibrary;
        var f = localStorage.getItem("alarmname$").toString(); // retrieve file by name
        l.getFileAsync(f).then(function (file) {
            // storagefile file is available, create URL from it
            var s = window.URL.createObjectURL(file);
            var x = document.getElementById("player1");
            x.setAttribute("src", s);
            x.play();
        });
Mickelson answered 9/7, 2017 at 14:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.