{"version":3,"sources":["jsdelivr-header.js","/npm/@bldr/api-media-server@0.2.3/src/main.js"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA,ACqDA,MAAM,aAAe,QAAQ,iBACvB,GAAK,QAAQ,MACb,KAAO,QAAQ,QAGf,KAAO,QAAQ,WACf,QAAU,QAAQ,WAClB,YAAc,QAAQ,WAAW,aACjC,cAAE,eAAkB,QAAQ,oBAG5B,gBAAE,iBAAoB,QAAQ,oBAC9B,WAAE,WAAU,aAAE,cAAiB,QAAQ,sBAEvC,YAAc,QAAQ,mBAKtB,OAAS,kBAKT,SAAW,OAAO,YAAY,SAKpC,IAAI,OAAS,GAOb,SAAS,gBACP,MAAM,EAAO,OAAO,UAAU,QAK9B,MADY,aAHC,mBAAmB,EAAK,SACpB,mBAAmB,EAAK,aAEI,EAAK,OAAO,EAAK,8BAEhE,CAKA,MAAM,YAAc,IAAI,YACtB,gBACA,CAAE,iBAAiB,EAAM,oBAAoB,IAO/C,IAAI,GAKJ,eAAe,YACR,WACG,YAAY,UAClB,GAAK,YAAY,GAAG,OAAO,UAAU,QAAQ,QAEjD,CAWA,SAAS,QAAS,GAChB,IAAI,EAAS,EACV,QAAQ,YAAa,IACrB,QAAQ,SAAU,KAClB,QAAQ,SAAU,KAClB,QAAQ,OAAQ,KAChB,QAAQ,MAAO,KACf,QAAQ,SAAU,KAClB,QAAQ,KAAM,MACd,QAAQ,KAAM,MACd,QAAQ,KAAM,MACd,QAAQ,KAAM,MACd,QAAQ,KAAM,MACd,QAAQ,KAAM,MACd,QAAQ,KAAM,MACjB,OAAO,cAAc,EACvB,CASA,SAAS,UAAW,GAClB,OAAO,EACJ,QAAQ,KAAM,MACd,QAAQ,KAAM,KACd,QAAQ,MAAO,KACf,QAAQ,MAAO,KACf,QAAQ,MAAO,KACf,QAAQ,MAAO,KACf,QAAQ,MAAO,KACf,QAAQ,MAAO,IACpB,CAWA,SAAS,6BAA8B,GAErC,GAAI,MAAM,QAAQ,GAChB,IAAK,MAAM,KAAQ,EACK,iBAAX,GACT,6BAA6B,QAI5B,GAAsB,iBAAX,EAChB,IAAK,MAAM,KAAa,EAAQ,CAC9B,MAAM,EAAY,aAAa,GAC/B,GAAI,IAAc,EAAW,CAC3B,MAAM,EAAQ,EAAO,GACrB,EAAO,GAAa,SACb,EAAO,EAChB,CAEiC,iBAAtB,EAAO,IAAyB,6BAA6B,EAAO,GACjF,CAEF,OAAO,CACT,CASA,SAAS,aAAc,GACrB,OAAO,KAAK,QAAQ,GAAU,QAAQ,IAAK,GAC7C,CASA,SAAS,UAAW,GAClB,OAAO,EAAK,QAAQ,WAAY,GAClC,CAOA,MAAM,YACJ,WAAA,EAAY,MAAE,EAAK,SAAE,EAAQ,WAAE,EAAU,KAAE,EAAI,iBAAE,IAI3C,IAAO,KAAK,MAAQ,GAKpB,IAAU,KAAK,SAAW,GAO1B,IAAY,KAAK,WAAa,GAQ9B,IAAM,KAAK,KAAO,GAQlB,IAAkB,KAAK,iBAAmB,EAChD,EAiCF,MAAM,yBAKJ,WAAA,CAAa,GACX,KAAK,QAAU,GACf,KAAK,MAAM,EACb,CAUA,aAAA,CAAe,GACb,MACM,EADW,GAAG,aAAa,EAAU,CAAE,SAAU,UAC/B,MAAM,MACxB,EAAc,IAAI,YAAY,CAAC,GAUrC,OATI,EAAO,OAAS,IAClB,EAAY,MAAQ,EAAO,IAEzB,EAAO,OAAS,GAAK,EAAO,KAC9B,EAAY,SAAW,EAAO,IAE5B,GAAG,WAAW,KAAK,KAAK,KAAK,QAAQ,GAAW,8BAClD,EAAY,kBAAmB,GAE1B,CACT,CAUA,KAAA,CAAO,GAIL,MAAM,GAFN,EAAW,KAAK,QAAQ,IAEE,MAAM,KAAK,KAE/B,EAAQ,EAAS,OAIjB,EAAc,GACpB,IAAK,IAAI,EAHQ,SAAS,MAAM,KAAK,KAAK,OAGd,EAAG,EAAQ,EAAO,IAAS,CACrD,IAAI,EAAa,EAAS,EAAQ,GAClC,EAAY,KAAK,GAEjB,MAKM,EAAW,IALI,EAAS,MAAM,EAAG,GAKJ,aAAa,KAAK,KAAK,KAC1D,GAAI,GAAG,WAAW,GAAW,CAC3B,MAAM,EAAc,KAAK,cAAc,GACvC,EAAY,KAAO,EAAY,KAAK,KAAK,KACzC,EAAY,WAAa,EACzB,KAAK,QAAQ,KAAK,EACpB,CACF,CACF,CAQA,gBAAI,GACF,OAAO,KAAK,QAAQ,KAAI,GAAe,EAAY,OACrD,CAKA,0BAAI,GACF,OAAO,KAAK,QAAQ,KAAK,QAAQ,OAAS,EAC5C,CAeA,aAAI,GACF,OAAO,KAAK,aAAa,KAAK,MAChC,CAOA,yBAAI,GACF,OAAO,KAAK,aAAa,MAAM,EAAG,KAAK,QAAQ,OAAS,EAC1D,CA2BA,cAAI,GACF,OAAO,KAAK,sBAAsB,KAAK,MACzC,CAKA,MAAI,GACF,OAAO,KAAK,uBAAuB,WAAW,QAAQ,QAAS,GACjE,CAQA,SAAI,GACF,OAAO,KAAK,uBAAuB,KACrC,CAQA,YAAI,GACF,GAAI,KAAK,uBAAuB,SAC9B,OAAO,KAAK,uBAAuB,QAEvC,CAQA,SAAI,GACF,OAAO,KAAK,QAAQ,GAAG,MAAM,QAAQ,UAAW,GAClD,CAOA,IAAA,GACE,OAAO,KAAK,OACd,EAMF,MAAM,gBACJ,WAAA,GACE,KAAK,MAAQ,CAAC,CAChB,CAMA,GAAA,CAAK,GACH,IAAI,EAAM,KAAK,MACX,EAAQ,EACZ,IAAK,MAAM,KAAS,EAAa,OACzB,EAAM,cAAc,IACxB,EAAI,EAAM,YAAc,CACtB,OAAQ,GAEV,EAAI,EAAM,YAAY,OAAO,MAAQ,GAEvC,EAAM,EAAI,EAAM,YAChB,GAAS,CAEb,CAEA,GAAA,GACE,OAAO,KAAK,KACd,EAGF,MAAM,gBAAkB,IAAI,gBAEtB,WAAa,IAAI,WAAW,QAKlC,MAAM,UACJ,WAAA,CAAa,GAMX,KAAK,SAAW,KAAK,QAAQ,GAM7B,KAAK,KAAO,EAAS,QAAQ,SAAU,IAAI,QAAQ,MAAO,IAM1D,KAAK,SAAW,KAAK,SAAS,EAChC,CAKA,aAAA,GACE,MAAM,EAAQ,GAAG,SAAS,KAAK,UA4B/B,OAtBA,KAAK,KAAO,EAAM,KAOlB,KAAK,aAAe,EAAM,QAM1B,KAAK,UAAY,aAAa,KAAK,UAOnC,KAAK,UAAY,KAAK,SAAS,KAAK,SAAU,IAAI,KAAK,aAEhD,IACT,CAoBA,SAAA,CAAW,GACT,OAAI,GAAG,WAAW,GACT,KAAK,SAAS,GAAG,aAAa,EAAU,SAE1C,CAAC,CACV,CAKA,YAAA,GACE,OAAO,KAAK,eACd,CAKA,kBAAA,GACE,IAAK,MAAM,KAAY,KACjB,EAAS,MAAM,cACV,KAAK,GAGhB,OAAO,IACT,CAQA,WAAA,CAAa,GACX,GAAsB,iBAAX,EAAqB,CAC9B,6BAA6B,GAC7B,IAAK,MAAM,KAAY,EACrB,KAAK,GAAY,EAAO,EAE5B,CACF,CAMA,gBAAA,GAKE,OAJA,KAAK,eACA,KAAK,KAAI,KAAK,GAAK,QAAQ,KAAK,YAChC,KAAK,QAAO,KAAK,MAAQ,UAAU,KAAK,KAC7C,KAAK,qBACE,IACT,EAOF,MAAM,cAAc,UAIlB,WAAA,CAAa,GACX,MAAM,GAON,KAAK,UAAY,GAAG,KAAK,eAEzB,MAAM,EAAO,KAAK,UAAU,KAAK,WACjC,KAAK,YAAY,EACnB,CAEA,YAAA,GACE,KAAK,gBACL,MAAM,EAAe,GAAG,KAAK,uBAU7B,OATA,KAAK,UAAY,WAAW,gBAAgB,KAAK,WAC7C,GAAG,WAAW,KAKhB,KAAK,cAAe,GAEtB,KAAK,oBACE,IACT,CAMA,iBAAA,GACE,MAAM,EAAqB,IACzB,IAAI,EACJ,GAAI,EAAQ,GACV,EAAS,OAAO,QACX,MAAI,EAAQ,KAGjB,MAAM,IAAI,MAAM,GAAG,KAAK,uEAFxB,EAAS,MAAM,GAGjB,CAGA,MADiB,GADA,KAAK,SAAS,QAAQ,IAAI,KAAK,YAAa,MAC9B,KAAU,KAAK,WACvC,EAET,IAAI,EAAQ,EACZ,KAAO,GAAG,WAAW,EAAkB,KACrC,GAAS,EAEX,GAAS,EACL,EAAQ,IAMV,KAAK,eAAiB,EAE1B,EAOF,MAAM,qBAAqB,UACzB,WAAA,CAAa,GACX,MAAM,GACN,MAAM,EAAO,KAAK,UAAU,GACxB,GAAM,KAAK,YAAY,GAE3B,MAAM,EAAe,IAAI,yBAAyB,GAClD,gBAAgB,IAAI,QAEK,IAAd,KAAK,OAAsB,KAAK,KAAO,CAAC,GACnD,IAAK,MAAM,IAAY,CAAC,KAAM,QAAS,WAAY,aAAc,cAC5B,IAAxB,KAAK,KAAK,KAA2B,KAAK,KAAK,GAAY,EAAa,IAQrF,KAAK,MAAQ,UAAU,KAAK,KAAK,OAOjC,KAAK,cAAgB,KAAK,iBAe1B,KAAK,kBAAoB,KAAK,mBAAoB,GAOlD,KAAK,GAAK,KAAK,KAAK,EACtB,CAQA,cAAA,GACE,OAAI,KAAK,KAAK,SACL,GAAG,KAAK,UAAU,UAAU,KAAK,KAAK,aAEtC,KAAK,KAEhB,CAeA,kBAAA,CAAoB,GAClB,IAAI,EAAM,EAAa,UAIvB,OAHI,KAAK,KAAK,WACZ,EAAM,GAAG,MAAQ,KAAK,KAAK,aAEtB,UAAU,EACnB,EAUF,SAAS,QAAS,GAChB,QACE,EAAS,QAAQ,iBAAmB,GACpC,EAAS,MAAM,eAIV,WAAW,QAAQ,EAC5B,CAOA,SAAS,eAAgB,GACvB,OAAI,EAAS,QAAQ,4BAA8B,CAIrD,CAOA,eAAe,mBAAoB,EAAU,GAC3C,IAAI,EACJ,IACoB,kBAAd,EACF,EAAS,IAAI,aAAa,GACH,WAAd,IACT,EAAS,IAAI,MAAM,IAErB,EAAS,EAAO,mBAChB,QAAQ,IAAI,EAAO,YACb,GAAG,WAAW,GAAW,UAAU,EAC3C,CAAE,MAAO,GACP,QAAQ,IAAI,GACZ,IAAI,EAAU,EAAS,QAAQ,OAAO,YAAY,SAAU,IAC5D,EAAU,EAAQ,QAAQ,IAAI,OAAO,MAAO,IAC5C,MAAM,EAAM,GAAG,OAAa,EAAM,SAAS,EAAM,UACjD,QAAQ,IAAI,GACZ,OAAO,KAAK,EACd,CACF,CAOA,eAAe,KAAM,EAAK,GACxB,MAAM,EAAQ,GAAG,YAAY,GAC7B,IAAK,MAAM,KAAY,EAAO,CAC5B,MAAM,EAAU,KAAK,KAAK,EAAK,GAE3B,GAAG,WAAW,IAAsC,SAA1B,EAAS,OAAO,EAAG,KAC3C,GAAG,SAAS,GAAS,oBACjB,KAAK,EAAS,IAEhB,cAAe,SAAU,EAAG,UAAU,GACtC,eAAe,IACb,iBAAkB,SAAU,EAAG,aAAa,GAC5C,QAAS,SAAU,EAAG,IAAI,IACrB,QAAQ,KACb,UAAW,SAAU,EAAG,MAAM,GAC9B,QAAS,SAAU,EAAG,IAAI,KAItC,CACF,CAOA,eAAe,OAAQ,GAAO,GAC5B,MAAM,EAAc,CAClB,IAAK,SACL,SAAU,SAgBR,GAVJ,WACE,QAAQ,IAAI,gBACZ,MAAM,EAAU,aAAa,UAC3B,MAAO,CAAC,QACR,GAIF,GAFA,QAAQ,IAAI,oBAAoB,EAAQ,OAAO,QAAQ,MAAO,OAC9D,QAAQ,IAAI,oBAAoB,EAAQ,OAAO,QAAQ,MAAO,OACvC,IAAnB,EAAQ,OAAc,MAAM,IAAI,MAAM,+CAC5C,CACU,GACV,MACM,EADc,aAAa,UAAU,MAAO,CAAC,YAAa,QAAS,GACxC,OAAO,QAAQ,MAAO,IACvD,QAAQ,IAAI,iBAAiB,WACvB,kBACA,qBACA,kBACN,MAAM,GAAQ,IAAI,MAAO,gBACnB,GAAG,WAAW,WAAW,UAAU,CAAE,MAAO,EAAO,IAAK,UACxD,KAAK,SAAU,CACnB,UAAY,IACN,EAAS,MAAM,wCACjB,QAAQ,IAAI,yBAAyB,KACrC,GAAG,WAAW,GAChB,EAEF,aAAc,MAAO,UAAqB,mBAAmB,EAAU,gBAAe,EACtF,MAAO,MAAO,UAAqB,mBAAmB,EAAU,SAAQ,UAIpE,GAAG,WAAW,mBAAmB,UAAU,CAAE,GAAI,eACjD,GAAG,WAAW,mBAAmB,UACrC,CACE,GAAI,OACJ,KAAM,gBAAgB,QAG1B,MAAM,GAAM,IAAI,MAAO,UAEvB,aADM,GAAG,WAAW,WAAW,UAAU,CAAE,MAAO,GAAS,CAAE,KAAM,CAAE,IAAK,EAAK,kBACxE,CACL,UAAU,EACV,QACA,MACA,SAAU,EAAM,EAChB,eACA,cAEJ,CAOA,eAAe,eACb,MAAM,QAAe,GAAG,iBAAiB,gBACnC,EAAO,YAAY,CAAE,KAAM,GAAK,CAAE,QAAQ,UAC1C,EAAO,YAAY,CAAE,GAAI,GAAK,CAAE,QAAQ,IAE9C,MAAM,QAAsB,GAAG,iBAAiB,uBAC1C,EAAc,YAAY,CAAE,GAAI,GAAK,CAAE,QAAQ,IAErD,MAAM,QAAgB,GAAG,iBAAiB,iBACpC,EAAQ,YAAY,CAAE,MAAO,IAGnC,MAAM,QAAwB,GAAG,iBAAiB,yBAC5C,EAAgB,YAAY,CAAE,GAAI,GAAK,CAAE,QAAQ,IAEvD,MAAM,EAAS,CAAC,EACV,QAAoB,GAAG,kBAAkB,UAC/C,IAAK,MAAM,KAAc,EAAa,CACpC,MAAM,QAAgB,GAAG,WAAW,EAAW,MAAM,cAAc,UACnE,EAAO,EAAW,MAAQ,CACxB,KAAM,EAAW,KACjB,QAAS,CAAC,GAEZ,IAAK,MAAM,KAAS,EAClB,EAAO,EAAW,MAAM,QAAQ,EAAM,MAAQ,EAAM,MAExD,CACA,OAAO,CACT,CAKA,eAAe,SACb,MAAM,QAAoB,GAAG,kBAAkB,UACzC,EAAqB,GAC3B,IAAK,MAAM,KAAc,QACjB,GAAG,eAAe,EAAW,MACnC,EAAmB,KAAK,EAAW,MAErC,MAAO,CACL,qBAEJ,CAKA,eAAe,iBAGb,MAAO,CACL,mBAHyB,SAIzB,yBAH+B,eAKnC,CAKA,eAAe,wBACP,GAAG,WAAW,UAAU,WAAW,CAAC,SACpC,GAAG,WAAW,iBAAiB,WAAW,CAAC,EACnD,CAYA,MAAM,aAAe,CACnB,WAAY,CACV,IAAK,CACH,oBAAqB,4DAEvB,KAAM,CACJ,MAAO,oEACP,KAAM,mCACN,KAAM,CACJ,eAAgB,wCAChB,YAAa,CACX,gCACA,4CACA,+DACA,iDACA,kDAEF,cAAe,CACb,GAAI,uCACJ,KAAM,mEACN,KAAM,wKAGV,UAAW,4EACX,OAAQ,wDAEV,MAAO,CACL,eAAgB,wCAChB,YAAa,CACX,uEACA,gFACA,iHACA,oHAEF,cAAe,CACb,KAAM,6CACN,OAAQ,qQACR,MAAO,4CACP,OAAQ,wCACR,OAAQ,6CAGZ,MAAO,CACL,MAAO,0EACP,QAAS,sDAYf,SAAS,kBAAmB,GAC1B,MAAM,EAAa,CAAC,SAAU,iBAC9B,IAAK,EAAW,MAAO,SACvB,GAAK,EAAW,SAAS,GAGvB,OAAO,EAFP,MAAM,IAAI,MAAM,sBAAsB,gCAAwC,IAIlF,CAWA,eAAe,iBAAkB,EAAI,EAAY,iBAC/C,EAAY,kBAAkB,GAC9B,MAAM,QAAe,GAAG,WAAW,GAAW,KAAK,CAAE,GAAI,IAAM,OAC/D,IAAK,EAAQ,MAAM,IAAI,MAAM,0CAA0C,kBAA0B,OACjG,IAAI,EAMJ,OAJE,EADgB,WAAd,EACQ,GAAG,EAAO,WAEV,EAAO,KAEZ,KAAK,KAAK,OAAO,YAAY,SAAU,EAChD,CAWA,eAAe,WAAY,EAAI,GAC7B,MAAM,QAAgB,iBAAiB,EAAI,GACrC,EAAS,OAAO,YAAY,OAClC,OAAK,GAAG,WAAW,IAKnB,aAAa,MAAM,EAAQ,CAAC,GAAU,CACpC,IAAK,CAGH,QAAS,QAGN,CACL,UACA,WAbO,CACL,MAAO,WAAW,qBAcxB,CAWA,eAAe,iBAAkB,EAAI,GACnC,MAAM,QAAgB,iBAAiB,EAAI,GACrC,EAAe,KAAK,QAAQ,GAQlC,OAPA,aAAa,MAAM,WAAY,CAAC,GAAe,CAC7C,IAAK,CAGH,QAAS,QAGN,CACL,eAEJ,CAKA,SAAS,kBAMP,MAAM,EAAM,UAiLZ,OA/KA,EAAI,GAAG,SAAS,gBACR,kBACA,cAAa,IAGrB,EAAI,IAAI,KAAK,CAAC,EAAK,KACjB,EAAI,KAAK,aAAa,WAAU,IAGlC,EAAI,IAAI,YAAY,CAAC,EAAK,KACxB,EAAI,KAAK,CACP,KAAM,YAAY,KAClB,QAAS,YAAY,SACtB,IAKH,EAAI,IAAI,UAAU,MAAO,EAAK,EAAK,KACjC,IACE,MAAM,EAAQ,EAAI,MAClB,GAAkC,IAA9B,OAAO,KAAK,GAAO,OAOrB,YANA,EAAI,OAAO,KAAK,KAAK,CACnB,MAAO,CACL,IAAK,4BACL,gBAAiB,aAAa,WAAW,SAM/C,EAAM,KAAO,kBAAkB,EAAM,MAGrC,MAAM,EAAU,CAAC,aAAc,mBAE/B,GADM,WAAY,IAAQ,EAAM,OAAS,oBACpC,EAAQ,SAAS,EAAM,QAC1B,MAAM,IAAI,MAAM,kBAAkB,EAAM,6BAA6B,KAIjE,UAAW,IAAQ,EAAM,MAAQ,MAGjC,WAAY,IAAQ,EAAM,OAAS,qBAEnC,YACN,MAAM,EAAa,GAAG,WAAW,EAAM,MAGvC,IAAI,EACA,EAEJ,GAAqB,eAAjB,EAAM,OAAyB,CACjC,MAAM,EAAa,CAAC,EACpB,EAAW,EAAM,OAAS,EAAM,OAChC,EAAO,EAAW,KAAK,EAAY,CAAE,WAAY,CAAE,IAAK,KACxD,QAAe,EAAK,MAEtB,MAAO,GAAqB,oBAAjB,EAAM,OAA8B,CAE7C,MAAM,EAAQ,IAAI,OAAmB,EAAM,OAlEnC,QAAQ,2BAA4B,QAkEQ,MAC9C,EAAS,CAAC,EAEhB,IAAI,EADJ,EAAO,EAAM,OAAS,EAED,gBAAjB,EAAM,OACR,EAAW,CACT,KAAK,GAEmB,kBAAjB,EAAM,SACf,EAAW,CACT,KAAK,EACL,IAAI,EACJ,KAAM,IAAI,EAAM,UAGpB,EAAO,EAAW,UAAU,CAAC,CAAE,UAAU,CAAE,cAC3C,QAAe,EAAK,SACtB,CACA,EAAI,KAAK,EACX,CAAE,MAAO,GACP,EAAK,EACP,KAKF,EAAI,IAAI,0BAA0B,MAAO,EAAK,EAAK,KACjD,IACE,MAAM,QAAe,GAAG,WAAW,mBAAmB,KAAK,CAAE,GAAI,QAAU,CAAE,WAAY,CAAE,IAAK,KAAO,OACvG,EAAI,KAAK,EAAO,KAClB,CAAE,MAAO,GACP,EAAK,EACP,KAKF,EAAI,IAAI,eAAe,MAAO,EAAK,EAAK,KACtC,UACQ,kBACN,EAAI,KAAK,CAAE,OAAQ,MACrB,CAAE,MAAO,GACP,EAAK,EACP,KAGF,EAAI,IAAI,cAAc,MAAO,EAAK,EAAK,KACrC,IACE,EAAI,WAAW,eACjB,CAAE,MAAO,GACP,EAAK,EACP,KAGF,EAAI,IAAI,cAAc,MAAO,EAAK,EAAK,KACrC,IACE,MAAM,EAAQ,EAAI,MAClB,IAAK,EAAM,GAAI,MAAM,IAAI,MAAM,2CAC1B,EAAM,OAAM,EAAM,KAAO,UACzB,EAAM,OAAM,EAAM,KAAO,iBACX,WAAf,EAAM,KACR,EAAI,WAAW,WAAW,EAAM,GAAI,EAAM,OAClB,WAAf,EAAM,MACf,EAAI,WAAW,iBAAiB,EAAM,GAAI,EAAM,MAEpD,CAAE,MAAO,GACP,EAAK,EACP,KAGF,EAAI,IAAI,iBAAiB,MAAO,EAAK,EAAK,KACxC,IACE,EAAI,WAAW,iBACjB,CAAE,MAAO,GACP,EAAK,EACP,KAGF,EAAI,IAAI,gBAAgB,MAAO,EAAK,EAAK,KACvC,IACE,EAAI,WAAW,UAEf,OAAS,EACX,CAAE,MAAO,GACP,EAAK,EACP,KAKF,EAAI,IAAI,gBAAgB,MAAO,EAAK,EAAK,KACvC,IACE,EAAI,KAAK,CACP,aAAc,GAAG,WAAW,UAAU,iBACtC,oBAAqB,GAAG,WAAW,iBAAiB,kBAExD,CAAE,MAAO,GACP,EAAK,EACP,KAGF,EAAI,IAAI,kBAAkB,MAAO,EAAK,EAAK,KACzC,IACE,EAAI,WAAW,GAAG,WAAW,WAC1B,KAAK,CAAC,EAAG,CAAE,WAAY,CAAE,IAAK,KAC9B,KAAK,CAAE,OAAQ,IACf,MAAM,IACN,UAEL,CAAE,MAAO,GACP,EAAK,EACP,KAGK,CACT,CAEA,OAAO,QAAU,CACf,gBACA,YACA,sBACA,oBACA,gCACA,0BACA,0BACA,kDACA,gCACA","file":"/npm/@bldr/api-media-server@0.2.3/src/main.js","sourceRoot":"","sourcesContent":["/**\n * Minified by jsDelivr using Terser v5.39.0.\n * Original file: /npm/@bldr/api-media-server@0.2.3/src/main.js\n *\n * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files\n */\n","/**\n * The REST API and command line interface of the BALDR media server.\n *\n * # Media types:\n *\n * - presentation (`Presentation()`)\n * - asset (`Asset()`)\n *   - multipart asset (`filename.jpg`, `filename_no02.jpg`, `filename_no03.jpg`)\n *\n * # Definition of the objects:\n *\n * A _presentation_ is a YAML file for the BALDR presentation app. It must have\n * the file name scheme `*.baldr.yml`. The media server stores the whole YAML\n * file in the MongoDB database.\n *\n * A _asset_ is a media file which has a meta data file in the YAML format.\n * The file name scheme for this meta data file is `media-file.jpg.yml`. The\n * suffix `.yml` has to be appended. Only the content of the meta data file\n * is stored into the database.\n *\n * # REST API\n * - `get`\n *   - `folder-title-tree`: Get the folder title tree as a hierarchical json\n *     object.\n * - `mgmt`\n *   - `flush`: Delete all media files (assets, presentations) from the database.\n *   - `init`: Initialize the MongoDB database\n *   - `open`: Open a media file specified by an ID. This query parameters are\n *     available:\n *       - `id`: The ID of the media file (required).\n *       - `type`: `presentations`, `assets`. The default value is\n *         `presentations.`\n *       - `with`: `editor` specified in `config.mediaServer.editor`\n *         (`/etc/baldr.json`) or `folder` to open the parent folder of the\n *         given media file. The default value is `editor`\n *\n *   - `re-init`: Re-Initialize the MongoDB database (Drop all collections and\n *     initialize)\n *   - `update`: Update the media server database (Flush and insert).\n *   - `query`: Getting results by using query parameters. This query parameters\n *     are available:\n *      - `type`: `assets` (default), `presentations` (what)\n *      - `method`: `exactMatch`, `substringSearch` (default) (how).\n *          - `exactMatch`: The query parameter `search` must be a perfect match\n *            to a top level database field to get a result.\n *          - `substringSearch`: The query parameter `search` is only a\n *            substring of the string to search in.\n *      - `field`: `id` (default), `title`, etc ... (where).\n *      - `search`: Some text to search for (search for).\n *      - `result`: `fullObjects` (default), `dynamicSelect`\n * - `stats`:\n *   - `count`: Count / sum of the media files (assets, presentations) in the\n *     database.\n *   - `updates`: Journal of the update processes with timestamps.\n *\n * @module @bldr/api-media-server\n */\n\n// Node packages.\nconst childProcess = require('child_process')\nconst fs = require('fs')\nconst path = require('path')\n\n// Third party packages.\nconst yaml = require('js-yaml')\nconst express = require('express')\nconst MongoClient = require('mongodb').MongoClient\nconst { transliterate } = require('transliteration')\n\n// Project packages.\nconst { bootstrapConfig } = require('@bldr/core-node')\nconst { AssetTypes, snakeToCamel } = require('@bldr/core-browser')\n\nconst packageJson = require('../package.json')\n\n/**\n * The configuration object from `/etc/baldr.json`\n */\nconst config = bootstrapConfig()\n\n/**\n * Base path of the media server file store.\n */\nconst basePath = config.mediaServer.basePath\n\n/**\n * A container array for all error messages send out via the REST API.\n */\nlet errors = []\n\n/* MongoDb setup **************************************************************/\n\n/**\n *\n */\nfunction setupMongoUrl () {\n  const conf = config.databases.mongodb\n  const user = encodeURIComponent(conf.user)\n  const password = encodeURIComponent(conf.password)\n  const authMechanism = 'DEFAULT'\n  const url = `mongodb://${user}:${password}@${conf.url}/${conf.dbName}?authMechanism=${authMechanism}`\n  return url\n}\n\n/**\n *\n */\nconst mongoClient = new MongoClient(\n  setupMongoUrl(),\n  { useNewUrlParser: true, useUnifiedTopology: true }\n)\n\n/**\n * The MongoDB Db instance\n * @type {mongodb~Db}\n */\nlet db\n\n/**\n * @return {Promise}\n */\nasync function connectDb () {\n  if (!db) {\n    await mongoClient.connect()\n    db = mongoClient.db(config.databases.mongodb.dbName)\n  }\n}\n\n/* Helper functions ***********************************************************/\n\n/**\n * This function can be used to generate ids from different file names.\n *\n * @param {String} input\n *\n * @returns {String}\n */\nfunction asciify (input) {\n  let output = input\n    .replace(/[\\(\\)';]/g, '')\n    .replace(/[,.] /g, '_')\n    .replace(/ +- +/g, '_')\n    .replace(/\\s+/g, '-')\n    .replace(/-+/g, '-')\n    .replace(/-*_-*/g, '_')\n    .replace(/Ä/g, 'Ae')\n    .replace(/ä/g, 'ae')\n    .replace(/Ö/g, 'Oe')\n    .replace(/ö/g, 'oe')\n    .replace(/Ü/g, 'Ue')\n    .replace(/ü/g, 'ue')\n    .replace(/ß/g, 'ss')\n  return transliterate(output)\n}\n\n/**\n * This function can be used to generate a title from an ID string.\n *\n * @param {String} input\n *\n * @returns {String}\n */\nfunction deasciify (input) {\n  return input\n    .replace(/_/g, ', ')\n    .replace(/-/g, ' ')\n    .replace(/Ae/g, 'Ä')\n    .replace(/ae/g, 'ä')\n    .replace(/Oe/g, 'Ö')\n    .replace(/oe/g, 'ö')\n    .replace(/Ue/g, 'Ü')\n    .replace(/ue/g, 'ü')\n}\n\n/**\n * Convert all properties in an object to camelCase in a recursive fashion.\n *\n * TODO: Use the function in @bldr/core-browser\n *\n * @param {Object} object\n *\n * @returns {Object}\n */\nfunction convertPropertiesToCamelCase (object) {\n  // Array\n  if (Array.isArray(object)) {\n    for (const item of object) {\n      if (typeof object === 'object') {\n        convertPropertiesToCamelCase(item)\n      }\n    }\n  // Object\n  } else if (typeof object === 'object') {\n    for (const snakeCase in object) {\n      const camelCase = snakeToCamel(snakeCase)\n      if (camelCase !== snakeCase) {\n        const value = object[snakeCase]\n        object[camelCase] = value\n        delete object[snakeCase]\n      }\n      // Object or array\n      if (typeof object[camelCase] === 'object') convertPropertiesToCamelCase(object[camelCase])\n    }\n  }\n  return object\n}\n\n/**\n * Get the extension from a file path.\n *\n * @param {String} filePath\n *\n * @returns {String}\n */\nfunction getExtension (filePath) {\n  return path.extname(filePath).replace('.', '')\n}\n\n/**\n * Strip HTML tags from a string.\n *\n * @param {String} text - A text containing HTML tags.\n *\n * @returns {String}\n */\nfunction stripTags (text) {\n  return text.replace(/<[^>]+>/g, '');\n}\n\n/* Media objects **************************************************************/\n\n/**\n * Hold some data about a folder and its title.\n */\nclass FolderTitle {\n  constructor({ title, subtitle, folderName, path, hasPraesentation }) {\n    /**\n     * @type {String}\n     */\n    if (title) this.title = title\n\n    /**\n     * @type {String}\n     */\n    if (subtitle) this.subtitle = subtitle\n\n    /**\n     * for example `10_Konzertierende-Musiker`\n     *\n     * @type {String}\n     */\n    if (folderName) this.folderName = folderName\n\n    /**\n     * The relative path of the folder inside the base path, for example\n     * `12/10_Interpreten/10_Konzertierende-Musiker`.\n     *\n     * @type {String}\n     */\n    if (path) this.path = path\n\n    /**\n     * True if the folder contains a file with the file name\n     * `Praesentation.baldr.yml`\n     *\n     * @type {Booelan}\n     */\n    if (hasPraesentation) this.hasPraesentation = hasPraesentation\n  }\n}\n\n/**\n * Hold metadata about a folder in a hierachical folder structure.\n *\n * ```js\n * HierarchicalFolderTitles {\n *   titles_: [\n *     FolderTitle {\n *       path: '06',\n *       title: '6. Jahrgangsstufe',\n *       folderName: '06'\n *     },\n *     FolderTitle {\n *       path: '06/20_Mensch-Zeit',\n *       title: 'Lernbereich 2: Musik - Mensch - Zeit',\n *       folderName: '20_Mensch-Zeit'\n *     },\n *     FolderTitle {\n *       path: '06/20_Mensch-Zeit/10_Bach',\n *       title: 'Johann Sebastian Bach: Musik als Bekenntnis',\n *       folderName: '10_Bach'\n *     },\n *     FolderTitle {\n *       path: '06/20_Mensch-Zeit/10_Bach/40_Bachs-vergebliche-Reise',\n *       title: 'Johann Sebastian Bachs Reise nach Berlin 1747',\n *       folderName: '40_Bachs-vergebliche-Reise'\n *     }\n *   ]\n * }\n * ```\n */\nclass HierarchicalFolderTitles {\n  /**\n   * @param {String} filePath - The path of a file in a folder with `title.txt`\n   *   files.\n   */\n  constructor (filePath) {\n    this.titles_ = []\n    this.read_(filePath)\n  }\n\n  /**\n   * Parse the `title.txt` text file. The first line of this file contains\n   * the title, the second lines contains the subtitle.\n   *\n   * @param {String} filePath - The absolute path of a `title.txt` file.\n   *\n   * @private\n   */\n  readTitleTxt_ (filePath) {\n    const titleRaw = fs.readFileSync(filePath, { encoding: 'utf-8' })\n    const titles = titleRaw.split('\\n')\n    const folderTitle = new FolderTitle({})\n    if (titles.length > 0) {\n      folderTitle.title = titles[0]\n    }\n    if (titles.length > 1 && titles[1]) {\n      folderTitle.subtitle = titles[1]\n    }\n    if (fs.existsSync(path.join(path.dirname(filePath), 'Praesentation.baldr.yml'))) {\n      folderTitle.hasPraesentation = true\n    }\n    return folderTitle\n  }\n\n  /**\n   * Read all `title.txt` files. Descend to all parent folders which contain\n   * a `title.txt` file.\n   *\n   * @param {String} filePath - The path of the presentation file.\n   *\n   * @private\n   */\n  read_ (filePath) {\n    // We need absolute paths. The cli gives us relative paths.\n    filePath = path.resolve(filePath)\n    // ['', 'var', 'data', 'baldr', 'media', '12', ..., 'Praesentation.baldr.yml']\n    const segments = filePath.split(path.sep)\n    // 10, 11\n    const depth = segments.length\n    // 5\n    const minDepth = basePath.split(path.sep).length\n    // To build the path property of the FolderTitle class.\n    const folderNames = []\n    for (let index = minDepth + 1; index < depth; index++) {\n      let folderName = segments[index - 1]\n      folderNames.push(folderName)\n      // [ '', 'var', 'data', 'baldr', 'media', '05' ]\n      const pathSegments = segments.slice(0, index)\n      // /var/data/baldr/media/05/title.txt\n      // /var/data/baldr/media/05/20_Mensch-Zeit/title.txt\n      // /var/data/baldr/media/05/20_Mensch-Zeit/10_Mozart/title.txt\n      // /var/data/baldr/media/05/20_Mensch-Zeit/10_Mozart/20_Biographie-Salzburg-Wien/title.txt\n      const titleTxt = [...pathSegments, 'title.txt'].join(path.sep)\n      if (fs.existsSync(titleTxt)) {\n        const folderTitle = this.readTitleTxt_(titleTxt)\n        folderTitle.path = folderNames.join(path.sep)\n        folderTitle.folderName = folderName\n        this.titles_.push(folderTitle)\n      }\n    }\n  }\n\n  /**\n   * An array of title strings.\n   *\n   * @type {array}\n   * @private\n   */\n  get titlesArray_() {\n    return this.titles_.map(folderTitle => folderTitle.title)\n  }\n\n  /**\n   * @private\n   */\n  get lastFolderTitleObject_ () {\n    return this.titles_[this.titles_.length - 1]\n  }\n\n  /**\n   * All titles concatenated with ` / ` (Include the first and the last title)\n   * without the subtitles.\n   *\n   *\n   * for example:\n   *\n   * 6. Jahrgangsstufe / Lernbereich 2: Musik - Mensch - Zeit /\n   * Johann Sebastian Bach: Musik als Bekenntnis /\n   * Johann Sebastian Bachs Reise nach Berlin 1747\n   *\n   * @returns {string}\n   */\n  get allTitles () {\n    return this.titlesArray_.join(' / ')\n  }\n\n  /**\n   * Not the first and last title as a array.\n   *\n   * @type {Array}\n   */\n  get curriculumTitlesArray() {\n    return this.titlesArray_.slice(1, this.titles_.length - 1)\n  }\n\n  /**\n   * Not the title of the first and the last folder.\n   *\n   * ```js\n   * HierarchicalFolderTitles {\n   *   titles_: [\n   *     FolderTitle {\n   *       title: '6. Jahrgangsstufe'\n   *     },\n   *     FolderTitle {\n   *       title: 'Lernbereich 2: Musik - Mensch - Zeit'\n   *     },\n   *     FolderTitle {\n   *       title: 'Johann Sebastian Bach: Musik als Bekenntnis'\n   *     },\n   *     FolderTitle {\n   *       title: 'Johann Sebastian Bachs Reise nach Berlin 1747'\n   *     }\n   *   ]\n   * }\n   * ```\n   *\n   * -> Lernbereich 2: Musik - Mensch - Zeit / Johann Sebastian Bach: Musik als Bekenntnis\n   *\n   */\n  get curriculum () {\n    return this.curriculumTitlesArray.join(' / ')\n  }\n\n  /**\n   * @returns {string}\n   */\n  get id () {\n    return this.lastFolderTitleObject_.folderName.replace(/\\d\\d_/, '')\n  }\n\n  /**\n   * The title. It is the first line in the text file `title.txt` in the\n   * same folder as the constructor `filePath` file.\n   *\n   * @returns {string}\n   */\n  get title () {\n    return this.lastFolderTitleObject_.title\n  }\n\n  /**\n   * The subtitle. It is the second line in the text file `title.txt` in the\n   * same folder as the constructor `filePath` file.\n   *\n   * @returns {string}\n   */\n  get subtitle () {\n    if (this.lastFolderTitleObject_.subtitle) {\n      return this.lastFolderTitleObject_.subtitle\n    }\n  }\n\n  /**\n   * The first folder level in the hierachical folder structure must be named\n   * with numbers.\n   *\n   * @returns {string}\n   */\n  get grade () {\n    return this.titles_[0].title.replace(/[^\\d]+$/, '')\n  }\n\n  /**\n   * List all `FolderTitle()` objects.\n   *\n   * @returns {Array}\n   */\n  list() {\n    return this.titles_\n  }\n}\n\n/**\n *\n */\nclass FolderTitleTree {\n  constructor () {\n    this.tree_ = {}\n  }\n\n  /**\n   *\n   * @param {HierarchicalFolderTitles} folderTitles\n   */\n  add (folderTitles) {\n    let tmp = this.tree_\n    let count = 1\n    for (const title of folderTitles.list()) {\n      if (!(title.folderName in tmp)) {\n        tmp[title.folderName] = {\n          _title: title\n        }\n        tmp[title.folderName]._title.level = count\n      }\n      tmp = tmp[title.folderName]\n      count += 1\n    }\n  }\n\n  get () {\n    return this.tree_\n  }\n}\n\nconst folderTitleTree = new FolderTitleTree()\n\nconst assetTypes = new AssetTypes(config)\n\n/**\n * Base class to be extended.\n */\nclass MediaFile {\n  constructor (filePath) {\n    /**\n     * Absolute path ot the file.\n     * @type {string}\n     * @private\n     */\n    this.absPath_ = path.resolve(filePath)\n\n    /**\n     * Relative path ot the file.\n     * @type {string}\n     */\n    this.path = filePath.replace(basePath, '').replace(/^\\//, '')\n\n    /**\n     * The basename (filename) of the file.\n     * @type {string}\n     */\n    this.filename = path.basename(filePath)\n  }\n\n  /**\n   * @private\n   */\n  addFileInfos_ () {\n    const stats = fs.statSync(this.absPath_)\n\n    /**\n     * The file size in bytes.\n     * @type {number}\n     */\n    this.size = stats.size\n\n    /**\n     * The timestamp indicating the last time this file was modified\n     * expressed in milliseconds since the POSIX Epoch.\n     * @type {float}\n     */\n    this.timeModified = stats.mtimeMs\n\n    /**\n     * The extension of the file.\n     * @type {string}\n     */\n    this.extension = getExtension(this.absPath_)\n\n    /**\n     * The basename (filename without extension) of the file.\n     * @type {string}\n     * @private\n     */\n    this.basename_ = path.basename(this.absPath_, `.${this.extension}`)\n\n    return this\n  }\n\n  /**\n   * Parse the info file of a media asset or the presenation file itself.\n   *\n   * Each media file can have a info file that stores additional\n   * metadata informations.\n   *\n   * File path:\n   * `/home/baldr/beethoven.jpg`\n   *\n   * Info file in the YAML file format:\n   * `/home/baldr/beethoven.jpg.yml`\n   *\n   * @param {string} filePath - The path of the YAML file.\n   *\n   * @returns {object}\n   *\n   * @private\n   */\n  readYaml_ (filePath) {\n    if (fs.existsSync(filePath)) {\n      return yaml.safeLoad(fs.readFileSync(filePath, 'utf8'))\n    }\n    return {}\n  }\n\n  /**\n   * Add metadata from the file system, like file size or timeModifed.\n   */\n  addFileInfos () {\n    return this.addFileInfos_()\n  }\n\n  /**\n   *\n   */\n  cleanTmpProperties () {\n    for (const property in this) {\n      if (property.match(/_$/)) {\n        delete this[property]\n      }\n    }\n    return this\n  }\n\n  /**\n   * Merge an object into the class object. Property can be in the `snake_case`\n   * or `kebab-case` form. They are converted in to `camelCase` in recursive fashin.\n   *\n   * @param {object} properties - Add an object to the class properties.\n   */\n  mergeObject (object) {\n    if (typeof object === 'object') {\n      convertPropertiesToCamelCase(object)\n      for (const property in object) {\n        this[property] = object[property]\n      }\n    }\n  }\n\n  /**\n   * Prepare the object for the insert into the MongoDB database\n   * Generate `id` and `title` if this properties are not present.\n   */\n  prepareForInsert () {\n    this.addFileInfos()\n    if (!this.id) this.id = asciify(this.basename_)\n    if (!this.title) this.title = deasciify(this.id)\n    this.cleanTmpProperties()\n    return this\n  }\n}\n\n/**\n * This class is used both for the entries in the MongoDB database as well for\n * the queries.\n */\nclass Asset extends MediaFile {\n  /**\n   * @param {string} filePath - The file path of the media file.\n   */\n  constructor (filePath) {\n    super(filePath)\n\n    /**\n     * The absolute path of the info file in the YAML format. On the absolute\n     * media file path `.yml` is appended.\n     * @type {string}\n     */\n    this.infoFile_ = `${this.absPath_}.yml`\n\n    const data = this.readYaml_(this.infoFile_)\n    this.mergeObject(data)\n  }\n\n  addFileInfos () {\n    this.addFileInfos_()\n    const previewImage = `${this.absPath_}_preview.jpg`\n    this.assetType = assetTypes.extensionToType(this.extension)\n    if (fs.existsSync(previewImage)) {\n      /**\n       * The absolute path of the preview image.\n       * @type {string}\n       */\n      this.previewImage = true\n    }\n    this.detectMultiparts_()\n    return this\n  }\n\n  /**\n   * Search for mutlipart assets. The naming scheme of multipart assets is:\n   * `filename.jpg`, `filename_no02.jpg`, `filename_no03.jpg`\n   */\n  detectMultiparts_ () {\n    const nextAssetFileName = (count) => {\n      let suffix\n      if (count < 10) {\n        suffix = `_no0${count}`\n      } else if (count < 100) {\n        suffix = `_no${count}`\n      } else {\n        throw new Error(`${this.absPath_} multipart asset counts greater than 100 are not supported.`)\n      }\n      const basePath = this.absPath_.replace(`.${this.extension}`, '')\n      const fileName = `${basePath}${suffix}.${this.extension}`\n      return fileName\n    }\n    let count = 2\n    while (fs.existsSync(nextAssetFileName(count))) {\n      count += 1\n    }\n    count -= 1 // The counter is increased before the file system check.\n    if (count > 1) {\n      /**\n       * The count of parts of a multipart asset.\n       *\n       * @type {Number}\n       */\n      this.multiPartCount = count\n    }\n  }\n}\n\n/**\n * The whole presentation YAML file converted to an Javascript object. All\n * properties are in `camelCase`.\n */\nclass Presentation extends MediaFile {\n  constructor (filePath) {\n    super(filePath)\n    const data = this.readYaml_(filePath)\n    if (data) this.mergeObject(data)\n\n    const folderTitles = new HierarchicalFolderTitles(filePath)\n    folderTitleTree.add(folderTitles)\n\n    if (typeof this.meta === 'undefined') this.meta = {}\n    for (const property of ['id', 'title', 'subtitle', 'curriculum', 'grade']) {\n      if (typeof this.meta[property] === 'undefined') this.meta[property] = folderTitles[property]\n    }\n\n    /**\n     * The plain text version of `this.meta.title`.\n     *\n     * @type {String}\n     */\n    this.title = stripTags(this.meta.title)\n\n    /**\n     * The plain text version of `this.meta.title (this.meta.subtitle)`\n     *\n     * @type {String}\n     */\n    this.titleSubtitle = this.titleSubtitle_()\n\n  /**\n   * The plain text version of `folderTitles.allTitles\n   * (this.meta.subtitle)`\n   *\n   * For example:\n   *\n   * 6. Jahrgangsstufe / Lernbereich 2: Musik - Mensch - Zeit /\n   * Johann Sebastian Bach: Musik als Bekenntnis /\n   * Johann Sebastian Bachs Reise nach Berlin 1747 (Ricercar a 3)\n   *\n   * @returns {String}\n   * @private\n   */\n    this.allTitlesSubtitle = this.allTitlesSubtitle_ (folderTitles)\n\n    /**\n     * Value is the same as `meta.id`\n     *\n     * @type {String}\n     */\n    this.id = this.meta.id\n  }\n\n  /**\n   * Generate the plain text version of `this.meta.title (this.meta.subtitle)`\n   *\n   * @returns {String}\n   * @private\n   */\n  titleSubtitle_ () {\n    if (this.meta.subtitle) {\n      return `${this.title} (${stripTags(this.meta.subtitle)})`\n    } else {\n      return this.title\n    }\n  }\n\n  /**\n   * Generate the plain text version of `folderTitles.allTitles\n   * (this.meta.subtitle)`\n   *\n   * For example:\n   *\n   * 6. Jahrgangsstufe / Lernbereich 2: Musik - Mensch - Zeit /\n   * Johann Sebastian Bach: Musik als Bekenntnis /\n   * Johann Sebastian Bachs Reise nach Berlin 1747 (Ricercar a 3)\n   *\n   * @returns {String}\n   * @private\n   */\n  allTitlesSubtitle_ (folderTitles) {\n    let all = folderTitles.allTitles\n    if (this.meta.subtitle) {\n      all = `${all} (${this.meta.subtitle})`\n    }\n    return stripTags(all)\n  }\n}\n\n/* Checks *********************************************************************/\n\n/**\n * Check if the given file is a media asset.\n *\n * @param {String} fileName\n */\nfunction isAsset (fileName) {\n  if (\n    fileName.indexOf('_preview.jpg') > -1 || // Preview image\n    fileName.match(/_no\\d\\d\\./) // Multipart asset\n  ) {\n    return false\n  }\n  return assetTypes.isAsset(fileName)\n}\n\n/**\n * Check if the given file is a presentation.\n *\n * @param {String} fileName\n */\nfunction isPresentation (fileName) {\n  if (fileName.indexOf('Praesentation.baldr.yml') > -1) {\n    return true\n  }\n  return false\n}\n\n/* Insert *********************************************************************/\n\n/**\n * @param {String} filePath\n */\nasync function insertObjectIntoDb (filePath, mediaType) {\n  let object\n  try {\n    if (mediaType === 'presentations') {\n      object = new Presentation(filePath)\n    } else if (mediaType === 'assets') {\n      object = new Asset(filePath)\n    }\n    object = object.prepareForInsert()\n    console.log(object.path)\n    await db.collection(mediaType).insertOne(object)\n  } catch (error) {\n    console.log(error)\n    let relPath = filePath.replace(config.mediaServer.basePath, '')\n    relPath = relPath.replace(new RegExp('^/'), '')\n    const msg = `${relPath}: [${error.name}] ${error.message}`\n    console.log(msg)\n    errors.push(msg)\n  }\n}\n\n/**\n * @param {string} dir\n * @param {object} on - An object with callbacks. Properties: `presentation`,\n *   `asset`, `all` (`presentation`, `asset`), `everyFile`.\n */\nasync function walk (dir, on) {\n  const files = fs.readdirSync(dir)\n  for (const fileName of files) {\n    const relPath = path.join(dir, fileName)\n    // Exclude .git/\n    if (fs.existsSync(relPath) && fileName.substr(0, 4) !== '.git') {\n      if (fs.statSync(relPath).isDirectory()) {\n        await walk(relPath, on)\n      } else {\n        if ('everyFile' in on) await on.everyFile(relPath)\n        if (isPresentation(fileName)) {\n          if ('presentation' in on) await on.presentation(relPath)\n          if ('all' in on) await on.all(relPath)\n        } else if (isAsset(fileName)) {\n          if ('asset' in on) await on.asset(relPath)\n          if ('all' in on) await on.all(relPath)\n        }\n      }\n    }\n  }\n}\n\n/**\n * Update the media server.\n *\n * @returns {Object}\n */\nasync function update (full = false) {\n  const gitSettings = {\n    cwd: basePath,\n    encoding: 'utf-8'\n  }\n\n  /**\n   * Run git pull on the `basePath`\n   */\n  function gitPull () {\n    console.log('Run git pull')\n    const gitPull = childProcess.spawnSync(\n      'git', ['pull'],\n      gitSettings\n    )\n    console.log(`git pull stderr: ${gitPull.stderr.replace(/\\n$/, '')}`)\n    console.log(`git pull stdout: ${gitPull.stdout.replace(/\\n$/, '')}`)\n    if (gitPull.status !== 0) throw new Error(`git pull exits with an non-zero status code.`)\n  }\n  if (full) gitPull()\n  const gitRevParse = childProcess.spawnSync('git', ['rev-parse', 'HEAD'], gitSettings)\n  const lastCommitId = gitRevParse.stdout.replace(/\\n$/, '')\n  console.log(`lastCommitId: ${lastCommitId}`)\n  await connectDb()\n  await initializeDb()\n  await flushMediaFiles()\n  const begin = new Date().getTime()\n  await db.collection('updates').insertOne({ begin: begin, end: 0 })\n  await walk(basePath, {\n    everyFile: (filePath) => {\n      if (filePath.match(/\\.(aux|out|log|synctex\\.gz|mscx,)$/)) {\n        console.log(`Delete temporary file ${filePath}`)\n        fs.unlinkSync(filePath)\n      }\n    },\n    presentation: async (filePath) => { await insertObjectIntoDb(filePath, 'presentations') },\n    asset: async (filePath) => { await insertObjectIntoDb(filePath, 'assets') }\n  })\n\n  // .replaceOne and upsert: Problems with merge objects?\n  await db.collection('folderTitleTree').deleteOne({ id: 'root' })\n  await db.collection('folderTitleTree').insertOne(\n    {\n      id: 'root',\n      tree: folderTitleTree.get()\n    }\n  )\n  const end = new Date().getTime()\n  await db.collection('updates').updateOne({ begin: begin }, { $set: { end: end, lastCommitId } })\n  return {\n    finished: true,\n    begin,\n    end,\n    duration: end - begin,\n    lastCommitId,\n    errors\n  }\n}\n\n/* MongoDb Management *********************************************************/\n\n/**\n * @returns {Promise}\n */\nasync function initializeDb () {\n  const assets = await db.createCollection('assets')\n  await assets.createIndex({ path: 1 }, { unique: true })\n  await assets.createIndex({ id: 1 }, { unique: true })\n\n  const presentations = await db.createCollection('presentations')\n  await presentations.createIndex({ id: 1 }, { unique: true })\n\n  const updates = await db.createCollection('updates')\n  await updates.createIndex({ begin: 1 })\n\n  // https://stackoverflow.com/a/35868933\n  const folderTitleTree = await db.createCollection('folderTitleTree')\n  await folderTitleTree.createIndex({ id: 1 }, { unique: true })\n\n  const result = {}\n  const collections = await db.listCollections().toArray()\n  for (const collection of collections) {\n    const indexes = await db.collection(collection.name).listIndexes().toArray()\n    result[collection.name] = {\n      name: collection.name,\n      indexes: {}\n    }\n    for (const index of indexes) {\n      result[collection.name].indexes[index.name] = index.unique\n    }\n  }\n  return result\n}\n\n/**\n * @returns {Promise}\n */\nasync function dropDb () {\n  const collections = await db.listCollections().toArray()\n  const droppedCollections = []\n  for (const collection of collections) {\n    await db.dropCollection(collection.name)\n    droppedCollections.push(collection.name)\n  }\n  return {\n    droppedCollections\n  }\n}\n\n/**\n * @returns {Promise}\n */\nasync function reInitializeDb () {\n  const resultdropDb = await dropDb()\n  const resultInitializeDb = await initializeDb()\n  return {\n    resultdropDb,\n    resultInitializeDb\n  }\n}\n\n/**\n * @returns {Promise}\n */\nasync function flushMediaFiles () {\n  await db.collection('assets').deleteMany({})\n  await db.collection('presentations').deleteMany({})\n}\n\n/* Express Rest API ***********************************************************/\n\n/**\n * This object hold jsons for displaying help messages in the browser on\n * some entry point urls.\n *\n * Update docs on the top of this file in the JSDoc block.\n *\n * @type {Object}\n */\nconst helpMessages = {\n  navigation: {\n    get: {\n      'folder-title-tree': 'Get the folder title tree as a hierarchical json object.'\n    },\n    mgmt: {\n      flush: 'Delete all media files (assets, presentations) from the database.',\n      init: 'Initialize the MongoDB database.',\n      open: {\n        '#description': 'Open a media file specified by an ID.',\n        '#examples': [\n          'mgmt/open?id=Beethoven_Egmont',\n          'mgmt/open?with=editor&id=Beethoven_Egmont',\n          'mgmt/open?with=editor&type=presentations&id=Beethoven_Egmont',\n          'mgmt/open?with=editor&type=assets&id=Beethoven',\n          'mgmt/open?with=folder&type=assets&id=Beethoven'\n        ],\n        '#parameters': {\n          id: 'The ID of the media file (required).',\n          type: '`presentations`, `assets`. The default value is `presentations.`',\n          with: '`editor` specified in `config.mediaServer.editor` (`/etc/baldr.json`) or `folder` to open the parent folder of the given media file. The default value is `editor`.'\n        }\n      },\n      're-init': 'Re-Initialize the MongoDB database (Drop all collections and initialize).',\n      update: 'Update the media server database (Flush and insert).'\n    },\n    query: {\n      '#description': 'Get results by using query parameters',\n      '#examples': [\n        'query?type=assets&field=id&method=exactMatch&search=Egmont-Ouverture',\n        'query?type=presentations&field=id&method=exactMatch&search=Beethoven_Marmotte',\n        'query?type=assets&field=path&method=substringSearch&search=35_Bilder-Ausstellung_Ueberblick&result=fullObjects',\n        'query?type=assets&field=path&method=substringSearch&search=35_Bilder-Ausstellung_Ueberblick&result=dynamicSelect'\n      ],\n      '#parameters': {\n        type: '`assets` (default), `presentations` (what)',\n        method: '`exactMatch`, `substringSearch` (default) (how). `exactMatch`: The query parameter `search` must be a perfect match to a top level database field to get a result. `substringSearch`: The query parameter `search` is only a substring of the string to search in.',\n        field: '`id` (default), `title`, etc ... (where).',\n        search: 'Some text to search for (search for).',\n        result: '`fullObjects` (default), `dynamicSelect`'\n      }\n    },\n    stats: {\n      count: 'Count / sum of the media files (assets, presentations) in the database.',\n      updates: 'Journal of the update processes with timestamps.'\n    }\n  }\n}\n\n/**\n * Throw an error if the media type is unkown. Provide a default value.\n *\n * @param {String} mediaType - At the moment `assets` and `presentation`\n *\n * @return {String}\n */\nfunction validateMediaType (mediaType) {\n  const mediaTypes = ['assets', 'presentations']\n  if (!mediaType) return 'assets'\n  if (!mediaTypes.includes(mediaType)) {\n    throw new Error(`Unkown media type “${mediaType}”! Allowed media types are: ${mediaTypes}`)\n  } else {\n    return mediaType\n  }\n}\n\n/**\n * Resolve a ID from a given media type (`assets`, `presentations`) to a\n * absolute path.\n *\n * @param {String} id - The id of the media type.\n * @param {String} mediaType - At the moment `assets` and `presentation`\n *\n * @return {String}\n */\nasync function getAbsPathFromId (id, mediaType = 'presentations') {\n  mediaType = validateMediaType(mediaType)\n  const result = await db.collection(mediaType).find({ id: id }).next()\n  if (!result) throw new Error(`Can not find media file with the type “${mediaType}” and the id “${id}”.`)\n  let relPath\n  if (mediaType === 'assets') {\n    relPath = `${result.path}.yml`\n  } else {\n    relPath = result.path\n  }\n  return path.join(config.mediaServer.basePath, relPath)\n}\n\n/**\n * Open a media file specified by an ID with an editor specified in\n *   `config.mediaServer.editor` (`/etc/baldr.json`).\n *\n * @param {String} id - The id of the media type.\n * @param {String} mediaType - At the moment `assets` and `presentation`\n *\n * @return {Object}\n */\nasync function openEditor (id, mediaType) {\n  const absPath = await getAbsPathFromId(id, mediaType)\n  const editor = config.mediaServer.editor\n  if (!fs.existsSync(editor)) {\n    return {\n      error: `Editor “${editor}” can’t be found.`\n    }\n  }\n  childProcess.spawn(editor, [absPath], {\n    env: {\n      // Not needed\n      //XAUTHORITY: '/run/user/1000/gdm/Xauthority',\n      DISPLAY: ':0'\n    }\n  })\n  return {\n    absPath,\n    editor\n  }\n}\n\n/**\n * Open the parent folder of a presentation, a media asset in a file explorer\n * GUI application.\n *\n * @param {String} id - The id of the media type.\n * @param {String} mediaType - At the moment `assets` and `presentation`\n *\n * @return {Object}\n */\nasync function openParentFolder (id, mediaType) {\n  const absPath = await getAbsPathFromId(id, mediaType)\n  const parentFolder = path.dirname(absPath)\n  childProcess.spawn('xdg-open', [parentFolder], {\n    env: {\n      // Not needed\n      //XAUTHORITY: '/run/user/1000/gdm/Xauthority',\n      DISPLAY: ':0'\n    }\n  })\n  return {\n    parentFolder\n  }\n}\n\n/**\n * Register the express js rest api in a giant function.\n */\nfunction registerRestApi () {\n  // https://stackoverflow.com/a/38427476/10193818\n  function escapeRegex (text) {\n    return text.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, '\\\\$&')\n  }\n\n  const app = express()\n\n  app.on('mount', async () => {\n    await connectDb()\n    await initializeDb()\n  })\n\n  app.get('/', (req, res) => {\n    res.json(helpMessages.navigation)\n  })\n\n  app.get('/version', (req, res) => {\n    res.json({\n      name: packageJson.name,\n      version: packageJson.version\n    })\n  })\n\n  /* query */\n\n  app.get('/query', async (req, res, next) => {\n    try {\n      const query = req.query\n      if (Object.keys(query).length === 0) {\n        res.status(500).send({\n          error: {\n            msg: 'Missing query parameters!',\n            navigationGuide: helpMessages.navigation.query\n          }\n        })\n        return\n      }\n      // type\n      query.type = validateMediaType(query.type)\n\n      // method\n      const methods = ['exactMatch', 'substringSearch']\n      if (!('method' in query)) query.method = 'substringSearch'\n      if (!methods.includes(query.method)) {\n        throw new Error(`Unkown method “${query.method}”! Allowed methods: ${methods}`)\n      }\n\n      // field\n      if (!('field' in query)) query.field = 'id'\n\n      // result\n      if (!('result' in query)) query.result = 'fullObjects'\n\n      await connectDb()\n      const collection = db.collection(query.type)\n\n      // find\n      let result\n      let find\n      // exactMatch\n      if (query.method === 'exactMatch') {\n        const findObject = {}\n        findObject[query.field] = query.search\n        find = collection.find(findObject, { projection: { _id: 0 } })\n        result = await find.next()\n      // substringSearch\n      } else if (query.method === 'substringSearch') {\n        // https://stackoverflow.com/a/38427476/10193818\n        const regex = new RegExp(escapeRegex(query.search), 'gi')\n        const $match = {}\n        $match[query.field] = regex\n        let $project\n        if (query.result === 'fullObjects') {\n          $project = {\n            _id: false\n          }\n        } else if (query.result === 'dynamicSelect') {\n          $project = {\n            _id: false,\n            id: true,\n            name: `$${query.field}`\n          }\n        }\n        find = collection.aggregate([{ $match }, { $project }])\n        result = await find.toArray()\n      }\n      res.json(result)\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  /* get */\n\n  app.get('/get/folder-title-tree', async (req, res, next) => {\n    try {\n      const result = await db.collection('folderTitleTree').find({ id: 'root' }, { projection: { _id: 0 } }).next()\n      res.json(result.tree)\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  /* mgmt = management */\n\n  app.get('/mgmt/flush', async (req, res, next) => {\n    try {\n      await flushMediaFiles()\n      res.json({ status: 'ok' })\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  app.get('/mgmt/init', async (req, res, next) => {\n    try {\n      res.json(await initializeDb())\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  app.get('/mgmt/open', async (req, res, next) => {\n    try {\n      const query = req.query\n      if (!query.id) throw new Error('You have to specify an ID (?id=myfile).')\n      if (!query.with) query.with = 'editor'\n      if (!query.type) query.type = 'presentations'\n      if (query.with === 'editor') {\n        res.json(await openEditor(query.id, query.type))\n      } else if (query.with === 'folder') {\n        res.json(await openParentFolder(query.id, query.type))\n      }\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  app.get('/mgmt/re-init', async (req, res, next) => {\n    try {\n      res.json(await reInitializeDb())\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  app.get('/mgmt/update', async (req, res, next) => {\n    try {\n      res.json(await update())\n      // Clear error message store.\n      errors = []\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  /* stats = statistics */\n\n  app.get('/stats/count', async (req, res, next) => {\n    try {\n      res.json({\n        assets: await db.collection('assets').countDocuments(),\n        presentations: await db.collection('presentations').countDocuments()\n      })\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  app.get('/stats/updates', async (req, res, next) => {\n    try {\n      res.json(await db.collection('updates')\n        .find({}, { projection: { _id: 0 } })\n        .sort({ begin: -1 })\n        .limit(20)\n        .toArray()\n      )\n    } catch (error) {\n      next(error)\n    }\n  })\n\n  return app\n}\n\nmodule.exports = {\n  asciify,\n  Asset,\n  assetTypes,\n  deasciify,\n  FolderTitleTree,\n  getExtension,\n  helpMessages,\n  HierarchicalFolderTitles,\n  registerRestApi,\n  walk\n}\n"]}