Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
#4: Implemented parsing data from provided URL using the Fetch API.
  • Loading branch information
Borewit committed Oct 6, 2018
1 parent cbdaf27 commit 6697da4
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 21 deletions.
24 changes: 7 additions & 17 deletions README.md
Expand Up @@ -85,14 +85,16 @@ import * as mm from 'music-metadata-browser';

### Module Functions:

There are currently two ways to parse (read) audio tracks:
There are currently three ways to parse (read) audio tracks:
1) parsing a Web API blob or file with the [parseBlob function](#parseBlob).
2) Using [Node.js streams](https://nodejs.org/api/stream.html) using the [parseStream function](#parseStream).
3) Provide a URL to [fetch the audio track from](#fetchUrl).

#### parseBlob function

To convert a [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob) or [File](https://developer.mozilla.org/en-US/docs/Web/API/File) into a [stream](https://nodejs.org/api/stream.html#stream_readable_streams),
[filereader-stream](https://www.npmjs.com/package/filereader-stream) is used.

```javascript
import * as mm from 'music-metadata-browser';

Expand Down Expand Up @@ -137,29 +139,17 @@ mm.parseStream(someReadStream, 'audio/mpeg', { fileSize: 26838 })
});
```

If you wish to stream your audio track over HTTP you need HTTP-client which provides a stream like [stream-http](https://www.npmjs.com/package/stream-stream):
### fetchUrl

If you wish to stream your audio track over HTTP you need can use `fetchFromUrl` which is using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) to retrieve the audio track:

```javascript
import * as mm from 'music-metadata-browser';
import http from "stream-http";

/**
* @param url Ensure the source the URL is pointing to, meets the CORS requirements
*/
function httpToStream(url) {
return new Promise(resolve => {
http.get(url, stream => {
resolve(stream);
});
});
}

/**
* Stream over HTTP from URL
*/
httpToStream(url).then(stream => {
mm.parseStream(stream, stream.headers["content-type"]);
});
return mm.fetchFromUrl(audioTrackUrl, options)
```

#### orderTags function
Expand Down
1 change: 1 addition & 0 deletions music-metadata-browser.iml
Expand Up @@ -10,5 +10,6 @@
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="tsconfig$roots" level="project" />
</component>
</module>
3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -52,6 +52,7 @@
"build": "npm run compile-dist",
"lint": "tslint 'src/**/*.ts' --exclude 'src/**/*.d.ts' 'test/**/*.ts' --exclude 'test/**/*.d.ts'",
"karma": "karma start",
"karma-firefox": "karma start --browsers Firefox",
"karma-once": "karma start --browsers Chrome --single-run",
"travis-karma": "karma start --browsers Firefox --single-run --reporters coverage-istanbul,spec",
"post-coveralls": "coveralls < coverage/lcov.info"
Expand Down Expand Up @@ -81,7 +82,9 @@
"dependencies": {
"assert": "^1.4.1",
"buffer": "^5.2.1",
"debug": "^4.0.1",
"music-metadata": "^3.1.1",
"remove": "^0.1.5",
"typedarray-to-buffer": "^3.1.5"
}
}
39 changes: 39 additions & 0 deletions src/fetch/Browser2NodeStream.ts
@@ -0,0 +1,39 @@
/**
* A mock readable-stream, using string to read from
*/
import { Readable } from 'stream';

/**
* Converts a ReadableStream
* https://developer.mozilla.org/en-US/docs/Web/API/ReadableStreamDefaultReader/read
*/
export class Browser2NodeStream extends Readable {

public bytesRead: number = 0;

private reader: ReadableStreamReader;

constructor(stream: ReadableStream) {
super();
this.reader = stream.getReader();
}

public _read() {
this.reader.read().then(res => {
if (res.done) {
this.push(null);
} else {
this.bytesRead += res.value.length;
this.push(res.value);
}
});
}

public _destroy(error: Error | null, callback: (error: Error | null) => void): void {
this.reader.cancel().then(() => {
callback(null);
}).catch(err => {
callback(err);
});
}
}
139 changes: 139 additions & 0 deletions src/index.spec.ts
@@ -1,3 +1,5 @@
// localStorage.debug = 'music-metadata-browser';

import * as Stream from 'stream';
import * as http from 'stream-http';
import * as mm from './index';
Expand Down Expand Up @@ -50,6 +52,123 @@ const parsers: IParserTest[] = [
return mm.parseBlob(blob, options);
});
}
},
{
methodDescription: 'fetchFromUrl()',
parseUrl: (audioTrackUrl, options) => {
return mm.fetchFromUrl(audioTrackUrl, options);
}
}
];

