@poe-platform/safe-bash 0.1.811 → 0.1.813
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audio-ast/id3-source.d.ts +27 -0
- package/dist/audio-ast/index.d.ts +5 -0
- package/dist/audio-ast/index.js +1001 -596
- package/dist/audio-ast/mp3-source.d.ts +18 -0
- package/dist/audio-ast/ogg-source.d.ts +25 -0
- package/dist/audio-ast/ogg-stream-source.d.ts +22 -0
- package/dist/audio-ast/vorbis.d.ts +14 -0
- package/dist/image-ast/index.js +139 -9
- package/dist/mp4-ast/containers/adapters.d.ts +7 -0
- package/dist/office-xml/retained-xml-document.d.ts +8 -0
- package/dist/pdf-ast/ast.d.ts +2 -0
- package/dist/pdf-ast/content/dictionary-cursor.d.ts +15 -0
- package/dist/pdf-ast/content/range-operator-parser.d.ts +4 -0
- package/dist/pdf-ast/content/stored-dictionary.d.ts +10 -0
- package/dist/pdf-ast/cos/lexer.d.ts +8 -5
- package/dist/pdf-ast/cos/object-reader.d.ts +4 -3
- package/dist/pdf-ast/cos/range-parser.d.ts +4 -0
- package/dist/pdf-ast/cos/retained-objects.d.ts +1 -0
- package/dist/pdf-ast/cos/retained-writer.d.ts +1 -0
- package/dist/pdf-ast/cos/value-parser.d.ts +32 -2
- package/dist/pdf-ast/edit/retained-append-attachments.d.ts +13 -0
- package/dist/pdf-ast/edit/retained-bookmarks.d.ts +13 -0
- package/dist/pdf-ast/edit/retained-graph.d.ts +6 -0
- package/dist/pdf-ast/edit/retained-save.d.ts +2 -0
- package/dist/pdf-ast/edit/retained-stamps.d.ts +9 -0
- package/dist/pdf-ast/extract/retained-form-field-details.d.ts +20 -0
- package/dist/pdf-ast/extract/retained-form-fields.d.ts +17 -4
- package/dist/pdf-ast/fonts/resolve.d.ts +3 -1
- package/dist/pdf-ast/index.d.ts +4 -1
- package/dist/pdf-ast/index.js +2127 -975
- package/dist/pdf-ast/retained-document.d.ts +4 -0
- package/dist/safe-bash/audio-ast.browser.js +7 -1
- package/dist/safe-bash/chunks/{chunk-HHSOUC3Z.js → chunk-HPQE675Q.js} +1 -1
- package/dist/safe-bash/chunks/{chunk-BUYRKFW2.js → chunk-I6ECNJMR.js} +1 -1
- package/dist/safe-bash/chunks/{pdftk-WRYEP3CA.js → pdftk-Y2TX74LY.js} +2 -1
- package/dist/safe-bash/command-chunks/biff-4ALVARRF-7SRX6R6N.js +5192 -0
- package/dist/safe-bash/command-chunks/{chunk-EHSM5ZQS.js → chunk-2DGJOOXM.js} +8 -8
- package/dist/safe-bash/command-chunks/{chunk-Q6S57NOQ.js → chunk-2E2H7MBH.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-MMBZTYXO.js → chunk-2PQRZDNR.js} +221 -0
- package/dist/safe-bash/command-chunks/{chunk-5OO2P27R.js → chunk-2V6FOLXV.js} +78 -25
- package/dist/safe-bash/command-chunks/{chunk-56NYSX65.js → chunk-2Z3WRRFT.js} +9 -9
- package/dist/safe-bash/command-chunks/chunk-3D24WDND.js +63 -0
- package/dist/safe-bash/command-chunks/{chunk-47NEG4UO.js → chunk-3JST2Q7G.js} +219 -43
- package/dist/safe-bash/command-chunks/{chunk-WGG6G2KG.js → chunk-3LJAXFH5.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-AHA6IWAJ.js → chunk-3TYDSGAH.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-YSMNXMAO.js → chunk-3Y4XZB24.js} +4 -4
- package/dist/safe-bash/command-chunks/{chunk-ZIIO6GRY.js → chunk-4PBNMKAT.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-JFYWVQ4V.js → chunk-4Z6W5EOC.js} +8 -8
- package/dist/safe-bash/command-chunks/{chunk-GEM47B2P.js → chunk-57DDYKE5.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-HARUYHH7.js → chunk-5HUICAWD.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-4CKFNXRN.js → chunk-5IFRAI7Y.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-RHYJVTJO.js → chunk-5KGEZ3RW.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-A5FZBR7X.js → chunk-5MFDRSGG.js} +4 -4
- package/dist/safe-bash/command-chunks/{chunk-7GKKMNL6.js → chunk-5P6I2T63.js} +8 -8
- package/dist/safe-bash/command-chunks/{chunk-6M6TM5Z5.js → chunk-5VQVZPP3.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-MIRL7HTF.js → chunk-6244HYJY.js} +1 -1
- package/dist/safe-bash/command-chunks/chunk-63EMPQ7Z.js +115 -0
- package/dist/safe-bash/command-chunks/{chunk-XRVKWGVQ.js → chunk-6CY5BHBS.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-XVP6V5K3.js → chunk-6IWE7PTK.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-6QBMND4A.js → chunk-6SRHWVEB.js} +1 -1
- package/dist/safe-bash/command-chunks/chunk-6ZHQB453.js +35 -0
- package/dist/safe-bash/command-chunks/chunk-73NYFNAS.js +173 -0
- package/dist/safe-bash/command-chunks/{chunk-VU3EQF7F.js → chunk-7NXTEWPR.js} +229 -75
- package/dist/safe-bash/command-chunks/{chunk-554Z6IKP.js → chunk-7PRL6RJ6.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-SDTPO2VV.js → chunk-7PZHWMK6.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-XZ2FWFNV.js → chunk-7QZ2HP2F.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-OEJM666F.js → chunk-7UC2Q6Z6.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-COKYZUBW.js → chunk-AJI55RS7.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-2VCKUNDZ.js → chunk-B2C3UPJS.js} +27 -17
- package/dist/safe-bash/command-chunks/{chunk-WXRYDALR.js → chunk-BBAB7LC4.js} +4 -4
- package/dist/safe-bash/command-chunks/{chunk-QYMEASQ6.js → chunk-BEHUSUOY.js} +4 -4
- package/dist/safe-bash/command-chunks/{chunk-Z2JW6M2D.js → chunk-BK7ISUJL.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-33YCKZZX.js → chunk-C5KLFZGT.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-TVH4JORU.js → chunk-C5QDEB3I.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-N4R2SHSS.js → chunk-CCZZVCHX.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-2VLAXKTS.js → chunk-CTDC4LJT.js} +121 -85
- package/dist/safe-bash/command-chunks/{chunk-S6R3IB36.js → chunk-D55REQA3.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-Y4EYCZFT.js → chunk-D6WJY6QO.js} +10 -10
- package/dist/safe-bash/command-chunks/{chunk-2CORNDGF.js → chunk-DBLHCJJU.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-SPW4GW4Z.js → chunk-DFI7QF3M.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-456PHA3L.js → chunk-DMCI6T6N.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-TW73OXPR.js → chunk-EJTNVXWF.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-QD7ZRDUN.js → chunk-ET2VPDUA.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-5LI2DLNZ.js → chunk-EXOQGXEU.js} +26 -26
- package/dist/safe-bash/command-chunks/{chunk-CRV2Q3PB.js → chunk-EZ4NQIPL.js} +1 -1
- package/dist/safe-bash/command-chunks/chunk-FWKLBDMK.js +0 -0
- package/dist/safe-bash/command-chunks/{chunk-22MOB45U.js → chunk-FWPVQGYK.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-QC5RMGSA.js → chunk-G3RUXKVY.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-JYUJAF4L.js → chunk-G7NENPYV.js} +4 -0
- package/dist/safe-bash/command-chunks/{chunk-KCOJA7EM.js → chunk-GKILM4HZ.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-HTVIYLVB.js → chunk-GNXTMSYN.js} +4 -4
- package/dist/safe-bash/command-chunks/{chunk-D2ASPM7R.js → chunk-GOBUDKZ3.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-RNSKXOG3.js → chunk-GVWVRPG6.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-ZHRGYR7B.js → chunk-HCKYUQ3L.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-AGLK7OJR.js → chunk-HDSIIQBS.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-DM2CPMB4.js → chunk-HKOMXGJS.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-IT7TAC3F.js → chunk-HVW53ARH.js} +16 -16
- package/dist/safe-bash/command-chunks/{chunk-M7QE2MN7.js → chunk-I4GMCWAT.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-5MB64DWR.js → chunk-IHSOOSNC.js} +9 -9
- package/dist/safe-bash/command-chunks/{chunk-LCZDBW55.js → chunk-JAHJWVGF.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-MOZD7RUY.js → chunk-JORIVUPM.js} +27 -11
- package/dist/safe-bash/command-chunks/{chunk-PMAM4N33.js → chunk-JUJHYPYL.js} +9 -9
- package/dist/safe-bash/command-chunks/{chunk-ENT3IV2G.js → chunk-KBUBSMCQ.js} +12 -12
- package/dist/safe-bash/command-chunks/{chunk-ZXR6LXV5.js → chunk-KCE5D4Z4.js} +48 -4
- package/dist/safe-bash/command-chunks/{chunk-FIIAYCWS.js → chunk-KZ5ODST6.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-PNGDJA6M.js → chunk-LD7BONZY.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-KJ4XXZIL.js → chunk-M3Z5PU2M.js} +582 -59
- package/dist/safe-bash/command-chunks/{chunk-LXLLA4PM.js → chunk-MKX4JAZ6.js} +17 -17
- package/dist/safe-bash/command-chunks/{chunk-RQIQ75W5.js → chunk-MNHBQBF6.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-A2CLZMQZ.js → chunk-MPKY37QR.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-K5GB2YMN.js → chunk-MUUWBZIC.js} +10 -10
- package/dist/safe-bash/command-chunks/{chunk-JOCFI7TG.js → chunk-N32GSM3J.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-EUT24Y5Z.js → chunk-NKTULXVD.js} +8 -8
- package/dist/safe-bash/command-chunks/{chunk-5HPU7D36.js → chunk-NMPED6L5.js} +4 -4
- package/dist/safe-bash/command-chunks/{chunk-QZZ73J2O.js → chunk-NPFHUS4X.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-X7FA7ZD5.js → chunk-O675URF4.js} +8 -8
- package/dist/safe-bash/command-chunks/{chunk-R6EGGK5M.js → chunk-P7EL2FWW.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-YS25EI54.js → chunk-PCCFLPUQ.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-RRL3Y562.js → chunk-PI6MOMHA.js} +11 -11
- package/dist/safe-bash/command-chunks/{chunk-26VCZHLJ.js → chunk-PIOQBOEK.js} +1 -1
- package/dist/safe-bash/command-chunks/chunk-PK36TVXB.js +175 -0
- package/dist/safe-bash/command-chunks/{chunk-72C3TZ2U.js → chunk-PKLJRB7W.js} +264 -129
- package/dist/safe-bash/command-chunks/{chunk-6CCQI6XZ.js → chunk-PSCOXGCA.js} +12 -12
- package/dist/safe-bash/command-chunks/{chunk-XPD636C4.js → chunk-PVGZMLC6.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-JQVHUSDD.js → chunk-PXWS3Q7L.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-HDJVJTCK.js → chunk-QFF5KBOM.js} +9 -9
- package/dist/safe-bash/command-chunks/{chunk-MPUHH2KR.js → chunk-QM2HZJSJ.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-UAXSE3XE.js → chunk-R2BMZAHG.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-KB7DUBVL.js → chunk-RGOQKIG6.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-D2KZ66W4.js → chunk-RLCQ7BN3.js} +187 -39
- package/dist/safe-bash/command-chunks/{chunk-B5LDP7AA.js → chunk-S2N2V5U4.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-OA75SGN2.js → chunk-SGPCXAG7.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-Z5QOVRVH.js → chunk-SU26FZLJ.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-QUM3SYAJ.js → chunk-SYSX46IV.js} +3 -3
- package/dist/safe-bash/command-chunks/chunk-T3E5RV3U.js +737 -0
- package/dist/safe-bash/command-chunks/{chunk-YORJDX4V.js → chunk-T5ERNELL.js} +4 -4
- package/dist/safe-bash/command-chunks/{chunk-KL3OIBKB.js → chunk-T6C5QP7T.js} +6495 -6343
- package/dist/safe-bash/command-chunks/{chunk-ATTWQVR2.js → chunk-TKRLZIFI.js} +17 -17
- package/dist/safe-bash/command-chunks/{chunk-ZMHEGDU2.js → chunk-TODWFDYB.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-YSY6R6AS.js → chunk-TQEB4DH4.js} +703 -102
- package/dist/safe-bash/command-chunks/{chunk-36XGIPOG.js → chunk-U5TWQFZY.js} +1 -1
- package/dist/safe-bash/command-chunks/{chunk-APEWTWCC.js → chunk-UAF4IX5H.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-6VPQ3CDZ.js → chunk-V37D6AHO.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-RID6VYS3.js → chunk-V6XBEYPC.js} +8 -8
- package/dist/safe-bash/command-chunks/{chunk-WJCN2IXI.js → chunk-VQOINPVF.js} +2 -2
- package/dist/safe-bash/command-chunks/{chunk-W5YSINXC.js → chunk-VVWXY6IA.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-HKATEVAP.js → chunk-WPVBUVFK.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-UYWSIIUX.js → chunk-X53AEG36.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-L5MCW6CM.js → chunk-X64XGIXZ.js} +9 -155
- package/dist/safe-bash/command-chunks/{chunk-Q73EMLLZ.js → chunk-X6RHJ6YN.js} +84 -5
- package/dist/safe-bash/command-chunks/{chunk-7PVHO47B.js → chunk-XG6UWQV5.js} +6 -6
- package/dist/safe-bash/command-chunks/{chunk-NQDIT667.js → chunk-XIDGHIGB.js} +5 -5
- package/dist/safe-bash/command-chunks/{chunk-DPR5CYQV.js → chunk-XITDDJFT.js} +2 -0
- package/dist/safe-bash/command-chunks/{chunk-CV7M2QJU.js → chunk-XLYIVBAR.js} +7 -7
- package/dist/safe-bash/command-chunks/{chunk-WVMHRNXZ.js → chunk-YUXTK3XA.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-SPLOIABU.js → chunk-YXD6VGVG.js} +3 -3
- package/dist/safe-bash/command-chunks/{chunk-2ZFLMWOX.js → chunk-Z5BIM5B7.js} +1 -1
- package/dist/safe-bash/command-chunks/chunk-ZYLGNIGA.js +156 -0
- package/dist/safe-bash/command-chunks/{command-engine-BEWABZ75.js → command-engine-X3OLN7N6.js} +206 -86
- package/dist/safe-bash/command-chunks/{engine-AXXFTGCL-QBQNBI6K.js → engine-XUCIFZ7V-LJFGZM6O.js} +1 -1
- package/dist/safe-bash/command-chunks/paradox-4FCZE53D-LQPGVMEL.js +365 -0
- package/dist/safe-bash/commands/pandoc/index.browser.js +1 -1
- package/dist/safe-bash/full-core.browser.js +2 -2
- package/dist/safe-bash/plugins/index.browser.js +2 -2
- package/dist/safe-bash-command-apply-patch/apply.js +4 -4
- package/dist/safe-bash-command-apply-patch/index.js +4 -4
- package/dist/safe-bash-command-apply-patch/matcher.js +2 -2
- package/dist/safe-bash-command-apply-patch/parser.js +2 -2
- package/dist/safe-bash-command-apply-patch/shared.d.ts +2 -0
- package/dist/safe-bash-command-apply-patch/shared.js +1 -1
- package/dist/safe-bash-command-diff/diff.js +1 -1
- package/dist/safe-bash-command-diff/index.js +1 -1
- package/dist/safe-bash-command-ffmpeg/index.js +1 -1
- package/dist/safe-bash-command-ffprobe/index.js +2 -2
- package/dist/safe-bash-command-ffprobe/media.d.ts +3 -1
- package/dist/safe-bash-command-ffprobe/media.js +2 -2
- package/dist/safe-bash-command-html-to-markdown/budget.js +2 -1
- package/dist/safe-bash-command-html-to-markdown/entities.js +4 -3
- package/dist/safe-bash-command-html-to-markdown/index.js +564 -110
- package/dist/safe-bash-command-html-to-markdown/input.js +6 -5
- package/dist/safe-bash-command-html-to-markdown/options.js +2 -1
- package/dist/safe-bash-command-html-to-markdown/parser.js +5 -4
- package/dist/safe-bash-command-html-to-markdown/render.js +6 -5
- package/dist/safe-bash-command-html-to-markdown/text.js +3 -2
- package/dist/safe-bash-command-htmlq/index.js +361 -37
- package/dist/safe-bash-command-htmlq/tree.d.ts +3 -2
- package/dist/safe-bash-command-pandoc/command.js +7 -7
- package/dist/safe-bash-command-pandoc/index.js +55 -55
- package/dist/safe-bash-command-patch/index.js +6 -6
- package/dist/safe-bash-command-patch/patch-envelope.js +53 -3
- package/dist/safe-bash-command-patch/patch-formats.d.ts +2 -2
- package/dist/safe-bash-command-patch/patch-formats.js +2 -2
- package/dist/safe-bash-command-patch/patch-gnu-reject.d.ts +2 -1
- package/dist/safe-bash-command-patch/patch-gnu-reject.js +4 -3
- package/dist/safe-bash-command-patch/patch-publication.js +2 -1
- package/dist/safe-bash-command-patch/patch.js +6 -6
- package/dist/safe-bash-command-patch/unified.d.ts +10 -2
- package/dist/safe-bash-command-patch/unified.js +1 -1
- package/dist/safe-bash-command-pdftk/index.d.ts +6 -2
- package/dist/safe-bash-command-pdftk/index.js +835 -243
- package/dist/safe-bash-command-pptx/command-engine.d.ts +15 -3
- package/dist/safe-bash-command-pptx/engine.d.ts +2 -2
- package/dist/safe-bash-command-pptx/engine.js +1 -1
- package/dist/safe-bash-command-pptx/index.js +9 -2
- package/dist/safe-bash-command-soffice/index.js +1 -1
- package/dist/safe-bash-command-ssconvert/index.js +2 -2
- package/dist/safe-bash-command-xmllint/index.js +22 -31
- package/dist/safe-bash-diff-engine/document.d.ts +2 -1
- package/dist/safe-bash-diff-engine/document.js +7 -4
- package/dist/safe-bash-presentation-engine/accessibility.js +31 -30
- package/dist/safe-bash-presentation-engine/animation-editing.js +16 -15
- package/dist/safe-bash-presentation-engine/animations.js +16 -15
- package/dist/safe-bash-presentation-engine/backgrounds.js +17 -16
- package/dist/safe-bash-presentation-engine/bytes.js +3 -3
- package/dist/safe-bash-presentation-engine/chart-editing.js +34 -33
- package/dist/safe-bash-presentation-engine/chart-object-operations.js +25 -24
- package/dist/safe-bash-presentation-engine/charts.js +31 -30
- package/dist/safe-bash-presentation-engine/command-placeholder.js +59 -55
- package/dist/safe-bash-presentation-engine/comments.js +17 -16
- package/dist/safe-bash-presentation-engine/connector-operations.js +32 -31
- package/dist/safe-bash-presentation-engine/connectors.js +29 -28
- package/dist/safe-bash-presentation-engine/content-types.js +4 -4
- package/dist/safe-bash-presentation-engine/creation.js +6 -6
- package/dist/safe-bash-presentation-engine/diff.js +32 -31
- package/dist/safe-bash-presentation-engine/drawing-enums.d.ts +10 -10
- package/dist/safe-bash-presentation-engine/drawing-format.js +20 -19
- package/dist/safe-bash-presentation-engine/drawing-operations.js +31 -30
- package/dist/safe-bash-presentation-engine/drawing-schema.js +21 -20
- package/dist/safe-bash-presentation-engine/equations.js +17 -16
- package/dist/safe-bash-presentation-engine/errors.js +2 -2
- package/dist/safe-bash-presentation-engine/fields.js +17 -16
- package/dist/safe-bash-presentation-engine/font-metrics.js +3 -3
- package/dist/safe-bash-presentation-engine/image-extraction.js +20 -19
- package/dist/safe-bash-presentation-engine/image-formatting.js +32 -31
- package/dist/safe-bash-presentation-engine/image-insertion.js +18 -17
- package/dist/safe-bash-presentation-engine/image-replacement.js +21 -20
- package/dist/safe-bash-presentation-engine/image-value.js +4 -4
- package/dist/safe-bash-presentation-engine/images.js +19 -18
- package/dist/safe-bash-presentation-engine/index.d.ts +6 -2
- package/dist/safe-bash-presentation-engine/index.js +112 -97
- package/dist/safe-bash-presentation-engine/layouts.js +16 -15
- package/dist/safe-bash-presentation-engine/length.js +3 -3
- package/dist/safe-bash-presentation-engine/links.js +16 -15
- package/dist/safe-bash-presentation-engine/masters.js +15 -14
- package/dist/safe-bash-presentation-engine/media-editing.js +24 -23
- package/dist/safe-bash-presentation-engine/media-extraction.js +17 -16
- package/dist/safe-bash-presentation-engine/media.js +16 -15
- package/dist/safe-bash-presentation-engine/memberships.js +15 -14
- package/dist/safe-bash-presentation-engine/notes.js +16 -15
- package/dist/safe-bash-presentation-engine/ole-enum.js +4 -4
- package/dist/safe-bash-presentation-engine/ole-insertion.js +20 -19
- package/dist/safe-bash-presentation-engine/opaque-objects.js +9 -9
- package/dist/safe-bash-presentation-engine/package-reader.js +5 -5
- package/dist/safe-bash-presentation-engine/package-tools.d.ts +3 -0
- package/dist/safe-bash-presentation-engine/package-tools.js +27 -17
- package/dist/safe-bash-presentation-engine/package-uri.js +3 -3
- package/dist/safe-bash-presentation-engine/package-writer.js +4 -4
- package/dist/safe-bash-presentation-engine/presentation-model.js +58 -54
- package/dist/safe-bash-presentation-engine/presentation-settings.js +14 -13
- package/dist/safe-bash-presentation-engine/properties.js +16 -15
- package/dist/safe-bash-presentation-engine/relationships.js +4 -4
- package/dist/safe-bash-presentation-engine/resource-limits.js +1 -1
- package/dist/safe-bash-presentation-engine/retained-archive-staging.d.ts +19 -0
- package/dist/safe-bash-presentation-engine/retained-extraction-output.d.ts +18 -0
- package/dist/safe-bash-presentation-engine/retained-inspection.js +14 -12
- package/dist/safe-bash-presentation-engine/retained-package-extraction.d.ts +22 -0
- package/dist/safe-bash-presentation-engine/retained-package-packing.d.ts +19 -0
- package/dist/safe-bash-presentation-engine/retained-package.d.ts +2 -0
- package/dist/safe-bash-presentation-engine/retained-package.js +6 -4
- package/dist/safe-bash-presentation-engine/retained-slide-publication.d.ts +24 -0
- package/dist/safe-bash-presentation-engine/retained-text.js +19 -17
- package/dist/safe-bash-presentation-engine/retained-xml-parts.d.ts +5 -1
- package/dist/safe-bash-presentation-engine/retained-xml-publication.d.ts +18 -0
- package/dist/safe-bash-presentation-engine/retained-xml-replacement.d.ts +13 -0
- package/dist/safe-bash-presentation-engine/sanitize.js +17 -16
- package/dist/safe-bash-presentation-engine/selectors.js +10 -10
- package/dist/safe-bash-presentation-engine/shape-groups.js +31 -30
- package/dist/safe-bash-presentation-engine/shape-operations.js +30 -29
- package/dist/safe-bash-presentation-engine/shape-path-operations.js +32 -31
- package/dist/safe-bash-presentation-engine/shape-paths.js +16 -15
- package/dist/safe-bash-presentation-engine/shape-presets.js +3 -3
- package/dist/safe-bash-presentation-engine/shape-selection.js +31 -30
- package/dist/safe-bash-presentation-engine/shape-transforms.js +18 -17
- package/dist/safe-bash-presentation-engine/shapes.d.ts +1 -1
- package/dist/safe-bash-presentation-engine/shapes.js +27 -26
- package/dist/safe-bash-presentation-engine/slide-copy.js +17 -16
- package/dist/safe-bash-presentation-engine/slide-import.js +18 -17
- package/dist/safe-bash-presentation-engine/slide-merge-split.js +20 -19
- package/dist/safe-bash-presentation-engine/slide-model.js +43 -42
- package/dist/safe-bash-presentation-engine/slide-removal.js +18 -17
- package/dist/safe-bash-presentation-engine/slide-transfer-budget.js +9 -9
- package/dist/safe-bash-presentation-engine/slides.d.ts +1 -0
- package/dist/safe-bash-presentation-engine/slides.js +23 -17
- package/dist/safe-bash-presentation-engine/table-operations.js +33 -32
- package/dist/safe-bash-presentation-engine/table-spans.js +32 -31
- package/dist/safe-bash-presentation-engine/tables.js +31 -30
- package/dist/safe-bash-presentation-engine/tags.js +16 -15
- package/dist/safe-bash-presentation-engine/template-bindings.js +33 -32
- package/dist/safe-bash-presentation-engine/template-repeat.js +40 -39
- package/dist/safe-bash-presentation-engine/text-fitting.js +27 -26
- package/dist/safe-bash-presentation-engine/text-frames.js +27 -26
- package/dist/safe-bash-presentation-engine/text-paragraphs.js +27 -26
- package/dist/safe-bash-presentation-engine/text-reading.js +11 -11
- package/dist/safe-bash-presentation-engine/text-replacement.js +17 -16
- package/dist/safe-bash-presentation-engine/text-runs.js +20 -19
- package/dist/safe-bash-presentation-engine/themes.js +16 -15
- package/dist/safe-bash-presentation-engine/transitions.js +16 -15
- package/dist/safe-bash-presentation-engine/validation.js +10 -9
- package/dist/safe-bash-presentation-engine/xml-parts.d.ts +2 -0
- package/dist/safe-bash-presentation-engine/xml-parts.js +23 -15
- package/dist/safe-bash-presentation-engine/xml-view.js +3 -3
- package/dist/safe-bash-presentation-engine/xml.js +3 -3
- package/dist/safe-bash-regex-engine/index.js +1 -1
- package/dist/safe-bash-regex-engine/python-glob.js +1 -1
- package/dist/safe-bash-regex-engine/text/regex.d.ts +19 -0
- package/dist/safe-bash-regex-engine/text/regex.js +1 -1
- package/dist/safe-bash-xml-engine/document.js +3 -3
- package/dist/safe-bash-xml-engine/evaluate.js +1 -1
- package/dist/safe-bash-xml-engine/index.d.ts +1 -0
- package/dist/safe-bash-xml-engine/index.js +8 -4
- package/dist/safe-bash-xml-engine/query.js +2 -2
- package/dist/safe-bash-xml-engine/recovery.d.ts +10 -0
- package/dist/safe-bash-xml-engine/stored-document.d.ts +1 -1
- package/dist/safe-bash-xml-engine/stored-document.js +1 -1
- package/dist/safe-bash-xml-engine/stored-evaluate.js +1 -1
- package/dist/spreadsheet-engine/formulas/functions/python-printf.js +56 -33
- package/dist/spreadsheet-format-xls/biff-encrypted-properties.d.ts +2 -2
- package/dist/spreadsheet-format-xls/biff-encrypted-properties.js +86 -24
- package/dist/spreadsheet-format-xls/biff-metadata.d.ts +1 -0
- package/dist/spreadsheet-format-xls/biff-metadata.js +2 -2
- package/dist/spreadsheet-format-xls/biff-properties-merge.d.ts +6 -2
- package/dist/spreadsheet-format-xls/biff-properties-merge.js +413 -296
- package/dist/spreadsheet-format-xls/biff-properties-write.js +40 -23
- package/dist/spreadsheet-format-xls/biff-properties.js +3 -68
- package/dist/spreadsheet-format-xls/biff-property-index.d.ts +23 -0
- package/dist/spreadsheet-format-xls/biff-property-index.js +93 -0
- package/dist/spreadsheet-format-xls/biff-property-name.d.ts +4 -0
- package/dist/spreadsheet-format-xls/biff-property-name.js +114 -0
- package/dist/spreadsheet-format-xls/biff-property-text.d.ts +6 -0
- package/dist/spreadsheet-format-xls/biff-property-text.js +68 -0
- package/dist/spreadsheet-format-xls/biff-property-transcode.d.ts +5 -0
- package/dist/spreadsheet-format-xls/biff-property-transcode.js +45 -0
- package/dist/spreadsheet-format-xls/biff-write-metadata.js +10 -1
- package/package.json +6 -3
- package/dist/safe-bash/command-chunks/biff-3EFLKBAH-NYWZOYQS.js +0 -4942
- package/dist/safe-bash/command-chunks/chunk-5Q22ZLNC.js +0 -127
- package/dist/safe-bash/command-chunks/chunk-JKLOBSMP.js +0 -56
- package/dist/safe-bash/command-chunks/chunk-JYTESTBS.js +0 -99
- package/dist/safe-bash/command-chunks/chunk-WH3TIOTS.js +0 -326
- package/dist/safe-bash/command-chunks/paradox-ZWWXYY4J-JYOSWSBL.js +0 -362
- package/dist/spreadsheet-format-xls/biff-properties-layout.js +0 -56
package/dist/audio-ast/index.js
CHANGED
|
@@ -74,6 +74,92 @@ function duration(samples, sampleRate) {
|
|
|
74
74
|
return samples / sampleRate;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// packages/audio-ast/src/ogg-stream.ts
|
|
78
|
+
function oggIdentification(head, size = head.length) {
|
|
79
|
+
const r = new Reader(head);
|
|
80
|
+
if (head.length >= 8 && r.text(0, 8) === "OpusHead") {
|
|
81
|
+
if (size < 19 || r.u8(8) > 15) throw new Error("Invalid Opus identification");
|
|
82
|
+
const channels = r.u8(9), preSkip = r.u16(10, true), mapping = r.u8(18);
|
|
83
|
+
if (mapping === 0 && channels > 2) throw new Error("Invalid Opus channels");
|
|
84
|
+
if (mapping !== 0) r.check(19, 2 + channels);
|
|
85
|
+
return { codec: "opus", sampleRate: 48e3, channels, preSkip };
|
|
86
|
+
}
|
|
87
|
+
if (head.length >= 7 && r.u8(0) === 1 && r.text(1, 6) === "vorbis") {
|
|
88
|
+
if (size !== 30 || r.u32(7, true) !== 0 || !(r.u8(29) & 1)) throw new Error("Invalid Vorbis identification");
|
|
89
|
+
return { codec: "vorbis", sampleRate: r.u32(12, true), channels: r.u8(11), preSkip: 0 };
|
|
90
|
+
}
|
|
91
|
+
throw new Error("Unsupported Ogg codec");
|
|
92
|
+
}
|
|
93
|
+
function oggStream(identification, final, size) {
|
|
94
|
+
const { codec, sampleRate, channels, preSkip } = identification;
|
|
95
|
+
if (!channels || !sampleRate) throw new Error("Invalid Ogg audio stream");
|
|
96
|
+
if (final > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("Ogg granule exceeds safe precision");
|
|
97
|
+
const samples = Math.max(0, Number(final) - preSkip), duration2 = samples / sampleRate;
|
|
98
|
+
return { codec, sampleRate, channels, samples, duration: duration2, bitrate: duration2 ? size * 8 / duration2 : 0 };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// packages/audio-ast/src/ogg-stream-source.ts
|
|
102
|
+
async function probeOggStreamSource(input, options = {}) {
|
|
103
|
+
const check = (source, offset2, length) => {
|
|
104
|
+
options.signal?.throwIfAborted();
|
|
105
|
+
if (!Number.isSafeInteger(source.size) || source.size < 0 || offset2 < 0 || length < 0 || length > source.size - offset2)
|
|
106
|
+
throw new Error("Truncated or invalid audio structure");
|
|
107
|
+
};
|
|
108
|
+
const read = async (source, offset2, length) => {
|
|
109
|
+
check(source, offset2, length);
|
|
110
|
+
const bytes = new Uint8Array(length);
|
|
111
|
+
for (let at = 0; at < length; ) {
|
|
112
|
+
const part = await source.read(offset2 + at, length - at);
|
|
113
|
+
options.signal?.throwIfAborted();
|
|
114
|
+
if (!part.length || part.length > length - at) throw new Error("Truncated or invalid audio structure");
|
|
115
|
+
bytes.set(part, at);
|
|
116
|
+
at += part.length;
|
|
117
|
+
}
|
|
118
|
+
await options.checkpoint?.();
|
|
119
|
+
options.signal?.throwIfAborted();
|
|
120
|
+
return new Reader(bytes);
|
|
121
|
+
};
|
|
122
|
+
options.signal?.throwIfAborted();
|
|
123
|
+
if (!Number.isSafeInteger(input.size) || input.size < 0 || input.granule < 0n) throw new RangeError("Invalid Ogg stream size or granule");
|
|
124
|
+
check(input.head, 0, 0);
|
|
125
|
+
const identification = oggIdentification((await read(input.head, 0, Math.min(input.head.size, 276))).bytes, input.head.size);
|
|
126
|
+
const { comments } = input, opus = identification.codec === "opus", prefix = opus ? 8 : 7;
|
|
127
|
+
const missing = opus ? "Missing OpusTags" : "Missing Vorbis comments";
|
|
128
|
+
if (!comments) throw new Error(missing);
|
|
129
|
+
const marker = await read(comments, 0, prefix);
|
|
130
|
+
if (opus ? marker.text(0, 8) !== "OpusTags" : marker.u8(0) !== 3 || marker.text(1, 6) !== "vorbis") throw new Error(missing);
|
|
131
|
+
const vendorLength = (await read(comments, prefix, 4)).u32(0, true);
|
|
132
|
+
check(comments, prefix + 4, vendorLength);
|
|
133
|
+
let offset = prefix + 4 + vendorLength;
|
|
134
|
+
const count = (await read(comments, offset, 4)).u32(0, true);
|
|
135
|
+
offset += 4;
|
|
136
|
+
if (count > (comments.size - offset) / 4) throw new Error("Invalid Vorbis comment count");
|
|
137
|
+
const tags = {};
|
|
138
|
+
for (let i = 0; i < count; i++) {
|
|
139
|
+
const length = (await read(comments, offset, 4)).u32(0, true);
|
|
140
|
+
offset += 4;
|
|
141
|
+
check(comments, offset, length);
|
|
142
|
+
if (options.onComment) {
|
|
143
|
+
await options.onComment({ offset, length });
|
|
144
|
+
options.signal?.throwIfAborted();
|
|
145
|
+
} else {
|
|
146
|
+
const decoder = new TextDecoder();
|
|
147
|
+
let text2 = "";
|
|
148
|
+
for (let at = 0; at < length; at += 16384) text2 += decoder.decode((await read(comments, offset + at, Math.min(16384, length - at))).bytes, { stream: true });
|
|
149
|
+
text2 += decoder.decode();
|
|
150
|
+
const split = text2.indexOf("=");
|
|
151
|
+
if (split > 0) {
|
|
152
|
+
const key = text2.slice(0, split), value = text2.slice(split + 1);
|
|
153
|
+
tags[key] = tags[key] ? `${tags[key]};${value}` : value;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
offset += length;
|
|
157
|
+
}
|
|
158
|
+
if (!opus && (offset === comments.size || !((await read(comments, offset, 1)).u8(0) & 1)))
|
|
159
|
+
throw new Error("Missing Vorbis comment framing bit");
|
|
160
|
+
return { tags, ...oggStream(identification, input.granule, input.size) };
|
|
161
|
+
}
|
|
162
|
+
|
|
77
163
|
// packages/audio-ast/src/vorbis.ts
|
|
78
164
|
var names = {
|
|
79
165
|
TITLE: "title",
|
|
@@ -158,98 +244,880 @@ function pictureBase64(picture) {
|
|
|
158
244
|
function addVorbisComment(tags, value) {
|
|
159
245
|
const split = value.indexOf("=");
|
|
160
246
|
if (split > 0) {
|
|
161
|
-
const
|
|
247
|
+
const name = normalizeVorbisCommentKey(value.slice(0, split));
|
|
162
248
|
tags[name] = tags[name] ? `${tags[name]}
|
|
163
249
|
${value.slice(split + 1)}` : value.slice(split + 1);
|
|
164
250
|
}
|
|
165
251
|
}
|
|
252
|
+
function normalizeVorbisCommentKey(value) {
|
|
253
|
+
const key = value.toUpperCase();
|
|
254
|
+
return names[key] ?? key;
|
|
255
|
+
}
|
|
166
256
|
|
|
167
|
-
// packages/audio-ast/src/
|
|
168
|
-
function
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const header = r.u8(offset), type = header & 127, size = r.u24(offset + 1);
|
|
174
|
-
last = !!(header & 128);
|
|
175
|
-
const data = r.slice(offset + 4, size), node = {
|
|
176
|
-
type: [
|
|
177
|
-
"STREAMINFO",
|
|
178
|
-
"PADDING",
|
|
179
|
-
"APPLICATION",
|
|
180
|
-
"SEEKTABLE",
|
|
181
|
-
"VORBIS_COMMENT",
|
|
182
|
-
"CUESHEET",
|
|
183
|
-
"PICTURE"
|
|
184
|
-
][type] ?? `BLOCK_${type}`,
|
|
185
|
-
offset,
|
|
186
|
-
size: size + 4,
|
|
187
|
-
data
|
|
188
|
-
};
|
|
189
|
-
nodes.push(node);
|
|
190
|
-
const block = new Reader(data);
|
|
191
|
-
if (type === 0) {
|
|
192
|
-
if (stream || offset !== 4 || size !== 34)
|
|
193
|
-
throw new Error("FLAC requires first unique STREAMINFO");
|
|
194
|
-
stream = flacStream(block, bytes.length);
|
|
195
|
-
const { sampleRate, channels, bitsPerSample, samples } = stream;
|
|
196
|
-
node.fields = {
|
|
197
|
-
minBlockSize: block.u16(0),
|
|
198
|
-
maxBlockSize: block.u16(2),
|
|
199
|
-
minFrameSize: block.u24(4),
|
|
200
|
-
maxFrameSize: block.u24(7),
|
|
201
|
-
sampleRate,
|
|
202
|
-
channels,
|
|
203
|
-
bitsPerSample,
|
|
204
|
-
totalSamples: samples,
|
|
205
|
-
md5: Array.from(block.slice(18, 16), (b) => b.toString(16).padStart(2, "0")).join("")
|
|
206
|
-
};
|
|
207
|
-
} else if (type === 4) {
|
|
208
|
-
const parsed = parseComments(data);
|
|
209
|
-
if (parsed.size !== size) throw new Error("Trailing Vorbis comment bytes");
|
|
210
|
-
Object.assign(tags, parsed.tags);
|
|
211
|
-
node.fields = { vendor: parsed.vendor, comments: parsed.comments };
|
|
212
|
-
} else if (type === 6) pictures.push(parsePicture(data));
|
|
213
|
-
else if (type === 3) {
|
|
214
|
-
if (size % 18) throw new Error("Invalid FLAC seek table");
|
|
215
|
-
const points = [];
|
|
216
|
-
for (let pos = 0; pos < size; pos += 18)
|
|
217
|
-
points.push({
|
|
218
|
-
sample: block.u64(pos),
|
|
219
|
-
offset: block.u64(pos + 8),
|
|
220
|
-
samples: block.u16(pos + 16)
|
|
221
|
-
});
|
|
222
|
-
node.fields = { points };
|
|
223
|
-
} else if (type === 127) throw new Error("Invalid FLAC metadata block");
|
|
224
|
-
offset += 4 + size;
|
|
257
|
+
// packages/audio-ast/src/ogg.ts
|
|
258
|
+
function oggCrc(bytes, crc = 0, pageOffset = 0) {
|
|
259
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
260
|
+
const offset = pageOffset + i;
|
|
261
|
+
crc ^= (offset >= 22 && offset < 26 ? 0 : bytes[i]) << 24;
|
|
262
|
+
for (let bit = 0; bit < 8; bit++) crc = crc << 1 ^ (crc & 2147483648 ? 79764919 : 0);
|
|
225
263
|
}
|
|
226
|
-
|
|
227
|
-
|
|
264
|
+
return crc >>> 0;
|
|
265
|
+
}
|
|
266
|
+
function readOgg(bytes) {
|
|
267
|
+
const r = new Reader(bytes), nodes = [], packets = [], logical = /* @__PURE__ */ new Map();
|
|
268
|
+
for (let offset = 0; offset < bytes.length; ) {
|
|
269
|
+
if (r.text(offset, 4) !== "OggS" || r.u8(offset + 4) !== 0) throw new Error("Invalid Ogg page");
|
|
270
|
+
const flags = r.u8(offset + 5), granule = r.u64(offset + 6, true), serial = r.u32(offset + 14, true), sequence = r.u32(offset + 18, true), count = r.u8(offset + 26), laces = r.slice(offset + 27, count);
|
|
271
|
+
if (flags & ~7) throw new Error("Invalid Ogg page flags");
|
|
272
|
+
const size = 27 + count + laces.reduce((sum, n) => sum + n, 0), page = r.slice(offset, size);
|
|
273
|
+
if (oggCrc(page) !== r.u32(offset + 22, true)) throw new Error("Ogg checksum mismatch");
|
|
274
|
+
let state = logical.get(serial);
|
|
275
|
+
if (!state) {
|
|
276
|
+
if (!(flags & 2) || sequence !== 0) throw new Error("Missing Ogg beginning-of-stream");
|
|
277
|
+
state = { packets: [], pending: [], sequence: -1, ended: false };
|
|
278
|
+
logical.set(serial, state);
|
|
279
|
+
}
|
|
280
|
+
if (state.ended || sequence !== state.sequence + 1 >>> 0 || !!(flags & 1) !== !!state.pending.length)
|
|
281
|
+
throw new Error("Invalid Ogg sequence/continuation");
|
|
282
|
+
state.sequence = sequence;
|
|
283
|
+
const completed = [];
|
|
284
|
+
let pos = offset + 27 + count;
|
|
285
|
+
for (const lace of laces) {
|
|
286
|
+
state.pending.push(r.slice(pos, lace));
|
|
287
|
+
pos += lace;
|
|
288
|
+
if (lace < 255) {
|
|
289
|
+
const packet = {
|
|
290
|
+
data: join(state.pending),
|
|
291
|
+
serial,
|
|
292
|
+
granule: 0xffffffffffffffffn,
|
|
293
|
+
bos: state.packets.length === 0,
|
|
294
|
+
eos: false
|
|
295
|
+
};
|
|
296
|
+
state.pending = [];
|
|
297
|
+
state.packets.push(packet);
|
|
298
|
+
packets.push(packet);
|
|
299
|
+
completed.push(packet);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (completed.length) {
|
|
303
|
+
completed[completed.length - 1].granule = granule;
|
|
304
|
+
if (flags & 4) completed[completed.length - 1].eos = true;
|
|
305
|
+
}
|
|
306
|
+
if (flags & 4) {
|
|
307
|
+
if (state.pending.length) throw new Error("Incomplete final Ogg packet");
|
|
308
|
+
state.ended = true;
|
|
309
|
+
}
|
|
228
310
|
nodes.push({
|
|
229
|
-
type: "
|
|
311
|
+
type: "OggS",
|
|
230
312
|
offset,
|
|
231
|
-
size
|
|
232
|
-
data:
|
|
313
|
+
size,
|
|
314
|
+
data: page,
|
|
315
|
+
fields: {
|
|
316
|
+
flags,
|
|
317
|
+
granule,
|
|
318
|
+
serial,
|
|
319
|
+
sequence,
|
|
320
|
+
checksum: r.u32(offset + 22, true),
|
|
321
|
+
lacing: laces
|
|
322
|
+
}
|
|
233
323
|
});
|
|
324
|
+
offset += size;
|
|
325
|
+
}
|
|
326
|
+
if (!logical.size || Array.from(logical.values()).some((s) => s.pending.length || !s.ended))
|
|
327
|
+
throw new Error("Incomplete Ogg stream");
|
|
328
|
+
return { nodes, packets };
|
|
329
|
+
}
|
|
330
|
+
function encodeOgg(packets) {
|
|
331
|
+
const pages = [], sequences = /* @__PURE__ */ new Map(), pending = /* @__PURE__ */ new Map();
|
|
332
|
+
for (const packet of packets) {
|
|
333
|
+
const group = pending.get(packet.serial) ?? [];
|
|
334
|
+
group.push(packet);
|
|
335
|
+
pending.set(packet.serial, group);
|
|
336
|
+
if (packet.granule === 0xffffffffffffffffn && !packet.eos) continue;
|
|
337
|
+
const laces = [], granules = [], boundaries = /* @__PURE__ */ new Set();
|
|
338
|
+
for (const item of group) {
|
|
339
|
+
for (let j = 0; j < Math.floor(item.data.length / 255); j++) {
|
|
340
|
+
laces.push(255);
|
|
341
|
+
granules.push(0xffffffffffffffffn);
|
|
342
|
+
}
|
|
343
|
+
laces.push(item.data.length % 255);
|
|
344
|
+
granules.push(item.granule);
|
|
345
|
+
boundaries.add(laces.length);
|
|
346
|
+
}
|
|
347
|
+
const body = join(group.map((p) => p.data));
|
|
348
|
+
let offset = 0;
|
|
349
|
+
for (let start = 0; start < laces.length; start += 255) {
|
|
350
|
+
const part = laces.slice(start, start + 255), last = start + 255 >= laces.length, size = part.reduce((a, b) => a + b, 0), header = new Uint8Array(27 + part.length), v = new DataView(header.buffer);
|
|
351
|
+
header.set(ascii("OggS"));
|
|
352
|
+
header[5] = (start && !boundaries.has(start) ? 1 : 0) | (group[0].bos && start === 0 ? 2 : 0) | (packet.eos && last ? 4 : 0);
|
|
353
|
+
let granule = 0xffffffffffffffffn;
|
|
354
|
+
for (let i = start; i < start + part.length; i++)
|
|
355
|
+
if (laces[i] < 255) granule = granules[i] === 0xffffffffffffffffn ? 0n : granules[i];
|
|
356
|
+
v.setBigUint64(6, granule, true);
|
|
357
|
+
v.setUint32(14, packet.serial, true);
|
|
358
|
+
const sequence = sequences.get(packet.serial) ?? 0;
|
|
359
|
+
v.setUint32(18, sequence, true);
|
|
360
|
+
sequences.set(packet.serial, sequence + 1);
|
|
361
|
+
header[26] = part.length;
|
|
362
|
+
header.set(part, 27);
|
|
363
|
+
const page = join([header, body.subarray(offset, offset + size)]);
|
|
364
|
+
new DataView(page.buffer).setUint32(22, oggCrc(page), true);
|
|
365
|
+
pages.push(page);
|
|
366
|
+
offset += size;
|
|
367
|
+
}
|
|
368
|
+
pending.delete(packet.serial);
|
|
369
|
+
}
|
|
370
|
+
if (pending.size) throw new Error("Ogg packets lack a final page granule");
|
|
371
|
+
return join(pages);
|
|
372
|
+
}
|
|
373
|
+
function parseOgg(bytes) {
|
|
374
|
+
const { nodes, packets } = readOgg(bytes), streams = [], tags = {}, pictures = [];
|
|
375
|
+
const serials = [...new Set(packets.map((p) => p.serial))];
|
|
376
|
+
for (const serial of serials) {
|
|
377
|
+
const group = packets.filter((p) => p.serial === serial), identification = oggIdentification(group[0].data);
|
|
378
|
+
const comment = group[1]?.data;
|
|
379
|
+
let comments;
|
|
380
|
+
if (identification.codec === "opus") {
|
|
381
|
+
if (!comment || new Reader(comment).text(0, 8) !== "OpusTags") throw new Error("Missing OpusTags");
|
|
382
|
+
const parsed = parseComments(comment.subarray(8));
|
|
383
|
+
Object.assign(tags, parsed.tags);
|
|
384
|
+
comments = parsed.comments;
|
|
385
|
+
} else {
|
|
386
|
+
if (!comment || comment[0] !== 3 || new Reader(comment).text(1, 6) !== "vorbis") throw new Error("Missing Vorbis comments");
|
|
387
|
+
const parsed = parseComments(comment.subarray(7));
|
|
388
|
+
if (!(comment[7 + parsed.size] & 1)) throw new Error("Missing Vorbis comment framing bit");
|
|
389
|
+
Object.assign(tags, parsed.tags);
|
|
390
|
+
comments = parsed.comments;
|
|
391
|
+
}
|
|
392
|
+
const final = group.filter((p) => p.granule !== 0xffffffffffffffffn).at(-1)?.granule ?? 0n;
|
|
393
|
+
const size = nodes.filter((n) => n.fields?.serial === serial).reduce((sum, n) => sum + n.size, 0);
|
|
394
|
+
const streamTags = {};
|
|
395
|
+
for (const comment2 of comments) {
|
|
396
|
+
const split = comment2.indexOf("=");
|
|
397
|
+
if (split <= 0) continue;
|
|
398
|
+
const key = comment2.slice(0, split), value = comment2.slice(split + 1);
|
|
399
|
+
streamTags[key] = streamTags[key] ? `${streamTags[key]};${value}` : value;
|
|
400
|
+
}
|
|
401
|
+
streams.push({ tags: streamTags, ...oggStream(identification, final, size) });
|
|
402
|
+
}
|
|
403
|
+
for (const value of (tags.METADATA_BLOCK_PICTURE ?? "").split("\n").filter(Boolean)) {
|
|
404
|
+
pictures.push(parsePicture(Uint8Array.from(atob(value), (c) => c.charCodeAt(0))));
|
|
405
|
+
}
|
|
406
|
+
let duration2 = 0, groupDuration = 0;
|
|
407
|
+
const active = /* @__PURE__ */ new Set();
|
|
408
|
+
for (const node of nodes) {
|
|
409
|
+
const serial = node.fields.serial, flags = node.fields.flags;
|
|
410
|
+
if (flags & 2) {
|
|
411
|
+
if (!active.size) {
|
|
412
|
+
duration2 += groupDuration;
|
|
413
|
+
groupDuration = 0;
|
|
414
|
+
}
|
|
415
|
+
active.add(serial);
|
|
416
|
+
groupDuration = Math.max(groupDuration, streams[serials.indexOf(serial)].duration);
|
|
417
|
+
}
|
|
418
|
+
if (flags & 4) active.delete(serial);
|
|
419
|
+
}
|
|
420
|
+
duration2 += groupDuration;
|
|
234
421
|
return {
|
|
235
|
-
format: "
|
|
422
|
+
format: "ogg",
|
|
236
423
|
data: bytes,
|
|
237
424
|
nodes,
|
|
425
|
+
streams,
|
|
238
426
|
tags,
|
|
239
427
|
pictures,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
bitrate: stream.bitrate
|
|
428
|
+
duration: duration2,
|
|
429
|
+
bitrate: duration2 ? bytes.length * 8 / duration2 : 0
|
|
243
430
|
};
|
|
244
431
|
}
|
|
245
|
-
function flacStream(block, byteLength) {
|
|
246
|
-
const packed = block.u64(10), sampleRate = Number(packed >> 44n), channels = Number(packed >> 41n & 7n) + 1, bitsPerSample = Number(packed >> 36n & 31n) + 1, samples = Number(packed & 0xfffffffffn), seconds2 = duration(samples, sampleRate);
|
|
247
|
-
return { codec: "flac", sampleRate, channels, bitsPerSample, samples, duration: seconds2, bitrate: seconds2 ? byteLength * 8 / seconds2 : 0 };
|
|
248
|
-
}
|
|
249
432
|
|
|
250
|
-
// packages/audio-ast/src/
|
|
251
|
-
async function
|
|
252
|
-
|
|
433
|
+
// packages/audio-ast/src/ogg-source.ts
|
|
434
|
+
async function* scanOggPages(source, options = {}) {
|
|
435
|
+
options.signal?.throwIfAborted();
|
|
436
|
+
const retained = "size" in source ? source : void 0;
|
|
437
|
+
if (retained && (!Number.isSafeInteger(retained.size) || retained.size < 0)) throw new RangeError("Invalid Ogg source size");
|
|
438
|
+
const iterator = !retained && Symbol.asyncIterator in source ? source[Symbol.asyncIterator]() : void 0;
|
|
439
|
+
let offset = 0, position = 0, done = false;
|
|
440
|
+
let pending = new Uint8Array(0);
|
|
441
|
+
const read = async (length, eof = false) => {
|
|
442
|
+
const out = new Uint8Array(length);
|
|
443
|
+
let used = 0;
|
|
444
|
+
while (used < length) {
|
|
445
|
+
options.signal?.throwIfAborted();
|
|
446
|
+
let chunk;
|
|
447
|
+
if (retained) {
|
|
448
|
+
if (offset === retained.size && eof && used === 0) return out.subarray(0, 0);
|
|
449
|
+
if (length - used > retained.size - offset) throw new Error("Truncated Ogg page");
|
|
450
|
+
chunk = await retained.read(offset, length - used);
|
|
451
|
+
options.signal?.throwIfAborted();
|
|
452
|
+
if (!chunk.length) throw new Error("Truncated Ogg page");
|
|
453
|
+
if (chunk.length > length - used) throw new Error("Ogg source returned more bytes than requested");
|
|
454
|
+
} else {
|
|
455
|
+
while (position === pending.length) {
|
|
456
|
+
const next = await iterator.next();
|
|
457
|
+
options.signal?.throwIfAborted();
|
|
458
|
+
if (next.done) {
|
|
459
|
+
done = true;
|
|
460
|
+
if (eof && used === 0) return out.subarray(0, 0);
|
|
461
|
+
throw new Error("Truncated Ogg page");
|
|
462
|
+
}
|
|
463
|
+
pending = next.value;
|
|
464
|
+
position = 0;
|
|
465
|
+
if (!pending.length) {
|
|
466
|
+
await options.checkpoint?.();
|
|
467
|
+
options.signal?.throwIfAborted();
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const length2 = Math.min(out.length - used, pending.length - position);
|
|
471
|
+
chunk = pending.subarray(position, position + length2);
|
|
472
|
+
position += length2;
|
|
473
|
+
}
|
|
474
|
+
out.set(chunk, used);
|
|
475
|
+
used += chunk.length;
|
|
476
|
+
offset += chunk.length;
|
|
477
|
+
if (!Number.isSafeInteger(offset)) throw new RangeError("Invalid Ogg source size");
|
|
478
|
+
}
|
|
479
|
+
await options.checkpoint?.();
|
|
480
|
+
options.signal?.throwIfAborted();
|
|
481
|
+
return out;
|
|
482
|
+
};
|
|
483
|
+
let failed = false;
|
|
484
|
+
const retire = async () => {
|
|
485
|
+
if (iterator && !done) {
|
|
486
|
+
try {
|
|
487
|
+
await iterator.return?.();
|
|
488
|
+
} catch (error) {
|
|
489
|
+
if (!failed) throw error;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
try {
|
|
494
|
+
for (; ; ) {
|
|
495
|
+
const start = offset, header = await read(27, true);
|
|
496
|
+
if (!header.length) return;
|
|
497
|
+
const r = new Reader(header);
|
|
498
|
+
if (r.text(0, 4) !== "OggS" || r.u8(4) !== 0) throw new Error("Invalid Ogg page");
|
|
499
|
+
const flags = r.u8(5);
|
|
500
|
+
if (flags & ~7) throw new Error("Invalid Ogg page flags");
|
|
501
|
+
const lacing = await read(r.u8(26)), payloadOffset = offset;
|
|
502
|
+
let crc = oggCrc(header);
|
|
503
|
+
crc = oggCrc(lacing, crc, 27);
|
|
504
|
+
let remaining = 0;
|
|
505
|
+
for (const length of lacing) remaining += length;
|
|
506
|
+
while (remaining) {
|
|
507
|
+
const pageOffset = offset - start, chunk = await read(Math.min(16384, remaining));
|
|
508
|
+
crc = oggCrc(chunk, crc, pageOffset);
|
|
509
|
+
remaining -= chunk.length;
|
|
510
|
+
}
|
|
511
|
+
const checksum = r.u32(22, true);
|
|
512
|
+
if (crc !== checksum) throw new Error("Ogg checksum mismatch");
|
|
513
|
+
yield {
|
|
514
|
+
offset: start,
|
|
515
|
+
size: offset - start,
|
|
516
|
+
payloadOffset,
|
|
517
|
+
flags,
|
|
518
|
+
granule: r.u64(6, true),
|
|
519
|
+
serial: r.u32(14, true),
|
|
520
|
+
sequence: r.u32(18, true),
|
|
521
|
+
checksum,
|
|
522
|
+
lacing
|
|
523
|
+
};
|
|
524
|
+
}
|
|
525
|
+
} catch (error) {
|
|
526
|
+
failed = true;
|
|
527
|
+
throw error;
|
|
528
|
+
} finally {
|
|
529
|
+
await retire();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// packages/audio-ast/src/id3.ts
|
|
534
|
+
var names2 = {
|
|
535
|
+
TIT2: "title",
|
|
536
|
+
TPE1: "artist",
|
|
537
|
+
TPE2: "albumArtist",
|
|
538
|
+
TALB: "album",
|
|
539
|
+
TRCK: "track",
|
|
540
|
+
TPOS: "disc",
|
|
541
|
+
TCON: "genre",
|
|
542
|
+
TDRC: "date",
|
|
543
|
+
TYER: "date",
|
|
544
|
+
TCOP: "copyright",
|
|
545
|
+
TSSE: "encoder",
|
|
546
|
+
TT2: "title",
|
|
547
|
+
TP1: "artist",
|
|
548
|
+
TP2: "albumArtist",
|
|
549
|
+
TAL: "album",
|
|
550
|
+
TRK: "track",
|
|
551
|
+
TPA: "disc",
|
|
552
|
+
TCO: "genre",
|
|
553
|
+
TYE: "date",
|
|
554
|
+
TCR: "copyright",
|
|
555
|
+
TEN: "encoder"
|
|
556
|
+
};
|
|
557
|
+
function synchsafe(r, offset) {
|
|
558
|
+
let value = 0;
|
|
559
|
+
for (let i = 0; i < 4; i++) {
|
|
560
|
+
const b = r.u8(offset + i);
|
|
561
|
+
if (b & 128) throw new Error("Invalid ID3 synchsafe size");
|
|
562
|
+
value = value * 128 + b;
|
|
563
|
+
}
|
|
564
|
+
return value;
|
|
565
|
+
}
|
|
566
|
+
function unsync(bytes) {
|
|
567
|
+
const result = [];
|
|
568
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
569
|
+
const b = bytes[i];
|
|
570
|
+
result.push(b);
|
|
571
|
+
if (b === 255 && bytes[i + 1] === 0) i++;
|
|
572
|
+
}
|
|
573
|
+
return Uint8Array.from(result);
|
|
574
|
+
}
|
|
575
|
+
function id3TagName(id) {
|
|
576
|
+
return names2[id] ?? id;
|
|
577
|
+
}
|
|
578
|
+
function id3TextEncoding(encoding, bytes) {
|
|
579
|
+
if (encoding === 0) return "latin1";
|
|
580
|
+
if (encoding === 3) return "utf-8";
|
|
581
|
+
if (encoding === 2) return "utf-16be";
|
|
582
|
+
if (encoding === 1) return bytes[0] === 254 && bytes[1] === 255 ? "utf-16be" : "utf-16le";
|
|
583
|
+
throw new Error("Invalid ID3 text encoding");
|
|
584
|
+
}
|
|
585
|
+
function text(bytes, encoding) {
|
|
586
|
+
return cleanText(new TextDecoder(id3TextEncoding(encoding, bytes)).decode(bytes));
|
|
587
|
+
}
|
|
588
|
+
function terminated(bytes, start, encoding) {
|
|
589
|
+
const width = encoding === 1 || encoding === 2 ? 2 : 1;
|
|
590
|
+
for (let i = start; i + width <= bytes.length; i += width)
|
|
591
|
+
if (bytes[i] === 0 && (width === 1 || bytes[i + 1] === 0)) return i;
|
|
592
|
+
throw new Error("Unterminated ID3 text field");
|
|
593
|
+
}
|
|
594
|
+
function parseId3(bytes) {
|
|
595
|
+
const input = new Reader(bytes), version = input.u8(3), flags = input.u8(5), length = synchsafe(input, 6);
|
|
596
|
+
if (![2, 3, 4].includes(version)) throw new Error("Unsupported ID3 version");
|
|
597
|
+
if (version === 2 && flags & 64) throw new Error("Compressed ID3v2.2 tag unsupported");
|
|
598
|
+
const footer = version === 4 && flags & 16 ? 10 : 0;
|
|
599
|
+
input.check(0, 10 + length + footer);
|
|
600
|
+
const payload = input.slice(10, length), r = new Reader(flags & 128 && version < 4 ? unsync(payload) : payload);
|
|
601
|
+
const nodes = [], tags = {}, pictures = [];
|
|
602
|
+
let pos = 0;
|
|
603
|
+
if (version >= 3 && flags & 64) {
|
|
604
|
+
const extended = version === 4 ? synchsafe(r, 0) : r.u32(0) + 4;
|
|
605
|
+
r.check(0, extended);
|
|
606
|
+
if (extended < (version === 3 ? 10 : 6)) throw new Error("Invalid ID3 extended header");
|
|
607
|
+
pos = extended;
|
|
608
|
+
}
|
|
609
|
+
while (pos < r.bytes.length && r.u8(pos) !== 0) {
|
|
610
|
+
const header = version === 2 ? 6 : 10, id = r.text(pos, version === 2 ? 3 : 4), size = version === 2 ? r.u24(pos + 3) : version === 4 ? synchsafe(r, pos + 4) : r.u32(pos + 4);
|
|
611
|
+
const frameFlags = version === 2 ? 0 : r.u16(pos + 8);
|
|
612
|
+
let data = r.slice(pos + header, size);
|
|
613
|
+
if (version === 4 && (flags & 128 || frameFlags & 2)) data = unsync(data);
|
|
614
|
+
const node = {
|
|
615
|
+
type: id,
|
|
616
|
+
offset: 10 + pos,
|
|
617
|
+
size: header + size,
|
|
618
|
+
data,
|
|
619
|
+
fields: { flags: frameFlags, version }
|
|
620
|
+
};
|
|
621
|
+
nodes.push(node);
|
|
622
|
+
const unsupported = version === 3 ? frameFlags & 192 : frameFlags & 12;
|
|
623
|
+
if (!unsupported && data.length) {
|
|
624
|
+
let prefix = 0;
|
|
625
|
+
if (version === 3 && frameFlags & 32) prefix++;
|
|
626
|
+
if (version === 4 && frameFlags & 64) prefix++;
|
|
627
|
+
if (version === 4 && frameFlags & 1) prefix += 4;
|
|
628
|
+
data = data.subarray(prefix);
|
|
629
|
+
if (id.startsWith("T") && id !== "TXXX" && id !== "TXX")
|
|
630
|
+
tags[id3TagName(id)] = text(data.subarray(1), data[0]);
|
|
631
|
+
else if (id === "COMM" || id === "COM") {
|
|
632
|
+
const encoding = data[0], end = terminated(data, 4, encoding), width = encoding === 1 || encoding === 2 ? 2 : 1;
|
|
633
|
+
tags.comment = text(data.subarray(end + width), encoding);
|
|
634
|
+
} else if (id === "APIC" || id === "PIC") {
|
|
635
|
+
const encoding = data[0], mimeEnd = id === "PIC" ? 4 : terminated(data, 1, 0);
|
|
636
|
+
const mime = id === "PIC" ? `image/${new Reader(data).text(1, 3) === "JPG" ? "jpeg" : new Reader(data).text(1, 3).toLowerCase()}` : text(data.subarray(1, mimeEnd), 0);
|
|
637
|
+
const typeOffset = id === "PIC" ? 4 : mimeEnd + 1, descriptionStart = typeOffset + 1, end = terminated(data, descriptionStart, encoding), width = encoding === 1 || encoding === 2 ? 2 : 1;
|
|
638
|
+
pictures.push({
|
|
639
|
+
type: data[typeOffset],
|
|
640
|
+
mime,
|
|
641
|
+
description: text(data.subarray(descriptionStart, end), encoding),
|
|
642
|
+
data: data.subarray(end + width)
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
pos += header + size;
|
|
647
|
+
}
|
|
648
|
+
if (footer && input.text(10 + length, 3) !== "3DI") throw new Error("Missing ID3 footer");
|
|
649
|
+
return { size: 10 + length + footer, nodes, tags, pictures, version };
|
|
650
|
+
}
|
|
651
|
+
function syncBytes(value) {
|
|
652
|
+
if (value > 268435455) throw new Error("ID3 tag too large");
|
|
653
|
+
return Uint8Array.from([
|
|
654
|
+
value >>> 21 & 127,
|
|
655
|
+
value >>> 14 & 127,
|
|
656
|
+
value >>> 7 & 127,
|
|
657
|
+
value & 127
|
|
658
|
+
]);
|
|
659
|
+
}
|
|
660
|
+
function encodeId3(tags, retained = [], pictures = []) {
|
|
661
|
+
const frames = [];
|
|
662
|
+
function frame(id, data) {
|
|
663
|
+
frames.push(join([ascii(id), syncBytes(data.length), new Uint8Array(2), data]));
|
|
664
|
+
}
|
|
665
|
+
for (const node of retained) {
|
|
666
|
+
const flags = Number(node.fields?.flags ?? 0);
|
|
667
|
+
if (flags & (node.fields?.version === 3 ? 192 : 12)) {
|
|
668
|
+
throw new Error(`Cannot safely rewrite transformed ID3 frame ${node.type}`);
|
|
669
|
+
}
|
|
670
|
+
if (node.type === "APIC" || node.type === "PIC" || names2[node.type] || node.type.startsWith("T") && node.type !== "TXXX" && node.type !== "TXX" || node.type === "COMM" || node.type === "COM")
|
|
671
|
+
continue;
|
|
672
|
+
if (node.type.length === 4 && !node.fields?.flags) frame(node.type, node.data);
|
|
673
|
+
else throw new Error(`Cannot safely rewrite ID3 frame ${node.type}`);
|
|
674
|
+
}
|
|
675
|
+
for (const [name, value] of Object.entries(tags)) {
|
|
676
|
+
const id = Object.keys(names2).find((key) => key.length === 4 && names2[key] === name) ?? name;
|
|
677
|
+
if (name === "comment")
|
|
678
|
+
frame(
|
|
679
|
+
"COMM",
|
|
680
|
+
join([
|
|
681
|
+
new Uint8Array([3]),
|
|
682
|
+
ascii("eng"),
|
|
683
|
+
new Uint8Array(1),
|
|
684
|
+
new TextEncoder().encode(value)
|
|
685
|
+
])
|
|
686
|
+
);
|
|
687
|
+
else {
|
|
688
|
+
if (id.length !== 4 || !id.startsWith("T")) throw new Error(`Unsupported ID3 tag ${name}`);
|
|
689
|
+
frame(id, join([new Uint8Array([3]), new TextEncoder().encode(value)]));
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
for (const picture of pictures)
|
|
693
|
+
frame(
|
|
694
|
+
"APIC",
|
|
695
|
+
join([
|
|
696
|
+
new Uint8Array([3]),
|
|
697
|
+
ascii(picture.mime),
|
|
698
|
+
new Uint8Array([0, picture.type]),
|
|
699
|
+
new TextEncoder().encode(picture.description),
|
|
700
|
+
new Uint8Array(1),
|
|
701
|
+
picture.data
|
|
702
|
+
])
|
|
703
|
+
);
|
|
704
|
+
const body = join(frames);
|
|
705
|
+
return join([ascii("ID3"), new Uint8Array([4, 0, 0]), syncBytes(body.length), body]);
|
|
706
|
+
}
|
|
707
|
+
function parseId3v1(bytes) {
|
|
708
|
+
const r = new Reader(bytes), tags = {};
|
|
709
|
+
for (const [name, offset, size] of [
|
|
710
|
+
["title", 3, 30],
|
|
711
|
+
["artist", 33, 30],
|
|
712
|
+
["album", 63, 30],
|
|
713
|
+
["date", 93, 4],
|
|
714
|
+
["comment", 97, 30]
|
|
715
|
+
])
|
|
716
|
+
tags[name] = cleanText(r.text(offset, size));
|
|
717
|
+
if (r.u8(125) === 0 && r.u8(126) !== 0) {
|
|
718
|
+
tags.track = String(r.u8(126));
|
|
719
|
+
tags.comment = cleanText(r.text(97, 28));
|
|
720
|
+
}
|
|
721
|
+
tags.genre = String(r.u8(127));
|
|
722
|
+
return tags;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// packages/audio-ast/src/id3-source.ts
|
|
726
|
+
var Cursor = class {
|
|
727
|
+
constructor(source, position, end, unsynchronized, options) {
|
|
728
|
+
this.source = source;
|
|
729
|
+
this.position = position;
|
|
730
|
+
this.end = end;
|
|
731
|
+
this.unsynchronized = unsynchronized;
|
|
732
|
+
this.options = options;
|
|
733
|
+
if (!Number.isSafeInteger(source.size) || source.size < 0 || !Number.isSafeInteger(position) || !Number.isSafeInteger(end) || position < 0 || end < position || end > source.size)
|
|
734
|
+
throw new Error("Truncated or invalid audio structure");
|
|
735
|
+
}
|
|
736
|
+
source;
|
|
737
|
+
position;
|
|
738
|
+
end;
|
|
739
|
+
unsynchronized;
|
|
740
|
+
options;
|
|
741
|
+
cache = new Uint8Array();
|
|
742
|
+
cacheOffset = 0;
|
|
743
|
+
async fill() {
|
|
744
|
+
this.options.signal?.throwIfAborted();
|
|
745
|
+
if (this.position >= this.end) throw new Error("Truncated or invalid audio structure");
|
|
746
|
+
if (this.position >= this.cacheOffset && this.position < this.cacheOffset + this.cache.length) return;
|
|
747
|
+
await this.options.checkpoint?.();
|
|
748
|
+
this.options.signal?.throwIfAborted();
|
|
749
|
+
const length = Math.min(16384, this.end - this.position);
|
|
750
|
+
const bytes = await this.source.read(this.position, length);
|
|
751
|
+
this.options.signal?.throwIfAborted();
|
|
752
|
+
if (bytes.length !== length) throw new Error("Truncated or invalid audio structure");
|
|
753
|
+
this.cache = bytes.slice();
|
|
754
|
+
this.cacheOffset = this.position;
|
|
755
|
+
}
|
|
756
|
+
async read(length, partial = false) {
|
|
757
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > 16384) throw new Error("Invalid ID3 header read");
|
|
758
|
+
this.options.signal?.throwIfAborted();
|
|
759
|
+
const result = new Uint8Array(length);
|
|
760
|
+
let count = 0;
|
|
761
|
+
while (count < length && this.position < this.end) {
|
|
762
|
+
await this.fill();
|
|
763
|
+
if (!this.unsynchronized) {
|
|
764
|
+
const size = Math.min(length - count, this.cacheOffset + this.cache.length - this.position);
|
|
765
|
+
result.set(this.cache.subarray(this.position - this.cacheOffset, this.position - this.cacheOffset + size), count);
|
|
766
|
+
count += size;
|
|
767
|
+
this.position += size;
|
|
768
|
+
} else {
|
|
769
|
+
while (count < length && this.position < this.cacheOffset + this.cache.length) {
|
|
770
|
+
const byte = this.cache[this.position++ - this.cacheOffset];
|
|
771
|
+
result[count++] = byte;
|
|
772
|
+
if (byte === 255 && this.position < this.end) {
|
|
773
|
+
if (this.position === this.cacheOffset + this.cache.length) await this.fill();
|
|
774
|
+
if (this.cache[this.position - this.cacheOffset] === 0) this.position++;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
if (!partial && count !== length) throw new Error("Truncated or invalid audio structure");
|
|
780
|
+
return result.subarray(0, count);
|
|
781
|
+
}
|
|
782
|
+
async skip(length, partial = false) {
|
|
783
|
+
this.options.signal?.throwIfAborted();
|
|
784
|
+
if (!Number.isSafeInteger(length) || length < 0) throw new Error("Truncated or invalid audio structure");
|
|
785
|
+
if (!this.unsynchronized) {
|
|
786
|
+
if (!partial && length > this.end - this.position) throw new Error("Truncated or invalid audio structure");
|
|
787
|
+
this.position += Math.min(length, this.end - this.position);
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
while (length && this.position < this.end) {
|
|
791
|
+
const bytes = await this.read(Math.min(length, 16384), partial);
|
|
792
|
+
length -= bytes.length;
|
|
793
|
+
}
|
|
794
|
+
if (length && !partial) throw new Error("Truncated or invalid audio structure");
|
|
795
|
+
}
|
|
796
|
+
async terminated(start, encoding) {
|
|
797
|
+
const width = encoding === 1 || encoding === 2 ? 2 : 1;
|
|
798
|
+
await this.skip(start, true);
|
|
799
|
+
while (this.position < this.end) {
|
|
800
|
+
const bytes = await this.read(width, true);
|
|
801
|
+
if (bytes.length < width) break;
|
|
802
|
+
if (bytes[0] === 0 && (width === 1 || bytes[1] === 0)) return;
|
|
803
|
+
}
|
|
804
|
+
throw new Error("Unterminated ID3 text field");
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
async function* readId3Text(source, span, options = {}) {
|
|
808
|
+
const cursor = new Cursor(source, span.offset, span.offset + span.length, span.unsynchronized, options);
|
|
809
|
+
const first = await cursor.read(Math.min(2, span.length), true);
|
|
810
|
+
const decoder = new TextDecoder(id3TextEncoding(span.encoding, first));
|
|
811
|
+
yield decoder.decode(first, { stream: true });
|
|
812
|
+
while (cursor.position < cursor.end) yield decoder.decode(await cursor.read(16384, true), { stream: true });
|
|
813
|
+
options.signal?.throwIfAborted();
|
|
814
|
+
yield decoder.decode();
|
|
815
|
+
}
|
|
816
|
+
async function probeId3Source(source, options = {}) {
|
|
817
|
+
const input = new Cursor(source, 0, source.size, false, options), header = new Reader(await input.read(10));
|
|
818
|
+
const version = header.u8(3), flags = header.u8(5), length = synchsafe(header, 6);
|
|
819
|
+
if (![2, 3, 4].includes(version)) throw new Error("Unsupported ID3 version");
|
|
820
|
+
if (version === 2 && flags & 64) throw new Error("Compressed ID3v2.2 tag unsupported");
|
|
821
|
+
const footer = version === 4 && flags & 16 ? 10 : 0, size = 10 + length + footer;
|
|
822
|
+
if (size > source.size) throw new Error("Truncated or invalid audio structure");
|
|
823
|
+
const cursor = new Cursor(source, 10, 10 + length, !!(flags & 128 && version < 4), options), tags = {};
|
|
824
|
+
if (version >= 3 && flags & 64) {
|
|
825
|
+
const prefix = new Reader(await cursor.read(4)), extended = version === 4 ? synchsafe(prefix, 0) : prefix.u32(0) + 4;
|
|
826
|
+
if (extended >= 4) await cursor.skip(extended - 4);
|
|
827
|
+
if (extended < (version === 3 ? 10 : 6)) throw new Error("Invalid ID3 extended header");
|
|
828
|
+
}
|
|
829
|
+
while (cursor.position < cursor.end) {
|
|
830
|
+
options.signal?.throwIfAborted();
|
|
831
|
+
await options.checkpoint?.();
|
|
832
|
+
const first = await cursor.read(1);
|
|
833
|
+
if (first[0] === 0) break;
|
|
834
|
+
const headerSize = version === 2 ? 6 : 10, bytes = new Uint8Array(headerSize);
|
|
835
|
+
bytes.set(first);
|
|
836
|
+
bytes.set(await cursor.read(headerSize - 1), 1);
|
|
837
|
+
const r = new Reader(bytes), id = r.text(0, version === 2 ? 3 : 4);
|
|
838
|
+
const frameSize = version === 2 ? r.u24(3) : version === 4 ? synchsafe(r, 4) : r.u32(4), frameFlags = version === 2 ? 0 : r.u16(8);
|
|
839
|
+
const offset = cursor.position;
|
|
840
|
+
await cursor.skip(frameSize);
|
|
841
|
+
const frame = new Cursor(source, offset, cursor.position, cursor.unsynchronized || !!(version === 4 && (flags & 128 || frameFlags & 2)), options);
|
|
842
|
+
const unsupported = version === 3 ? frameFlags & 192 : frameFlags & 12;
|
|
843
|
+
if (unsupported || !frameSize) continue;
|
|
844
|
+
let prefix = 0;
|
|
845
|
+
if (version === 3 && frameFlags & 32) prefix++;
|
|
846
|
+
if (version === 4 && frameFlags & 64) prefix++;
|
|
847
|
+
if (version === 4 && frameFlags & 1) prefix += 4;
|
|
848
|
+
await frame.skip(prefix, true);
|
|
849
|
+
let key, encoding;
|
|
850
|
+
if (id.startsWith("T") && id !== "TXXX" && id !== "TXX") {
|
|
851
|
+
key = id3TagName(id);
|
|
852
|
+
encoding = (await frame.read(1, true))[0];
|
|
853
|
+
} else if (id === "COMM" || id === "COM") {
|
|
854
|
+
encoding = (await frame.read(1, true))[0];
|
|
855
|
+
await frame.terminated(3, encoding);
|
|
856
|
+
key = "comment";
|
|
857
|
+
} else if (id === "APIC" || id === "PIC") {
|
|
858
|
+
encoding = (await frame.read(1, true))[0];
|
|
859
|
+
if (id === "PIC") await frame.read(3);
|
|
860
|
+
else await frame.terminated(0, 0);
|
|
861
|
+
await frame.skip(1, true);
|
|
862
|
+
await frame.terminated(0, encoding);
|
|
863
|
+
id3TextEncoding(encoding, new Uint8Array());
|
|
864
|
+
}
|
|
865
|
+
if (key !== void 0) {
|
|
866
|
+
id3TextEncoding(encoding, new Uint8Array());
|
|
867
|
+
const span = { key, offset: frame.position, length: frame.end - frame.position, unsynchronized: frame.unsynchronized, encoding };
|
|
868
|
+
if (options.onTag) {
|
|
869
|
+
await options.onTag(span);
|
|
870
|
+
options.signal?.throwIfAborted();
|
|
871
|
+
} else {
|
|
872
|
+
let value = "";
|
|
873
|
+
for await (const text2 of readId3Text(source, span, options)) value += text2;
|
|
874
|
+
tags[key] = cleanText(value);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (footer) {
|
|
879
|
+
const tail = new Cursor(source, 10 + length, size, false, options);
|
|
880
|
+
if (new Reader(await tail.read(3)).text(0, 3) !== "3DI") throw new Error("Missing ID3 footer");
|
|
881
|
+
}
|
|
882
|
+
options.signal?.throwIfAborted();
|
|
883
|
+
return { size, version, tags };
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// packages/audio-ast/src/mpeg.ts
|
|
887
|
+
function mpegFrame(header, offset, end) {
|
|
888
|
+
const versionBits = header >>> 19 & 3, layer = header >>> 17 & 3, bitrateIndex = header >>> 12 & 15, rateIndex = header >>> 10 & 3;
|
|
889
|
+
if (header >>> 21 !== 2047 || versionBits === 1 || layer !== 1 || !bitrateIndex || bitrateIndex === 15 || rateIndex === 3)
|
|
890
|
+
throw new Error(`Invalid MPEG Layer III frame at ${offset}`);
|
|
891
|
+
const version = versionBits === 3 ? 1 : versionBits === 2 ? 2 : 2.5;
|
|
892
|
+
const rate = [44100, 48e3, 32e3][rateIndex] / (version === 1 ? 1 : version === 2 ? 2 : 4);
|
|
893
|
+
const bitrate = (version === 1 ? [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320] : [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160])[bitrateIndex] * 1e3;
|
|
894
|
+
const count = version === 1 ? 1152 : 576, frameSize = Math.floor((version === 1 ? 144 : 72) * bitrate / rate) + (header >>> 9 & 1), ch = header >>> 6 & 3;
|
|
895
|
+
if (frameSize > end - offset) throw new Error("Truncated MPEG frame");
|
|
896
|
+
const channels = ch === 3 ? 1 : 2;
|
|
897
|
+
const fields = {
|
|
898
|
+
version,
|
|
899
|
+
layer: 3,
|
|
900
|
+
bitrate,
|
|
901
|
+
sampleRate: rate,
|
|
902
|
+
channels,
|
|
903
|
+
samples: count,
|
|
904
|
+
padding: header >>> 9 & 1
|
|
905
|
+
};
|
|
906
|
+
return { version, rate, count, frameSize, channels, fields };
|
|
907
|
+
}
|
|
908
|
+
function mpegVbr(r, offset, frameSize, header, fields) {
|
|
909
|
+
const version = Number(fields.version), channels = Number(fields.channels);
|
|
910
|
+
const side = version === 1 ? channels === 1 ? 17 : 32 : channels === 1 ? 9 : 17, crc = header & 65536 ? 0 : 2;
|
|
911
|
+
const xing = offset + 4 + crc + side;
|
|
912
|
+
if (xing + 8 <= offset + frameSize && ["Xing", "Info"].includes(r.text(xing, 4))) {
|
|
913
|
+
const flags = r.u32(xing + 4);
|
|
914
|
+
let pos = xing + 8;
|
|
915
|
+
const vbr = { type: r.text(xing, 4), flags };
|
|
916
|
+
if (flags & 1) {
|
|
917
|
+
vbr.frames = r.u32(pos);
|
|
918
|
+
pos += 4;
|
|
919
|
+
}
|
|
920
|
+
if (flags & 2) {
|
|
921
|
+
vbr.bytes = r.u32(pos);
|
|
922
|
+
pos += 4;
|
|
923
|
+
}
|
|
924
|
+
if (flags & 4) {
|
|
925
|
+
vbr.toc = r.slice(pos, 100);
|
|
926
|
+
pos += 100;
|
|
927
|
+
}
|
|
928
|
+
if (flags & 8) {
|
|
929
|
+
vbr.quality = r.u32(pos);
|
|
930
|
+
pos += 4;
|
|
931
|
+
}
|
|
932
|
+
if (pos > offset + frameSize) throw new Error("Xing header exceeds frame");
|
|
933
|
+
fields.vbr = vbr;
|
|
934
|
+
if (pos + 24 <= offset + frameSize && ["LAME", "Lavc", "Lavf"].includes(r.text(pos, 4))) {
|
|
935
|
+
const packed = r.u24(pos + 21);
|
|
936
|
+
fields.encoderDelay = packed >>> 12;
|
|
937
|
+
fields.encoderPadding = packed & 4095;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const vbri = offset + 36;
|
|
941
|
+
if (vbri + 26 <= offset + frameSize && r.text(vbri, 4) === "VBRI")
|
|
942
|
+
fields.vbr = {
|
|
943
|
+
type: "VBRI",
|
|
944
|
+
version: r.u16(vbri + 4),
|
|
945
|
+
delay: r.u16(vbri + 6),
|
|
946
|
+
quality: r.u16(vbri + 8),
|
|
947
|
+
bytes: r.u32(vbri + 10),
|
|
948
|
+
frames: r.u32(vbri + 14),
|
|
949
|
+
entries: r.u16(vbri + 18),
|
|
950
|
+
scale: r.u16(vbri + 20),
|
|
951
|
+
entryBytes: r.u16(vbri + 22),
|
|
952
|
+
framesPerEntry: r.u16(vbri + 24)
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function mpegStream(state, first) {
|
|
956
|
+
let { samples, seconds: seconds2, audioBytes } = state;
|
|
957
|
+
const { sampleRate, channels, frames } = state;
|
|
958
|
+
if (!frames) throw new Error("MP3 contains no MPEG frames");
|
|
959
|
+
const vbr = first.fields?.vbr;
|
|
960
|
+
const declared = vbr?.frames;
|
|
961
|
+
if (typeof declared === "number" && declared > 0) {
|
|
962
|
+
if (declared !== frames && declared !== frames - 1)
|
|
963
|
+
throw new Error("MPEG VBR frame count mismatch");
|
|
964
|
+
const encodedSamples = declared * (sampleRate >= 32e3 ? 1152 : 576);
|
|
965
|
+
const delay = Number(first.fields?.encoderDelay ?? 0), padding = Number(first.fields?.encoderPadding ?? 0);
|
|
966
|
+
if (delay + padding > encodedSamples) throw new Error("Invalid MPEG encoder delay/padding");
|
|
967
|
+
samples = encodedSamples - delay - padding;
|
|
968
|
+
seconds2 = duration(samples, sampleRate);
|
|
969
|
+
if (declared === frames - 1) audioBytes -= first.size;
|
|
970
|
+
first.fields.encodedSamples = encodedSamples;
|
|
971
|
+
}
|
|
972
|
+
const encodedSeconds = duration(Number(first.fields?.encodedSamples ?? samples), sampleRate);
|
|
973
|
+
const bitrate = encodedSeconds ? audioBytes * 8 / encodedSeconds : 0;
|
|
974
|
+
return { codec: "mp3", sampleRate, channels, samples, duration: seconds2, bitrate };
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// packages/audio-ast/src/mp3-source.ts
|
|
978
|
+
async function probeMp3Source(source, options = {}) {
|
|
979
|
+
const read = async (offset2, length) => {
|
|
980
|
+
options.signal?.throwIfAborted();
|
|
981
|
+
if (!Number.isSafeInteger(source.size) || source.size < 0 || offset2 < 0 || length < 0 || length > source.size - offset2)
|
|
982
|
+
throw new Error("Truncated or invalid audio structure");
|
|
983
|
+
const bytes = await source.read(offset2, length);
|
|
984
|
+
options.signal?.throwIfAborted();
|
|
985
|
+
if (bytes.length !== length) throw new Error("Truncated or invalid audio structure");
|
|
986
|
+
return new Reader(bytes);
|
|
987
|
+
};
|
|
988
|
+
const marker = (await read(0, 3)).text(0, 3);
|
|
989
|
+
let end = source.size, offset = 0;
|
|
990
|
+
const tags = {};
|
|
991
|
+
if (end >= 128) {
|
|
992
|
+
const tail = await read(end - 128, 128);
|
|
993
|
+
if (tail.text(0, 3) === "TAG") {
|
|
994
|
+
const values = parseId3v1(tail.bytes);
|
|
995
|
+
end -= 128;
|
|
996
|
+
if (options.onTag) for (const [key, value] of Object.entries(values)) {
|
|
997
|
+
await options.onTag({ key, value });
|
|
998
|
+
options.signal?.throwIfAborted();
|
|
999
|
+
}
|
|
1000
|
+
else Object.assign(tags, values);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
if (marker === "ID3") {
|
|
1004
|
+
const id3 = await probeId3Source(source, { ...options, ...options.onTag ? { onTag: options.onTag } : {} });
|
|
1005
|
+
offset = id3.size;
|
|
1006
|
+
Object.assign(tags, id3.tags);
|
|
1007
|
+
}
|
|
1008
|
+
let samples = 0, seconds2 = 0, audioBytes = 0, sampleRate = 0, channels = 0, frames = 0;
|
|
1009
|
+
let firstOffset = 0;
|
|
1010
|
+
let first = { size: 0, fields: {} };
|
|
1011
|
+
while (offset < end) {
|
|
1012
|
+
await options.checkpoint?.();
|
|
1013
|
+
options.signal?.throwIfAborted();
|
|
1014
|
+
const header = (await read(offset, 4)).u32(0), frame = mpegFrame(header, offset, end);
|
|
1015
|
+
if (sampleRate && (sampleRate !== frame.rate || channels !== frame.channels)) throw new Error("Inconsistent MPEG stream");
|
|
1016
|
+
sampleRate = frame.rate;
|
|
1017
|
+
channels = frame.channels;
|
|
1018
|
+
if (!frames) {
|
|
1019
|
+
const metadata = await read(offset, Math.min(192, source.size - offset));
|
|
1020
|
+
mpegVbr(new Reader(metadata.bytes.slice()), 0, frame.frameSize, header, frame.fields);
|
|
1021
|
+
first = { size: frame.frameSize, fields: frame.fields };
|
|
1022
|
+
firstOffset = offset;
|
|
1023
|
+
}
|
|
1024
|
+
samples += frame.count;
|
|
1025
|
+
seconds2 += duration(frame.count, frame.rate);
|
|
1026
|
+
audioBytes += frame.frameSize;
|
|
1027
|
+
frames++;
|
|
1028
|
+
offset += frame.frameSize;
|
|
1029
|
+
}
|
|
1030
|
+
options.signal?.throwIfAborted();
|
|
1031
|
+
const stream = mpegStream({ samples, seconds: seconds2, audioBytes, sampleRate, channels, frames }, first);
|
|
1032
|
+
return { format: "mp3", nodes: [{ type: "MPEG", offset: firstOffset, size: first.size, data: new Uint8Array(), fields: first.fields }], streams: [stream], tags, duration: stream.duration, bitrate: stream.duration ? source.size * 8 / stream.duration : 0 };
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// packages/audio-ast/src/flac.ts
|
|
1036
|
+
function parseFlac(bytes) {
|
|
1037
|
+
const r = new Reader(bytes), nodes = [], pictures = [];
|
|
1038
|
+
const tags = {};
|
|
1039
|
+
let offset = 4, last = false, stream;
|
|
1040
|
+
while (!last) {
|
|
1041
|
+
const header = r.u8(offset), type = header & 127, size = r.u24(offset + 1);
|
|
1042
|
+
last = !!(header & 128);
|
|
1043
|
+
const data = r.slice(offset + 4, size), node = {
|
|
1044
|
+
type: [
|
|
1045
|
+
"STREAMINFO",
|
|
1046
|
+
"PADDING",
|
|
1047
|
+
"APPLICATION",
|
|
1048
|
+
"SEEKTABLE",
|
|
1049
|
+
"VORBIS_COMMENT",
|
|
1050
|
+
"CUESHEET",
|
|
1051
|
+
"PICTURE"
|
|
1052
|
+
][type] ?? `BLOCK_${type}`,
|
|
1053
|
+
offset,
|
|
1054
|
+
size: size + 4,
|
|
1055
|
+
data
|
|
1056
|
+
};
|
|
1057
|
+
nodes.push(node);
|
|
1058
|
+
const block = new Reader(data);
|
|
1059
|
+
if (type === 0) {
|
|
1060
|
+
if (stream || offset !== 4 || size !== 34)
|
|
1061
|
+
throw new Error("FLAC requires first unique STREAMINFO");
|
|
1062
|
+
stream = flacStream(block, bytes.length);
|
|
1063
|
+
const { sampleRate, channels, bitsPerSample, samples } = stream;
|
|
1064
|
+
node.fields = {
|
|
1065
|
+
minBlockSize: block.u16(0),
|
|
1066
|
+
maxBlockSize: block.u16(2),
|
|
1067
|
+
minFrameSize: block.u24(4),
|
|
1068
|
+
maxFrameSize: block.u24(7),
|
|
1069
|
+
sampleRate,
|
|
1070
|
+
channels,
|
|
1071
|
+
bitsPerSample,
|
|
1072
|
+
totalSamples: samples,
|
|
1073
|
+
md5: Array.from(block.slice(18, 16), (b) => b.toString(16).padStart(2, "0")).join("")
|
|
1074
|
+
};
|
|
1075
|
+
} else if (type === 4) {
|
|
1076
|
+
const parsed = parseComments(data);
|
|
1077
|
+
if (parsed.size !== size) throw new Error("Trailing Vorbis comment bytes");
|
|
1078
|
+
Object.assign(tags, parsed.tags);
|
|
1079
|
+
node.fields = { vendor: parsed.vendor, comments: parsed.comments };
|
|
1080
|
+
} else if (type === 6) pictures.push(parsePicture(data));
|
|
1081
|
+
else if (type === 3) {
|
|
1082
|
+
if (size % 18) throw new Error("Invalid FLAC seek table");
|
|
1083
|
+
const points = [];
|
|
1084
|
+
for (let pos = 0; pos < size; pos += 18)
|
|
1085
|
+
points.push({
|
|
1086
|
+
sample: block.u64(pos),
|
|
1087
|
+
offset: block.u64(pos + 8),
|
|
1088
|
+
samples: block.u16(pos + 16)
|
|
1089
|
+
});
|
|
1090
|
+
node.fields = { points };
|
|
1091
|
+
} else if (type === 127) throw new Error("Invalid FLAC metadata block");
|
|
1092
|
+
offset += 4 + size;
|
|
1093
|
+
}
|
|
1094
|
+
if (!stream) throw new Error("Missing FLAC STREAMINFO");
|
|
1095
|
+
if (offset < bytes.length)
|
|
1096
|
+
nodes.push({
|
|
1097
|
+
type: "FRAMES",
|
|
1098
|
+
offset,
|
|
1099
|
+
size: bytes.length - offset,
|
|
1100
|
+
data: r.slice(offset, bytes.length - offset)
|
|
1101
|
+
});
|
|
1102
|
+
return {
|
|
1103
|
+
format: "flac",
|
|
1104
|
+
data: bytes,
|
|
1105
|
+
nodes,
|
|
1106
|
+
tags,
|
|
1107
|
+
pictures,
|
|
1108
|
+
streams: [stream],
|
|
1109
|
+
duration: stream.duration,
|
|
1110
|
+
bitrate: stream.bitrate
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
function flacStream(block, byteLength) {
|
|
1114
|
+
const packed = block.u64(10), sampleRate = Number(packed >> 44n), channels = Number(packed >> 41n & 7n) + 1, bitsPerSample = Number(packed >> 36n & 31n) + 1, samples = Number(packed & 0xfffffffffn), seconds2 = duration(samples, sampleRate);
|
|
1115
|
+
return { codec: "flac", sampleRate, channels, bitsPerSample, samples, duration: seconds2, bitrate: seconds2 ? byteLength * 8 / seconds2 : 0 };
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// packages/audio-ast/src/flac-source.ts
|
|
1119
|
+
async function probeFlacSource(source, options = {}) {
|
|
1120
|
+
const check = (offset2, length, end = source.size) => {
|
|
253
1121
|
options.signal?.throwIfAborted();
|
|
254
1122
|
if (!Number.isSafeInteger(source.size) || !Number.isSafeInteger(offset2) || !Number.isSafeInteger(length) || offset2 < 0 || length < 0 || length > end - offset2)
|
|
255
1123
|
throw new Error("Truncated or invalid audio structure");
|
|
@@ -739,239 +1607,50 @@ function resample(pcm, sampleRate, method = "sinc") {
|
|
|
739
1607
|
}
|
|
740
1608
|
let sum = 0, weight = 0;
|
|
741
1609
|
for (let j = Math.max(0, Math.ceil(position - radius)); j <= Math.min(frames - 1, Math.floor(position + radius)); j++) {
|
|
742
|
-
const distance = position - j, x = Math.PI * distance * cutoff;
|
|
743
|
-
const w = (x === 0 ? 1 : Math.sin(x) / x) * (0.5 + 0.5 * Math.cos(Math.PI * distance / radius));
|
|
744
|
-
sum += ch[j] * w;
|
|
745
|
-
weight += w;
|
|
746
|
-
}
|
|
747
|
-
return weight ? sum / weight : 0;
|
|
748
|
-
})
|
|
749
|
-
)
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
function transformAudio(pcm, effects) {
|
|
753
|
-
validatePcm(pcm);
|
|
754
|
-
let result = {
|
|
755
|
-
sampleRate: pcm.sampleRate,
|
|
756
|
-
channels: pcm.channels.map((ch) => ch.slice())
|
|
757
|
-
};
|
|
758
|
-
for (const effect of effects) {
|
|
759
|
-
switch (effect.type) {
|
|
760
|
-
case "trim":
|
|
761
|
-
result = trim(result, effect.startSec, effect.durationSec);
|
|
762
|
-
break;
|
|
763
|
-
case "pad":
|
|
764
|
-
result = pad(result, effect.leadSec, effect.trailSec);
|
|
765
|
-
break;
|
|
766
|
-
case "rate":
|
|
767
|
-
result = resample(result, effect.sampleRate, effect.method);
|
|
768
|
-
break;
|
|
769
|
-
case "channels":
|
|
770
|
-
result = remix(result, effect.channels, effect.matrix);
|
|
771
|
-
break;
|
|
772
|
-
case "extract":
|
|
773
|
-
result = extractChannels(result, effect.channels);
|
|
774
|
-
break;
|
|
775
|
-
case "normalize":
|
|
776
|
-
result = normalize(result, effect.targetDb);
|
|
777
|
-
break;
|
|
778
|
-
case "fade":
|
|
779
|
-
result = fade(result, effect.inSec, effect.outSec);
|
|
780
|
-
break;
|
|
781
|
-
default:
|
|
782
|
-
throw new Error("Unsupported audio effect");
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
return result;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// packages/audio-ast/src/id3.ts
|
|
789
|
-
var names2 = {
|
|
790
|
-
TIT2: "title",
|
|
791
|
-
TPE1: "artist",
|
|
792
|
-
TPE2: "albumArtist",
|
|
793
|
-
TALB: "album",
|
|
794
|
-
TRCK: "track",
|
|
795
|
-
TPOS: "disc",
|
|
796
|
-
TCON: "genre",
|
|
797
|
-
TDRC: "date",
|
|
798
|
-
TYER: "date",
|
|
799
|
-
TCOP: "copyright",
|
|
800
|
-
TSSE: "encoder",
|
|
801
|
-
TT2: "title",
|
|
802
|
-
TP1: "artist",
|
|
803
|
-
TP2: "albumArtist",
|
|
804
|
-
TAL: "album",
|
|
805
|
-
TRK: "track",
|
|
806
|
-
TPA: "disc",
|
|
807
|
-
TCO: "genre",
|
|
808
|
-
TYE: "date",
|
|
809
|
-
TCR: "copyright",
|
|
810
|
-
TEN: "encoder"
|
|
811
|
-
};
|
|
812
|
-
function synchsafe(r, offset) {
|
|
813
|
-
let value = 0;
|
|
814
|
-
for (let i = 0; i < 4; i++) {
|
|
815
|
-
const b = r.u8(offset + i);
|
|
816
|
-
if (b & 128) throw new Error("Invalid ID3 synchsafe size");
|
|
817
|
-
value = value * 128 + b;
|
|
818
|
-
}
|
|
819
|
-
return value;
|
|
820
|
-
}
|
|
821
|
-
function unsync(bytes) {
|
|
822
|
-
const result = [];
|
|
823
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
824
|
-
const b = bytes[i];
|
|
825
|
-
result.push(b);
|
|
826
|
-
if (b === 255 && bytes[i + 1] === 0) i++;
|
|
827
|
-
}
|
|
828
|
-
return Uint8Array.from(result);
|
|
829
|
-
}
|
|
830
|
-
function text(bytes, encoding) {
|
|
831
|
-
if (encoding === 0) return cleanText(new TextDecoder("latin1").decode(bytes));
|
|
832
|
-
if (encoding === 3) return cleanText(new TextDecoder().decode(bytes));
|
|
833
|
-
if (encoding === 2) return cleanText(new TextDecoder("utf-16be").decode(bytes));
|
|
834
|
-
if (encoding === 1)
|
|
835
|
-
return cleanText(
|
|
836
|
-
new TextDecoder(bytes[0] === 254 && bytes[1] === 255 ? "utf-16be" : "utf-16le").decode(bytes)
|
|
837
|
-
);
|
|
838
|
-
throw new Error("Invalid ID3 text encoding");
|
|
839
|
-
}
|
|
840
|
-
function terminated(bytes, start, encoding) {
|
|
841
|
-
const width = encoding === 1 || encoding === 2 ? 2 : 1;
|
|
842
|
-
for (let i = start; i + width <= bytes.length; i += width)
|
|
843
|
-
if (bytes[i] === 0 && (width === 1 || bytes[i + 1] === 0)) return i;
|
|
844
|
-
throw new Error("Unterminated ID3 text field");
|
|
845
|
-
}
|
|
846
|
-
function parseId3(bytes) {
|
|
847
|
-
const input = new Reader(bytes), version = input.u8(3), flags = input.u8(5), length = synchsafe(input, 6);
|
|
848
|
-
if (![2, 3, 4].includes(version)) throw new Error("Unsupported ID3 version");
|
|
849
|
-
if (version === 2 && flags & 64) throw new Error("Compressed ID3v2.2 tag unsupported");
|
|
850
|
-
const footer = version === 4 && flags & 16 ? 10 : 0;
|
|
851
|
-
input.check(0, 10 + length + footer);
|
|
852
|
-
const payload = input.slice(10, length), r = new Reader(flags & 128 && version < 4 ? unsync(payload) : payload);
|
|
853
|
-
const nodes = [], tags = {}, pictures = [];
|
|
854
|
-
let pos = 0;
|
|
855
|
-
if (version >= 3 && flags & 64) {
|
|
856
|
-
const extended = version === 4 ? synchsafe(r, 0) : r.u32(0) + 4;
|
|
857
|
-
r.check(0, extended);
|
|
858
|
-
if (extended < (version === 3 ? 10 : 6)) throw new Error("Invalid ID3 extended header");
|
|
859
|
-
pos = extended;
|
|
860
|
-
}
|
|
861
|
-
while (pos < r.bytes.length && r.u8(pos) !== 0) {
|
|
862
|
-
const header = version === 2 ? 6 : 10, id = r.text(pos, version === 2 ? 3 : 4), size = version === 2 ? r.u24(pos + 3) : version === 4 ? synchsafe(r, pos + 4) : r.u32(pos + 4);
|
|
863
|
-
const frameFlags = version === 2 ? 0 : r.u16(pos + 8);
|
|
864
|
-
let data = r.slice(pos + header, size);
|
|
865
|
-
if (version === 4 && (flags & 128 || frameFlags & 2)) data = unsync(data);
|
|
866
|
-
const node = {
|
|
867
|
-
type: id,
|
|
868
|
-
offset: 10 + pos,
|
|
869
|
-
size: header + size,
|
|
870
|
-
data,
|
|
871
|
-
fields: { flags: frameFlags, version }
|
|
872
|
-
};
|
|
873
|
-
nodes.push(node);
|
|
874
|
-
const unsupported = version === 3 ? frameFlags & 192 : frameFlags & 12;
|
|
875
|
-
if (!unsupported && data.length) {
|
|
876
|
-
let prefix = 0;
|
|
877
|
-
if (version === 3 && frameFlags & 32) prefix++;
|
|
878
|
-
if (version === 4 && frameFlags & 64) prefix++;
|
|
879
|
-
if (version === 4 && frameFlags & 1) prefix += 4;
|
|
880
|
-
data = data.subarray(prefix);
|
|
881
|
-
if (id.startsWith("T") && id !== "TXXX" && id !== "TXX")
|
|
882
|
-
tags[names2[id] ?? id] = text(data.subarray(1), data[0]);
|
|
883
|
-
else if (id === "COMM" || id === "COM") {
|
|
884
|
-
const encoding = data[0], end = terminated(data, 4, encoding), width = encoding === 1 || encoding === 2 ? 2 : 1;
|
|
885
|
-
tags.comment = text(data.subarray(end + width), encoding);
|
|
886
|
-
} else if (id === "APIC" || id === "PIC") {
|
|
887
|
-
const encoding = data[0], mimeEnd = id === "PIC" ? 4 : terminated(data, 1, 0);
|
|
888
|
-
const mime = id === "PIC" ? `image/${new Reader(data).text(1, 3) === "JPG" ? "jpeg" : new Reader(data).text(1, 3).toLowerCase()}` : text(data.subarray(1, mimeEnd), 0);
|
|
889
|
-
const typeOffset = id === "PIC" ? 4 : mimeEnd + 1, descriptionStart = typeOffset + 1, end = terminated(data, descriptionStart, encoding), width = encoding === 1 || encoding === 2 ? 2 : 1;
|
|
890
|
-
pictures.push({
|
|
891
|
-
type: data[typeOffset],
|
|
892
|
-
mime,
|
|
893
|
-
description: text(data.subarray(descriptionStart, end), encoding),
|
|
894
|
-
data: data.subarray(end + width)
|
|
895
|
-
});
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
pos += header + size;
|
|
899
|
-
}
|
|
900
|
-
if (footer && input.text(10 + length, 3) !== "3DI") throw new Error("Missing ID3 footer");
|
|
901
|
-
return { size: 10 + length + footer, nodes, tags, pictures, version };
|
|
902
|
-
}
|
|
903
|
-
function syncBytes(value) {
|
|
904
|
-
if (value > 268435455) throw new Error("ID3 tag too large");
|
|
905
|
-
return Uint8Array.from([
|
|
906
|
-
value >>> 21 & 127,
|
|
907
|
-
value >>> 14 & 127,
|
|
908
|
-
value >>> 7 & 127,
|
|
909
|
-
value & 127
|
|
910
|
-
]);
|
|
911
|
-
}
|
|
912
|
-
function encodeId3(tags, retained = [], pictures = []) {
|
|
913
|
-
const frames = [];
|
|
914
|
-
function frame(id, data) {
|
|
915
|
-
frames.push(join([ascii(id), syncBytes(data.length), new Uint8Array(2), data]));
|
|
916
|
-
}
|
|
917
|
-
for (const node of retained) {
|
|
918
|
-
const flags = Number(node.fields?.flags ?? 0);
|
|
919
|
-
if (flags & (node.fields?.version === 3 ? 192 : 12)) {
|
|
920
|
-
throw new Error(`Cannot safely rewrite transformed ID3 frame ${node.type}`);
|
|
921
|
-
}
|
|
922
|
-
if (node.type === "APIC" || node.type === "PIC" || names2[node.type] || node.type.startsWith("T") && node.type !== "TXXX" && node.type !== "TXX" || node.type === "COMM" || node.type === "COM")
|
|
923
|
-
continue;
|
|
924
|
-
if (node.type.length === 4 && !node.fields?.flags) frame(node.type, node.data);
|
|
925
|
-
else throw new Error(`Cannot safely rewrite ID3 frame ${node.type}`);
|
|
926
|
-
}
|
|
927
|
-
for (const [name, value] of Object.entries(tags)) {
|
|
928
|
-
const id = Object.keys(names2).find((key) => key.length === 4 && names2[key] === name) ?? name;
|
|
929
|
-
if (name === "comment")
|
|
930
|
-
frame(
|
|
931
|
-
"COMM",
|
|
932
|
-
join([
|
|
933
|
-
new Uint8Array([3]),
|
|
934
|
-
ascii("eng"),
|
|
935
|
-
new Uint8Array(1),
|
|
936
|
-
new TextEncoder().encode(value)
|
|
937
|
-
])
|
|
938
|
-
);
|
|
939
|
-
else {
|
|
940
|
-
if (id.length !== 4 || !id.startsWith("T")) throw new Error(`Unsupported ID3 tag ${name}`);
|
|
941
|
-
frame(id, join([new Uint8Array([3]), new TextEncoder().encode(value)]));
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
for (const picture of pictures)
|
|
945
|
-
frame(
|
|
946
|
-
"APIC",
|
|
947
|
-
join([
|
|
948
|
-
new Uint8Array([3]),
|
|
949
|
-
ascii(picture.mime),
|
|
950
|
-
new Uint8Array([0, picture.type]),
|
|
951
|
-
new TextEncoder().encode(picture.description),
|
|
952
|
-
new Uint8Array(1),
|
|
953
|
-
picture.data
|
|
954
|
-
])
|
|
955
|
-
);
|
|
956
|
-
const body = join(frames);
|
|
957
|
-
return join([ascii("ID3"), new Uint8Array([4, 0, 0]), syncBytes(body.length), body]);
|
|
1610
|
+
const distance = position - j, x = Math.PI * distance * cutoff;
|
|
1611
|
+
const w = (x === 0 ? 1 : Math.sin(x) / x) * (0.5 + 0.5 * Math.cos(Math.PI * distance / radius));
|
|
1612
|
+
sum += ch[j] * w;
|
|
1613
|
+
weight += w;
|
|
1614
|
+
}
|
|
1615
|
+
return weight ? sum / weight : 0;
|
|
1616
|
+
})
|
|
1617
|
+
)
|
|
1618
|
+
};
|
|
958
1619
|
}
|
|
959
|
-
function
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1620
|
+
function transformAudio(pcm, effects) {
|
|
1621
|
+
validatePcm(pcm);
|
|
1622
|
+
let result = {
|
|
1623
|
+
sampleRate: pcm.sampleRate,
|
|
1624
|
+
channels: pcm.channels.map((ch) => ch.slice())
|
|
1625
|
+
};
|
|
1626
|
+
for (const effect of effects) {
|
|
1627
|
+
switch (effect.type) {
|
|
1628
|
+
case "trim":
|
|
1629
|
+
result = trim(result, effect.startSec, effect.durationSec);
|
|
1630
|
+
break;
|
|
1631
|
+
case "pad":
|
|
1632
|
+
result = pad(result, effect.leadSec, effect.trailSec);
|
|
1633
|
+
break;
|
|
1634
|
+
case "rate":
|
|
1635
|
+
result = resample(result, effect.sampleRate, effect.method);
|
|
1636
|
+
break;
|
|
1637
|
+
case "channels":
|
|
1638
|
+
result = remix(result, effect.channels, effect.matrix);
|
|
1639
|
+
break;
|
|
1640
|
+
case "extract":
|
|
1641
|
+
result = extractChannels(result, effect.channels);
|
|
1642
|
+
break;
|
|
1643
|
+
case "normalize":
|
|
1644
|
+
result = normalize(result, effect.targetDb);
|
|
1645
|
+
break;
|
|
1646
|
+
case "fade":
|
|
1647
|
+
result = fade(result, effect.inSec, effect.outSec);
|
|
1648
|
+
break;
|
|
1649
|
+
default:
|
|
1650
|
+
throw new Error("Unsupported audio effect");
|
|
1651
|
+
}
|
|
972
1652
|
}
|
|
973
|
-
|
|
974
|
-
return tags;
|
|
1653
|
+
return result;
|
|
975
1654
|
}
|
|
976
1655
|
|
|
977
1656
|
// packages/audio-ast/src/mp3.ts
|
|
@@ -999,27 +1678,11 @@ function parseMp3(bytes) {
|
|
|
999
1678
|
}
|
|
1000
1679
|
let samples = 0, seconds2 = 0, audioBytes = 0, sampleRate = 0, channels = 0, frames = 0;
|
|
1001
1680
|
while (offset < end) {
|
|
1002
|
-
const header = r.u32(offset),
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
const version = versionBits === 3 ? 1 : versionBits === 2 ? 2 : 2.5;
|
|
1006
|
-
const rate = [44100, 48e3, 32e3][rateIndex] / (version === 1 ? 1 : version === 2 ? 2 : 4);
|
|
1007
|
-
const bitrate2 = (version === 1 ? [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320] : [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160])[bitrateIndex] * 1e3;
|
|
1008
|
-
const count = version === 1 ? 1152 : 576, frameSize = Math.floor((version === 1 ? 144 : 72) * bitrate2 / rate) + (header >>> 9 & 1), ch = header >>> 6 & 3;
|
|
1009
|
-
if (frameSize > end - offset) throw new Error("Truncated MPEG frame");
|
|
1010
|
-
if (sampleRate && (sampleRate !== rate || channels !== (ch === 3 ? 1 : 2)))
|
|
1011
|
-
throw new Error("Inconsistent MPEG stream");
|
|
1681
|
+
const header = r.u32(offset), frame = mpegFrame(header, offset, end);
|
|
1682
|
+
const { rate, count, frameSize, fields } = frame;
|
|
1683
|
+
if (sampleRate && (sampleRate !== rate || channels !== frame.channels)) throw new Error("Inconsistent MPEG stream");
|
|
1012
1684
|
sampleRate = rate;
|
|
1013
|
-
channels =
|
|
1014
|
-
const fields = {
|
|
1015
|
-
version,
|
|
1016
|
-
layer: 3,
|
|
1017
|
-
bitrate: bitrate2,
|
|
1018
|
-
sampleRate: rate,
|
|
1019
|
-
channels,
|
|
1020
|
-
samples: count,
|
|
1021
|
-
padding: header >>> 9 & 1
|
|
1022
|
-
};
|
|
1685
|
+
channels = frame.channels;
|
|
1023
1686
|
const node = {
|
|
1024
1687
|
type: "MPEG",
|
|
1025
1688
|
offset,
|
|
@@ -1028,288 +1691,24 @@ function parseMp3(bytes) {
|
|
|
1028
1691
|
fields
|
|
1029
1692
|
};
|
|
1030
1693
|
nodes.push(node);
|
|
1031
|
-
if (!frames)
|
|
1032
|
-
const side = version === 1 ? channels === 1 ? 17 : 32 : channels === 1 ? 9 : 17, crc = header & 65536 ? 0 : 2;
|
|
1033
|
-
const xing = offset + 4 + crc + side;
|
|
1034
|
-
if (xing + 8 <= offset + frameSize && ["Xing", "Info"].includes(r.text(xing, 4))) {
|
|
1035
|
-
const flags = r.u32(xing + 4);
|
|
1036
|
-
let pos = xing + 8;
|
|
1037
|
-
const vbr2 = { type: r.text(xing, 4), flags };
|
|
1038
|
-
if (flags & 1) {
|
|
1039
|
-
vbr2.frames = r.u32(pos);
|
|
1040
|
-
pos += 4;
|
|
1041
|
-
}
|
|
1042
|
-
if (flags & 2) {
|
|
1043
|
-
vbr2.bytes = r.u32(pos);
|
|
1044
|
-
pos += 4;
|
|
1045
|
-
}
|
|
1046
|
-
if (flags & 4) {
|
|
1047
|
-
vbr2.toc = r.slice(pos, 100);
|
|
1048
|
-
pos += 100;
|
|
1049
|
-
}
|
|
1050
|
-
if (flags & 8) {
|
|
1051
|
-
vbr2.quality = r.u32(pos);
|
|
1052
|
-
pos += 4;
|
|
1053
|
-
}
|
|
1054
|
-
if (pos > offset + frameSize) throw new Error("Xing header exceeds frame");
|
|
1055
|
-
fields.vbr = vbr2;
|
|
1056
|
-
if (pos + 24 <= offset + frameSize && ["LAME", "Lavc", "Lavf"].includes(r.text(pos, 4))) {
|
|
1057
|
-
const packed = r.u24(pos + 21);
|
|
1058
|
-
fields.encoderDelay = packed >>> 12;
|
|
1059
|
-
fields.encoderPadding = packed & 4095;
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
const vbri = offset + 36;
|
|
1063
|
-
if (vbri + 26 <= offset + frameSize && r.text(vbri, 4) === "VBRI")
|
|
1064
|
-
fields.vbr = {
|
|
1065
|
-
type: "VBRI",
|
|
1066
|
-
version: r.u16(vbri + 4),
|
|
1067
|
-
delay: r.u16(vbri + 6),
|
|
1068
|
-
quality: r.u16(vbri + 8),
|
|
1069
|
-
bytes: r.u32(vbri + 10),
|
|
1070
|
-
frames: r.u32(vbri + 14),
|
|
1071
|
-
entries: r.u16(vbri + 18),
|
|
1072
|
-
scale: r.u16(vbri + 20),
|
|
1073
|
-
entryBytes: r.u16(vbri + 22),
|
|
1074
|
-
framesPerEntry: r.u16(vbri + 24)
|
|
1075
|
-
};
|
|
1076
|
-
}
|
|
1694
|
+
if (!frames) mpegVbr(r, offset, frameSize, header, fields);
|
|
1077
1695
|
samples += count;
|
|
1078
1696
|
seconds2 += duration(count, rate);
|
|
1079
1697
|
audioBytes += frameSize;
|
|
1080
1698
|
frames++;
|
|
1081
1699
|
offset += frameSize;
|
|
1082
1700
|
}
|
|
1083
|
-
|
|
1084
|
-
const
|
|
1085
|
-
const declared = vbr?.frames;
|
|
1086
|
-
if (typeof declared === "number" && declared > 0) {
|
|
1087
|
-
if (declared !== frames && declared !== frames - 1)
|
|
1088
|
-
throw new Error("MPEG VBR frame count mismatch");
|
|
1089
|
-
const encodedSamples = declared * (sampleRate >= 32e3 ? 1152 : 576);
|
|
1090
|
-
const delay = Number(first.fields?.encoderDelay ?? 0), padding = Number(first.fields?.encoderPadding ?? 0);
|
|
1091
|
-
if (delay + padding > encodedSamples) throw new Error("Invalid MPEG encoder delay/padding");
|
|
1092
|
-
samples = encodedSamples - delay - padding;
|
|
1093
|
-
seconds2 = duration(samples, sampleRate);
|
|
1094
|
-
if (declared === frames - 1) audioBytes -= first.size;
|
|
1095
|
-
first.fields.encodedSamples = encodedSamples;
|
|
1096
|
-
}
|
|
1097
|
-
const encodedSeconds = duration(Number(first.fields?.encodedSamples ?? samples), sampleRate);
|
|
1098
|
-
const bitrate = encodedSeconds ? audioBytes * 8 / encodedSeconds : 0;
|
|
1701
|
+
const first = nodes.find((n) => n.type === "MPEG");
|
|
1702
|
+
const stream = mpegStream({ samples, seconds: seconds2, audioBytes, sampleRate, channels, frames }, { size: first?.size ?? 0, fields: first?.fields ?? {} });
|
|
1099
1703
|
return {
|
|
1100
1704
|
format: "mp3",
|
|
1101
1705
|
data: bytes,
|
|
1102
1706
|
nodes,
|
|
1103
1707
|
tags,
|
|
1104
1708
|
pictures,
|
|
1105
|
-
streams: [
|
|
1106
|
-
duration:
|
|
1107
|
-
bitrate:
|
|
1108
|
-
};
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// packages/audio-ast/src/ogg.ts
|
|
1112
|
-
function oggCrc(bytes) {
|
|
1113
|
-
let crc = 0;
|
|
1114
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
1115
|
-
crc ^= (i >= 22 && i < 26 ? 0 : bytes[i]) << 24;
|
|
1116
|
-
for (let bit = 0; bit < 8; bit++) crc = crc << 1 ^ (crc & 2147483648 ? 79764919 : 0);
|
|
1117
|
-
}
|
|
1118
|
-
return crc >>> 0;
|
|
1119
|
-
}
|
|
1120
|
-
function readOgg(bytes) {
|
|
1121
|
-
const r = new Reader(bytes), nodes = [], packets = [], logical = /* @__PURE__ */ new Map();
|
|
1122
|
-
for (let offset = 0; offset < bytes.length; ) {
|
|
1123
|
-
if (r.text(offset, 4) !== "OggS" || r.u8(offset + 4) !== 0) throw new Error("Invalid Ogg page");
|
|
1124
|
-
const flags = r.u8(offset + 5), granule = r.u64(offset + 6, true), serial = r.u32(offset + 14, true), sequence = r.u32(offset + 18, true), count = r.u8(offset + 26), laces = r.slice(offset + 27, count);
|
|
1125
|
-
if (flags & ~7) throw new Error("Invalid Ogg page flags");
|
|
1126
|
-
const size = 27 + count + laces.reduce((sum, n) => sum + n, 0), page = r.slice(offset, size);
|
|
1127
|
-
if (oggCrc(page) !== r.u32(offset + 22, true)) throw new Error("Ogg checksum mismatch");
|
|
1128
|
-
let state = logical.get(serial);
|
|
1129
|
-
if (!state) {
|
|
1130
|
-
if (!(flags & 2) || sequence !== 0) throw new Error("Missing Ogg beginning-of-stream");
|
|
1131
|
-
state = { packets: [], pending: [], sequence: -1, ended: false };
|
|
1132
|
-
logical.set(serial, state);
|
|
1133
|
-
}
|
|
1134
|
-
if (state.ended || sequence !== state.sequence + 1 >>> 0 || !!(flags & 1) !== !!state.pending.length)
|
|
1135
|
-
throw new Error("Invalid Ogg sequence/continuation");
|
|
1136
|
-
state.sequence = sequence;
|
|
1137
|
-
const completed = [];
|
|
1138
|
-
let pos = offset + 27 + count;
|
|
1139
|
-
for (const lace of laces) {
|
|
1140
|
-
state.pending.push(r.slice(pos, lace));
|
|
1141
|
-
pos += lace;
|
|
1142
|
-
if (lace < 255) {
|
|
1143
|
-
const packet = {
|
|
1144
|
-
data: join(state.pending),
|
|
1145
|
-
serial,
|
|
1146
|
-
granule: 0xffffffffffffffffn,
|
|
1147
|
-
bos: state.packets.length === 0,
|
|
1148
|
-
eos: false
|
|
1149
|
-
};
|
|
1150
|
-
state.pending = [];
|
|
1151
|
-
state.packets.push(packet);
|
|
1152
|
-
packets.push(packet);
|
|
1153
|
-
completed.push(packet);
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
if (completed.length) {
|
|
1157
|
-
completed[completed.length - 1].granule = granule;
|
|
1158
|
-
if (flags & 4) completed[completed.length - 1].eos = true;
|
|
1159
|
-
}
|
|
1160
|
-
if (flags & 4) {
|
|
1161
|
-
if (state.pending.length) throw new Error("Incomplete final Ogg packet");
|
|
1162
|
-
state.ended = true;
|
|
1163
|
-
}
|
|
1164
|
-
nodes.push({
|
|
1165
|
-
type: "OggS",
|
|
1166
|
-
offset,
|
|
1167
|
-
size,
|
|
1168
|
-
data: page,
|
|
1169
|
-
fields: {
|
|
1170
|
-
flags,
|
|
1171
|
-
granule,
|
|
1172
|
-
serial,
|
|
1173
|
-
sequence,
|
|
1174
|
-
checksum: r.u32(offset + 22, true),
|
|
1175
|
-
lacing: laces
|
|
1176
|
-
}
|
|
1177
|
-
});
|
|
1178
|
-
offset += size;
|
|
1179
|
-
}
|
|
1180
|
-
if (!logical.size || Array.from(logical.values()).some((s) => s.pending.length || !s.ended))
|
|
1181
|
-
throw new Error("Incomplete Ogg stream");
|
|
1182
|
-
return { nodes, packets };
|
|
1183
|
-
}
|
|
1184
|
-
function encodeOgg(packets) {
|
|
1185
|
-
const pages = [], sequences = /* @__PURE__ */ new Map(), pending = /* @__PURE__ */ new Map();
|
|
1186
|
-
for (const packet of packets) {
|
|
1187
|
-
const group = pending.get(packet.serial) ?? [];
|
|
1188
|
-
group.push(packet);
|
|
1189
|
-
pending.set(packet.serial, group);
|
|
1190
|
-
if (packet.granule === 0xffffffffffffffffn && !packet.eos) continue;
|
|
1191
|
-
const laces = [], granules = [], boundaries = /* @__PURE__ */ new Set();
|
|
1192
|
-
for (const item of group) {
|
|
1193
|
-
for (let j = 0; j < Math.floor(item.data.length / 255); j++) {
|
|
1194
|
-
laces.push(255);
|
|
1195
|
-
granules.push(0xffffffffffffffffn);
|
|
1196
|
-
}
|
|
1197
|
-
laces.push(item.data.length % 255);
|
|
1198
|
-
granules.push(item.granule);
|
|
1199
|
-
boundaries.add(laces.length);
|
|
1200
|
-
}
|
|
1201
|
-
const body = join(group.map((p) => p.data));
|
|
1202
|
-
let offset = 0;
|
|
1203
|
-
for (let start = 0; start < laces.length; start += 255) {
|
|
1204
|
-
const part = laces.slice(start, start + 255), last = start + 255 >= laces.length, size = part.reduce((a, b) => a + b, 0), header = new Uint8Array(27 + part.length), v = new DataView(header.buffer);
|
|
1205
|
-
header.set(ascii("OggS"));
|
|
1206
|
-
header[5] = (start && !boundaries.has(start) ? 1 : 0) | (group[0].bos && start === 0 ? 2 : 0) | (packet.eos && last ? 4 : 0);
|
|
1207
|
-
let granule = 0xffffffffffffffffn;
|
|
1208
|
-
for (let i = start; i < start + part.length; i++)
|
|
1209
|
-
if (laces[i] < 255) granule = granules[i] === 0xffffffffffffffffn ? 0n : granules[i];
|
|
1210
|
-
v.setBigUint64(6, granule, true);
|
|
1211
|
-
v.setUint32(14, packet.serial, true);
|
|
1212
|
-
const sequence = sequences.get(packet.serial) ?? 0;
|
|
1213
|
-
v.setUint32(18, sequence, true);
|
|
1214
|
-
sequences.set(packet.serial, sequence + 1);
|
|
1215
|
-
header[26] = part.length;
|
|
1216
|
-
header.set(part, 27);
|
|
1217
|
-
const page = join([header, body.subarray(offset, offset + size)]);
|
|
1218
|
-
new DataView(page.buffer).setUint32(22, oggCrc(page), true);
|
|
1219
|
-
pages.push(page);
|
|
1220
|
-
offset += size;
|
|
1221
|
-
}
|
|
1222
|
-
pending.delete(packet.serial);
|
|
1223
|
-
}
|
|
1224
|
-
if (pending.size) throw new Error("Ogg packets lack a final page granule");
|
|
1225
|
-
return join(pages);
|
|
1226
|
-
}
|
|
1227
|
-
function parseOgg(bytes) {
|
|
1228
|
-
const { nodes, packets } = readOgg(bytes), streams = [], tags = {}, pictures = [];
|
|
1229
|
-
const serials = [...new Set(packets.map((p) => p.serial))];
|
|
1230
|
-
for (const serial of serials) {
|
|
1231
|
-
const group = packets.filter((p) => p.serial === serial), head = group[0].data, r = new Reader(head);
|
|
1232
|
-
let codec, sampleRate, channels, preSkip = 0;
|
|
1233
|
-
let comments;
|
|
1234
|
-
if (head.length >= 8 && r.text(0, 8) === "OpusHead") {
|
|
1235
|
-
if (head.length < 19 || r.u8(8) > 15) throw new Error("Invalid Opus identification");
|
|
1236
|
-
codec = "opus";
|
|
1237
|
-
sampleRate = 48e3;
|
|
1238
|
-
channels = r.u8(9);
|
|
1239
|
-
preSkip = r.u16(10, true);
|
|
1240
|
-
const mapping = r.u8(18);
|
|
1241
|
-
if (mapping === 0 && channels > 2) throw new Error("Invalid Opus channels");
|
|
1242
|
-
if (mapping !== 0) r.check(19, 2 + channels);
|
|
1243
|
-
const comment = group[1]?.data;
|
|
1244
|
-
if (!comment || new Reader(comment).text(0, 8) !== "OpusTags")
|
|
1245
|
-
throw new Error("Missing OpusTags");
|
|
1246
|
-
const parsed = parseComments(comment.subarray(8));
|
|
1247
|
-
Object.assign(tags, parsed.tags);
|
|
1248
|
-
comments = parsed.comments;
|
|
1249
|
-
} else if (head.length >= 7 && r.u8(0) === 1 && r.text(1, 6) === "vorbis") {
|
|
1250
|
-
if (head.length !== 30 || r.u32(7, true) !== 0 || !(r.u8(29) & 1))
|
|
1251
|
-
throw new Error("Invalid Vorbis identification");
|
|
1252
|
-
codec = "vorbis";
|
|
1253
|
-
channels = r.u8(11);
|
|
1254
|
-
sampleRate = r.u32(12, true);
|
|
1255
|
-
const comment = group[1]?.data;
|
|
1256
|
-
if (!comment || comment[0] !== 3 || new Reader(comment).text(1, 6) !== "vorbis")
|
|
1257
|
-
throw new Error("Missing Vorbis comments");
|
|
1258
|
-
const parsed = parseComments(comment.subarray(7));
|
|
1259
|
-
if (!(comment[7 + parsed.size] & 1)) throw new Error("Missing Vorbis comment framing bit");
|
|
1260
|
-
Object.assign(tags, parsed.tags);
|
|
1261
|
-
comments = parsed.comments;
|
|
1262
|
-
} else throw new Error("Unsupported Ogg codec");
|
|
1263
|
-
if (!channels || !sampleRate) throw new Error("Invalid Ogg audio stream");
|
|
1264
|
-
const final = group.filter((p) => p.granule !== 0xffffffffffffffffn).at(-1)?.granule ?? 0n;
|
|
1265
|
-
if (final > BigInt(Number.MAX_SAFE_INTEGER))
|
|
1266
|
-
throw new Error("Ogg granule exceeds safe precision");
|
|
1267
|
-
const samples = Math.max(0, Number(final) - preSkip), duration3 = samples / sampleRate;
|
|
1268
|
-
const size = nodes.filter((n) => n.fields?.serial === serial).reduce((sum, n) => sum + n.size, 0);
|
|
1269
|
-
const streamTags = {};
|
|
1270
|
-
for (const comment of comments) {
|
|
1271
|
-
const split = comment.indexOf("=");
|
|
1272
|
-
if (split <= 0) continue;
|
|
1273
|
-
const key = comment.slice(0, split), value = comment.slice(split + 1);
|
|
1274
|
-
streamTags[key] = streamTags[key] ? `${streamTags[key]};${value}` : value;
|
|
1275
|
-
}
|
|
1276
|
-
streams.push({
|
|
1277
|
-
tags: streamTags,
|
|
1278
|
-
codec,
|
|
1279
|
-
sampleRate,
|
|
1280
|
-
channels,
|
|
1281
|
-
samples,
|
|
1282
|
-
duration: duration3,
|
|
1283
|
-
bitrate: duration3 ? size * 8 / duration3 : 0
|
|
1284
|
-
});
|
|
1285
|
-
}
|
|
1286
|
-
for (const value of (tags.METADATA_BLOCK_PICTURE ?? "").split("\n").filter(Boolean)) {
|
|
1287
|
-
pictures.push(parsePicture(Uint8Array.from(atob(value), (c) => c.charCodeAt(0))));
|
|
1288
|
-
}
|
|
1289
|
-
let duration2 = 0, groupDuration = 0;
|
|
1290
|
-
const active = /* @__PURE__ */ new Set();
|
|
1291
|
-
for (const node of nodes) {
|
|
1292
|
-
const serial = node.fields.serial, flags = node.fields.flags;
|
|
1293
|
-
if (flags & 2) {
|
|
1294
|
-
if (!active.size) {
|
|
1295
|
-
duration2 += groupDuration;
|
|
1296
|
-
groupDuration = 0;
|
|
1297
|
-
}
|
|
1298
|
-
active.add(serial);
|
|
1299
|
-
groupDuration = Math.max(groupDuration, streams[serials.indexOf(serial)].duration);
|
|
1300
|
-
}
|
|
1301
|
-
if (flags & 4) active.delete(serial);
|
|
1302
|
-
}
|
|
1303
|
-
duration2 += groupDuration;
|
|
1304
|
-
return {
|
|
1305
|
-
format: "ogg",
|
|
1306
|
-
data: bytes,
|
|
1307
|
-
nodes,
|
|
1308
|
-
streams,
|
|
1309
|
-
tags,
|
|
1310
|
-
pictures,
|
|
1311
|
-
duration: duration2,
|
|
1312
|
-
bitrate: duration2 ? bytes.length * 8 / duration2 : 0
|
|
1709
|
+
streams: [stream],
|
|
1710
|
+
duration: stream.duration,
|
|
1711
|
+
bitrate: stream.duration ? bytes.length * 8 / stream.duration : 0
|
|
1313
1712
|
};
|
|
1314
1713
|
}
|
|
1315
1714
|
|
|
@@ -1694,13 +2093,19 @@ export {
|
|
|
1694
2093
|
extractChannels,
|
|
1695
2094
|
fade,
|
|
1696
2095
|
normalize,
|
|
2096
|
+
normalizeVorbisCommentKey,
|
|
1697
2097
|
pad,
|
|
1698
2098
|
parseAudio,
|
|
1699
2099
|
probeAudio,
|
|
1700
2100
|
probeFlacSource,
|
|
2101
|
+
probeId3Source,
|
|
2102
|
+
probeMp3Source,
|
|
2103
|
+
probeOggStreamSource,
|
|
1701
2104
|
probeWavSource,
|
|
2105
|
+
readId3Text,
|
|
1702
2106
|
remix,
|
|
1703
2107
|
resample,
|
|
2108
|
+
scanOggPages,
|
|
1704
2109
|
stats,
|
|
1705
2110
|
transformAudio,
|
|
1706
2111
|
trim,
|