mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-09-27 19:03:46 +02:00
Add metadata tag support to Conversion API
This commit is contained in:
@@ -47,6 +47,7 @@ import {
|
||||
} from './misc';
|
||||
import { Output, TrackType } from './output';
|
||||
import { AudioSample, VideoSample } from './sample';
|
||||
import { MetadataTags, validateMetadataTags } from './tags';
|
||||
|
||||
/**
|
||||
* The options for media file conversion.
|
||||
@@ -84,6 +85,15 @@ export type ConversionOptions = {
|
||||
/** The time in the input file in seconds at which the output file should end. Must be greater than `start`. */
|
||||
end: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* A callback that returns or resolves to the descriptive metadata tags that should be written to the output file.
|
||||
* As input, this function will be passed the tags of the input file, allowing you to modify, augment or extend
|
||||
* them.
|
||||
*
|
||||
* If no function is set, the input's metadata tags will be copied to the output.
|
||||
*/
|
||||
tags?: (inputTags: MetadataTags) => MaybePromise<MetadataTags>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -397,6 +407,9 @@ export class Conversion {
|
||||
&& options.trim.start >= options.trim.end) {
|
||||
throw new TypeError('options.trim.start must be less than options.trim.end.');
|
||||
}
|
||||
if (options.tags !== undefined && typeof options.tags !== 'function') {
|
||||
throw new TypeError('options.tags, when provided, must be a function.');
|
||||
}
|
||||
|
||||
this._options = options;
|
||||
this.input = options.input;
|
||||
@@ -480,6 +493,32 @@ export class Conversion {
|
||||
// Let's give the user a notice/warning about discarded tracks so they aren't confused
|
||||
console.warn('Some tracks had to be discarded from the conversion:', unintentionallyDiscardedTracks);
|
||||
}
|
||||
|
||||
// Now, let's deal with metadata tags
|
||||
|
||||
const inputTags = await this.input.getMetadataTags();
|
||||
let outputTags: MetadataTags;
|
||||
|
||||
if (this._options.tags) {
|
||||
const result = await this._options.tags(inputTags);
|
||||
validateMetadataTags(result);
|
||||
|
||||
outputTags = result;
|
||||
} else {
|
||||
outputTags = inputTags;
|
||||
}
|
||||
|
||||
// Somewhat dirty but pragmatic
|
||||
const inputAndOutputFormatMatch = (await this.input.getFormat()).mimeType === this.output.format.mimeType;
|
||||
const rawTagsAreUnchanged = inputTags.raw === outputTags.raw;
|
||||
|
||||
if (inputTags.raw && rawTagsAreUnchanged && !inputAndOutputFormatMatch) {
|
||||
// If the input and output formats aren't the same, copying over raw metadata tags makes no sense and only
|
||||
// results in junk tags, so let's cut them out.
|
||||
delete outputTags.raw;
|
||||
}
|
||||
|
||||
this.output.setMetadataTags(outputTags);
|
||||
}
|
||||
|
||||
/** Executes the conversion process. Resolves once conversion is complete. */
|
||||
|
||||
@@ -252,7 +252,12 @@ export class MatroskaDemuxer extends Demuxer {
|
||||
// Load metadata tags from each segment lazily (only once)
|
||||
for (const segment of this.segments) {
|
||||
if (!segment.metadataTagsCollected) {
|
||||
await this.loadSegmentMetadata(segment);
|
||||
if (this.reader.fileSize !== null) {
|
||||
await this.loadSegmentMetadata(segment);
|
||||
} else {
|
||||
// The seeking would be too crazy, let's not
|
||||
}
|
||||
|
||||
segment.metadataTagsCollected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +223,8 @@ export class Output<
|
||||
/**
|
||||
* Sets descriptive metadata tags about the media file, such as title, author, date, or cover art. When called
|
||||
* multiple times, only the metadata from the last call will be used.
|
||||
*
|
||||
* Must be called before output is started.
|
||||
*/
|
||||
setMetadataTags(tags: MetadataTags) {
|
||||
validateMetadataTags(tags);
|
||||
|
||||
@@ -36,6 +36,8 @@ export abstract class Source {
|
||||
abstract _retrieveSize(): MaybePromise<number | null>;
|
||||
/** @internal */
|
||||
abstract _read(start: number, end: number): MaybePromise<ReadResult | null>;
|
||||
/** @internal */
|
||||
abstract get _supportsRandomAccess(): boolean;
|
||||
|
||||
/** @internal */
|
||||
private _sizePromise: Promise<number | null> | null = null;
|
||||
@@ -113,6 +115,11 @@ export class BufferSource extends Source {
|
||||
offset: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get _supportsRandomAccess() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,6 +217,11 @@ export class BlobSource extends Source {
|
||||
|
||||
worker.running = false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get _supportsRandomAccess() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const URL_SOURCE_MIN_LOAD_AMOUNT = 0.5 * 2 ** 20; // 0.5 MiB
|
||||
@@ -488,6 +500,11 @@ export class UrlSource extends Source {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get _supportsRandomAccess() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -559,6 +576,11 @@ export class FilePathSource extends Source {
|
||||
_retrieveSize(): MaybePromise<number> {
|
||||
return this._streamSource._retrieveSize();
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get _supportsRandomAccess() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -728,6 +750,11 @@ export class StreamSource extends Source {
|
||||
|
||||
worker.running = false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get _supportsRandomAccess() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
type ReadableStreamSourcePendingSlice = {
|
||||
@@ -987,6 +1014,11 @@ export class ReadableStreamSource extends Source {
|
||||
|
||||
this._pulling = false;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
get _supportsRandomAccess() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
type PrefetchProfile = (start: number, end: number, workers: ReadWorker[]) => {
|
||||
|
||||
@@ -11,6 +11,14 @@
|
||||
* Common tags are normalized by Mediabunny into a uniform format, while the `raw` field can be used to directly read or
|
||||
* write the underlying metadata tags (which differ by format).
|
||||
*
|
||||
* - For MP4/QuickTime files, the metadata refers to the data in `'moov'`-level `'udta'` and `'meta'` atoms.
|
||||
* - For Matroska files, the metadata refers to the Tags and Attachments elements whose target is 50 (MOVIE).
|
||||
* - For MP3 files, the metadata refers to the ID3v2 or ID3v1 tags.
|
||||
* - For Ogg files, there is no global metadata so instead, the metadata refers to the combined metadata of all tracks,
|
||||
* in Vorbis-style comment headers.
|
||||
* - For WAVE files, the metadata refers to the chunks within the RIFF INFO chunk.
|
||||
* - For ADTS files, there is no metadata.
|
||||
*
|
||||
* @group Metadata tags
|
||||
* @public
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user