UNPKG

mp3

Version:
174 lines (139 loc) 6.04 kB
var AV = require('av'); var ID3v23Stream = require('./id3').ID3v23Stream; var ID3v22Stream = require('./id3').ID3v22Stream; var MP3FrameHeader = require('./header'); var MP3Stream = require('./stream'); var MP3Demuxer = AV.Demuxer.extend(function() { AV.Demuxer.register(this); this.probe = function(stream) { var off = stream.offset; // skip id3 metadata if it exists var id3header = MP3Demuxer.getID3v2Header(stream); if (id3header) stream.advance(10 + id3header.length); // attempt to read the header of the first audio frame var s = new MP3Stream(new AV.Bitstream(stream)); var header = null; try { header = MP3FrameHeader.decode(s); } catch (e) {}; // go back to the beginning, for other probes stream.seek(off); return !!header; }; this.getID3v2Header = function(stream) { if (stream.peekString(0, 3) == 'ID3') { stream = AV.Stream.fromBuffer(stream.peekBuffer(0, 10)); stream.advance(3); // 'ID3' var major = stream.readUInt8(); var minor = stream.readUInt8(); var flags = stream.readUInt8(); var bytes = stream.readBuffer(4).data; var length = (bytes[0] << 21) | (bytes[1] << 14) | (bytes[2] << 7) | bytes[3]; return { version: '2.' + major + '.' + minor, major: major, minor: minor, flags: flags, length: length }; } return null; }; const XING_OFFSETS = [[32, 17], [17, 9]]; this.prototype.parseDuration = function(header) { var stream = this.stream; var frames; var offset = stream.offset; if (!header || header.layer !== 3) return false; // Check for Xing/Info tag stream.advance(XING_OFFSETS[header.flags & MP3FrameHeader.FLAGS.LSF_EXT ? 1 : 0][header.nchannels() === 1 ? 1 : 0]); var tag = stream.readString(4); if (tag === 'Xing' || tag === 'Info') { var flags = stream.readUInt32(); if (flags & 1) frames = stream.readUInt32(); if (flags & 2) var size = stream.readUInt32(); if (flags & 4 && frames && size) { for (var i = 0; i < 100; i++) { var b = stream.readUInt8(); var pos = b / 256 * size | 0; var time = i / 100 * (frames * header.nbsamples() * 32) | 0; this.addSeekPoint(pos, time); } } if (flags & 8) stream.advance(4); } else { // Check for VBRI tag (always 32 bytes after end of mpegaudio header) stream.seek(offset + 4 + 32); tag = stream.readString(4); if (tag == 'VBRI' && stream.readUInt16() === 1) { // Check tag version stream.advance(4); // skip delay and quality stream.advance(4); // skip size frames = stream.readUInt32(); var entries = stream.readUInt16(); var scale = stream.readUInt16(); var bytesPerEntry = stream.readUInt16(); var framesPerEntry = stream.readUInt16(); var fn = 'readUInt' + (bytesPerEntry * 8); var pos = 0; for (var i = 0; i < entries; i++) { this.addSeekPoint(pos, framesPerEntry * i); pos += stream[fn](); } } } if (!frames) return false; this.emit('duration', (frames * header.nbsamples() * 32) / header.samplerate * 1000 | 0); return true; }; this.prototype.readChunk = function() { var stream = this.stream; if (!this.sentInfo) { // read id3 metadata if it exists var id3header = MP3Demuxer.getID3v2Header(stream); if (id3header) { stream.advance(10); if (id3header.major > 2) { var id3 = new ID3v23Stream(id3header, stream); } else { var id3 = new ID3v22Stream(id3header, stream); } this.emit('metadata', id3.read()); } // read the header of the first audio frame var off = stream.offset; var s = new MP3Stream(new AV.Bitstream(stream)); var header = MP3FrameHeader.decode(s); if (!header) return this.emit('error', 'Could not find first frame.'); this.emit('format', { formatID: 'mp3', sampleRate: header.samplerate, channelsPerFrame: header.nchannels(), bitrate: header.bitrate, floatingPoint: true }); var sentDuration = this.parseDuration(header); stream.advance(off - stream.offset); // if there were no Xing/VBRI tags, guesstimate the duration based on data size and bitrate this.dataSize = 0; if (!sentDuration) { this.on('end', function() { this.emit('duration', this.dataSize * 8 / header.bitrate * 1000 | 0); }); } this.sentInfo = true; } while (stream.available(1)) { var buffer = stream.readSingleBuffer(stream.remainingBytes()); this.dataSize += buffer.length; this.emit('data', buffer); } }; }); module.exports = MP3Demuxer;