From 3f48930abfbc09a6620e6f5c6ea382f1da13a12b Mon Sep 17 00:00:00 2001 From: trancore Date: Wed, 20 Nov 2024 00:02:44 +0900 Subject: [PATCH 1/9] first commit From fdc8a731286838f8f25ee3eea6520da6970c23e6 Mon Sep 17 00:00:00 2001 From: trancore Date: Wed, 20 Nov 2024 00:30:12 +0900 Subject: [PATCH 2/9] feature: fix const RIFF_HEADER --- src/utils/musicMetadata.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/utils/musicMetadata.ts b/src/utils/musicMetadata.ts index e0741ad..41074f9 100644 --- a/src/utils/musicMetadata.ts +++ b/src/utils/musicMetadata.ts @@ -32,7 +32,6 @@ const HEADER_FRAME_BYTES = 10 as const; */ const ID3_HEADER_EXTENSION = { ID3: [73, 68, 51], - RIFF: [82, 73, 70, 70], } as const; /** * ID3タグフレームIDとUTF-16文字コードのペア @@ -57,6 +56,12 @@ const VORBIS_COMMENT = { LENGTH: "LENGTH", GENRE: "GENRE", } as const; +/** + * RIFFヘッダ拡張子とUTF-16文字コードのペア + */ +const RIFF_HEADER = { + RIFF: [82, 73, 70, 70], +} as const; const RIFF_LIST_TYPE_INFO_ID = { IAAT: "IAAT", // アーティスト名 @@ -903,10 +908,10 @@ function RIFFTagReader(musicData: Uint8Array) { */ function isWAVE(): boolean { return ( - musicData[0] === ID3_HEADER_EXTENSION["RIFF"][0] && - musicData[1] === ID3_HEADER_EXTENSION["RIFF"][1] && - musicData[2] === ID3_HEADER_EXTENSION["RIFF"][2] && - musicData[3] === ID3_HEADER_EXTENSION["RIFF"][3] + musicData[0] === RIFF_HEADER["RIFF"][0] && + musicData[1] === RIFF_HEADER["RIFF"][1] && + musicData[2] === RIFF_HEADER["RIFF"][2] && + musicData[3] === RIFF_HEADER["RIFF"][3] ); } From d470e70dca151a74822221981ac519d29c88c859 Mon Sep 17 00:00:00 2001 From: trancore Date: Thu, 21 Nov 2024 00:01:22 +0900 Subject: [PATCH 3/9] feature: add getMetadataMP4 and mp4BoxTagReader --- src/utils/musicMetadata.ts | 121 +++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/src/utils/musicMetadata.ts b/src/utils/musicMetadata.ts index 41074f9..424f62a 100644 --- a/src/utils/musicMetadata.ts +++ b/src/utils/musicMetadata.ts @@ -98,6 +98,17 @@ export function getMusicMetadata(musicData: Uint8Array): Metadata | undefined { return dataWAVE; } + // const { data: dataApev2, isApev2 } = getMetadataAAC(musicData); + const { isMp4Box } = getMetadataMp4(musicData); + console.log( + "🚀 ~ getMusicMetadata ~ musicData:", + new TextDecoder().decode(musicData), + ); + console.log("🚀 ~ getMusicMetadata ~ isMp4Box():", isMp4Box()); + if (isMp4Box()) { + // return dataWAVE; + } + return; } @@ -1212,3 +1223,113 @@ function getMetadataWAVE(musicData: Uint8Array) { isWAVE, }; } + +/** + * Mp4Boxの読み込み関数。 + * @param {Uint8Array} musicData 音楽バイナリデータ + */ +function mp4BoxTagReader(musicData: Uint8Array) { + const mp4BoxMetadata = { + title: "", + artist: "", + album: "", + albumArtist: "", + length: "", + genre: "", + }; + + /** + * MP4Boxのクロージャ関数 + */ + function mp4Box() { + // ChunkIDの4バイト分をスキップ + let index = 4; + + return { + getIndex: function () { + return index; + }, + setIndex: function (newIndex: number) { + index = newIndex; + }, + increment: function (byNum: number) { + index += byNum; + }, + readText: function (index: number, byteNum: 1 | 2 | 3 | 4) { + const size = getIntNumberFromBinary(musicData, index, byteNum, true); + + let text = ""; + for (let j = 0; j < size; j++) { + text += String.fromCharCode(musicData[byteNum + index + j]); + } + + return { + text, + skip: byteNum + size - 1, + }; + }, + }; + } + + /** + * MP4Boxかどうかを判定する。 + * @returns {boolean} true: APEv2である / false: APEv2ではない + */ + function isMp4Box(): boolean { + return ( + // musicData[0] === APEV2_HEADER["APEV2"][0] && + // musicData[1] === APEV2_HEADER["APEV2"][1] && + // musicData[2] === APEV2_HEADER["APEV2"][2] && + // musicData[3] === APEV2_HEADER["APEV2"][3] && + // musicData[4] === APEV2_HEADER["APEV2"][4] && + // musicData[5] === APEV2_HEADER["APEV2"][5] && + // musicData[6] === APEV2_HEADER["APEV2"][6] && + // musicData[7] === APEV2_HEADER["APEV2"][7] + ); + } + + /** + * MP4Boxを読み込む。 + * @returns {void} + */ + function readMp4Boxs(): void { + const { getIndex, increment, readText } = mp4Box(); + for (;;) { + return; + } + } + + return { isMp4Box }; +} + +/** + * MP4の音楽メタデータの取得 + * @param {Uint8Array} musicData 音楽バイナリデータ + * @returns MP4の音楽メタデータ | MP4でない場合はundefined + */ +function getMetadataMp4(musicData: Uint8Array) { + const { isMp4Box } = mp4BoxTagReader(musicData); + + // read(); + + // if (isWAVE()) { + // const musicMetadata: Metadata = { + // title: getTitle(), + // artist: getArtist(), + // album: getAlbum(), + // albumArtists: getAlbumArtist(), + // genre: getGenre(), + // // WAVEはアルバムワークが定義されていないので空文字のまま + // albumWork: "", + // }; + + // return { + // data: musicMetadata, + // isWAVE, + // }; + // } + + return { + isMp4Box, + }; +} From ce0b0e21689a31faba356bb5f0cf8e419d1fbf8f Mon Sep 17 00:00:00 2001 From: trancore Date: Fri, 22 Nov 2024 01:17:15 +0900 Subject: [PATCH 4/9] feature: get "meta" box type in readMp4Box method --- src/utils/musicMetadata.ts | 95 ++++++++++++++++++++++++++++++-------- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/src/utils/musicMetadata.ts b/src/utils/musicMetadata.ts index 424f62a..b3c7f68 100644 --- a/src/utils/musicMetadata.ts +++ b/src/utils/musicMetadata.ts @@ -79,6 +79,14 @@ const RIFF_LIST_TYPE_INFO_ID = { // 作成に使用されたソフトウェア名 ISFT: "ISFT", } as const; +/** + * MP4のBoxType + */ +const MP4_BOX_TYPE = { + FTYP: "ftyp", + MOOV: "moov", + META: "meta", +}; type ID3V2Version = keyof typeof ID3_V2_VERSION; @@ -104,7 +112,7 @@ export function getMusicMetadata(musicData: Uint8Array): Metadata | undefined { "🚀 ~ getMusicMetadata ~ musicData:", new TextDecoder().decode(musicData), ); - console.log("🚀 ~ getMusicMetadata ~ isMp4Box():", isMp4Box()); + if (isMp4Box()) { // return dataWAVE; } @@ -1242,8 +1250,7 @@ function mp4BoxTagReader(musicData: Uint8Array) { * MP4Boxのクロージャ関数 */ function mp4Box() { - // ChunkIDの4バイト分をスキップ - let index = 4; + let index = 0; return { getIndex: function () { @@ -1276,16 +1283,16 @@ function mp4BoxTagReader(musicData: Uint8Array) { * @returns {boolean} true: APEv2である / false: APEv2ではない */ function isMp4Box(): boolean { - return ( - // musicData[0] === APEV2_HEADER["APEV2"][0] && - // musicData[1] === APEV2_HEADER["APEV2"][1] && - // musicData[2] === APEV2_HEADER["APEV2"][2] && - // musicData[3] === APEV2_HEADER["APEV2"][3] && - // musicData[4] === APEV2_HEADER["APEV2"][4] && - // musicData[5] === APEV2_HEADER["APEV2"][5] && - // musicData[6] === APEV2_HEADER["APEV2"][6] && - // musicData[7] === APEV2_HEADER["APEV2"][7] + // const boxSize = getIntNumberFromBinary(musicData, 0, 4); + const ftyp = getIntNumberFromBinary(musicData, 4, 4); + const ftypText = String.fromCharCode( + (ftyp >> 24) & 0xff, + (ftyp >> 16) & 0xff, + (ftyp >> 8) & 0xff, + ftyp & 0xff, ); + + return ftypText === MP4_BOX_TYPE.FTYP; } /** @@ -1293,13 +1300,65 @@ function mp4BoxTagReader(musicData: Uint8Array) { * @returns {void} */ function readMp4Boxs(): void { - const { getIndex, increment, readText } = mp4Box(); - for (;;) { - return; + const { getIndex, setIndex, increment, readText } = mp4Box(); + + console.log("🚀 ~ readMp4Boxs ~ musicData.length:", musicData.length); + + for (let i = 0; i < musicData.length; i++) { + const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); + increment(4); + i += 4; + + const boxTypeNumber = getIntNumberFromBinary(musicData, getIndex(), 4); + increment(4); + i += 4; + + const boxTypeText = String.fromCharCode( + (boxTypeNumber >> 24) & 0xff, + (boxTypeNumber >> 16) & 0xff, + (boxTypeNumber >> 8) & 0xff, + boxTypeNumber & 0xff, + ); + + if (boxTypeText === MP4_BOX_TYPE.MOOV) { + for (let j = 0; j < boxSize - 8; j++) { + const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); + increment(4); + j += 4; + + const boxTypeNumber = getIntNumberFromBinary( + musicData, + getIndex(), + 4, + ); + increment(4); + j += 4; + + const boxTypeText = String.fromCharCode( + (boxTypeNumber >> 24) & 0xff, + (boxTypeNumber >> 16) & 0xff, + (boxTypeNumber >> 8) & 0xff, + boxTypeNumber & 0xff, + ); + + if (boxTypeText === MP4_BOX_TYPE.META) { + console.log("🚀 ~ readMp4Boxs ~ boxText:", boxTypeText); + } else { + const skip = boxSize - 8; + increment(skip); + j += skip; + } + } + return; + } else { + const skip = boxSize - 8; + increment(skip); + i += skip; + } } } - return { isMp4Box }; + return { isMp4Box, read: readMp4Boxs }; } /** @@ -1308,9 +1367,9 @@ function mp4BoxTagReader(musicData: Uint8Array) { * @returns MP4の音楽メタデータ | MP4でない場合はundefined */ function getMetadataMp4(musicData: Uint8Array) { - const { isMp4Box } = mp4BoxTagReader(musicData); + const { isMp4Box, read } = mp4BoxTagReader(musicData); - // read(); + read(); // if (isWAVE()) { // const musicMetadata: Metadata = { From de21e71d9868f7423a5e029271269a880fca6180 Mon Sep 17 00:00:00 2001 From: trancore Date: Fri, 22 Nov 2024 23:40:54 +0900 Subject: [PATCH 5/9] feature: add logic to get metadata in "meta" box type --- src/utils/musicMetadata.ts | 83 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/src/utils/musicMetadata.ts b/src/utils/musicMetadata.ts index b3c7f68..bf21993 100644 --- a/src/utils/musicMetadata.ts +++ b/src/utils/musicMetadata.ts @@ -1343,6 +1343,89 @@ function mp4BoxTagReader(musicData: Uint8Array) { if (boxTypeText === MP4_BOX_TYPE.META) { console.log("🚀 ~ readMp4Boxs ~ boxText:", boxTypeText); + console.log("🚀 ~ readMp4Boxs ~ boxSize:", boxSize); + for (let k = 0; k < boxSize; k++) { + const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); + // TODO boxSizeが0のままサイイズ数が取れていない。 + console.log("🚀 ~ readMp4Boxs ~ boxSize:", boxSize); + increment(4); + k += 4; + + const boxTypeNumber = getIntNumberFromBinary( + musicData, + getIndex(), + 4, + ); + increment(4); + k += 4; + + const boxTypeText = String.fromCharCode( + (boxTypeNumber >> 24) & 0xff, + (boxTypeNumber >> 16) & 0xff, + (boxTypeNumber >> 8) & 0xff, + boxTypeNumber & 0xff, + ); + + if (boxTypeText === "©nam") { + const boxSize = getIntNumberFromBinary( + musicData, + getIndex(), + 4, + ); + increment(4); + k += 4; + + const boxTypeNumber = getIntNumberFromBinary( + musicData, + getIndex(), + 4, + ); + increment(4); + k += 4; + + const boxTypeText = String.fromCharCode( + (boxTypeNumber >> 24) & 0xff, + (boxTypeNumber >> 16) & 0xff, + (boxTypeNumber >> 8) & 0xff, + boxTypeNumber & 0xff, + ); + + if (boxTypeText === "data") { + const boxSize = getIntNumberFromBinary( + musicData, + getIndex(), + 4, + ); + increment(4); + k += 4; + + const boxTypeNumber = getIntNumberFromBinary( + musicData, + getIndex(), + 4, + ); + increment(4); + k += 4; + + const boxTypeText = String.fromCharCode( + (boxTypeNumber >> 24) & 0xff, + (boxTypeNumber >> 16) & 0xff, + (boxTypeNumber >> 8) & 0xff, + boxTypeNumber & 0xff, + ); + // TODO 取得したいmetadataの想定 + console.log("🚀 ~ readMp4Boxs ~ boxTypeText:", boxTypeText); + } else { + const skip = boxSize - 8; + increment(skip); + k += skip; + } + } else { + const skip = boxSize - 8; + increment(skip); + k += skip; + } + } } else { const skip = boxSize - 8; increment(skip); From 739c221e5698a2ffba31283d0a2b92ffd60a1ca6 Mon Sep 17 00:00:00 2001 From: trancore Date: Sun, 24 Nov 2024 22:52:35 +0900 Subject: [PATCH 6/9] debug: add console --- src/utils/musicMetadata.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/utils/musicMetadata.ts b/src/utils/musicMetadata.ts index bf21993..e5bde6a 100644 --- a/src/utils/musicMetadata.ts +++ b/src/utils/musicMetadata.ts @@ -1302,8 +1302,6 @@ function mp4BoxTagReader(musicData: Uint8Array) { function readMp4Boxs(): void { const { getIndex, setIndex, increment, readText } = mp4Box(); - console.log("🚀 ~ readMp4Boxs ~ musicData.length:", musicData.length); - for (let i = 0; i < musicData.length; i++) { const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); increment(4); @@ -1340,16 +1338,18 @@ function mp4BoxTagReader(musicData: Uint8Array) { (boxTypeNumber >> 8) & 0xff, boxTypeNumber & 0xff, ); + console.log("🚀 ~ readMp4Boxs ~ boxTypeText:", boxTypeText); + // TODO 後ろの方のmetaを参照していることに注意 if (boxTypeText === MP4_BOX_TYPE.META) { console.log("🚀 ~ readMp4Boxs ~ boxText:", boxTypeText); console.log("🚀 ~ readMp4Boxs ~ boxSize:", boxSize); for (let k = 0; k < boxSize; k++) { - const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); - // TODO boxSizeが0のままサイイズ数が取れていない。 - console.log("🚀 ~ readMp4Boxs ~ boxSize:", boxSize); - increment(4); - k += 4; + // const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); + // // TODO boxSizeが0のままサイイズ数が取れていない。 + // console.log("🚀 ~ readMp4Boxs ~ boxSize:", boxSize); + // increment(4); + // k += 4; const boxTypeNumber = getIntNumberFromBinary( musicData, @@ -1365,6 +1365,7 @@ function mp4BoxTagReader(musicData: Uint8Array) { (boxTypeNumber >> 8) & 0xff, boxTypeNumber & 0xff, ); + console.log("🚀 ~ readMp4Boxs ~ boxTypeTextここ:", boxTypeText); if (boxTypeText === "©nam") { const boxSize = getIntNumberFromBinary( @@ -1421,9 +1422,9 @@ function mp4BoxTagReader(musicData: Uint8Array) { k += skip; } } else { - const skip = boxSize - 8; - increment(skip); - k += skip; + // const skip = boxSize - 8; + // increment(skip); + // k += skip; } } } else { From e957d026f8cec5433d1dde2f218111c4cba511de Mon Sep 17 00:00:00 2001 From: trancore Date: Tue, 25 Feb 2025 22:36:49 +0900 Subject: [PATCH 7/9] wip: add readBox method --- src/utils/musicMetadata.ts | 350 +++++++++++++++++++++++-------------- 1 file changed, 218 insertions(+), 132 deletions(-) diff --git a/src/utils/musicMetadata.ts b/src/utils/musicMetadata.ts index e5bde6a..13ffd86 100644 --- a/src/utils/musicMetadata.ts +++ b/src/utils/musicMetadata.ts @@ -85,7 +85,14 @@ const RIFF_LIST_TYPE_INFO_ID = { const MP4_BOX_TYPE = { FTYP: "ftyp", MOOV: "moov", + UDTA: "udta", + TITL: "titl", + PERF: "perf", + ALBM: "albm", + YRRC: "yrrc", META: "meta", + HDLR: "hdlr", + ILST: "ilst", }; type ID3V2Version = keyof typeof ID3_V2_VERSION; @@ -1262,17 +1269,64 @@ function mp4BoxTagReader(musicData: Uint8Array) { increment: function (byNum: number) { index += byNum; }, - readText: function (index: number, byteNum: 1 | 2 | 3 | 4) { - const size = getIntNumberFromBinary(musicData, index, byteNum, true); + readBox: function (boxIndex: number, preBoxSize: number) { + let skip = boxIndex; + + // BoxSize > 1 なら BoxSize, BoxType を含む全体の長さ。 + // BoxSize == 0 なら MP4ファイルの終端(BinaryStreamの終端)までがBoxSizeとみなされる + // BoxSize == 1 なら BoxData の先頭に8byteの BoxSize (largesize) が格納される + const boxSize = getIntNumberFromBinary(musicData, index, 4); + index += 4; + skip += 4; + + const boxTypeNumber = getIntNumberFromBinary(musicData, index, 4); + index += 4; + skip += 4; + + const boxTypeText = String.fromCharCode( + (boxTypeNumber >> 24) & 0xff, + (boxTypeNumber >> 16) & 0xff, + (boxTypeNumber >> 8) & 0xff, + boxTypeNumber & 0xff, + ); + + const boxDataSize = boxSize === 0 ? preBoxSize - 8 : boxSize - 8; + const boxData = musicData.slice(index, index + boxDataSize); + + if (boxSize === 0) { + skip += boxData.length; + } + // TODO boxSizeOne === 1の時の処理 + return { + boxSize, + boxType: boxTypeText, + boxData, + skip, + }; + }, + readText: function (length: number) { let text = ""; - for (let j = 0; j < size; j++) { - text += String.fromCharCode(musicData[byteNum + index + j]); + for (let n = length; n > 0; n--) { + text += String.fromCharCode(musicData[index]); + index += 1; } + return text; + }, + readMeta: function (boxIndex: number) { + let skip = boxIndex; + + const metaSize = getIntNumberFromBinary(musicData, index, 4); + index += 4; + skip += 4; + + const metaDataSize = metaSize - 4; + const metaData = musicData.slice(index, index + metaDataSize); return { - text, - skip: byteNum + size - 1, + metaSize, + metaData, + skip, }; }, }; @@ -1299,150 +1353,182 @@ function mp4BoxTagReader(musicData: Uint8Array) { * MP4Boxを読み込む。 * @returns {void} */ - function readMp4Boxs(): void { - const { getIndex, setIndex, increment, readText } = mp4Box(); + function readMp4Boxes(): void { + const { getIndex, setIndex, increment, readBox, readText, readMeta } = + mp4Box(); + // Data One for (let i = 0; i < musicData.length; i++) { - const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); - increment(4); - i += 4; - - const boxTypeNumber = getIntNumberFromBinary(musicData, getIndex(), 4); - increment(4); - i += 4; - - const boxTypeText = String.fromCharCode( - (boxTypeNumber >> 24) & 0xff, - (boxTypeNumber >> 16) & 0xff, - (boxTypeNumber >> 8) & 0xff, - boxTypeNumber & 0xff, - ); - - if (boxTypeText === MP4_BOX_TYPE.MOOV) { - for (let j = 0; j < boxSize - 8; j++) { - const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); - increment(4); - j += 4; - - const boxTypeNumber = getIntNumberFromBinary( - musicData, - getIndex(), - 4, - ); - increment(4); - j += 4; - - const boxTypeText = String.fromCharCode( - (boxTypeNumber >> 24) & 0xff, - (boxTypeNumber >> 16) & 0xff, - (boxTypeNumber >> 8) & 0xff, - boxTypeNumber & 0xff, - ); - console.log("🚀 ~ readMp4Boxs ~ boxTypeText:", boxTypeText); - - // TODO 後ろの方のmetaを参照していることに注意 - if (boxTypeText === MP4_BOX_TYPE.META) { - console.log("🚀 ~ readMp4Boxs ~ boxText:", boxTypeText); - console.log("🚀 ~ readMp4Boxs ~ boxSize:", boxSize); - for (let k = 0; k < boxSize; k++) { - // const boxSize = getIntNumberFromBinary(musicData, getIndex(), 4); - // // TODO boxSizeが0のままサイイズ数が取れていない。 - // console.log("🚀 ~ readMp4Boxs ~ boxSize:", boxSize); - // increment(4); - // k += 4; - - const boxTypeNumber = getIntNumberFromBinary( - musicData, - getIndex(), - 4, - ); - increment(4); - k += 4; - - const boxTypeText = String.fromCharCode( - (boxTypeNumber >> 24) & 0xff, - (boxTypeNumber >> 16) & 0xff, - (boxTypeNumber >> 8) & 0xff, - boxTypeNumber & 0xff, - ); - console.log("🚀 ~ readMp4Boxs ~ boxTypeTextここ:", boxTypeText); - - if (boxTypeText === "©nam") { - const boxSize = getIntNumberFromBinary( - musicData, - getIndex(), - 4, + const { + boxType: boxTypeOne, + boxData: boxDataOne, + skip: skipOne, + } = readBox(i, musicData.length); + + if (boxTypeOne === MP4_BOX_TYPE.MOOV) { + // Data Two + for (let j = 0; j < boxDataOne.length; j++) { + const { + boxSize: boxSizeTwo, + boxType: boxTypeTwo, + boxData: boxDataTwo, + skip: skipTwo, + } = readBox(j, boxDataOne.length); + + if (boxTypeTwo === MP4_BOX_TYPE.UDTA) { + // Data Three + for (let k = 0; k < boxDataTwo.length; k++) { + const { + boxSize: boxSizeThree, + boxType: boxTypeThree, + boxData: boxDataThree, + skip: skipThree, + } = readBox(k, boxDataTwo.length); + + if (boxTypeThree === MP4_BOX_TYPE.TITL) { + // Data Four + for (let l = 0; l < boxDataThree.length; l++) { + const { + boxSize: boxSizeFour, + boxType: boxTypeFour, + boxData: boxDataFour, + skip: skipFour, + } = readBox(l, boxDataThree.length); + + const text = readText(boxDataFour.length); + console.log("🚀 ~ readMp4Boxes ~ TITL:", text); + + l += skipFour; + } + } else if (boxTypeThree === MP4_BOX_TYPE.ALBM) { + // Data Four + for (let l = 0; l < boxDataThree.length; l++) { + const { + boxSize: boxSizeFour, + boxType: boxTypeFour, + boxData: boxDataFour, + skip: skipFour, + } = readBox(l, boxDataThree.length); + + const text = readText(boxDataFour.length); + console.log("🚀 ~ readMp4Boxes ~ ALBM:", text); + + l += skipFour; + } + } else if (boxTypeThree === MP4_BOX_TYPE.META) { + /** + * @see https://developer.apple.com/documentation/quicktime-file-format/metadata_atom + */ + + // DEBUG: あとで削除する + console.log( + "🚀 ~ boxDataFour ~ boxDataThree:", + new TextDecoder().decode(boxDataThree), ); - increment(4); - k += 4; + console.log("🚀 ~ readMp4Boxes ~ boxDataThree:", boxDataThree); - const boxTypeNumber = getIntNumberFromBinary( - musicData, - getIndex(), - 4, - ); + // meta size前の4バイト分をスキップ increment(4); - k += 4; - - const boxTypeText = String.fromCharCode( - (boxTypeNumber >> 24) & 0xff, - (boxTypeNumber >> 16) & 0xff, - (boxTypeNumber >> 8) & 0xff, - boxTypeNumber & 0xff, - ); - if (boxTypeText === "data") { - const boxSize = getIntNumberFromBinary( - musicData, - getIndex(), - 4, - ); - increment(4); - k += 4; - - const boxTypeNumber = getIntNumberFromBinary( - musicData, - getIndex(), - 4, - ); - increment(4); - k += 4; - - const boxTypeText = String.fromCharCode( - (boxTypeNumber >> 24) & 0xff, - (boxTypeNumber >> 16) & 0xff, - (boxTypeNumber >> 8) & 0xff, - boxTypeNumber & 0xff, - ); - // TODO 取得したいmetadataの想定 - console.log("🚀 ~ readMp4Boxs ~ boxTypeText:", boxTypeText); - } else { - const skip = boxSize - 8; - increment(skip); - k += skip; + // Data Four + for (let l = 0; l < boxDataThree.length - 4; l++) { + const { metaSize, metaData, skip } = readMeta(l); + const metaType = readText(4); + const metaSizeOpt = metaData.length - 4; + console.log("🚀 ~ readMp4Boxes ~ metaType:", metaType); + + /** + * @see https://developer.apple.com/documentation/quicktime-file-format/metadata_handler_atom + */ + if (metaType === MP4_BOX_TYPE.HDLR) { + const text = readText(metaSizeOpt); + console.log("🚀 ~ readMp4Boxes ~ HDLR:", text); + } + if (metaType === MP4_BOX_TYPE.ILST) { + const text = readText(metaSizeOpt); + console.log("🚀 ~ readMp4Boxes ~ ILST:", text); + } + if (metaType === MP4_BOX_TYPE.META) { + const text = readText(metaSizeOpt); + console.log("🚀 ~ readMp4Boxes ~ META:", text); + // ここからID3v2タグ + for (let m = 0; m < metaSizeOpt; m++) { + // isID3FrameIDとreadID3FrameSizeが参考になる + } + } + // const { + // boxSize: boxSizeFour, + // boxType: boxTypeFour, + // boxData: boxDataFour, + // skip: skipFour, + // } = readBox(l, boxDataThree.length); + + // // DEBUG: あとで削除する + // console.log( + // "🚀 ~ boxDataFour ~ boxDataFour:", + // new TextDecoder().decode(boxDataFour), + // ); + // const metaType = readText(boxDataFour.length); + // console.log("🚀 ~ readMp4Boxes ~ metaType:", metaType); + // const boxTypeNumber = getIntNumberFromBinary( + // musicData, + // getIndex(), + // 4, + // ); + // const boxTypeText = String.fromCharCode( + // (boxTypeNumber >> 24) & 0xff, + // (boxTypeNumber >> 16) & 0xff, + // (boxTypeNumber >> 8) & 0xff, + // boxTypeNumber & 0xff, + // ); + // increment(4); + // l += 4; + // if (boxTypeText === MP4_BOX_TYPE.HDLR) { + // /** + // * @see https://developer.apple.com/documentation/quicktime-file-format/metadata_handler_atom + // */ + // // Data Five + // for (let m = 0; m < boxDataThree.length; m++) { + // const { + // boxSize: boxSizeFive, + // boxType: boxTypeFive, + // boxData: boxDataFive, + // skip: skipFive, + // } = readBox(l, boxDataThree.length); + // const text = readText(boxDataFour.length); + // console.log("🚀 ~ readMp4Boxes ~ META:", text); + // /** + // * @see https://developer.apple.com/documentation/quicktime-file-format/metadata_handler_atom + // */ + // m += skipFour; + // } + // } + l += skip; } } else { - // const skip = boxSize - 8; - // increment(skip); - // k += skip; + increment(boxDataThree.length); + k += boxDataThree.length; } + + k += skipThree; } } else { - const skip = boxSize - 8; - increment(skip); - j += skip; + increment(boxDataTwo.length); + j += boxDataTwo.length; } + + j += skipTwo; } - return; } else { - const skip = boxSize - 8; - increment(skip); - i += skip; + increment(boxDataOne.length); + i += boxDataOne.length; } + + i += skipOne; } } - return { isMp4Box, read: readMp4Boxs }; + return { isMp4Box, read: readMp4Boxes }; } /** From 1f14f5d5347987ba67f49f0ae01a49c56ffdaf59 Mon Sep 17 00:00:00 2001 From: trancore Date: Mon, 12 May 2025 22:05:17 +0900 Subject: [PATCH 8/9] feature: push the implementation so far --- src/utils/music.ts | 4 +- src/utils/musicMetadata.ts | 94 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/utils/music.ts b/src/utils/music.ts index 3103507..624ac60 100644 --- a/src/utils/music.ts +++ b/src/utils/music.ts @@ -33,10 +33,10 @@ export function getAudioUint8Array(file: File): Promise { * @returns {Uint8Array} UTF-8コード文字配列の画像データ */ export function getImageInUint8Array( - musicData: Uint8Array, + musicData: Uint8Array, beginIndex: number, size: number, -): Uint8Array { +): Uint8Array { return musicData.subarray(beginIndex, beginIndex + size); } diff --git a/src/utils/musicMetadata.ts b/src/utils/musicMetadata.ts index 13ffd86..79b91bb 100644 --- a/src/utils/musicMetadata.ts +++ b/src/utils/musicMetadata.ts @@ -1329,6 +1329,63 @@ function mp4BoxTagReader(musicData: Uint8Array) { skip, }; }, + isID3FrameID: function ( + IDName: keyof typeof ID3_FRAME_ID, + metaData: Uint8Array, + index: number, + ): boolean { + return ( + metaData[index] === ID3_FRAME_ID[IDName][0] && + metaData[index + 1] === ID3_FRAME_ID[IDName][1] && + metaData[index + 2] === ID3_FRAME_ID[IDName][2] && + metaData[index + 3] === ID3_FRAME_ID[IDName][3] + ); + }, + readID3Text: function ( + index: number, + size: number, + ): { text: string; skip: number } { + const encodeIndex = index + HEADER_FRAME_BYTES; + const code = musicData[encodeIndex]; + + let text = ""; + if (code === HEXADECIMAL["0x00"]) { + // ISO-8859-1(Latin-1) + text = getStringLatin1(musicData, encodeIndex + 1, size - 1); + } else if (code === HEXADECIMAL["0x01"]) { + // UTF-16 with BOM + text = getStringUTF16(musicData, encodeIndex + 1, size - 3); + } else if (code === HEXADECIMAL["0x02"]) { + // UTF-16BE without BOM + text = getStringUTF16(musicData, encodeIndex + 1, size - 1); + } else if (code === HEXADECIMAL["0x03"]) { + // UTF-8 (v2.4) + text = getStringUTF8(musicData, encodeIndex + 1, size - 1); + } + + return { + text: text, + skip: HEADER_FRAME_BYTES + size, + }; + }, + /** + * フレームサイズを読み込む。 + * 4バイト分のデータを結合し、32ビットの整数値を生成する。 + * @param {number} index UTF-8文字コード配列のインデックス。 + * @returns {number} フレームサイズ。 + */ + readID3FrameSize: function (index: number): number { + return ( + // 24ビット左にシフト + (musicData[index + 4] << 24) | + // 16ビット左にシフト + (musicData[index + 5] << 16) | + // 8ビット左にシフト + (musicData[index + 6] << 8) | + // シフト操作無し + musicData[index + 7] + ); + }, }; } @@ -1354,8 +1411,17 @@ function mp4BoxTagReader(musicData: Uint8Array) { * @returns {void} */ function readMp4Boxes(): void { - const { getIndex, setIndex, increment, readBox, readText, readMeta } = - mp4Box(); + const { + getIndex, + setIndex, + increment, + readBox, + readText, + readMeta, + isID3FrameID, + readID3Text, + readID3FrameSize, + } = mp4Box(); // Data One for (let i = 0; i < musicData.length; i++) { @@ -1449,11 +1515,29 @@ function mp4BoxTagReader(musicData: Uint8Array) { console.log("🚀 ~ readMp4Boxes ~ ILST:", text); } if (metaType === MP4_BOX_TYPE.META) { - const text = readText(metaSizeOpt); - console.log("🚀 ~ readMp4Boxes ~ META:", text); + // ここでhrlr分をスキップする + // meta size前の4バイト分をスキップ + increment(4); + // ここからID3v2タグ - for (let m = 0; m < metaSizeOpt; m++) { + for (let m = 0; m < metaSizeOpt - 4; m++) { + const { metaSize, metaData, skip } = readMeta(m); + const metaType = readText(4); + const metaSizeOpt = metaData.length - 4; + console.log( + "🚀 ~ now ~ readMp4Boxes ~ metaType:", + metaType, + ); + + if (metaType === MP4_BOX_TYPE.HDLR) { + const text = readText(metaSizeOpt); + console.log("🚀 ~ readMp4Boxes ~ HDLR:", text); + } // isID3FrameIDとreadID3FrameSizeが参考になる + if (isID3FrameID("TPE1", metaData, m)) { + // + } + m += skip; } } // const { From 7cfe162fae02369f958347558b68376fda956d55 Mon Sep 17 00:00:00 2001 From: trancore Date: Mon, 12 May 2025 22:07:48 +0900 Subject: [PATCH 9/9] fix: define Uint8Array --- src/utils/musicMetadata.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/utils/musicMetadata.ts b/src/utils/musicMetadata.ts index 79b91bb..73e1f75 100644 --- a/src/utils/musicMetadata.ts +++ b/src/utils/musicMetadata.ts @@ -97,7 +97,9 @@ const MP4_BOX_TYPE = { type ID3V2Version = keyof typeof ID3_V2_VERSION; -export function getMusicMetadata(musicData: Uint8Array): Metadata | undefined { +export function getMusicMetadata( + musicData: Uint8Array, +): Metadata | undefined { const { data: dataMp3, isID3v2 } = getMetadataMp3(musicData); if (isID3v2()) { return dataMp3; @@ -133,7 +135,7 @@ export function getMusicMetadata(musicData: Uint8Array): Metadata | undefined { * @see https://atmarkit.itmedia.co.jp/icd/root/24/92677424.html * @param musicData UTF-8文字コード配列の音楽データ */ -function ID3v2TagReader(musicData: Uint8Array) { +function ID3v2TagReader(musicData: Uint8Array) { const ID3Frames = { tit2: "", tpe1: "", @@ -440,7 +442,7 @@ function ID3v2TagReader(musicData: Uint8Array) { * MP3の音楽メタデータの取得 * @param {Uint8Array} musicData 音楽バイナリデータ` */ -function getMetadataMp3(musicData: Uint8Array): { +function getMetadataMp3(musicData: Uint8Array): { data?: Metadata; isID3v2: () => boolean; } { @@ -487,7 +489,7 @@ function getMetadataMp3(musicData: Uint8Array): { * VorbisCommentの読み込み関数。 * @param musicData 音楽情報 UTF-8文字コード配列の音楽データ */ -function vorbisCommentTagReader(musicData: Uint8Array) { +function vorbisCommentTagReader(musicData: Uint8Array) { const vorbisCommentMetadataBlocks = { title: "", artist: "", @@ -814,7 +816,7 @@ function vorbisCommentTagReader(musicData: Uint8Array) { * @param {Uint8Array} musicData 音楽バイナリデータ * @returns FLACの音楽メタデータ | FLACでない場合はundefined */ -function getMetadataFLAC(musicData: Uint8Array): { +function getMetadataFLAC(musicData: Uint8Array): { data?: Metadata; isFLAC: () => boolean; } {