Permalink
Browse files

Landed preliminary support for reading from buffers

  • Loading branch information...
1 parent 41e91b0 commit 7a4608455a7ffe0eb3f301480532680ca0332e67 @nikhilm committed May 23, 2012
Showing with 299 additions and 14 deletions.
  1. +5 −1 README.md
  2. +66 −0 spec/buffersSpec.js
  3. +73 −0 src/bufferstream.cc
  4. +37 −0 src/bufferstream.h
  5. +107 −11 src/taglib.cc
  6. +9 −0 src/taglib.h
  7. +1 −1 test.sh
  8. +1 −1 wscript
View
@@ -58,6 +58,7 @@ The `examples` show usage.
## API
### read(path, callback)
+### read(buffer, format, callback)
The function you will most likely want to use. `callback` should have signature
`callback(err, tag, audioProperties)` where `tag` and `audioProperties` are
@@ -90,6 +91,9 @@ integers:
Writing audio properties is not supported.
+In the second variant, which can read from a buffer, `format` should be
+a string as specified in [addResolvers](#addResolvers).
+
### tag(path, callback)
Read the tag from the file at `path` _asynchronously_. The callback should have
@@ -141,7 +145,7 @@ Save any changes in the Tag meta-data to disk _synchronously_.
Returns whether the tag is empty or not.
-### taglib.addResolvers(\[resolver1\[, resolver2\[, ...]]])
+### taglib.addResolvers(\[resolver1\[, resolver2\[, ...]]]) {#addResolvers}
Adds JavaScript functions that will be called to resolve the filetype of
a file. Each resolver will be added to the front of the resolver queue. So the
View
@@ -0,0 +1,66 @@
+var assert = require('assert'),
+ vows = require('vows'),
+ fs = require('fs'),
+ Taglib = require(__dirname + '/../taglib');
+
+vows.describe('taglib bindings: Buffers')
+.addBatch({
+ 'reading metadata from mp3 buffer': {
+ topic: function() {
+ var buf = fs.readFileSync(__dirname+'/sample.mp3');
+ Taglib.read(buf, 'mpeg', this.callback);
+ },
+
+ 'should be called with three arguments': function (err, tag, props) {
+ assert.isNull(err);
+ assert.isObject(tag);
+ assert.isObject(props);
+ },
+
+ 'reading tags': {
+ topic: function() {
+ var buf = fs.readFileSync(__dirname+'/sample.mp3');
+ Taglib.read(buf, 'mpeg', this.callback);
+ },
+
+ 'title should be `A bit-bucket full of tags`': function (tag) {
+ assert.equal(tag.title, 'A bit-bucket full of tags');
+ },
+ 'artist should be by `gitzer\'s`': function (tag) {
+ assert.equal(tag.artist, 'gitzer\'s');
+ },
+ 'album should be on `Waffles for free!`': function (tag) {
+ assert.equal(tag.album, "Waffles for free!");
+ },
+ 'track should be the first': function (tag) {
+ assert.equal(tag.track, 1);
+ },
+ 'should be from 2011': function (tag) {
+ assert.equal(tag.year, 2011);
+ },
+ 'should have a silly comment': function(tag) {
+ assert.equal(tag.comment, "Salami Wiglet.");
+ }
+ },
+
+ 'reading audioProperties': {
+ topic: function() {
+ var buf = fs.readFileSync(__dirname+'/blip.mp3');
+ Taglib.read(buf, 'mpeg', this.callback);
+ },
+
+ 'should have length 1 second': function(err, _, prop) {
+ assert.equal(prop.length, 1);
+ },
+ 'should have bitrate 128kbps': function(err, _, prop) {
+ assert.equal(prop.bitrate, 128);
+ },
+ 'should have sampleRate 44100Hz': function(err, _, prop) {
+ assert.equal(prop.sampleRate, 44100);
+ },
+ 'should have 2 channels': function(err, _, prop) {
+ assert.equal(prop.channels, 2);
+ }
+ },
+ },
+}).export(module);
View
@@ -0,0 +1,73 @@
+#include "bufferstream.h"
+
+#include <node_buffer.h>
+
+#include "taglib.h"
+
+using namespace v8;
+using namespace node;
+using namespace node_taglib;
+
+namespace node_taglib {
+BufferStream::BufferStream(Handle<Object> buffer)
+ : TagLib::IOStream()
+ , m_data(Buffer::Data(buffer))
+ , m_length(Buffer::Length(buffer))
+ , m_offset(0)
+{
+}
+
+BufferStream::~BufferStream()
+{
+}
+
+TagLib::ByteVector BufferStream::readBlock(TagLib::ulong length) {
+ return TagLib::ByteVector(m_data, m_length).mid(m_offset, length);
+}
+
+void BufferStream::writeBlock(const TagLib::ByteVector &data) {
+ fprintf(stderr, "writeBlock called aborting\n");
+ abort();
+}
+
+void BufferStream::insert(const TagLib::ByteVector &data, TagLib::ulong start, TagLib::ulong replace) {
+ fprintf(stderr, "insert called aborting\n");
+ abort();
+}
+
+void BufferStream::removeBlock(TagLib::ulong start, TagLib::ulong length) {
+ fprintf(stderr, "removeBlock called aborting\n");
+ abort();
+}
+
+void BufferStream::seek(long offset, TagLib::IOStream::Position p) {
+ if (p == TagLib::IOStream::Beginning) {
+ m_offset = offset;
+ }
+ else if (p == TagLib::IOStream::Current) {
+ m_offset += offset;
+ }
+ else if (p == TagLib::IOStream::End) {
+ m_offset = length() + offset;
+ }
+ assert(m_offset >= 0);
+}
+
+void BufferStream::clear() {
+ fprintf(stderr, "clear called aborting\n");
+ abort();
+}
+
+long BufferStream::tell() const {
+ return m_offset;
+}
+
+long BufferStream::length() {
+ return m_length;
+}
+
+void BufferStream::truncate(long length) {
+ fprintf(stderr, "truncaled called aborting\n");
+ abort();
+}
+}
View
@@ -0,0 +1,37 @@
+#ifndef NODE_TAGLIB_BUFFERSTREAM_H
+#define NODE_TAGLIB_BUFFERSTREAM_H
+
+#include <node.h>
+
+#include <taglib.h>
+#include <tstring.h>
+#include <tiostream.h>
+
+namespace node_taglib {
+class BufferStream : public TagLib::IOStream {
+public:
+ BufferStream(v8::Handle<v8::Object> buffer);
+
+ ~BufferStream();
+
+ TagLib::FileName name() const { return TagLib::String::null.toCString(); }
+
+ TagLib::ByteVector readBlock(TagLib::ulong length);
+ void writeBlock(const TagLib::ByteVector &data);
+ void insert(const TagLib::ByteVector &data, TagLib::ulong start=0, TagLib::ulong replace=0);
+ void removeBlock(TagLib::ulong start=0, TagLib::ulong length=0);
+ bool readOnly() const { return true; }
+ bool isOpen() const { return true; }
+ void seek(long offset, TagLib::IOStream::Position p=TagLib::IOStream::Beginning);
+ void clear();
+ long tell() const;
+ long length();
+ void truncate(long length);
+
+private:
+ char *m_data;
+ long m_length;
+ long m_offset;
+};
+}
+#endif
View
@@ -9,6 +9,8 @@
#include <string.h>
#include <v8.h>
+#include <node_buffer.h>
+
#include <asffile.h>
#include <mpegfile.h>
#include <vorbisfile.h>
@@ -29,6 +31,7 @@
#include "audioproperties.h"
#include "tag.h"
+#include "bufferstream.h"
using namespace v8;
using namespace node;
@@ -59,6 +62,30 @@ int CreateFileRefPath(TagLib::FileName path, TagLib::FileRef **ref) {
return error;
}
+int CreateFileRefFile(TagLib::File *file, TagLib::FileRef **ref) {
+ TagLib::FileRef *f = NULL;
+ int error = 0;
+ if (file == NULL) {
+ *ref = NULL;
+ return EBADF;
+ }
+
+ f = new TagLib::FileRef(file);
+
+ if (f->isNull() || !f->tag())
+ {
+ error = EINVAL;
+ delete f;
+ }
+
+ if (error != 0)
+ *ref = NULL;
+ else
+ *ref = f;
+
+ return error;
+}
+
Handle<String> ErrorToString(int error) {
HandleScope scope;
std::string err;
@@ -72,6 +99,10 @@ Handle<String> ErrorToString(int error) {
err = "Failed to extract tags";
break;
+ case EBADF:
+ err = "Unknown file format (check format string)";
+ break;
+
default:
err = "Unknown error";
break;
@@ -83,20 +114,42 @@ Handle<String> ErrorToString(int error) {
v8::Handle<v8::Value> AsyncReadFile(const v8::Arguments &args) {
HandleScope scope;
- if (args.Length() < 1 || !args[0]->IsString())
- return ThrowException(String::New("Expected string 'path' as first argument"));
-
- String::Utf8Value path(args[0]->ToString());
+ if (args.Length() < 1) {
+ return ThrowException(String::New("Expected string or buffer as first argument"));
+ }
- if (args.Length() < 2 || !args[1]->IsFunction())
- return ThrowException(String::New("Expected callback function as second argument"));
+ if (args[0]->IsString()) {
+ if (args.Length() < 2 || !args[1]->IsFunction())
+ return ThrowException(String::New("Expected callback function as second argument"));
- Local<Function> callback = Local<Function>::Cast(args[1]);
+ }
+ else if (Buffer::HasInstance(args[0])) {
+ if (args.Length() < 2 || !args[1]->IsString())
+ return ThrowException(String::New("Expected string 'format' as second argument"));
+ if (args.Length() < 3 || !args[2]->IsFunction())
+ return ThrowException(String::New("Expected callback function as third argument"));
+ }
+ else {
+ return ThrowException(String::New("Expected string or buffer as first argument"));
+ }
AsyncBaton *baton = new AsyncBaton;
+ baton->path = 0;
+ baton->format = TagLib::String::null;
+ baton->stream = 0;
+
baton->request.data = baton;
- baton->path = strdup(*path);
- baton->callback = Persistent<Function>::New(callback);
+ if (args[0]->IsString()) {
+ String::Utf8Value path(args[0]->ToString());
+ baton->path = strdup(*path);
+ baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[1]));
+
+ }
+ else {
+ baton->format = NodeStringToTagLibString(args[1]->ToString());
+ baton->stream = new BufferStream(args[0]->ToObject());
+ baton->callback = Persistent<Function>::New(Local<Function>::Cast(args[2]));
+ }
baton->error = 0;
uv_queue_work(uv_default_loop(), &baton->request, AsyncReadFileDo, AsyncReadFileAfter);
@@ -108,9 +161,52 @@ void AsyncReadFileDo(uv_work_t *req) {
AsyncBaton *baton = static_cast<AsyncBaton*>(req->data);
TagLib::FileRef *f;
- int error;
- baton->error = node_taglib::CreateFileRefPath(baton->path, &f);
+ if (baton->path) {
+ baton->error = node_taglib::CreateFileRefPath(baton->path, &f);
+ }
+ else {
+ assert(baton->stream);
+ TagLib::File *file = 0;
+ baton->format = baton->format.upper();
+ if (baton->format == "MPEG")
+ file = new TagLib::MPEG::File(baton->stream, TagLib::ID3v2::FrameFactory::instance());
+ else if (baton->format == "OGG")
+ file = new TagLib::Ogg::Vorbis::File(baton->stream);
+ else if (baton->format == "OGG/FLAC")
+ file = new TagLib::Ogg::FLAC::File(baton->stream);
+ else if (baton->format == "FLAC")
+ file = new TagLib::FLAC::File(baton->stream, TagLib::ID3v2::FrameFactory::instance());
+ else if (baton->format == "MPC")
+ file = new TagLib::MPC::File(baton->stream);
+ else if (baton->format == "WV")
+ file = new TagLib::WavPack::File(baton->stream);
+ else if (baton->format == "SPX")
+ file = new TagLib::Ogg::Speex::File(baton->stream);
+ else if (baton->format == "TTA")
+ file = new TagLib::TrueAudio::File(baton->stream);
+ else if (baton->format == "MP4")
+ file = new TagLib::MP4::File(baton->stream);
+ else if (baton->format == "ASF")
+ file = new TagLib::ASF::File(baton->stream);
+ else if (baton->format == "AIFF")
+ file = new TagLib::RIFF::AIFF::File(baton->stream);
+ else if (baton->format == "WAV")
+ file = new TagLib::RIFF::WAV::File(baton->stream);
+ else if (baton->format == "APE")
+ file = new TagLib::APE::File(baton->stream);
+ // module, nst and wow are possible but uncommon baton->formatensions
+ else if (baton->format == "MOD")
+ file = new TagLib::Mod::File(baton->stream);
+ else if (baton->format == "S3M")
+ file = new TagLib::S3M::File(baton->stream);
+ else if (baton->format == "IT")
+ file = new TagLib::IT::File(baton->stream);
+ else if (baton->format == "XM")
+ file = new TagLib::XM::File(baton->stream);
+
+ baton->error = node_taglib::CreateFileRefFile(file, &f);
+ }
if (baton->error == 0) {
baton->fileRef = f;
View
@@ -8,8 +8,10 @@
namespace node_taglib {
class Tag;
+class BufferStream;
int CreateFileRefPath(TagLib::FileName path, TagLib::FileRef **ref);
+int CreateFileRefFile(TagLib::File *file, TagLib::FileRef **ref);
v8::Handle<v8::String> ErrorToString(int error);
v8::Handle<v8::Value> TagLibStringToString( TagLib::String s );
TagLib::String NodeStringToTagLibString( v8::Local<v8::Value> s );
@@ -23,7 +25,14 @@ struct AsyncBaton {
uv_work_t request;
v8::Persistent<v8::Function> callback;
int error;
+
TagLib::FileName path; /* only used by read/tag, not save */
+ // OR
+ TagLib::String format;
+ BufferStream *stream; // File takes over ownership of the stream
+ // and FileRef takes over ownership of the File
+ // so don't do BufferStream deletion
+
TagLib::FileRef *fileRef; /* only used by taglib.read */
Tag *tag; /* only used by taglib.tag */
};
Oops, something went wrong.

0 comments on commit 7a46084

Please sign in to comment.