const webAmpTracks = [
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Diablo_Swing_Orchestra_-_01_-_Heroines.mp3',
duration: 322.612245,
metaData: {
title: 'Heroines',
artist: 'Diablo Swing Orchestra'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Eclectek_-_02_-_We_Are_Going_To_Eclecfunk_Your_Ass.mp3',
duration: 190.093061,
metaData: {
title: 'We Are Going To Eclecfunk Your Ass',
artist: 'Eclectek'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Auto-Pilot_-_03_-_Seventeen.mp3',
duration: 214.622041,
metaData: {
title: 'Seventeen',
artist: 'Auto-Pilot'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Muha_-_04_-_Microphone.mp3',
duration: 181.838367,
metaData: {
title: 'Microphone',
artist: 'Muha'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Just_Plain_Ant_-_05_-_Stumble.mp3',
duration: 86.047347,
metaData: {
title: 'Stumble',
artist: 'Just Plain Ant'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Sleaze_-_06_-_God_Damn.mp3',
duration: 226.795102,
metaData: {
title: 'God Damn',
artist: 'Sleaze'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Juanitos_-_07_-_Hola_Hola_Bossa_Nova.mp3',
duration: 207.072653,
metaData: {
title: 'Hola Hola Bossa Nova',
artist: 'Juanitos'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Entertainment_for_the_Braindead_-_08_-_Resolutions_Chris_Summer_Remix.mp3',
duration: 314.331429,
metaData: {
title: 'Resolutions (Chris Summer Remix)',
artist: 'Entertainment for the Braindead'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Nobara_Hayakawa_-_09_-_Trail.mp3',
duration: 204.042449,
metaData: {
title: 'Trail',
artist: 'Nobara Hayakawa'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/Paper_Navy_-_10_-_Tongue_Tied.mp3',
duration: 201.116735,
metaData: {
title: 'Tongue Tied',
artist: 'Paper Navy'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/60_Tigres_-_11_-_Garage.mp3',
duration: 245.394286,
metaData: {
title: 'Garage',
artist: '60 Tigres'
}
},
{
url:
'https://raw.githubusercontent.com/captbaritone/webamp-music/4b556fbf/CM_aka_Creative_-_12_-_The_Cycle_Featuring_Mista_Mista.mp3',
duration: 221.44,
metaData: {
title: 'The Cycle (Featuring Mista Mista)',
artist: 'CM aka Creative'
}
}
];

Expand Down Expand Up @@ -100,3 +219,23 @@ describe('music-metadata-browser', () => {
});

});

describe('Parse WebAmp tracks', () => {

parsers.forEach(parser => {

describe(`Parser: ${parser.methodDescription}`, () => {

webAmpTracks.forEach(track => {
it(`track ${track.metaData.artist} - ${track.metaData.title}`, () => {
return parser.parseUrl(track.url).then(metadata => {
expect(metadata.common.artist).toEqual(track.metaData.artist);
expect(metadata.common.title).toEqual(track.metaData.title);
});
});
});
});

});

});
51 changes: 48 additions & 3 deletions src/index.ts
@@ -1,9 +1,11 @@
'use strict';

import {Buffer} from 'buffer';
import { Buffer } from 'buffer';
import * as initDebug from 'debug';
import * as mm from 'music-metadata/lib/core';
import * as Type from 'music-metadata/lib/type';
import * as toBuffer from 'typedarray-to-buffer';
import { Browser2NodeStream } from './fetch/Browser2NodeStream';

const debug = initDebug('music-metadata-browser');

export type IAudioMetadata = Type.IAudioMetadata;
export type IOptions = Type.IOptions;
Expand Down Expand Up @@ -41,6 +43,49 @@ export function parseBlob(blob: Blob, options?: IOptions): Promise<IAudioMetadat
});
}

/**
* Parse fetched file, using the Web Fetch API
* @param {string} audioTrackUrl URL to download the audio track from
* @param {IOptions} options Parsing options
* @returns {Promise<IAudioMetadata>}
*/
export function fetchFromUrl(audioTrackUrl: string, options?: IOptions): Promise<IAudioMetadata> {
return fetch(audioTrackUrl).then(response => {
const contentType = response.headers.get('Content-Type');
const headers = [];
response.headers.forEach(header => {
headers.push(header);
});
if (response.ok) {
if (response.body) {
const stream = new Browser2NodeStream(response.body);
return this.parseStream(stream, contentType, options).then(res => {
debug(`Closing stream 1bytesRead=${stream.bytesRead} / fileSize=${ options && options.fileSize ? options.fileSize : '?'}`);
stream.destroy();
return res;
});
} else {
// Fall back on Blob
return response.blob().then(blob => {
return this.parseBlob(blob, options);
});
}
} else {
throw new Error(`HTTP error status=${response.status}: ${response.statusText}`);
}

});
}

/**
* Parse audio from ITokenizer source
* @param {strtok3.ITokenizer} Audio source implementing the tokenizer interface
* @param {string} mimeType <string> Content specification MIME-type, e.g.: 'audio/mpeg'
* @param {IOptions} options Parsing options
* @returns {Promise<IAudioMetadata>}
*/
export const parseFromTokenizer = mm.parseFromTokenizer;

/**
* Convert Web API File to Node Buffer
* @param {Blob} blob Web API Blob
Expand Down
2 changes: 1 addition & 1 deletion tslint.json
Expand Up @@ -10,7 +10,7 @@
"object-literal-key-quotes": [true, "as-needed"],
"variable-name": [true, "ban-keywords"],
"object-literal-sort-keys": [false],
"max-line-length": [true, 150],
"max-line-length": [true, 200],
"switch-default": false,
"prefer-for-of": false,
"arrow-parens": [true, "ban-single-arg-parens"],
Expand Down

0 comments on commit 6697da4

Please sign in to comment.