Skip to content

Commit

Permalink
🎉 Add initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
LinusU committed Jan 25, 2017
0 parents commit 9206911
Show file tree
Hide file tree
Showing 8 changed files with 1,188 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/node_modules/
/npm-debug.log
Binary file added fixture/fixture-otto.woff2
Binary file not shown.
Binary file added fixture/fixture.mid
Binary file not shown.
63 changes: 63 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const fileType = require('file-type')
const Transform = require('readable-stream/transform')

const kChunks = Symbol('chunks')
const kLength = Symbol('length')
const kDone = Symbol('done')
const kResult = Symbol('result')
const kEmitFileType = Symbol('emit-file-type')

const TARGET_BYTES = 4100

class FileType extends Transform {
constructor () {
super()

this[kChunks] = []
this[kLength] = 0
this[kDone] = false
this[kResult] = null
}

fileTypePromise () {
return new Promise((resolve, reject) => {
if (this[kDone]) return resolve(this[kResult])

this.once('error', reject)
this.once('file-type', resolve)
})
}

[kEmitFileType] () {
this[kDone] = true
this[kResult] = fileType(Buffer.concat(this[kChunks]))

this.emit('file-type', this[kResult])

delete this[kChunks]
delete this[kLength]
}

_transform (chunk, _, cb) {
if (!this[kDone]) {
this[kChunks].push(chunk)
this[kLength] += chunk.length

if (this[kLength] >= TARGET_BYTES) {
this[kEmitFileType]()
}
}

cb(null, chunk)
}

_flush (cb) {
if (!this[kDone]) {
this[kEmitFileType]()
}

cb(null)
}
}

module.exports = FileType
18 changes: 18 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "stream-file-type",
"version": "0.0.0",
"license": "MIT",
"repository": "LinusU/stream-file-type",
"scripts": {
"test": "standard && mocha"
},
"dependencies": {
"file-type": "^4.1.0",
"readable-stream": "^2.2.2"
},
"devDependencies": {
"hasha": "^2.2.0",
"mocha": "^3.2.0",
"standard": "^8.6.0"
}
}
38 changes: 38 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Stream File Type

Get the [file type](https://github.com/sindresorhus/file-type) by inspecting a stream.

## Usage

```js
const fs = require('fs')
const FileType = require('stream-file-type')

const input = fs.createReadStream('cat.jpg')
const detector = new FileType()

detector.on('file-type', (fileType) => {
if (fileType === null) {
console.log(`The mime type of "cat.jpg" could not be determined`)
} else {
console.log(`The file "cat.jpg" has the "${fileType.mime}" mime type`)
}
})

input.pipe(detector).resume()
```

## API

### `new FileType() => DuplexStream`

Returns a new `DuplexStream` that will detect the file type of the content passing thru. All the data is passed as-is right thru the stream, and can be further piped to another destination.

When enough bytes have come thru to determine the file type (currently 4100) the event `file-type` will be emitted with the result of the detection. The result will either be `null` or an object with `ext` and `mime`.

- `ext` - One of the [supported file types](https://github.com/sindresorhus/file-type#supported-file-types)
- `mime` - The [MIME type](http://en.wikipedia.org/wiki/Internet_media_type)

### `FileType#fileTypePromise() => Promise`

Returns a `Promise` of the detected file type. If the `file-type` event has already been emitted, the promise will be resolved with the result, otherwise the promise will be resolved when the file-type is detected, or rejected if an error occurs.
45 changes: 45 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* eslint-env mocha */

const fs = require('fs')
const assert = require('assert')
const hasha = require('hasha')

const FileType = require('./')

describe('stream-file-type', () => {
it('should detect the file type of a stream', () => {
const input = fs.createReadStream('fixture/fixture.mid')
const detector = new FileType()

input.pipe(detector).resume()

let emitted
detector.on('file-type', fileType => { emitted = fileType })

return detector.fileTypePromise()
.then(type => assert.deepEqual(type, { ext: 'mid', mime: 'audio/midi' }))
.then(() => assert.deepEqual(emitted, { ext: 'mid', mime: 'audio/midi' }))
})

it('should pass the input as-is', () => {
const input = fs.createReadStream('fixture/fixture.mid')
const detector = new FileType()

return hasha.fromStream(input.pipe(detector), { algorithm: 'sha1' })
.then(hash => assert.equal(hash, '02cd79ed1d5f07eef736f79122aa89e6fe4f0d4b'))
})

it('should handle files larger than 4100 bytes', () => {
const input = fs.createReadStream('fixture/fixture-otto.woff2')
const detector = new FileType()

input.pipe(detector).resume()

let emitted
detector.on('file-type', fileType => { emitted = fileType })

return detector.fileTypePromise()
.then(type => assert.deepEqual(type, { ext: 'woff2', mime: 'application/font-woff' }))
.then(() => assert.deepEqual(emitted, { ext: 'woff2', mime: 'application/font-woff' }))
})
})
Loading

0 comments on commit 9206911

Please sign in to comment.