How can I read a file's metadata in Node.js, beyond what the fs.statSync provides without using a library? - javascript

This is a topic where I can't seem to find the answer on the Node.js docs (I know it's possible because of libraries like exif), nor can I find an answer on the internet without everyone saying to just use a library.
I don't want to use a library, so I want to do this natively and learn more about reading file metadata, and maybe eventually updating the metadata too while building my own mini-tool.
If I run something like fs.statSync() I can get generic metadata that returns in the Stats object; but, in my case, I'm looking for all the other metadata, NOT just the basic file info like size, birthtime, etc.
I want the other metadata like dimensions, date taken, and especially things you'd see in image, video, or audio files.
Maybe there's something like:
const deepMetaData = fs.readFileSync().getMetaDataAsString();
console.info(/Date Taken/.test(deepMetaData)); // true
or
const deepMetaData = fs.createReadStream().buffer().toString();
const dateTaken = deepMetaData.match(/Date Taken: (\d{4}-\d{2}-\d{2})/)[1];
console.info(dateTaken);
If I need to work with buffers, streams, whatever, instead of a string output, that's cool too. Ideally something synchronous. So if there's a simple example someone could provide of how to read that kind of meta data without a library, I'll at least be able to look up the methods used from that to understand more later and leverage the docs associated with whatever approach. Thank you!

