import * as imagemin from 'imagemin' ; import imageminPngquant from 'imagemin-pngquant' ; import * as assert from 'assert' ; import tinify from 'tinify' ; interface IOptions { quality : [ number , number ] ; tinypngKey ? : string ; } export default class LottieCompress { public lottieJson : any ; public options : IOptions ; constructor ( lottieJson , options ? ) { let _lottieJson ; if ( typeof lottieJson === 'string' ) { try { _lottieJson = JSON . parse ( lottieJson ) ; } catch ( error ) { } } else if ( typeof lottieJson === 'object' ) { _lottieJson = JSON . parse ( JSON . stringify ( lottieJson ) ) ; } assert ( typeof _lottieJson === 'object' , 'Parameters of illegal' ) ; this . lottieJson = _lottieJson ; if ( options && options . quality && ! Array . isArray ( options . quality ) ) { const [ min = 75 , max = 90 ] = options . quality . split ( '-' ) ; options . quality = [ parseInt ( min ) / 100 , parseInt ( max ) / 100 ] ; } this . options = { quality : [ 0.75 , 0.9 ] , ... options , } ; } /**
     * execute
     */ public async execute ( ) { await this . miniBase64 ( ) ; return this . getMiniAttr ( ) ; } param lottieFile param options public async miniBase64 ( ) { const assetsPromise = this . lottieJson . assets . map ( async ( asset ) => { const quality = this . options . quality ; if ( ! asset . u && ! asset . p || quality && quality [ 0 ] === 1 && quality [ 1 ] === 1 ) { return asset ; } let imageData = null ; let extname = 'png' ; const imagestring = asset . p ; if ( imagestring . includes ( 'base64,' ) ) { const base64str = imagestring . slice ( imagestring . indexOf ( 'base64,' ) + 7 ) ; extname = imagestring . slice ( imagestring . indexOf ( 'data:image/' ) + 11 , imagestring . indexOf ( ';base64' ) ) ; imageData = Buffer . from ( base64str , 'base64' ) ; } else if ( imagestring . startsWith ( 'http' ) ) { return asset ; } else { return asset ; } let newBuf ; if ( this . options . tinypngKey ) { tinify . key = this . options . tinypngKey ; const source = tinify . fromBuffer ( imageData ) ; newBuf = await source . toBuffer ( ) ; } else { newBuf = await imagemin . buffer ( imageData , { plugins : [ imageminPngquant ( { quality } ) , ] , } ) ; } return { ... asset , e : 1 , u : '' , p : 'data:image/' + extname + ';base64,' + newBuf . toString ( 'base64' ) , } ; } ) ; const assets = await Promise . all ( assetsPromise ) ; this . lottieJson . assets = assets ; return this . lottieJson ; } param { * } data return { * } public getMiniAttr ( ) { let miniFile = JSON . parse ( JSON . stringify ( this . lottieJson ) ) ; miniFile . tiny = this . options . quality [ 0 ] ; miniFile . fr = Math . round ( miniFile . fr ) ; miniFile . op = Math . round ( miniFile . op ) ; miniFile = this . attrEqualZip ( miniFile , 'id:refId' ) ; miniFile = this . attrMiniNumber ( miniFile , [ 't' , 'sr' , 'to' , 'ti' , 'color' , 'ip' , 'op' , 'st' , 's' , 'e' ] ) ; miniFile = this . deleteAttr ( miniFile , [ 'n' , 'mn' ] ) ; miniFile = this . combNumber ( miniFile ) ; miniFile = this . fixAttrIndIsUndefined ( miniFile ) ; return miniFile ; } param { string } stream param { string } fileds return { string } public async tinybase64 ( stream , fileds ) { const bufferString = await this . getStream ( stream ) ; const _bufferString = bufferString . toString ( ) ; const base64str = _bufferString . slice ( _bufferString . indexOf ( 'base64,' ) + 7 ) ; const imageData = new Buffer ( base64str , 'base64' ) ; const newBuf = await imagemin . buffer ( imageData , { plugins : [ imageminPngquant ( { quality : this . options . quality } ) , ] , } ) ; return newBuf ; } public async getJsonStream ( stream ) { const data = await this . getStream ( stream ) ; try { return JSON . parse ( data . toString ( ) ) ; } catch ( e ) { return { success : false , error : '文件解析错误' , } ; } } public async getStream ( stream ) { return await new Promise ( ( resolve ) => { let data = '' ; const buffers = [ ] ; let nread = 0 ; stream . on ( 'data' , chuck => { buffers . push ( chuck ) ; nread += chuck . length ; } ) . on ( 'end' , ( ) => { let buffer ; switch ( buffers . length ) { case 0 : buffer = new Buffer ( 0 ) ; break ; case 1 : buffer = buffers [ 0 ] ; break ; default : buffer = new Buffer ( nread ) ; for ( let i = 0 , pos = 0 ; i < buffers . length ; i ++ ) { const chunk = buffers [ i ] ; chunk . copy ( buffer , pos ) ; pos += chunk . length ; } break ; } data += buffer . toString ( ) ; resolve ( data . toString ( ) ) ; } ) ; } ) ; } param { number } number return { string } public _string10to62 ( number ) { const chars = '0123456789abcdefghijklmnopqrstuvwxyz' . split ( '' ) ; const radix = chars . length ; const arr = [ ] ; let qutient = + number ; do { const mod = qutient % radix ; qutient = ( qutient - mod ) / radix ; arr . unshift ( chars [ mod ] ) ; } while ( qutient ) ; return arr . join ( '' ) ; } param { object } obj return { Array } public _loops ( obj ) { const list = [ ] ; for ( const i in obj ) { if ( i ) { list . push ( i ) ; } } return list ; } param { object } obj param { Array } attrNames return { object } public _loopsAttr ( obj , attrNames ) { const req = { } ; const innerLoops = ( innerObj ) => { const list = this . _loops ( innerObj ) ; ( list || [ ] ) . forEach ( attr => { if ( attrNames . indexOf ( attr ) !== - 1 ) { if ( ! req [ attr ] ) { req [ attr ] = [ innerObj [ attr ] ] ; } else { req [ attr ] . push ( innerObj [ attr ] ) ; } } if ( typeof innerObj [ attr ] !== 'string' ) { innerLoops ( innerObj [ attr ] ) ; } } ) ; } ; innerLoops ( obj ) ; return req ; } param { object } obj param { Array } attrNames return { object } public _getMinAttrList ( obj , attrNames ) { const req = this . _loopsAttr ( obj , attrNames ) ; for ( const itemList in req ) { if ( itemList ) { req [ itemList ] = Array . from ( new Set ( req [ itemList ] ) ) ; } } return req ; } param { object } obj param { Array } attrNames return { object } public _getValueMap ( obj , attrNames ) { const resultobj = { } ; const attrKeyObj = this . _getMinAttrList ( obj , attrNames ) ; for ( const itemList in attrKeyObj ) { if ( itemList ) { const _obj = { } ; attrKeyObj [ itemList ] . forEach ( ( item , index ) => { _obj [ item ] = this . _string10to62 ( index ) ; } ) ; resultobj [ itemList ] = _obj ; } } return resultobj ; } param { number } num param { number } n return { number } public _cutNumber ( num , n ? ) { if ( typeof num !== 'number' ) { return num ; } return Number ( num . toFixed ( n || 3 ) ) ; } param { number } num return { number } public _cutColorNumber ( num ) { if ( typeof num !== 'number' ) { return num ; } return Number ( ( Math . round ( num * 255 ) / 255 ) . toFixed ( 3 ) ) ; } param { object } obj param { Array } attrNames param { * } value return { object } public _resetAttr ( obj , attrNames , value ? ) { const innerLoops = ( innerObj ) => { const list = this . _loops ( innerObj ) ; ( list || [ ] ) . forEach ( attr => { if ( attrNames . indexOf ( attr ) !== - 1 ) { switch ( typeof value ) { case 'undefined' : delete innerObj [ attr ] ; break ; case 'object' : innerObj [ attr ] = value && value [ attr ] [ innerObj [ attr ] ] ; break ; default : innerObj [ attr ] = value ; break ; } } if ( typeof innerObj [ attr ] !== 'string' ) { innerLoops ( innerObj [ attr ] ) ; } } ) ; } ; innerLoops ( obj ) ; return obj ; } param { object } obj param { Array } attrNames param { Number } cut return { object } public _resetToTi ( obj , attrNames ) { const isColor = attrNames . indexOf ( 'color' ) !== - 1 ; if ( isColor ) { attrNames . splice ( attrNames . indexOf ( 'color' ) , 1 ) ; } const innerLoops = ( innerObj , parentAttr ) : void => { const list = this . _loops ( innerObj ) ; ( list || [ ] ) . forEach ( attr => { const innerItem = innerObj [ attr ] ; if ( isColor && attr === 'k' ) { if ( ( parentAttr === 'c' || parentAttr === 'v' ) && Array . isArray ( innerItem ) ) { innerObj [ attr ] = innerItem . map ( item => { return this . _cutColorNumber ( item ) ; } ) ; } } if ( attrNames . indexOf ( attr ) !== - 1 ) { if ( innerItem && ( Array . isArray ( innerItem ) || typeof innerItem === 'number' ) ) { innerObj [ attr ] = typeof innerItem === 'number' ? this . _cutNumber ( innerItem ) : innerItem . length && innerItem . map ( item => { if ( innerItem . length === 4 ) { return this . _cutColorNumber ( item ) ; } return this . _cutNumber ( item ) ; } ) ; } } if ( typeof innerObj [ attr ] !== 'string' ) { innerLoops ( innerObj [ attr ] , attr ) ; } } ) ; } ; innerLoops ( obj , '' ) ; return obj ; } param { object } obj return { object } public combNumber ( obj ) { const numberList = [ ] ; const innerLoops = ( innerObj , map ) : void => { const list = this . _loops ( innerObj ) ; ( list || [ ] ) . forEach ( attr => { const innerItem = innerObj [ attr ] ; if ( typeof innerItem === 'number' ) { if ( map ) { if ( map [ innerItem ] ) { innerObj [ attr ] = map [ innerItem ] ; } } else { numberList . push ( innerItem ) ; } } if ( typeof innerItem !== 'string' ) { innerLoops ( innerItem , map ) ; } } ) ; } ; innerLoops ( obj , undefined ) ; const tmpObj = { } ; const result = [ ] ; numberList . forEach ( ( a ) => { const key = ( typeof a ) + a ; if ( ! tmpObj [ key ] ) { tmpObj [ key ] = true ; result . push ( a ) ; } } ) ; result . sort ( ( a , b ) => { return Number ( a ) - Number ( b ) ; } ) ; const diffArray = [ ] ; let t = 0 ; result . forEach ( ( num , index , arr ) => { if ( arr [ index + 1 ] && ( arr [ index + 1 ] - num ) . toFixed ( 3 ) === '0.001' ) { diffArray [ t ] ? diffArray [ t ] . push ( num ) : diffArray [ t ] = [ num ] ; } else { if ( arr [ index - 1 ] && ( num - arr [ index - 1 ] ) . toFixed ( 3 ) === '0.001' ) { diffArray [ t ] ? diffArray [ t ] . push ( num ) : diffArray [ t ] = [ num ] ; t ++ ; } } } ) ; const map = { } ; diffArray . forEach ( item => { item . forEach ( ( num , index , arr ) => { if ( index % 2 === 1 ) { const preNum = arr [ index - 1 ] ; if ( num . toString ( ) . length <= preNum . toString ( ) . length ) { map [ preNum ] = num ; } else { map [ num ] = preNum ; } } } ) ; } ) ; innerLoops ( obj , map ) ; return obj ; } param { object } obj param { Array } attrNames return { object } public attrZip ( obj , attrNames ) { const resultobj = this . _resetAttr ( obj , attrNames , this . _getValueMap ( obj , attrNames ) ) ; return resultobj ; } param { object } obj param { Array } attrNames return { object } public attrEqualZip ( obj , attrNames ) { if ( ! ( typeof attrNames === 'string' && attrNames . split ( ':' ) [ 1 ] ) ) { return obj ; } const equlAttrs = attrNames . split ( ':' ) ; const map = this . _getValueMap ( obj , equlAttrs [ 0 ] ) ; map [ equlAttrs [ 1 ] ] = map [ equlAttrs [ 0 ] ] ; const resultobj = this . _resetAttr ( obj , [ equlAttrs [ 0 ] , equlAttrs [ 1 ] ] , map ) ; return resultobj ; } param { object } obj param { Array } attrNames return { object } public attrMiniNumber ( obj , attrNames ) { const resultobj = this . _resetToTi ( obj , attrNames ) ; return resultobj ; } param { object } obj param { Array } attrNames return { object } public deleteAttr ( obj , attrNames ) { const resultobj = this . _resetAttr ( obj , attrNames ) ; return resultobj ; } param { object } obj return { object } public fixAttrIndIsUndefined ( obj ) { obj . layers . forEach ( ( layer , index ) => { if ( layer . ind === undefined ) { layer . ind = index + 1 ; } } ) ; obj . assets . forEach ( asset => { if ( asset . layers ) { asset . layers . forEach ( ( layer , index ) => { if ( layer . ind === undefined ) { layer . ind = index + 1 ; } } ) ; } } ) ; return obj ; } }