@@ -9,6 +9,8 @@ import type { IWarningCollector } from '../common/MetadataCollector.js';
99import type { IComment , ILyricsTag } from '../type.js' ;
1010import { makeUnexpectedFileContentError } from '../ParseError.js' ;
1111import { decodeUintBE } from '../common/Util.js' ;
12+ import { ChapterInfo , type IChapterInfo } from './ID3v2ChapterToken.js' ;
13+ import { getFrameHeaderLength , readFrameHeader } from './FrameHeader.js' ;
1214
1315const debug = initDebug ( 'music-metadata:id3v2:frame-parser' ) ;
1416
@@ -50,6 +52,24 @@ export interface IGeneralEncapsulatedObject {
5052 data : Uint8Array ;
5153}
5254
55+ export type Chapter = {
56+ label : string ;
57+ info : IChapterInfo ;
58+ frames : Map < string , unknown > ,
59+ }
60+
61+ export type TableOfContents = {
62+ label : string ;
63+ flags : {
64+ /** If set, this is the top-level table of contents */
65+ topLevel : boolean ;
66+ /** If set, the child element IDs are in a defined order */
67+ ordered : boolean ;
68+ } ;
69+ childElementIds : string [ ] ;
70+ frames : Map < string , unknown > ;
71+ }
72+
5373const defaultEnc = 'latin1' ; // latin1 == iso-8859-1;
5474const urlEnc : ITextEncoding = { encoding : defaultEnc , bom : false } ;
5575
@@ -156,6 +176,9 @@ export class FrameParser {
156176 case 'TRK' :
157177 case 'TRCK' :
158178 case 'TPOS' :
179+ case 'TIT1' :
180+ case 'TIT2' :
181+ case 'TIT3' :
159182 output = text ;
160183 break ;
161184 case 'TCOM' :
@@ -394,6 +417,79 @@ export class FrameParser {
394417 break ;
395418 }
396419
420+ // ID3v2 Chapters 1.0
421+ // https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2-chapters-1.0.html#chapter-frame
422+ case 'CHAP' : { // // Chapter frame
423+ debug ( "Reading CHAP" ) ;
424+ fzero = util . findZero ( uint8Array , defaultEnc ) ;
425+
426+ const chapter : Chapter = {
427+ label : util . decodeString ( uint8Array . subarray ( 0 , fzero ) , defaultEnc ) ,
428+ info : ChapterInfo . get ( uint8Array , offset ) ,
429+ frames : new Map ( )
430+ } ;
431+ offset += fzero + 1 + ChapterInfo . len ;
432+
433+ while ( offset < length ) {
434+ const subFrame = readFrameHeader ( uint8Array . subarray ( offset ) , this . major , this . warningCollector ) ;
435+ const headerSize = getFrameHeaderLength ( this . major ) ;
436+ offset += headerSize ;
437+ const subOutput = this . readData ( uint8Array . subarray ( offset , offset + subFrame . length ) , subFrame . id , includeCovers ) ;
438+ offset += subFrame . length ;
439+
440+ chapter . frames . set ( subFrame . id , subOutput ) ;
441+ }
442+ output = chapter ;
443+ break ;
444+ }
445+
446+ // ID3v2 Chapters 1.0
447+ // https://mutagen-specs.readthedocs.io/en/latest/id3/id3v2-chapters-1.0.html#table-of-contents-frame
448+ case 'CTOC' : { // Table of contents frame
449+ debug ( 'Reading CTOC' ) ;
450+
451+ // Element ID (null-terminated latin1)
452+ const idEnd = util . findZero ( uint8Array , defaultEnc ) ;
453+ const label = util . decodeString ( uint8Array . subarray ( 0 , idEnd ) , defaultEnc ) ;
454+ offset = idEnd + 1 ;
455+
456+ // Flags
457+ const flags = uint8Array [ offset ++ ] ;
458+ const topLevel = ( flags & 0x02 ) !== 0 ;
459+ const ordered = ( flags & 0x01 ) !== 0 ;
460+
461+ // Child element IDs
462+ const entryCount = uint8Array [ offset ++ ] ;
463+ const childElementIds : string [ ] = [ ] ;
464+ for ( let i = 0 ; i < entryCount && offset < length ; i ++ ) {
465+ const end = util . findZero ( uint8Array . subarray ( offset ) , defaultEnc ) ;
466+ const childId = util . decodeString ( uint8Array . subarray ( offset , offset + end ) , defaultEnc ) ;
467+ childElementIds . push ( childId ) ;
468+ offset += end + 1 ;
469+ }
470+
471+ const toc : TableOfContents = {
472+ label,
473+ flags : { topLevel, ordered } ,
474+ childElementIds,
475+ frames : new Map ( )
476+ } ;
477+
478+ // Optional embedded sub-frames (e.g. TIT2) follow after the child list
479+ while ( offset < length ) {
480+ const subFrame = readFrameHeader ( uint8Array . subarray ( offset ) , this . major , this . warningCollector ) ;
481+ const headerSize = getFrameHeaderLength ( this . major ) ;
482+ offset += headerSize ;
483+ const subOutput = this . readData ( uint8Array . subarray ( offset , offset + subFrame . length ) , subFrame . id , includeCovers ) ;
484+ offset += subFrame . length ;
485+
486+ toc . frames . set ( subFrame . id , subOutput ) ;
487+ }
488+
489+ output = toc ;
490+ break ;
491+ }
492+
397493 default :
398494 debug ( `Warning: unsupported id3v2-tag-type: ${ type } ` ) ;
399495 break ;
0 commit comments