import FARME_TYPES , { FrameTypeValueMap } from '../constants/frameTypes' ; import GENRES from '../constants/genres' ; import IMAGE_TYPES from '../constants/imageTypes' ; import { IBytes , IID3V2Tag , ITXXXMap , IV2VersionInfo } from '../interface' ; import { getEndpointOfBytes , readBytesToISO8859 , readBytesToString , readBytesToUTF8 , skipPaddingZeros } from '../utils' ; const V2_MIN_LENGTH = 20 ; export default function parseV2Data ( bytes ) { if ( ! bytes || bytes . length < V2_MIN_LENGTH ) { return false ; } const tags = parseV2Header ( bytes . slice ( 0 , 10 ) ) ; if ( ! tags ) { return false ; } const flags = tags . version . flags ; if ( flags . unsync ) { throw new Error ( 'no support for unsynchronisation' ) ; } let headerSize = 10 ; if ( flags . xheader ) { headerSize += calcTagSize ( bytes . slice ( 10 , 14 ) ) ; } const tagSize = calcTagSize ( bytes . slice ( 6 , 10 ) ) ; parseV2Frames ( bytes . slice ( headerSize , tagSize + headerSize ) , tags ) ; return tags ; } description param bytes function parseV2Header ( bytes ) { if ( ! bytes || bytes . length < 10 ) { return false ; } const identity = readBytesToUTF8 ( bytes , 3 ) ; if ( identity !== 'ID3' ) { return false ; } const flagByte = bytes [ 5 ] ; const version = { major : 2 , minor : bytes [ 3 ] , revision : bytes [ 4 ] , flags : { unsync : ( flagByte & 0x80 ) !== 0 , xheader : ( flagByte & 0x40 ) !== 0 , experimental : ( flagByte & 0x20 ) !== 0 , } , } ; return { version } ; } param bytes export function calcTagSize ( bytes ) { return ( bytes [ 0 ] & 0x7f ) * 0x200000 + ( bytes [ 1 ] & 0x7f ) * 0x4000 + ( bytes [ 2 ] & 0x7f ) * 0x80 + ( bytes [ 3 ] & 0x7f ) ; } param bytes export function calcFrameSize ( bytes ) { return bytes . length < 4 ? 0 : bytes [ 0 ] * 0x1000000 + bytes [ 1 ] * 0x10000 + bytes [ 2 ] * 0x100 + bytes [ 3 ] ; } function parseV2Frames ( bytes , tags ) { let position = 0 ; const version = tags . version ; while ( position < bytes . length ) { const size = calcFrameSize ( bytes . slice ( position + 4 ) ) ; if ( size === 0 ) { break ; } const slice = bytes . slice ( position , position + 10 + size ) ; if ( ! slice . length ) { break ; } const frame = parseFrame ( slice , version . minor , size ) ; if ( frame . tag ) { if ( FrameTypeValueMap [ frame . id as string ] === 'array' ) { if ( tags [ frame . tag ] ) { tags [ frame . tag ] . push ( frame . value ) ; } else { tags [ frame . tag ] = [ frame . value ] ; } } else { tags [ frame . tag ] = frame . value ; } } position += slice . length ; } } description param bytes param minor param size function parseFrame ( bytes , minor , size ) { const result = { id : null as string | null , tag : null as string | null , value : null as string | object | null , } ; const header = { id : readBytesToUTF8 ( bytes , 4 ) , type : null as string | null , size , flags : [ bytes [ 8 ] , bytes [ 9 ] , ] , } ; header . type = header . id [ 0 ] ; result . id = header . id ; if ( minor === 4 ) { } if ( header . flags [ 1 ] !== 0 ) { return result ; } if ( ! ( header . id in FARME_TYPES ) ) { return result ; } result . tag = FARME_TYPES [ header . id ] ; let encoding = 0 ; let variableStart = 0 ; let variableLength = 0 ; let i = 0 ; /**
     * Text information frames, structure is:
     * <Header for 'Text information frame', ID: "T000" - "TZZZ", excluding "TXXX">
     * Text encoding    $xx
     * Information    <text string according to encoding>
     */ if ( header . type === 'T' ) { encoding = bytes [ 10 ] ; if ( header . id === 'TXXX' ) { variableStart = 11 ; variableLength = getEndpointOfBytes ( bytes , encoding , variableStart ) - variableStart ; const value = { description : readBytesToString ( bytes . slice ( variableStart ) , encoding , variableLength ) , value : '' , } as ITXXXMap ; variableStart += variableLength + 1 ; variableStart = skipPaddingZeros ( bytes , variableStart ) ; value . value = readBytesToString ( bytes . slice ( variableStart ) , encoding ) ; result . value = value ; } else { result . value = readBytesToString ( bytes . slice ( 11 ) , encoding ) ; if ( header . id === 'TCON' && result . value !== null ) { if ( result . value [ 0 ] === '(' ) { const handledTCON = result . value . match ( /\(\d+\)/g ) ; if ( handledTCON ) { result . value = handledTCON . map ( ( v ) => GENRES [ + v . slice ( 1 , - 1 ) ] ) . join ( ',' ) ; } } else { const genre = parseInt ( result . value , 10 ) ; if ( ! isNaN ( genre ) ) { result . value = GENRES [ genre ] ; } } } } } else if ( header . type === 'W' ) { if ( header . id === 'WXXX' && bytes [ 10 ] === 0 ) { result . value = readBytesToISO8859 ( bytes . slice ( 11 ) ) ; } else { result . value = readBytesToISO8859 ( bytes . slice ( 10 ) ) ; } } else if ( header . id === 'COMM' || header . id === 'USLT' ) { encoding = bytes [ 10 ] ; variableStart = 14 ; variableLength = 0 ; const language = readBytesToISO8859 ( bytes . slice ( 11 ) , 3 ) ; variableLength = getEndpointOfBytes ( bytes , encoding , variableStart ) - variableStart ; const description = readBytesToString ( bytes . slice ( variableStart ) , encoding , variableLength ) ; variableStart = skipPaddingZeros ( bytes , variableStart + variableLength + 1 ) ; result . value = { language , description , value : readBytesToString ( bytes . slice ( variableStart ) , encoding ) , } ; } else if ( header . id === 'APIC' ) { encoding = bytes [ 10 ] ; const image = { type : null as string | null , mime : null as string | null , description : null as string | null , data : null as ArrayLike < number > | null , } ; variableStart = 11 ; variableLength = getEndpointOfBytes ( bytes , 0 , variableStart ) - variableStart ; image . mime = readBytesToString ( bytes . slice ( variableStart ) , 0 , variableLength ) ; image . type = IMAGE_TYPES [ bytes [ variableStart + variableLength + 1 ] ] || 'other' ; variableStart += variableLength + 2 ; variableLength = 0 ; for ( i = variableStart ; ; i ++ ) { if ( bytes [ i ] === 0 ) { variableLength = i - variableStart ; break ; } } image . description = variableLength === 0 ? null : readBytesToString ( bytes . slice ( variableStart ) , encoding , variableLength ) ; variableStart = skipPaddingZeros ( bytes , variableStart + variableLength + 1 ) ; image . data = bytes . slice ( variableStart ) ; result . value = image ; } else if ( header . id === 'IPLS' ) { encoding = bytes [ 10 ] ; result . value = readBytesToString ( bytes . slice ( 11 ) , encoding ) ; } else if ( header . id === 'OWNE' ) { encoding = bytes [ 10 ] ; variableStart = 11 ; variableLength = getEndpointOfBytes ( bytes , encoding , variableStart ) ; const pricePayed = readBytesToISO8859 ( bytes . slice ( variableStart ) , variableLength ) ; variableStart += variableLength + 1 ; const dateOfPurch = readBytesToISO8859 ( bytes . slice ( variableStart ) , 8 ) ; variableStart += 8 ; result . value = { pricePayed , dateOfPurch , seller : readBytesToString ( bytes . slice ( variableStart ) , encoding ) , } ; } else { } return result ; }