mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-30 20:33:55 +02:00
Add metadata-based duration retrieval
This commit is contained in:
+78
-2
@@ -12,9 +12,23 @@ import { Input } from '../input';
|
||||
import { InputAudioTrack, InputAudioTrackBacking } from '../input-track';
|
||||
import { DEFAULT_TRACK_DISPOSITION, MetadataTags } from '../metadata';
|
||||
import { PacketRetrievalOptions } from '../media-sink';
|
||||
import { assert, AsyncMutex, binarySearchExact, binarySearchLessOrEqual, UNDETERMINED_LANGUAGE } from '../misc';
|
||||
import {
|
||||
assert,
|
||||
AsyncMutex,
|
||||
binarySearchExact,
|
||||
binarySearchLessOrEqual,
|
||||
toDataView,
|
||||
UNDETERMINED_LANGUAGE,
|
||||
} from '../misc';
|
||||
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
||||
import { Mp3FrameHeader, getXingOffset, INFO, XING } from '../../shared/mp3-misc';
|
||||
import {
|
||||
Mp3FrameHeader,
|
||||
getXingOffset,
|
||||
INFO,
|
||||
XING,
|
||||
XingFlags,
|
||||
computeAverageMp3FrameSize,
|
||||
} from '../../shared/mp3-misc';
|
||||
import {
|
||||
ID3_V1_TAG_SIZE,
|
||||
ID3_V2_HEADER_SIZE,
|
||||
@@ -37,8 +51,13 @@ export class Mp3Demuxer extends Demuxer {
|
||||
|
||||
metadataPromise: Promise<void> | null = null;
|
||||
firstFrameHeader: Mp3FrameHeader | null = null;
|
||||
firstFrameHeaderPos: number | null = null;
|
||||
loadedSamples: Sample[] = []; // All samples from the start of the file to lastLoadedPos
|
||||
metadataTags: MetadataTags | null = null;
|
||||
xingData: {
|
||||
frameCount: number | null;
|
||||
fileSize: number | null;
|
||||
} | null = null;
|
||||
|
||||
tracks: InputAudioTrack[] = [];
|
||||
|
||||
@@ -109,12 +128,33 @@ export class Mp3Demuxer extends Demuxer {
|
||||
|
||||
if (isXing) {
|
||||
// There's no actual audio data in this frame, so let's skip it
|
||||
|
||||
if (!this.xingData) {
|
||||
let xingDataSlice = this.reader.requestSlice(result.startPos + xingOffset + 4, 12);
|
||||
if (xingDataSlice instanceof Promise) xingDataSlice = await xingDataSlice;
|
||||
if (xingDataSlice) {
|
||||
const xingData = readBytes(xingDataSlice, 12);
|
||||
const view = toDataView(xingData);
|
||||
const flags = view.getUint32(0, false);
|
||||
|
||||
this.xingData = {
|
||||
frameCount: (flags & XingFlags.FrameCount)
|
||||
? view.getUint32(4, false)
|
||||
: null,
|
||||
fileSize: (flags & XingFlags.FileSize)
|
||||
? view.getUint32(8, false)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.firstFrameHeader) {
|
||||
this.firstFrameHeader = header;
|
||||
this.firstFrameHeaderPos = result.startPos;
|
||||
}
|
||||
|
||||
if (header.sampleRate !== this.firstFrameHeader.sampleRate) {
|
||||
@@ -138,6 +178,38 @@ export class Mp3Demuxer extends Demuxer {
|
||||
return;
|
||||
}
|
||||
|
||||
async getDurationFromMetadata(): Promise<number | null> {
|
||||
await this.readMetadata();
|
||||
assert(this.firstFrameHeader !== null);
|
||||
assert(this.firstFrameHeaderPos !== null);
|
||||
|
||||
if (this.xingData) {
|
||||
if (this.xingData.frameCount !== null) {
|
||||
return this.xingData.frameCount
|
||||
* this.firstFrameHeader.audioSamplesInFrame
|
||||
/ this.firstFrameHeader.sampleRate;
|
||||
}
|
||||
} else {
|
||||
// No Xing, assuming CBR
|
||||
|
||||
if (this.reader.fileSize !== null) {
|
||||
const averageFrameSize = computeAverageMp3FrameSize(
|
||||
this.firstFrameHeader.lowSamplingFrequency,
|
||||
this.firstFrameHeader.layer,
|
||||
this.firstFrameHeader.bitrate,
|
||||
this.firstFrameHeader.sampleRate,
|
||||
);
|
||||
const frameCount = (this.reader.fileSize - this.firstFrameHeaderPos) / averageFrameSize;
|
||||
|
||||
return Math.round(frameCount)
|
||||
* this.firstFrameHeader.audioSamplesInFrame
|
||||
/ this.firstFrameHeader.sampleRate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async getMimeType() {
|
||||
return 'audio/mpeg';
|
||||
}
|
||||
@@ -237,6 +309,10 @@ class Mp3AudioTrackBacking implements InputAudioTrackBacking {
|
||||
return null;
|
||||
}
|
||||
|
||||
getDurationFromMetadata() {
|
||||
return this.demuxer.getDurationFromMetadata();
|
||||
}
|
||||
|
||||
async getLiveRefreshInterval() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import { assert, toDataView } from '../misc';
|
||||
import { toDataView } from '../misc';
|
||||
import { metadataTagsAreEmpty } from '../metadata';
|
||||
import { Muxer } from '../muxer';
|
||||
import { Output, OutputAudioTrack } from '../output';
|
||||
@@ -107,14 +107,14 @@ export class Mp3Muxer extends Muxer {
|
||||
|
||||
this.validateAndNormalizeTimestamp(track, packet.timestamp, packet.type === 'key');
|
||||
|
||||
if (writeXingHeader) {
|
||||
this.framePositions.push(this.writer.getPos());
|
||||
}
|
||||
|
||||
this.writer.write(packet.data);
|
||||
this.frameCount++;
|
||||
|
||||
await this.writer.flush();
|
||||
|
||||
if (writeXingHeader) {
|
||||
this.framePositions.push(this.writer.getPos());
|
||||
}
|
||||
} finally {
|
||||
release();
|
||||
}
|
||||
@@ -132,20 +132,20 @@ export class Mp3Muxer extends Muxer {
|
||||
const release = await this.mutex.acquire();
|
||||
|
||||
const endPos = this.writer.getPos();
|
||||
const audioDataEndPos = endPos - this.xingFramePos;
|
||||
|
||||
this.writer.seek(this.xingFramePos);
|
||||
|
||||
const toc = new Uint8Array(100);
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const index = Math.floor(this.framePositions.length * (i / 100));
|
||||
assert(index !== -1 && index < this.framePositions.length);
|
||||
|
||||
const byteOffset = this.framePositions[index]!;
|
||||
toc[i] = 256 * (byteOffset / endPos);
|
||||
const byteOffset = this.framePositions[index]! - this.xingFramePos;
|
||||
toc[i] = 256 * (byteOffset / audioDataEndPos);
|
||||
}
|
||||
|
||||
this.xingFrameData.frameCount = this.frameCount;
|
||||
this.xingFrameData.fileSize = endPos;
|
||||
this.xingFrameData.fileSize = audioDataEndPos;
|
||||
this.xingFrameData.toc = toc;
|
||||
|
||||
if (this.format._options.onXingFrame) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
getXingOffset,
|
||||
KILOBIT_RATES,
|
||||
XING,
|
||||
XingFlags,
|
||||
} from '../../shared/mp3-misc';
|
||||
|
||||
export type XingFrameData = {
|
||||
@@ -96,13 +97,13 @@ export class Mp3Writer {
|
||||
|
||||
let flags = 0;
|
||||
if (data.frameCount !== null) {
|
||||
flags |= 1;
|
||||
flags |= XingFlags.FrameCount;
|
||||
}
|
||||
if (data.fileSize !== null) {
|
||||
flags |= 2;
|
||||
flags |= XingFlags.FileSize;
|
||||
}
|
||||
if (data.toc !== null) {
|
||||
flags |= 4;
|
||||
flags |= XingFlags.Toc;
|
||||
}
|
||||
|
||||
this.writeU32(flags);
|
||||
|
||||
Reference in New Issue
Block a user