From 24b7023961bfe5d203e28390a351c768743da0ae Mon Sep 17 00:00:00 2001 From: zhaohappy <2281756061@qq.com> Date: Thu, 6 Feb 2025 19:11:18 +0800 Subject: [PATCH] =?UTF-8?q?feat(avformat):=20AVStream=20=E7=9A=84=20metada?= =?UTF-8?q?ta=20key=20=E4=BD=BF=E7=94=A8=E6=9E=9A=E4=B8=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/avformat/formats/IFlacFormat.ts | 8 +- src/avformat/formats/IMatroskaFormat.ts | 5 +- src/avformat/formats/IOggFormat.ts | 14 +- src/avformat/formats/IWebVttFormat.ts | 5 +- src/avformat/formats/OMatroskaFormat.ts | 15 +- src/avformat/formats/OMp3Format.ts | 25 +-- src/avformat/formats/OOggFormat.ts | 19 ++- .../formats/mov/function/buildIndex.ts | 7 +- src/avformat/formats/mov/parsing/elst.ts | 3 +- src/avformat/formats/mov/parsing/hdlr.ts | 3 +- src/avformat/formats/mov/parsing/mdhd.ts | 9 +- src/avformat/formats/mov/parsing/stsd.ts | 7 +- src/avformat/formats/mov/parsing/tkhd.ts | 11 +- src/avformat/formats/mov/writing/hdlr.ts | 5 +- src/avformat/formats/mov/writing/mdhd.ts | 7 +- src/avformat/formats/mov/writing/tkhd.ts | 5 +- src/avformat/formats/mp3/id3v2.ts | 6 +- src/avformat/formats/mp3/type.ts | 2 +- src/avformat/formats/ogg/vorbis.ts | 58 +++++++ src/avplayer/AVPlayer.ts | 10 +- src/avtranscoder/AVTranscoder.ts | 4 +- src/avutil/stringEnum.ts | 151 ++++++++++++++++++ 22 files changed, 307 insertions(+), 72 deletions(-) diff --git a/src/avformat/formats/IFlacFormat.ts b/src/avformat/formats/IFlacFormat.ts index 59ffb307..805b7949 100644 --- a/src/avformat/formats/IFlacFormat.ts +++ b/src/avformat/formats/IFlacFormat.ts @@ -44,6 +44,8 @@ import { AV_MILLI_TIME_BASE_Q, NOPTS_VALUE_BIGINT } from 'avutil/constant' import seekInBytes from '../function/seekInBytes' import * as array from 'common/util/array' import { avRescaleQ } from 'avutil/util/rational' +import { AVStreamMetadataKey } from 'avutil/stringEnum' +import { parseVorbisComment } from './ogg/vorbis' const PACKET_SIZE = 1024 @@ -188,13 +190,13 @@ export default class IFlacFormat extends IFormat { const vendorStringLength = await formatContext.ioReader.readUint32() const vendorString = await formatContext.ioReader.readString(vendorStringLength) const userCommentListLength = await formatContext.ioReader.readUint32() - const comments = [] + const comments: string[] = [] for (let i = 0; i < userCommentListLength; i++) { const length = await formatContext.ioReader.readUint32() comments.push(await formatContext.ioReader.readString(length)) } - stream.metadata['vendor'] = vendorString - stream.metadata['comments'] = comments + stream.metadata[AVStreamMetadataKey.VENDOR] = vendorString + parseVorbisComment(comments, stream.metadata) formatContext.ioReader.setEndian(true) } else if (blockType === MetaDataBlockType.CUESHEET) { diff --git a/src/avformat/formats/IMatroskaFormat.ts b/src/avformat/formats/IMatroskaFormat.ts index 18b9db10..98a4cb73 100644 --- a/src/avformat/formats/IMatroskaFormat.ts +++ b/src/avformat/formats/IMatroskaFormat.ts @@ -68,6 +68,7 @@ import concatTypeArray from 'common/function/concatTypeArray' import * as text from 'common/util/text' import isDef from 'common/function/isDef' import * as naluUtil from 'avutil/util/nalu' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default class IMatroskaFormat extends IFormat { @@ -148,10 +149,10 @@ export default class IMatroskaFormat extends IFormat { } if (track.language) { - stream.metadata['language'] = track.language + stream.metadata[AVStreamMetadataKey.LANGUAGE] = track.language } if (track.name) { - stream.metadata['name'] = track.name + stream.metadata[AVStreamMetadataKey.TITLE] = track.name } if (track.audio) { diff --git a/src/avformat/formats/IOggFormat.ts b/src/avformat/formats/IOggFormat.ts index 70d08135..ee6bf81b 100644 --- a/src/avformat/formats/IOggFormat.ts +++ b/src/avformat/formats/IOggFormat.ts @@ -30,7 +30,7 @@ import { OggPage, OggsCommentPage, PagePayload } from './ogg/OggPage' import { AVCodecID, AVMediaType } from 'avutil/codec' import * as logger from 'common/util/logger' import { OpusOggsIdPage, OpusOggsCommentPage } from './ogg/opus' -import { VorbisOggsIdPage, VorbisOggsCommentPage } from './ogg/vorbis' +import { VorbisOggsIdPage, VorbisOggsCommentPage, parseVorbisComment } from './ogg/vorbis' import * as errorType from 'avutil/error' import concatTypeArray from 'common/function/concatTypeArray' import IFormat from './IFormat' @@ -47,6 +47,7 @@ import { avRescaleQ } from 'avutil/util/rational' import * as array from 'common/util/array' import SafeUint8Array from 'cheap/std/buffer/SafeUint8Array' import * as bigint from 'common/util/bigint' +import { AVStreamMetadataKey } from 'avutil/stringEnum' interface IOggFormatPrivateData { serialNumber: number @@ -166,16 +167,9 @@ export default class IOggFormat extends IFormat { private addComment(comments: OggsCommentPage, stream: AVStream) { if (comments.vendorString) { - stream.metadata['vendor'] = comments.vendorString + stream.metadata[AVStreamMetadataKey.VENDOR] = comments.vendorString } - array.each(comments.comments.list, (comment) => { - const item = comment.split('=') - if (item.length > 1) { - const key = item.shift() - const value = item.join('=') - stream.metadata[key] = value - } - }) + parseVorbisComment(comments.comments.list, stream.metadata) } private async createStream(formatContext: AVIFormatContext, payload: Uint8Array) { diff --git a/src/avformat/formats/IWebVttFormat.ts b/src/avformat/formats/IWebVttFormat.ts index b08a4896..b31ba734 100644 --- a/src/avformat/formats/IWebVttFormat.ts +++ b/src/avformat/formats/IWebVttFormat.ts @@ -39,6 +39,7 @@ import * as array from 'common/util/array' import * as text from 'common/util/text' import { hhColonDDColonSSDotMill2Int64 } from 'common/util/time' import { NOPTS_VALUE_BIGINT } from 'avutil/constant' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default class IWebVttFormat extends IFormat { @@ -98,7 +99,7 @@ export default class IWebVttFormat extends IFormat { const header = await formatContext.ioReader.readLine() if (header.indexOf('-') > 0) { - stream.metadata['title'] = header.split('-').pop().trim() + stream.metadata[AVStreamMetadataKey.TITLE] = header.split('-').pop().trim() } this.index = 0 @@ -189,7 +190,7 @@ export default class IWebVttFormat extends IFormat { } catch (error) { - stream.metadata['styles'] = styles + stream.metadata[AVStreamMetadataKey.STYLES] = styles return 0 } diff --git a/src/avformat/formats/OMatroskaFormat.ts b/src/avformat/formats/OMatroskaFormat.ts index 21ea294e..03422e9e 100644 --- a/src/avformat/formats/OMatroskaFormat.ts +++ b/src/avformat/formats/OMatroskaFormat.ts @@ -51,6 +51,7 @@ import * as hevc from 'avutil/codecs/hevc' import * as vvc from 'avutil/codecs/vvc' import * as intread from 'avutil/util/intread' import { Uint8ArrayInterface } from 'common/io/interface' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export interface OMatroskaFormatOptions { isLive?: boolean @@ -214,14 +215,14 @@ export default class OMatroskaFormat extends OFormat { crypto.random(this.random) context.attachments.entry.push({ uid: this.randomView.getBigUint64(0), - name: stream.metadata['name'] || 'unknown', - mime: stream.metadata['mime'] || 'unknown', + name: stream.metadata[AVStreamMetadataKey.TITLE] || 'unknown', + mime: stream.metadata[AVStreamMetadataKey.MIME] || 'unknown', data: { data: mapUint8Array(stream.codecpar.extradata, reinterpret_cast(stream.codecpar.extradataSize)), size: static_cast(stream.codecpar.extradataSize), pos: -1n }, - description: stream.metadata['description'] || 'unknown' + description: stream.metadata[AVStreamMetadataKey.DESCRIPTION] || 'unknown' }) } else { @@ -237,11 +238,11 @@ export default class OMatroskaFormat extends OFormat { size: static_cast(stream.codecpar.extradataSize) } } - if (stream.metadata['language']) { - track.language = stream.metadata['language'] + if (stream.metadata[AVStreamMetadataKey.LANGUAGE]) { + track.language = stream.metadata[AVStreamMetadataKey.LANGUAGE] } - if (stream.metadata['name']) { - track.name = stream.metadata['name'] + if (stream.metadata[AVStreamMetadataKey.TITLE]) { + track.name = stream.metadata[AVStreamMetadataKey.TITLE] } switch (stream.codecpar.codecType) { diff --git a/src/avformat/formats/OMp3Format.ts b/src/avformat/formats/OMp3Format.ts index 928ab88f..f65607ba 100644 --- a/src/avformat/formats/OMp3Format.ts +++ b/src/avformat/formats/OMp3Format.ts @@ -42,6 +42,7 @@ import * as id3v2 from './mp3/id3v2' import { mapUint8Array } from 'cheap/std/memory' import * as text from 'common/util/text' import * as object from 'common/util/object' +import { AVStreamMetadataKey } from 'avutil/stringEnum' const XING_NUM_BAGS = 400 @@ -247,10 +248,10 @@ export default class OMp3Format extends OFormat { // we write it, because some (broken) tools always expect it to be present this.xingWriter.writeUint32(0) - const metadata = stream.metadata as Mp3MetaData + const metadata = stream.metadata as Mp3MetaData || {} - if (metadata?.encoder) { - const buffer = text.encode(metadata.encoder) + if (metadata[AVStreamMetadataKey.ENCODER]) { + const buffer = text.encode(metadata[AVStreamMetadataKey.ENCODER]) this.xingWriter.writeBuffer(buffer.subarray(0, 9)) } else { @@ -412,7 +413,7 @@ export default class OMp3Format extends OFormat { return stream.codecpar.codecId === AVCodecID.AV_CODEC_ID_MP3 }) - const metadata = stream.metadata as Mp3MetaData + const metadata = stream.metadata as Mp3MetaData || {} const id1Buffer = new Uint8Array(ID3V1_SIZE) const id1Writer = new BufferWriter(id1Buffer) @@ -426,28 +427,28 @@ export default class OMp3Format extends OFormat { } } - if (metadata.title) { - writeText(metadata.title) + if (metadata[AVStreamMetadataKey.TITLE]) { + writeText(metadata[AVStreamMetadataKey.TITLE]) } else { id1Writer.skip(30) } - if (metadata.artist) { - writeText(metadata.artist) + if (metadata[AVStreamMetadataKey.ARTIST]) { + writeText(metadata[AVStreamMetadataKey.ARTIST]) } else { id1Writer.skip(30) } - if (metadata.album) { - writeText(metadata.album) + if (metadata[AVStreamMetadataKey.ALBUM]) { + writeText(metadata[AVStreamMetadataKey.ALBUM]) } else { id1Writer.skip(30) } id1Buffer[127] = 0xff - if (metadata.genre) { - id1Buffer[127] = +metadata.genre + if (metadata[AVStreamMetadataKey.GENRE]) { + id1Buffer[127] = +metadata[AVStreamMetadataKey.GENRE] } formatContext.ioWriter.writeBuffer(id1Buffer) diff --git a/src/avformat/formats/OOggFormat.ts b/src/avformat/formats/OOggFormat.ts index 0075ef17..d2254f7b 100644 --- a/src/avformat/formats/OOggFormat.ts +++ b/src/avformat/formats/OOggFormat.ts @@ -35,7 +35,7 @@ import * as logger from 'common/util/logger' import { getAVPacketData } from 'avutil/util/avpacket' import { NOPTS_VALUE_BIGINT } from 'avutil/constant' import { OpusOggsCommentPage, OpusOggsIdPage } from './ogg/opus' -import { VorbisOggsCommentPage, VorbisOggsIdPage } from './ogg/vorbis' +import { addVorbisComment, VorbisOggsCommentPage, VorbisOggsIdPage } from './ogg/vorbis' import { mapUint8Array } from 'cheap/std/memory' import IOReaderSync from 'common/io/IOReaderSync' import * as errorType from 'avutil/error' @@ -178,6 +178,10 @@ export default class OOggFormat extends OFormat { const idPage = new OpusOggsIdPage() const commentPage = new OpusOggsCommentPage() idPage.streamIndex = stream.index + const list = addVorbisComment(stream.metadata) + list.forEach((value) => { + commentPage.addComment(value) + }) idPage.setCodec(stream.codecpar) commentPage.streamIndex = stream.index this.headerPagesPayload = [ @@ -197,6 +201,11 @@ export default class OOggFormat extends OFormat { idPage.streamIndex = stream.index commentPage.streamIndex = stream.index + const list = addVorbisComment(stream.metadata) + list.forEach((value) => { + commentPage.addComment(value) + }) + this.cacheWriter.reset() idPage.write(this.cacheWriter) this.writePage(stream, formatContext.ioWriter, this.cacheWriter.getBuffer().slice(), 2) @@ -264,6 +273,10 @@ export default class OOggFormat extends OFormat { const commentPage = new OggsCommentPage() commentPage.streamIndex = stream.index + const list = addVorbisComment(stream.metadata) + list.forEach((value) => { + commentPage.addComment(value) + }) this.cacheWriter.setEndian(true) this.cacheWriter.reset() @@ -290,6 +303,10 @@ export default class OOggFormat extends OFormat { const commentPage = new OggsCommentPage() commentPage.streamIndex = stream.index + const list = addVorbisComment(stream.metadata) + list.forEach((value) => { + commentPage.addComment(value) + }) this.cacheWriter.reset() commentPage.write(this.cacheWriter) this.writePage(stream, formatContext.ioWriter, this.cacheWriter.getBuffer().slice(), 0) diff --git a/src/avformat/formats/mov/function/buildIndex.ts b/src/avformat/formats/mov/function/buildIndex.ts index 1b9a05bc..a538171e 100644 --- a/src/avformat/formats/mov/function/buildIndex.ts +++ b/src/avformat/formats/mov/function/buildIndex.ts @@ -30,6 +30,7 @@ import { AVMediaType } from 'avutil/codec' import * as logger from 'common/util/logger' import { avRescaleQ } from 'avutil/util/rational' import { AV_MILLI_TIME_BASE_Q, NOPTS_VALUE_BIGINT } from 'avutil/constant' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export function buildIndex(stream: Stream, movContext: MOVContext) { @@ -68,14 +69,14 @@ export function buildIndex(stream: Stream, movContext: MOVContext) { const samplesIndex: Sample[] = [] - if (!movContext.ignoreEditlist && stream.metadata.elst?.length) { + if (!movContext.ignoreEditlist && stream.metadata[AVStreamMetadataKey.ELST]?.length) { let timeOffset = 0n let editStartIndex = 0 let unsupported = false let emptyDuration = 0n let startTime = 0n - for (let i = 0; i < stream.metadata.elst.length; i++) { - const e = stream.metadata.elst[i] + for (let i = 0; i < stream.metadata[AVStreamMetadataKey.ELST].length; i++) { + const e = stream.metadata[AVStreamMetadataKey.ELST][i] if (i === 0 && e.mediaTime === -1n) { emptyDuration = e.segmentDuration editStartIndex = 1 diff --git a/src/avformat/formats/mov/parsing/elst.ts b/src/avformat/formats/mov/parsing/elst.ts index 25ce61cc..6f0cd1b9 100644 --- a/src/avformat/formats/mov/parsing/elst.ts +++ b/src/avformat/formats/mov/parsing/elst.ts @@ -27,6 +27,7 @@ import IOReader from 'common/io/IOReader' import Stream from 'avutil/AVStream' import { Atom, ElstEntry, MOVContext } from '../type' import * as logger from 'common/util/logger' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default async function read(ioReader: IOReader, stream: Stream, atom: Atom, movContext: MOVContext) { @@ -64,7 +65,7 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato }) } - stream.metadata.elst = entries + stream.metadata[AVStreamMetadataKey.ELST] = entries const remainingLength = atom.size - Number(ioReader.getPos() - now) if (remainingLength > 0) { diff --git a/src/avformat/formats/mov/parsing/hdlr.ts b/src/avformat/formats/mov/parsing/hdlr.ts index 6ec198bc..dfc48325 100644 --- a/src/avformat/formats/mov/parsing/hdlr.ts +++ b/src/avformat/formats/mov/parsing/hdlr.ts @@ -29,6 +29,7 @@ import { Atom, MOVContext } from '../type' import * as logger from 'common/util/logger' import isDef from 'common/function/isDef' import { HandlerType2MediaType } from '../mov' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default async function read(ioReader: IOReader, stream: Stream, atom: Atom, movContext: MOVContext) { const now = ioReader.getPos() @@ -63,7 +64,7 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato if (skip) { await ioReader.skip(1) } - stream.metadata['handlerName'] = await ioReader.readString(len - (skip ? 1 : 0)) + stream.metadata[AVStreamMetadataKey.HANDLER_NAME] = await ioReader.readString(len - (skip ? 1 : 0)) } } diff --git a/src/avformat/formats/mov/parsing/mdhd.ts b/src/avformat/formats/mov/parsing/mdhd.ts index 10629bcb..7af9cabd 100644 --- a/src/avformat/formats/mov/parsing/mdhd.ts +++ b/src/avformat/formats/mov/parsing/mdhd.ts @@ -27,6 +27,7 @@ import IOReader from 'common/io/IOReader' import Stream from 'avutil/AVStream' import { Atom, MOVContext } from '../type' import * as logger from 'common/util/logger' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default async function read(ioReader: IOReader, stream: Stream, atom: Atom, movContext: MOVContext) { const now = ioReader.getPos() @@ -56,8 +57,8 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato stream.duration = duration stream.timeBase.den = timescale stream.timeBase.num = 1 - stream.metadata['creationTime'] = creationTime - stream.metadata['modificationTime'] = modificationTime + stream.metadata[AVStreamMetadataKey.CREATION_TIME] = creationTime + stream.metadata[AVStreamMetadataKey.MODIFICATION_TIME] = modificationTime const language = await ioReader.readUint16() @@ -68,8 +69,8 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato const languageString = String.fromCharCode(chars[0] + 0x60, chars[1] + 0x60, chars[2] + 0x60) - stream.metadata['language'] = language - stream.metadata['languageString'] = languageString + stream.metadata[AVStreamMetadataKey.LANGUAGE] = language + stream.metadata[AVStreamMetadataKey.LANGUAGE_STRING] = languageString await ioReader.skip(2) diff --git a/src/avformat/formats/mov/parsing/stsd.ts b/src/avformat/formats/mov/parsing/stsd.ts index 67113578..7f3a9de1 100644 --- a/src/avformat/formats/mov/parsing/stsd.ts +++ b/src/avformat/formats/mov/parsing/stsd.ts @@ -45,6 +45,7 @@ import colr from './colr' import ac3 from './dac3' import eac3 from './dec3' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default async function read(ioReader: IOReader, stream: Stream, atom: Atom, movContext: MOVContext) { const now = ioReader.getPos() @@ -90,7 +91,7 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato // revision level await ioReader.skip(2) // vendor - stream.metadata['vendorId'] = await ioReader.readString(4) + stream.metadata[AVStreamMetadataKey.VENDOR_ID] = await ioReader.readString(4) // temporal quality await ioReader.skip(4) // spatial quality @@ -114,7 +115,7 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato if (len > 31) { len = 31 } - stream.metadata['encoder'] = await ioReader.readString(len) + stream.metadata[AVStreamMetadataKey.ENCODER] = await ioReader.readString(len) if (len < 31) { await ioReader.skip(31 - len) } @@ -235,7 +236,7 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato // Revision level await ioReader.skip(2) - stream.metadata['vendorId'] = await ioReader.readString(4) + stream.metadata[AVStreamMetadataKey.VENDOR_ID] = await ioReader.readString(4) stream.codecpar.chLayout.nbChannels = await ioReader.readUint16() stream.codecpar.bitsPerCodedSample = await ioReader.readUint16() diff --git a/src/avformat/formats/mov/parsing/tkhd.ts b/src/avformat/formats/mov/parsing/tkhd.ts index 6dad57fa..be1531be 100644 --- a/src/avformat/formats/mov/parsing/tkhd.ts +++ b/src/avformat/formats/mov/parsing/tkhd.ts @@ -28,6 +28,7 @@ import Stream, { AVDisposition } from 'avutil/AVStream' import { Atom, MOVContext, MOVStreamContext } from '../type' import * as logger from 'common/util/logger' import { TKHDFlags } from '../boxType' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default async function read(ioReader: IOReader, stream: Stream, atom: Atom, movContext: MOVContext) { const streamContext = stream.privData as MOVStreamContext @@ -43,15 +44,15 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato } if (version === 1) { - stream.metadata['creationTime'] = await ioReader.readUint64() - stream.metadata['modificationTime'] = await ioReader.readUint64() + stream.metadata[AVStreamMetadataKey.CREATION_TIME] = await ioReader.readUint64() + stream.metadata[AVStreamMetadataKey.MODIFICATION_TIME] = await ioReader.readUint64() streamContext.trackId = await ioReader.readUint32() await ioReader.skip(4) streamContext.duration = await ioReader.readUint64() } else { - stream.metadata['creationTime'] = static_cast(await ioReader.readUint32()) - stream.metadata['modificationTime'] = static_cast(await ioReader.readUint32()) + stream.metadata[AVStreamMetadataKey.CREATION_TIME] = static_cast(await ioReader.readUint32()) + stream.metadata[AVStreamMetadataKey.MODIFICATION_TIME] = static_cast(await ioReader.readUint32()) streamContext.trackId = await ioReader.readUint32() await ioReader.skip(4) streamContext.duration = static_cast(await ioReader.readUint32()) @@ -79,7 +80,7 @@ export default async function read(ioReader: IOReader, stream: Stream, atom: Ato streamContext.width = (await ioReader.readUint32()) >> 16 streamContext.height = (await ioReader.readUint32()) >> 16 - stream.metadata['matrix'] = matrix + stream.metadata[AVStreamMetadataKey.MATRIX] = matrix const remainingLength = atom.size - Number(ioReader.getPos() - now) if (remainingLength > 0) { diff --git a/src/avformat/formats/mov/writing/hdlr.ts b/src/avformat/formats/mov/writing/hdlr.ts index 127d4e40..bfaf8ffe 100644 --- a/src/avformat/formats/mov/writing/hdlr.ts +++ b/src/avformat/formats/mov/writing/hdlr.ts @@ -28,6 +28,7 @@ import { MOVContext } from '../type' import IOWriter from 'common/io/IOWriterSync' import { BoxType } from '../boxType' import { AVMediaType } from 'avutil/codec' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default function write(ioWriter: IOWriter, stream: Stream, movContext: MOVContext) { @@ -61,8 +62,8 @@ export default function write(ioWriter: IOWriter, stream: Stream, movContext: MO descr = 'SubtitleHandler' } else { - if (stream.metadata['handlerName']) { - descr = stream.metadata['handlerName'] + if (stream.metadata[AVStreamMetadataKey.HANDLER_NAME]) { + descr = stream.metadata[AVStreamMetadataKey.HANDLER_NAME] } if (stream.metadata['hdlrType']) { hdlrType = stream.metadata['hdlrType'] diff --git a/src/avformat/formats/mov/writing/mdhd.ts b/src/avformat/formats/mov/writing/mdhd.ts index 08e8f93f..b7e03655 100644 --- a/src/avformat/formats/mov/writing/mdhd.ts +++ b/src/avformat/formats/mov/writing/mdhd.ts @@ -29,6 +29,7 @@ import IOWriter from 'common/io/IOWriterSync' import { BoxType } from '../boxType' import { UINT32_MAX } from 'avutil/constant' import getSampleDuration from '../function/getSampleDuration' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default function write(ioWriter: IOWriter, stream: Stream, movContext: MOVContext) { @@ -36,9 +37,9 @@ export default function write(ioWriter: IOWriter, stream: Stream, movContext: MO const duration = getSampleDuration(streamContext) - const creationTime = stream.metadata['creationTime'] || 0 - const modificationTime = stream.metadata['modificationTime'] || 0 - const languge = stream.metadata['language'] || 21956 + const creationTime = stream.metadata[AVStreamMetadataKey.CREATION_TIME] || 0 + const modificationTime = stream.metadata[AVStreamMetadataKey.MODIFICATION_TIME] || 0 + const languge = stream.metadata[AVStreamMetadataKey.LANGUAGE] || 21956 let version = duration < static_cast(UINT32_MAX) ? 0 : 1 version = creationTime < UINT32_MAX ? 0 : 1 diff --git a/src/avformat/formats/mov/writing/tkhd.ts b/src/avformat/formats/mov/writing/tkhd.ts index edfaf7fa..ac72eecd 100644 --- a/src/avformat/formats/mov/writing/tkhd.ts +++ b/src/avformat/formats/mov/writing/tkhd.ts @@ -32,6 +32,7 @@ import writeMatrix from './function/writeMatrix' import { AVMediaType } from 'avutil/codec' import { avRescaleQ } from 'avutil/util/rational' import getSampleDuration from '../function/getSampleDuration' +import { AVStreamMetadataKey } from 'avutil/stringEnum' export default function write(ioWriter: IOWriter, stream: Stream, movContext: MOVContext) { @@ -45,8 +46,8 @@ export default function write(ioWriter: IOWriter, stream: Stream, movContext: MO num: 1 } ) - const creationTime = stream.metadata['creationTime'] || 0 - const modificationTime = stream.metadata['modificationTime'] || 0 + const creationTime = stream.metadata[AVStreamMetadataKey.CREATION_TIME] || 0 + const modificationTime = stream.metadata[AVStreamMetadataKey.MODIFICATION_TIME] || 0 const layer = streamContext.layer || 0 const alternateGroup = streamContext.alternateGroup || 0 let width = stream.codecpar.width > 0 ? stream.codecpar.width : 0 diff --git a/src/avformat/formats/mp3/id3v2.ts b/src/avformat/formats/mp3/id3v2.ts index 90bd2a80..d90a4812 100644 --- a/src/avformat/formats/mp3/id3v2.ts +++ b/src/avformat/formats/mp3/id3v2.ts @@ -192,7 +192,7 @@ export async function parse(ioReader: IOReader, len: int32, id3v2: ID3V2, metada metadata.composer = content break case 'TENC': - metadata.encodedBy = content + metadata.vendor = content break case 'TLAN': metadata.language = content @@ -335,8 +335,8 @@ export function write(ioWriter: IOWriterSync, version: number, padding: int32, m writeText('TCOM', metadata.composer) } - if (metadata.encodedBy) { - writeText('TENC', metadata.encodedBy) + if (metadata.vendor) { + writeText('TENC', metadata.vendor) } if (metadata.language) { diff --git a/src/avformat/formats/mp3/type.ts b/src/avformat/formats/mp3/type.ts index 2411e9c6..35fe34ef 100644 --- a/src/avformat/formats/mp3/type.ts +++ b/src/avformat/formats/mp3/type.ts @@ -42,7 +42,7 @@ export interface Mp3MetaData { language?: string performer?: string publisher?: string - encodedBy?: string + vendor?: string composer?: string compilation?: string creationTime?: string diff --git a/src/avformat/formats/ogg/vorbis.ts b/src/avformat/formats/ogg/vorbis.ts index f6bb6222..c44d64ef 100644 --- a/src/avformat/formats/ogg/vorbis.ts +++ b/src/avformat/formats/ogg/vorbis.ts @@ -33,6 +33,64 @@ import IOWriter from 'common/io/IOWriterSync' import AVCodecParameters from 'avutil/struct/avcodecparameters' import { OggsCommentPage, PagePayload } from './OggPage' import IOReaderSync from 'common/io/IOReaderSync' +import { Data } from 'common/types/type' +import { AVStreamMetadataKey } from 'avutil/stringEnum' +import * as object from 'common/util/object' +import isDef from 'common/function/isDef' + +const CommentKeyMap = { + 'album': AVStreamMetadataKey.ALBUM, + 'artist': AVStreamMetadataKey.ARTIST, + 'description': AVStreamMetadataKey.DESCRIPTION, + 'encoder': AVStreamMetadataKey.ENCODER, + 'title': AVStreamMetadataKey.TITLE, + 'tracknumber': AVStreamMetadataKey.TRACK, + 'date': AVStreamMetadataKey.DATE, + 'genre': AVStreamMetadataKey.GENRE, + 'comment': AVStreamMetadataKey.COMMENT, + 'albumartist': AVStreamMetadataKey.ALBUM_ARTIST, + 'composer': AVStreamMetadataKey.COMPOSER, + 'performer': AVStreamMetadataKey.PERFORMER, + 'discnumber': AVStreamMetadataKey.DISC, + 'organization': AVStreamMetadataKey.VENDOR, + 'copyright': AVStreamMetadataKey.COPYRIGHT, + 'license': AVStreamMetadataKey.LICENSE, + 'isrc': AVStreamMetadataKey.ISRC, + 'lyrics': AVStreamMetadataKey.LYRICS, + 'language': AVStreamMetadataKey.LANGUAGE, + 'label': AVStreamMetadataKey.VENDOR, + 'script': AVStreamMetadataKey.LYRICS, + 'encoded_by': AVStreamMetadataKey.VENDOR +} + +export function parseVorbisComment(list: string[], metadata: Data) { + if (!list) { + return + } + list.forEach((value) => { + const l = value.split('=') + if (l.length === 2) { + const k = l[0].trim().toLowerCase() + const v = l[1].trim() + if (CommentKeyMap[k]) { + metadata[CommentKeyMap[k]] = v + } + else { + metadata[k.toLowerCase()] = v + } + } + }) +} + +export function addVorbisComment(metadata: Data) { + const list: string[] = [] + object.each(CommentKeyMap, (value, key) => { + if (isDef(metadata[value])) { + list.push(`${key.toUpperCase()}=${metadata[value]}`) + } + }) + return list +} export class VorbisOggsIdPage implements PagePayload { diff --git a/src/avplayer/AVPlayer.ts b/src/avplayer/AVPlayer.ts index 2e038175..39c031a7 100644 --- a/src/avplayer/AVPlayer.ts +++ b/src/avplayer/AVPlayer.ts @@ -71,7 +71,7 @@ import * as bigint from 'common/util/bigint' import getMediaSource from './function/getMediaSource' import JitterBufferController from './JitterBufferController' import getAudioCodec from 'avutil/function/getAudioCodec' -import { Ext2Format, mediaType2AVMediaType } from 'avutil/stringEnum' +import { AVStreamMetadataKey, Ext2Format, mediaType2AVMediaType } from 'avutil/stringEnum' import { AVDisposition, AVStreamInterface } from 'avutil/AVStream' import { AVFormatContextInterface } from 'avformat/AVFormatContext' import dump, { dumpCodecName, dumpKey } from 'avformat/dump' @@ -1059,10 +1059,10 @@ export default class AVPlayer extends Emitter implements ControllerObserver { externalSubtitleTask.streamId = stream.id if (externalSubtitle.lang) { - stream.metadata['language'] = externalSubtitle.lang + stream.metadata[AVStreamMetadataKey.LANGUAGE] = externalSubtitle.lang } if (externalSubtitle.title) { - stream.metadata['title'] = externalSubtitle.title + stream.metadata[AVStreamMetadataKey.TITLE] = externalSubtitle.title } const handleStatus = this.status === AVPlayerStatus.PAUSED @@ -1766,8 +1766,8 @@ export default class AVPlayer extends Emitter implements ControllerObserver { : this.canvas // 处理旋转 - if (videoStream.metadata['matrix']) { - this.renderRotate = -(Math.atan2(videoStream.metadata['matrix'][3], videoStream.metadata['matrix'][0]) * (180 / Math.PI)) + if (videoStream.metadata[AVStreamMetadataKey.MATRIX]) { + this.renderRotate = -(Math.atan2(videoStream.metadata[AVStreamMetadataKey.MATRIX][3], videoStream.metadata[AVStreamMetadataKey.MATRIX][0]) * (180 / Math.PI)) } // 注册一个视频渲染任务 diff --git a/src/avtranscoder/AVTranscoder.ts b/src/avtranscoder/AVTranscoder.ts index 715b6567..2ed9c211 100644 --- a/src/avtranscoder/AVTranscoder.ts +++ b/src/avtranscoder/AVTranscoder.ts @@ -49,7 +49,7 @@ import getVideoCodec from 'avutil/function/getVideoCodec' import * as mutex from 'cheap/thread/mutex' import AudioEncodePipeline from 'avpipeline/AudioEncodePipeline' import VideoEncodePipeline from 'avpipeline/VideoEncodePipeline' -import { AudioCodecString2CodecId, Ext2Format, Ext2IOLoader, +import { AudioCodecString2CodecId, AVStreamMetadataKey, Ext2Format, Ext2IOLoader, Format2AVFormat, PixfmtString2AVPixelFormat, SampleFmtString2SampleFormat, VideoCodecString2CodecId } from 'avutil/stringEnum' @@ -621,7 +621,7 @@ export default class AVTranscoder extends Emitter implements ControllerObserver newStream.duration = avRescaleQ(static_cast(task.options.duration), AV_MILLI_TIME_BASE_Q, newStream.timeBase) } - newStream.metadata['encoder'] = `libmedia-${defined(VERSION)}` + newStream.metadata[AVStreamMetadataKey.ENCODER] = `libmedia-${defined(VERSION)}` return newStream } diff --git a/src/avutil/stringEnum.ts b/src/avutil/stringEnum.ts index c7fec2b2..74df8642 100644 --- a/src/avutil/stringEnum.ts +++ b/src/avutil/stringEnum.ts @@ -4,6 +4,157 @@ import { AVColorPrimaries, AVColorRange, AVColorSpace, AVColorTransferCharacteri import { AVSampleFormat } from './audiosamplefmt' import { AVDisposition } from './AVStream' +export const enum AVStreamMetadataKey { + /** + * 表演者(歌手、乐队等) + */ + ARTIST = 'artist', + /** + * 自由文本评论 + */ + COMMENT = 'comment', + /** + * 版权声明 + */ + COPYRIGHT = 'copyright', + /** + * 发行年份(通常为 YYYY 格式) + */ + DATE = 'date', + /** + * 音乐流派 + */ + GENRE = 'genre', + /** + * 语言 + */ + LANGUAGE = 'language', + /** + * 语言描述 + */ + LANGUAGE_STRING = 'languageString', + /** + * 歌曲或作品的标题 + */ + TITLE = 'title', + /** + * 专辑名称 + */ + ALBUM = 'album', + /** + * 曲目编号 + */ + TRACK = 'track', + /** + * 用于编码音频文件的软件信息 + */ + ENCODER = 'encoder', + /** + * 时间参数 + */ + TIME_CODE = 'timecode', + /** + * 发行商 + */ + VENDOR = 'vendor', + /** + * 发行商标识 + */ + VENDOR_ID = 'vendorId', + /** + * 海报 + */ + POSTER = 'poster', + /** + * 歌词 + */ + LYRICS = 'lyrics', + /** + * 专辑的主要艺术家(与 ARTIST 区分开,适用于合集专辑) + */ + ALBUM_ARTIST = 'albumArtist', + /** + * 如果是多张 CD 的专辑,标识当前曲目所在的 CD + */ + DISC = 'disc', + /** + * 具体的演奏者或表演者 + */ + PERFORMER = 'performer', + /** + * 发行者 + */ + PUBLISHER = 'publisher', + /** + * 作曲者 + */ + COMPOSER = 'composer', + /** + * 编曲者 + */ + COMPILATION = 'compilation', + /** + * 创建时间 + */ + CREATION_TIME = 'creationTime', + /** + * 最后更改时间 + */ + MODIFICATION_TIME = 'modificationTime', + /** + * 专辑排序 + */ + ALBUM_SORT = 'albumSort', + /** + * 表演者排序 + */ + ARTIST_SORT = 'artistSort', + /** + * 标题排序 + */ + TITLE_SORT = 'titleSort', + /** + * 分组 + */ + GROUPING = 'grouping', + /** + * 额外的描述信息 + */ + DESCRIPTION = 'description', + /** + * 许可信息 + */ + LICENSE = 'license', + /** + * 国际标准录音代码 + */ + ISRC = 'isrc', + /** + * 情绪标签,如 Happy、Sad + */ + MOOD = 'mood', + /** + * mp4 的 elst box 内容 + */ + ELST = 'elst', + /** + * mp4 的旋转矩阵 + */ + MATRIX = 'matrix', + /** + * 某些媒体的样式(如 webvtt) + */ + STYLES = 'styles', + /** + * 媒体的 mime + */ + MIME = 'mime', + /** + * mp4 的 handlerName + */ + HANDLER_NAME = 'handlerName' +} + export const CodecId2MimeType = { [AVCodecID.AV_CODEC_ID_MP3]: 'mp3', [AVCodecID.AV_CODEC_ID_AAC]: 'mp4a.40',