Nodejs fs functions like fs.statSync() provide OS level metadata on the file only (such as createDate, modificationDate, file size, etc...). These are properties of the file in the file system. These do NOT have anything at all to do with the actual data of the file itself.
When you talk about EXIF (for a photo), this is parsed from the file data itself. To know about that type of data, you must read and parse at least the beginning of the file and you must be able to recognize and understand all the different file formats that you might encounter. For photos, this would include JPEG, PNG, HEIC, GIF, etc... Each of those have different file formats and will require unique code for understanding the metadata embedded in the file.
Nodejs does not have support for any of that built-in.
So, it will take custom code for each file type. If you further want to include other types of files like videos, you need to extend your list of different file types you can read, parse and understand. For the depth of files you're talking about this is a big job, particular when it comes to testing against all the different variants of files and metadata that exist out in the wild.
I personally would be fine with implementing my own code for one particular file type like JPEG, but if I was tasked with supporting dozens of types of files and particularly if tasked with supporting the wide range of video file formats, I'd immediately seek out help from existing libraries that have already done all the time consuming work to research, write and test how to properly read and understand all the variants.
I know it's possible because of libraries like exif
This is an example of a library that reads the beginning of the image file, parses it according to the expected format and knows how to interpret all the possible tags that can be in the EXIF header and what they all mean.
So if there's a simple example someone could provide of how to read that kind of meta data without a library
Go study the code for the EXIF library and see how it works. If you're going to implement it yourself, that's how you have to do it. I'm still not sure why you'd avoid using working libraries if they already exist. That is one of the biggest advantages of the nodejs ecosystem - you can build on all the open source code that already exists without reimplementing it all from scratch yourself and spend your coding time on parts of your problem that someone else has not already implemented.
how would one read that metadata using node?
You literally have to read the data from the file (usually at the start of the file). You can use any of the mechanisms that the fs module provides. For example, you can use fs.createReadStream() and then stream in the file, parsing and interpreting it as data arrives and then stop the stream when you get past the end of the metadata. Of, you can open a file handle using fs.open() and use fs.read() to read chunks of the file until you have read enough to have all the metadata.
You HAVE an example sitting right in front of you of code that does this in the EXIF library on NPM that you already seem to know about. Just go examine its code. The code is ALL there.
I'm just looking for a simple answer on getting that info, even if it's a blob of strings.
This is perhaps your main problem. There is no simple answer to get that info and it doesn't just exist as a blob of strings. These files are sometimes binary files (for space efficiency reasons). You have to learn how to read and parse binary data. Go study the code in the EXIF library and see what it is already doing and you can learn from that. There is no better example to start with.
But, for a simple example using the heic filetype, this will grab the first 5000 characters of the file's metadata, which can then be searched:
const fileDescriptor = fs.openSync(absPathToHeicPhoto);
const charCount = 5000;
const buffer = Buffer.alloc(charCount);
const headerBytes = fs.readSync(fileDescriptor, buffer, 0, charCount);
const bufferAsStr = buffer.toString('utf8', 0, charCount);
console.info(/\d{4}:\d{2}:\d{2}/.test(bufferAsStr));
FYI, I looked at the code for this EXIF library on NPM and it's poorly implemented. It uses fs.readFile() to load the ENTIRE image into RAM (even though it only needs a fraction of the data at the start of the file). This is a poor implementation for this reason (memory and disk inefficient).
But, it does have a method called processImage and one called extractExifData that process the binary data of the file to parse out the EXIF info. These are links to its actual code. You can start learning there.
FYI, as a photographer, I use a command line program called exiftool that will dump exif info to stdout or to a file for many images. As a different approach, you could just run that tool from your nodejs program (using the child_process module and capture its output and use that output, letting it do the hard work you just operate on the generated output.

Related

Read zip file from URL to ArrayBuffer with Javascript

I have a front end javascript library into which I'm attempting to load a zip file. The libraries documentation suggests that if I wish to be loading a zip file from a URL (I do) then I need to have the data in an arraybuffer.
As I'm not a Javascript person I'm having some trouble discerning how I might be able to do this.
The code I believe that I ultimately need to use is the following, where I assume buffer is the name of my buffer:
shp(buffer).then(function(geojson){});
//or
shp.parseZip(buffer)->returns zip
So the question is, if I have a file at example.com/myfile.zip how do I use vanilla javascript to get that into an ArrayBuffer? I assume once that is done I'll have what I need to feed into that code snippet above.
Plenty of googleing keeps getting me results that don't seem relevant. The File_API docs by Mozilla don't provide an example that I can see.
You can use fetch.
fetch('example.com/myfile.zip').then(res => res.arrayBuffer()).then(arrayBuffer => {
// use ArrayBuffer
});

Node not reading full contents of file

I'm trying to get it to read & parse a json file to update it, but it's not reading the full file, it's stopping after a lot of the file and just not reading any more of it. It's a massive json file because I can't really store it as anything else, besides multiple json files.
The code of CacheManager is here
The size of what it read is 143,360, and the actual size of the file is 153,840. I've never really ran into the issue, so I have no clue how to remedy it. I'm using fs-extra in the code, but I've verified that the same issue happens with the built-in fs module. I've printed out the content of what it got as well, so I can see that it is reading the file, and it is reading the right content, it's just not getting all of it. I'll link the right content and what it's getting. It's cut off at the end, you can see the part of the json for the md5. The code writing it to the file is just writing the raw content of the read file here (look at the part below the first screenshot to see the regular code)
If the issue is caused by the size of the file, you may look in some streaming parsing alternatives to standard JSON parse (like https://www.npmjs.com/package/stream-json).
Note: I'll check and let you know
Edit for the reader: so far it seems some kind of race and SO caching, discussion in the comments.

LSL HttpServer - Serving large Notecards from Prim Inventory on Secondlife

I am writing a Media-HUD that runs totally on local notecard files stored within the prim's inventory, with notecards named like index.html, style.css, icon.svg etc.
My hope is to use the LSL HttpServer functions, and the script's URL to create a totally self-contained media based HUD that is easy to edit like editing any web page.
This is completely possible on its own, however there is a limitation in that, the pages must fit into the memory allocated to the LSL script. Under mono this is only 64kb.
I want to remove this limitation, by somehow, perhaps from javascript, reading in each 'file' from a notecard line by line in the users browser itself (thusly, getting around the memory limit by only bringing one notecard line into memory at a time).
Is there a way to do this? generate a entire file in javascript procedurally by loading in the strings making it up line by line, and then serve it as though it were a whole file? I'm not sure how feasible this is.
Any idea's/guidance greatly appreciated!
You could do this through Javascript using XMLHttpRequest. jQuery's wrapper for this is called Ajax. You could request each line individually, which would be slightly slower, or read in a number of lines at a time, at the script's leisure. http_request is not throttled so either works. Note that the loader has to be sent in a single response, because the LSL server has no way of pushing data "piecemeal" like an actual server does.
Notes:
llGetNotecardLine only returns the first 255 bytes per line.
llHTTPResponse must be called within ~20 seconds of the request, so you can't feasibly read more than 20 lines from a notecard at a time.
I'm not sure how this would work for non-DOM filetypes. All files would need to be embed-able in the HTML using the Javascript DOM. To my knowledge, Javascript can't arbitrarily create an external file and serve it to itself. Obviously it would not work for non-text filetypes, but you could certainly load in the rest of the HTML, CSS, and HTML5 SVG. Basically, if it can go in a single HTML file, you could load it in through Javascript.
I have no experience with React but it gives a good example of what is possible on the UI side with loading things in purely through Javascript.
So less than 64 thousand characters in memory at most per script. I will give you some advise that might make your task feasable:
External resources
Minimize the amount of code you have to have in your notecards by sourcing popular libraries from the web Bootstrap, React and such.
You will have to rely on their mirror's availability thought. But it will greatly reduce the amount of memory needed to provide pretty and functional pages.
Minify your code
Use tools like Uglify or Closure Compiler to make your javascript lighter. Though you must be careful, as these tools will fit all your code a single long line by the default, and you can't read lines longer than 255 characters with LSL. Luckily you can customize your options in these tools to limit the amount of characters per line.
Divide and conquer
Since a single script can't handle much memory, make dedicated scripts. One could serve resources (act as a file server, providing your html and js) while the other would receive API request calls, to handle the application's logic.

Generating code vs file format

So my friend and I had an argument that we couldn't resolve. He is writing a general purpose web game library and a map editor. The map editor saves maps as XML, but the map editor can also load a Lua script that exports the details of the map into a Javascript file that looks something like this(he didn't want to post the code, so this is just a snippet with the names changed):
// This probably isn't valid code, but this is the idea of the code generator
(function() {
Game.Level1 = function (state) {
GameEngine.Group.call(this);
var Object0 = new Game.Lo(new GameEngine.Point(654 , 975.13), 15, state);
var slot123 = new GameEngine.TimeSlot(123); //Start
slot123.addEvent(new GameEngine.Event(Object0, "x", "current", 15, 200));
...
The idea is that the game library would just run this code instead of having to parse a map file and generate objects on the fly. And the Lua script in the map editor that generates the code could be modified by anyone who wanted to output code in a different language for a different library. (not limited to scripting languages).
I've never heard of this idea, usually i'd expect the map data to be in a standard format like JSON or XML and have the game library parse it.
So given that his library is written in javascript and his map can generate javascript to load files, what are the tradeoffs between running the generated code vs parsing JSON/XML and generating objects from that?
In a generic sense loading the metadata from another script, can give added flexibility to the script generator about how the data is sent, displayed etc. For example, you can have complete math expressions, conditionals etc as the part of the loaded script, that will be parsed and loaded seamlessly by the script parser(interpreter). It might be harder to do the same thing by using XML or JSON ( imagine sending an expression to do Miller Cylindrical Projection via XML, on the fly).
I've seen many situations where the app creates its own scripting language (MAXscript, MEL for Maya) to provide flexibility to the user. These are probably not analogous to your friend's usage of Java script to load metadata. But IMHO, it is a continuous spectrum, starting from metadata text files, to XML,JSON, expression parsing, to full fledged script parsing.
On the other hand, sending complicated scripts, will mean exposing part your code base. Also everyone knows what XML does, and you can expect a non-programmer to use/modify XML files. They are comfortable doing it. They may not be comfortable even reading what they think of as 'programs' or 'scripts'. I've seen this first hand in my company, where the artists were uneasy modify a Lua file. They were comfortable modifying the same information, if it was a simple text file. There might some security issues as well, but I'm really not that familiar with it, so I can't comment.

I want to unzip a file with javascript from a file or blob object

I would like to read a pptx file in javascript, so I would like to unzip it and read the content in memory. I don't want to store the file first on a server. I want to choose a file with a input type file and just use the file of the input-element and read it binary or something like that.
I found a lot of libraries to unzip zip-files from url, I tried to look at the code but I couldn't figure it out to use it for a blob or byte array.
I can read some stuff like the things described here: http://en.wikipedia.org/wiki/ZIP_%28file_format%29#File_headers
But I don't know how deflating works on byte- or bit-level.
(You've said you want to use an input element, so I'm guessing this is browser-based JavaScript.)
Your first step will be to use the File API to read the file as a binary string. See my answer to this other question for an example of that. Then you'll need to find a library. A quick search discovered this one that implements both inflate and deflate. (I don't have personal experience using it, just found it in an answer to this other question.)
Naturally this will only work on quite modern browsers that support the File API. Otherwise, you have no option but to push the file to a server and do the work there, since you can't access the content of the file in the browser without the File API.

Categories

Resources