hyperframes 0.8.142 → 0.8.144
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/{add-YS2RCWEK.js → add-CWRTVGDB.js} +18 -17
- package/dist/{animatedGifPrep-SAMCDT7T.js → animatedGifPrep-JTOHIWI4.js} +15 -14
- package/dist/{asset-BLWC2NWB.js → asset-KU2Z4UFM.js} +4 -4
- package/dist/{assetCataloger-FVNBBRP3.js → assetCataloger-3CS2P7PA.js} +8 -7
- package/dist/{auth-DRZB7OAC.js → auth-IDSOIQWG.js} +1 -1
- package/dist/{autoUpdate-I5I3BOJK.js → autoUpdate-373B4BA2.js} +2 -2
- package/dist/{backgroundChecks-VR2QHGNY.js → backgroundChecks-53Q3NAMG.js} +3 -3
- package/dist/backgroundChecksWorker.js +3 -3
- package/dist/{batchRender-4YUHQI6X.js → batchRender-UKZA6BME.js} +11 -10
- package/dist/{beats-7OT7ALV3.js → beats-N5JGEOFR.js} +33 -28
- package/dist/{benchmark-Q2XQCRTR.js → benchmark-CUN3LN4V.js} +15 -14
- package/dist/{browser-7VAWQQZC.js → browser-NCZT5AP6.js} +40 -56
- package/dist/{bundleWithLocalizedFonts-DDMLLL2R.js → bundleWithLocalizedFonts-FWC7PE2D.js} +2 -2
- package/dist/{capture-HEJS6M5A.js → capture-QBLG3VJR.js} +26 -25
- package/dist/{capture-U7ABPKS5.js → capture-YLS32UHT.js} +2 -2
- package/dist/{catalog-DVJ3XPDN.js → catalog-HIWNAFHW.js} +19 -18
- package/dist/{catch-up-DF6AVSN2.js → catch-up-HGS7KIAC.js} +5 -5
- package/dist/{check-KXORRWAE.js → check-FH6UYYLO.js} +78 -66
- package/dist/{checkBrowser-J7HZJTRH.js → checkBrowser-RNWNOMCZ.js} +93 -41
- package/dist/{chunk-R2JEKLDZ.js → chunk-2DA2BBNY.js} +3 -3
- package/dist/{chunk-TQFG7HTI.js → chunk-3RM2ETQT.js} +0 -168
- package/dist/{chunk-YFT3BL7N.js → chunk-3VYNNWVA.js} +30 -12
- package/dist/{chunk-7FCDA4RH.js → chunk-3XTEXWNN.js} +8 -5
- package/dist/{chunk-PFCFR3KZ.js → chunk-442JNXRM.js} +1 -1
- package/dist/{chunk-BPZVDBHY.js → chunk-4LAR447G.js} +1 -1
- package/dist/{chunk-AM5L5YIA.js → chunk-4OA4LDTU.js} +1 -1
- package/dist/{chunk-DZEVA7DI.js → chunk-4OC7MQNB.js} +2 -2
- package/dist/{chunk-75JJVANE.js → chunk-54QISXOC.js} +3 -3
- package/dist/{chunk-FZK35GT4.js → chunk-5HJ343GH.js} +1 -1
- package/dist/{chunk-IDX52YJN.js → chunk-5VMR4WBS.js} +458 -159
- package/dist/{chunk-OSUKETIH.js → chunk-5WADG7PR.js} +1 -1
- package/dist/{chunk-JYYOAKHS.js → chunk-6IIGT7BX.js} +1 -1
- package/dist/{chunk-6UYZB4TY.js → chunk-7RAYSCYT.js} +4 -4
- package/dist/{chunk-WTMUCJ6Z.js → chunk-7SDCIWHQ.js} +11 -2
- package/dist/{chunk-LAOCA3MQ.js → chunk-A4Q7W4LV.js} +13 -8
- package/dist/{chunk-B6SMSALD.js → chunk-A53CBD24.js} +18 -12
- package/dist/{chunk-4PFZPP2N.js → chunk-A6S35NYA.js} +54 -16
- package/dist/{chunk-JOTLPC6S.js → chunk-AUBOF2PN.js} +1 -1
- package/dist/{chunk-WCJGTLPF.js → chunk-BHVFTYSI.js} +6 -3
- package/dist/chunk-BSQ7XANM.js +210 -0
- package/dist/{chunk-LCSMOLGX.js → chunk-BYSBRF24.js} +69 -8
- package/dist/{chunk-U7E736AJ.js → chunk-DDIVWXN7.js} +32 -14
- package/dist/{chunk-KOEWEFHK.js → chunk-DIRJ322I.js} +10 -5
- package/dist/{chunk-HMUF7FEO.js → chunk-EX6C672Y.js} +2 -2
- package/dist/{chunk-QGRFOOLE.js → chunk-F2SMS24Q.js} +288 -85
- package/dist/{chunk-5Z63TBTX.js → chunk-FH7WODJH.js} +1 -1
- package/dist/{chunk-XTLBLWOV.js → chunk-GEWKFRFI.js} +1 -1
- package/dist/{chunk-RVCWIIZ7.js → chunk-GV5R5B7Q.js} +1 -1
- package/dist/{chunk-TY6XOHEM.js → chunk-GZM2EFOE.js} +3 -3
- package/dist/chunk-GZVP4TWN.js +76 -0
- package/dist/{chunk-QXPCARFE.js → chunk-I5KY7EZP.js} +1 -1
- package/dist/{chunk-IW2CKH6A.js → chunk-IEIJRMLZ.js} +94 -59
- package/dist/chunk-IR35BCLK.js +13 -0
- package/dist/{chunk-JFKL3WAE.js → chunk-IWFLMU2N.js} +104 -19
- package/dist/{chunk-EFDXGAAD.js → chunk-JEHFZQYT.js} +1 -1
- package/dist/{chunk-IHFUKJOR.js → chunk-K2FLNJVB.js} +8 -2
- package/dist/{chunk-KDPYVTEU.js → chunk-KBB4RG6F.js} +6 -1
- package/dist/{chunk-LJAU4TPS.js → chunk-KFKZ3ISO.js} +2 -2
- package/dist/{sherpa-ZYTNFHD5.js → chunk-L5TUCXWR.js} +11 -44
- package/dist/{chunk-KYN364DU.js → chunk-LC3RQURJ.js} +7 -9
- package/dist/{chunk-IMY4QQTQ.js → chunk-LFX2HEYP.js} +8 -8
- package/dist/{chunk-PXSM4HPB.js → chunk-MAYCCGMQ.js} +12 -3
- package/dist/{chunk-OZUQF4NB.js → chunk-MQZVBHEY.js} +123 -8
- package/dist/{chunk-FWRNQOLT.js → chunk-MXNDKDFT.js} +3 -3
- package/dist/{chunk-A4KQZXZI.js → chunk-NE55C6S5.js} +4 -2
- package/dist/{chunk-GYE63V4P.js → chunk-NOPBBSLA.js} +24 -19
- package/dist/{chunk-H2X52H2N.js → chunk-NV4RXIQ7.js} +3 -3
- package/dist/{chunk-WGVOWWVK.js → chunk-OIDN7G42.js} +3 -3
- package/dist/{chunk-DPHJKWP5.js → chunk-OUSLVRO4.js} +107 -108
- package/dist/{chunk-JLKMWY2T.js → chunk-P6WH6QTV.js} +8 -5
- package/dist/{chunk-7M4KAPAA.js → chunk-PUGIRFPX.js} +2 -2
- package/dist/{chunk-6RAD4BCC.js → chunk-QATWIE4K.js} +2 -2
- package/dist/{chunk-D2KNWIQ6.js → chunk-QFSCCQIR.js} +1 -1
- package/dist/{chunk-XGMNQQTJ.js → chunk-RG3QWWIE.js} +1 -1
- package/dist/{chunk-MVTUV4TP.js → chunk-RI53TS2Y.js} +14 -6
- package/dist/{chunk-LQASVU3I.js → chunk-RVFEDAUV.js} +12 -4
- package/dist/{chunk-GK4AIFIH.js → chunk-SDYDNHEE.js} +34 -3
- package/dist/{chunk-WSGY5CDQ.js → chunk-SLCFKB3Y.js} +14 -10
- package/dist/{chunk-2NCHQUPX.js → chunk-SQLK7SJL.js} +1 -1
- package/dist/{chunk-PMRBLBUP.js → chunk-T42U2W2R.js} +14 -2
- package/dist/chunk-UPFUC4DB.js +136 -0
- package/dist/{chunk-IXCTG2WY.js → chunk-VYSUZEJG.js} +5 -5
- package/dist/chunk-WJB7WI4J.js +3753 -0
- package/dist/{chunk-YEIFI443.js → chunk-WPNE5NEP.js} +1 -1
- package/dist/{chunk-YHRZQJXH.js → chunk-XGFHYAYN.js} +265 -68
- package/dist/{chunk-LBKDQKWV.js → chunk-XU5GGV6R.js} +1 -1
- package/dist/{chunk-LTGGTEMA.js → chunk-Y6UPYWJS.js} +2 -2
- package/dist/{chunk-OKCR3JTO.js → chunk-YJXHMIZA.js} +13 -10
- package/dist/{chunk-JU6Y5TTZ.js → chunk-YUAXFWSQ.js} +3 -3
- package/dist/{chunk-TVCRUG2S.js → chunk-ZEUONDQG.js} +1 -1
- package/dist/{chunk-THXFSPLV.js → chunk-ZIRIDSDB.js} +1 -1
- package/dist/{chunk-6UM3R7PP.js → chunk-ZOA2SC4P.js} +129 -60
- package/dist/{clean-KTHTV5G5.js → clean-KPX27IKJ.js} +27 -26
- package/dist/cli.js +70 -160
- package/dist/{cloud-ARSZWALC.js → cloud-P7DAOISN.js} +4 -4
- package/dist/{cloudrun-NM7RXOCL.js → cloudrun-XM56TBOH.js} +13 -12
- package/dist/commands/contrast-audit.browser.js +60 -4
- package/dist/commands/layout-audit.browser.js +68 -0
- package/dist/{compare-CY4UO3RW.js → compare-F6YAFZZM.js} +37 -36
- package/dist/{compiler-VTH2D3ID.js → compiler-JZ4GNFMG.js} +16 -10
- package/dist/{component-FRUIAXIR.js → component-3CFOGIQA.js} +5 -5
- package/dist/{compositions-CUSY6ZYY.js → compositions-CPCMWF24.js} +38 -33
- package/dist/{delete-KT3VHP4L.js → delete-GYVKOKB6.js} +4 -4
- package/dist/{deterministicFonts-F6WIVL2W.js → deterministicFonts-U2BIMGPG.js} +3 -3
- package/dist/{dist-7SZU7MA7.js → dist-KDJTFJHA.js} +7 -6
- package/dist/{dist-2GC6XKRQ.js → dist-YPWQDWJ4.js} +42 -40
- package/dist/{doctor-4ROTBL5U.js → doctor-GWI3B6JT.js} +21 -20
- package/dist/{dom-IROKGVJI.js → dom-37K6YOH4.js} +2 -2
- package/dist/{events-QJDVMKSR.js → events-BRRUDPPT.js} +3 -3
- package/dist/{events-Q5PXT5UC.js → events-V2QFN4YM.js} +2 -2
- package/dist/{feedback-PQ7TWESA.js → feedback-CXMD7FWL.js} +14 -13
- package/dist/{ffmpeg-QIUFEFTY.js → ffmpeg-T5SH2EFJ.js} +3 -2
- package/dist/{figma-JN5DDBDS.js → figma-YABDWD66.js} +3 -3
- package/dist/fontLocalizeCli.js +18 -17
- package/dist/{get-KT6OTERO.js → get-L2SOQKHX.js} +4 -4
- package/dist/{gpuPolicy-LCBWVGEH.js → gpuPolicy-NBJNWHYS.js} +19 -16
- package/dist/{grade-compare-VS3RP73G.js → grade-compare-STXQPKYQ.js} +39 -38
- package/dist/{help-AD6FIZIO.js → help-4H6BSFLB.js} +2 -2
- package/dist/{history-ZRWYRY7R.js → history-4IQOLTHJ.js} +6 -6
- package/dist/hyperframe-runtime.js +56 -56
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +56 -56
- package/dist/hyperframes-player.global.js +3 -3
- package/dist/hyperframes-slideshow.global.js +23 -23
- package/dist/{info-WBBQA2AX.js → info-2CZOTNBC.js} +17 -16
- package/dist/{init-A2XBDFGF.js → init-6BXK42VS.js} +46 -46
- package/dist/{inspect-CLTZHA5O.js → inspect-VKU47TBH.js} +38 -37
- package/dist/{installer-WQDRVXH4.js → installer-T2BSSUDY.js} +11 -10
- package/dist/{keyframes-3FYOK6H6.js → keyframes-IEYVHRPI.js} +10 -10
- package/dist/{lambda-Y2PA2BGS.js → lambda-TIKG25CK.js} +12 -11
- package/dist/{layout-4KP5O7IJ.js → layout-XOI4PT7A.js} +38 -37
- package/dist/{lint-V4GRFHOJ.js → lint-DWCMVIX5.js} +14 -13
- package/dist/{lintProject-RG2KGAXB.js → lintProject-UBZ3TDHU.js} +7 -6
- package/dist/{list-OQ4VED6E.js → list-7GXKG5QM.js} +5 -5
- package/dist/{login-W47NKN5S.js → login-BQ5L2UHX.js} +4 -4
- package/dist/{manager-CT3D67CG.js → manager-BPSR6D3V.js} +2 -2
- package/dist/{manager-524RPYO7.js → manager-RFZNG5YT.js} +2 -2
- package/dist/{manager-DXB7JDHU.js → manager-YKMHR6LN.js} +9 -6
- package/dist/{media-treatment-HJYL3EME.js → media-treatment-IFB7OSZ3.js} +24 -22
- package/dist/{models-DYBAD2GQ.js → models-TY4PJ545.js} +68 -27
- package/dist/{motionShot-7NE2YBB3.js → motionShot-WXMFHICW.js} +33 -33
- package/dist/{normalize-IGLHCWTF.js → normalize-U3SL5VG3.js} +19 -8
- package/dist/{normalize-audio-INKCLMS2.js → normalize-audio-RFD776HQ.js} +10 -6
- package/dist/{open-TYC5THVY.js → open-7IXXGO2E.js} +5 -5
- package/dist/parakeetRunner-IHLYYMTZ.js +67 -0
- package/dist/{pipeline-EVKEPQEW.js → pipeline-TXKPLL2U.js} +20 -18
- package/dist/{play-QMZEM6X3.js → play-J2KP64UK.js} +31 -29
- package/dist/{preflight-Y5DN5D37.js → preflight-B4AFK3RG.js} +4 -4
- package/dist/{present-Y7IK3C4W.js → present-MHHNRVRA.js} +26 -25
- package/dist/{preview-5OQRYE6A.js → preview-CONUOFFQ.js} +28 -27
- package/dist/{producer-Q6G6JU7B.js → producer-ZI46K7HH.js} +1 -1
- package/dist/{projectConfig-RJN3DKDB.js → projectConfig-FGGVG5MV.js} +13 -12
- package/dist/{publication-QCPGGN3U.js → publication-TXFBMW6B.js} +8 -7
- package/dist/{publish-VZLFIGYY.js → publish-YZSPBFRD.js} +18 -17
- package/dist/{remove-background-TKOEHBA5.js → remove-background-4ATVLHLG.js} +22 -20
- package/dist/{render-KIIIF6PS.js → render-6FBYLYGJ.js} +20 -19
- package/dist/{render-XTXE5CRU.js → render-GFW5BPES.js} +13 -12
- package/dist/{render-X3NFNVEQ.js → render-MH4IUPWB.js} +41 -40
- package/dist/{render-batch-M55YZLZ5.js → render-batch-XUCZ2PUJ.js} +13 -12
- package/dist/{renderCancellation-X5BEHAZU.js → renderCancellation-XF7M7IUP.js} +18 -16
- package/dist/renderSetupWorker.js +37 -48
- package/dist/{resolver-GAMXRA42.js → resolver-W7AYCQGS.js} +10 -9
- package/dist/sherpa-LDUB5NLY.js +75 -0
- package/dist/sherpaWorker.js +2 -1
- package/dist/skills/hyperframes-cli/references/doctor-browser.md +24 -1
- package/dist/skills/hyperframes-cli/references/lint-validate-inspect.md +1 -1
- package/dist/skills/media-use/scripts/lib/cache.mjs +28 -14
- package/dist/skills/media-use/scripts/lib/cache.test.mjs +110 -0
- package/dist/skills/media-use/scripts/lib/duck.mjs +2 -1
- package/dist/skills/media-use/scripts/lib/duck.test.mjs +87 -0
- package/dist/skills/media-use/scripts/lib/probe.mjs +6 -1
- package/dist/skills/media-use/scripts/lib/probe.test.mjs +73 -0
- package/dist/skills/media-use/scripts/lib/words.mjs +1 -1
- package/dist/{skills-CZHVAD3J.js → skills-VENERS5X.js} +7 -7
- package/dist/{skillsUpdateCheck-6WLLX53A.js → skillsUpdateCheck-UXFYTACG.js} +3 -3
- package/dist/{slideshow-EJ4IMY6P.js → slideshow-4OJEPWNZ.js} +1 -1
- package/dist/{snapshot-K7BTJPYQ.js → snapshot-C6T74HTI.js} +44 -40
- package/dist/{src-YUW26JU5.js → src-FS5RSVEI.js} +17 -16
- package/dist/{src-3I3RH3V5.js → src-VUJ2FUFK.js} +18 -13
- package/dist/{staticProjectServer-IKCSGN2B.js → staticProjectServer-V2RZ4DWD.js} +23 -22
- package/dist/studio/assets/hyperframes-player-Bw_jIaSX.js +473 -0
- package/dist/studio/assets/{index-B-wffzRH.js → index-BVM9NuWl.js} +153 -153
- package/dist/studio/assets/{index-D3Vij9t1.js → index-Cj7W_c7p.js} +1 -1
- package/dist/studio/assets/{index-D5q2_7Gw.js → index-DyH9dT7b.js} +1 -1
- package/dist/studio/{chunk-TFLU2MGI.js → chunk-3GHTHPZD.js} +8 -1
- package/dist/studio/chunk-3GHTHPZD.js.map +1 -0
- package/dist/studio/{chunk-H2DUOXTF.js → chunk-G3EDKRZH.js} +3 -3
- package/dist/studio/{chunk-UYWON5AX.js → chunk-PEAKCOUW.js} +7 -3
- package/dist/studio/chunk-PEAKCOUW.js.map +1 -0
- package/dist/studio/{chunk-LOVBLSMR.js → chunk-Y2OHHNM2.js} +35 -19
- package/dist/studio/chunk-Y2OHHNM2.js.map +1 -0
- package/dist/studio/{domEditingLayers-KBTPQKGA.js → domEditingLayers-PYST4G45.js} +3 -3
- package/dist/studio/index.html +1 -1
- package/dist/studio/index.js +261 -230
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/player.js +3 -3
- package/dist/studio/ui.d.ts +4 -5
- package/dist/studio/ui.js +1 -1
- package/dist/{studio-api-SQ3NXFSM.js → studio-api-TQE7KWKO.js} +22 -17
- package/dist/{studioServer-VVEO7XCH.js → studioServer-CYKSQK7L.js} +81 -64
- package/dist/{synthesize-77PTU6C5.js → synthesize-UCI4X5O7.js} +6 -6
- package/dist/{telemetry-FQDKR5XA.js → telemetry-A23PPRD6.js} +3 -3
- package/dist/{timeline-N6ZASFNE.js → timeline-T3QSGT3A.js} +143 -99
- package/dist/{tokens-2OIIBBCW.js → tokens-RT5HWA6C.js} +4 -4
- package/dist/{transcribe-7YTUCXDX.js → transcribe-FOLNOC5G.js} +116 -52
- package/dist/{transcribe-4F6MK73N.js → transcribe-HCF64LQK.js} +23 -20
- package/dist/{tts-TOHSPWEG.js → tts-B6AHFQEO.js} +6 -6
- package/dist/{updateCheck-AZ2COZFR.js → updateCheck-27PIP3AA.js} +2 -2
- package/dist/{upgrade-QAJEYB4X.js → upgrade-TVGXBUPJ.js} +2 -2
- package/dist/{urlDownloader-LSMFEO3R.js → urlDownloader-KBLBJKRL.js} +14 -13
- package/dist/{validate-GW5MDL36.js → validate-ZMYGK4QI.js} +40 -27
- package/dist/{video-PRNOKUK3.js → video-AVE5GIGM.js} +15 -14
- package/package.json +1 -1
- package/dist/chunk-J4UX3RS3.js +0 -3717
- package/dist/studio/assets/hyperframes-player-sAsRuV73.js +0 -473
- package/dist/studio/chunk-LOVBLSMR.js.map +0 -1
- package/dist/studio/chunk-TFLU2MGI.js.map +0 -1
- package/dist/studio/chunk-UYWON5AX.js.map +0 -1
- package/dist/{chunk-HBBJFK6I.js → chunk-APDLWWFG.js} +0 -0
- package/dist/{screenshotCapture-TEUIWXWN.js → screenshotCapture-3JXXUZND.js} +3 -3
- /package/dist/studio/{chunk-H2DUOXTF.js.map → chunk-G3EDKRZH.js.map} +0 -0
- /package/dist/studio/{domEditingLayers-KBTPQKGA.js.map → domEditingLayers-PYST4G45.js.map} +0 -0
package/dist/sherpaWorker.js
CHANGED
|
@@ -11,10 +11,11 @@ import {
|
|
|
11
11
|
droppedSpeechGaps,
|
|
12
12
|
silenceCuts,
|
|
13
13
|
spliceGap
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-KBB4RG6F.js";
|
|
15
15
|
import {
|
|
16
16
|
readWav
|
|
17
17
|
} from "./chunk-UOFAE5RF.js";
|
|
18
|
+
import "./chunk-IR35BCLK.js";
|
|
18
19
|
import "./chunk-M2WBOQ2Q.js";
|
|
19
20
|
|
|
20
21
|
// src/whisper/sherpaWorker.ts
|
|
@@ -28,7 +28,7 @@ Run `doctor` first when:
|
|
|
28
28
|
|
|
29
29
|
Common issues:
|
|
30
30
|
|
|
31
|
-
- **Missing FFmpeg** — install via `brew install ffmpeg` (macOS) or your package manager.
|
|
31
|
+
- **Missing FFmpeg** — install via `brew install ffmpeg` (macOS) or your package manager, or [configure existing binaries](#configure-ffmpeg-without-a-package-manager).
|
|
32
32
|
- **Missing bundled Chrome** — run `npx hyperframes browser ensure`.
|
|
33
33
|
- **Low memory** — close other Chromes, reduce `--workers`, or use `--quality draft`.
|
|
34
34
|
- **Chrome exits instantly inside an agent sandbox (macOS)** — seatbelt-style sandboxes
|
|
@@ -44,6 +44,29 @@ Common issues:
|
|
|
44
44
|
blocker is identified, BEFORE any optional fallback work: a later session failure must
|
|
45
45
|
not erase the report of work already done.
|
|
46
46
|
|
|
47
|
+
### Configure FFmpeg without a package manager
|
|
48
|
+
|
|
49
|
+
Get FFmpeg and FFprobe binaries for your operating system and architecture from the [FFmpeg download page](https://ffmpeg.org/download.html). Both must be executable. You can use them without Homebrew or a system-wide installation:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export HYPERFRAMES_FFMPEG_PATH="/absolute/path/to/ffmpeg"
|
|
53
|
+
export HYPERFRAMES_FFPROBE_PATH="/absolute/path/to/ffprobe"
|
|
54
|
+
npx hyperframes doctor --json
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Set both variables in the environment that launches HyperFrames. On Windows, point them at `ffmpeg.exe` and `ffprobe.exe` using your shell's environment-variable syntax. A configured path takes precedence over automatic discovery; a missing configured file is reported as missing.
|
|
58
|
+
|
|
59
|
+
Alternatively, put the binaries in `.hyperframes/bin/ffmpeg` and `.hyperframes/bin/ffprobe` (`.exe` on Windows), then run HyperFrames from that project's directory. Discovery checks `PATH` before this project-local directory, so use the environment variables when you need a particular build.
|
|
60
|
+
|
|
61
|
+
Verify the **FFmpeg** and **FFprobe** entries in `doctor --json` report `ok: true` and the expected paths. To gate only these encoding dependencies with `jq`:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx hyperframes doctor --json | jq -e \
|
|
65
|
+
'[.checks[] | select(.name == "FFmpeg" or .name == "FFprobe")] | length == 2 and all(.ok)'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The report's top-level `ok` also includes optional tools such as Docker and local voice providers; those failures do not require installing them for a local video render. Check Chrome separately with the browser commands below.
|
|
69
|
+
|
|
47
70
|
## browser
|
|
48
71
|
|
|
49
72
|
```bash
|
|
@@ -50,7 +50,7 @@ One command, one Chrome boot. `check` runs the linter first and skips the browse
|
|
|
50
50
|
|
|
51
51
|
Every finding carries a selector, the element's `data-*` identity, the composition source file, a bbox, and the sample time: jump straight from the JSON to the HTML you must edit and re-run.
|
|
52
52
|
|
|
53
|
-
**Severity is persistence-aware.** A dynamic issue observed at a single grid sample (an entrance/exit transient) demotes to info and never gates. Issues held across samples gate the exit code, a held `content_overlap` is an error, and a held, partially-visible `canvas_overflow` breaching ≥5% of the canvas promotes to warning. Coordinate-frame findings (`escaped_container`, `panel_out_of_canvas`, `connector_detached`) flag geometry computed in one frame but rendered in another — an element far outside its offset parent, a painted panel stuck across the canvas edge, a connector line detached from every node. Text drawn into a `<canvas>` has no DOM box, so `canvas_overflow` cannot see it; `canvas_content_at_edge` warns when a canvas's pixels show sharp content (drawn text, hard shapes) along the frame edge — mark intentional full-bleed art (particles, photos) with `data-layout-allow-overflow`. If a 3s+ composition shows no visible change across every sample, `check` fails with `sweep_static`: a
|
|
53
|
+
**Severity is persistence-aware.** A dynamic issue observed at a single grid sample (an entrance/exit transient) demotes to info and never gates. Issues held across samples gate the exit code, a held `content_overlap` is an error, and a held, partially-visible `canvas_overflow` breaching ≥5% of the canvas promotes to warning. Coordinate-frame findings (`escaped_container`, `panel_out_of_canvas`, `connector_detached`) flag geometry computed in one frame but rendered in another — an element far outside its offset parent, a painted panel stuck across the canvas edge, a connector line detached from every node. Text drawn into a `<canvas>` has no DOM box, so `canvas_overflow` cannot see it; `canvas_content_at_edge` warns when a canvas's pixels show sharp content (drawn text, hard shapes) along the frame edge — mark intentional full-bleed art (particles, photos) with `data-layout-allow-overflow`. If a 3s+ composition shows no visible change across every sample, `check` fails with `sweep_static`: a frame that never changes while a GSAP timeline or tween, or a CSS or Web animation, holds the same time without ever finishing means the seek is not driving the page, so it refuses to pass. Any other frame that never changes (a still title card, an audio-only advance) is a warning that `--strict` fails; a composition meant to be still can take `data-no-timeline` on its root to skip it. The fingerprint includes per-element opacity, so opacity-only reveals (code typing, staggered fades) count as motion — but only while they're still in flight at the sampled times. The classic trap is a reveal that completes early and then holds a static frame for the rest of the duration: every sample lands on the settled state and check warns that nothing on screen moved (`--strict` fails it). Spread the reveal across the timeline or keep one continuously animated element alive (a blinking caret is idiomatic for code typing) — don't bolt on a slow position drift just to appease the check.
|
|
54
54
|
|
|
55
55
|
**Escape hatches** (mark intent in the HTML, then re-run):
|
|
56
56
|
|
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
readFileSync,
|
|
3
|
+
writeFileSync,
|
|
4
|
+
mkdirSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
copyFileSync,
|
|
7
|
+
statSync,
|
|
8
|
+
} from "node:fs";
|
|
2
9
|
import { join, basename } from "node:path";
|
|
3
10
|
import { createHash } from "node:crypto";
|
|
4
11
|
import { readManifest, appendRecord, normalizePrompt } from "./manifest.mjs";
|
|
@@ -45,9 +52,10 @@ export function findGlobalBySha(shaPrefix) {
|
|
|
45
52
|
.toLowerCase()
|
|
46
53
|
.trim();
|
|
47
54
|
if (!p) return null;
|
|
48
|
-
const
|
|
55
|
+
const matchingRecords = readGlobalManifest().filter(
|
|
49
56
|
(r) => r.reusable && typeof r.sha === "string" && r.sha.startsWith(p),
|
|
50
57
|
);
|
|
58
|
+
const matches = Array.from(new Map(matchingRecords.map((r) => [r.sha, r])).values());
|
|
51
59
|
if (matches.length === 0) return null;
|
|
52
60
|
if (matches.length > 1) {
|
|
53
61
|
const exact = matches.find((r) => r.sha === p);
|
|
@@ -58,27 +66,36 @@ export function findGlobalBySha(shaPrefix) {
|
|
|
58
66
|
}
|
|
59
67
|
|
|
60
68
|
function validateCacheHit(match) {
|
|
61
|
-
if (!match?.sha) return null;
|
|
62
|
-
|
|
69
|
+
if (!match?.sha || !match.cached_path) return null;
|
|
70
|
+
if (!isComplete(cacheEntryDir(globalMediaDir(), match.sha))) return null;
|
|
71
|
+
try {
|
|
72
|
+
const file = statSync(match.cached_path);
|
|
73
|
+
return file.isFile() && file.size > 0 ? match : null;
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
export function cacheGet(prompt, type) {
|
|
66
80
|
const key = normalizePrompt(prompt);
|
|
67
81
|
if (!key) return null;
|
|
68
|
-
return
|
|
82
|
+
return (
|
|
69
83
|
readGlobalManifest().find(
|
|
70
84
|
(r) =>
|
|
71
85
|
r.reusable &&
|
|
72
86
|
normalizePrompt(r.provenance?.prompt) === key &&
|
|
73
|
-
(type == null || r.type === type)
|
|
74
|
-
|
|
87
|
+
(type == null || r.type === type) &&
|
|
88
|
+
validateCacheHit(r),
|
|
89
|
+
) ?? null
|
|
75
90
|
);
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
export function cacheGetByEntity(entity) {
|
|
79
94
|
const lower = entity.toLowerCase();
|
|
80
|
-
return
|
|
81
|
-
readGlobalManifest().find(
|
|
95
|
+
return (
|
|
96
|
+
readGlobalManifest().find(
|
|
97
|
+
(r) => r.reusable && r.entity && r.entity.toLowerCase() === lower && validateCacheHit(r),
|
|
98
|
+
) ?? null
|
|
82
99
|
);
|
|
83
100
|
}
|
|
84
101
|
|
|
@@ -86,7 +103,7 @@ export function cachePut(filePath, record) {
|
|
|
86
103
|
const sha = contentHash(filePath);
|
|
87
104
|
// Idempotent: same content already promoted -> don't duplicate the global
|
|
88
105
|
// record. ponytail: skips usage_count bump; add it when the metric is needed.
|
|
89
|
-
const existing = readGlobalManifest().
|
|
106
|
+
const existing = readGlobalManifest().findLast((r) => r.sha === sha && validateCacheHit(r));
|
|
90
107
|
if (existing) return { sha, cached_path: existing.cached_path, deduped: true };
|
|
91
108
|
|
|
92
109
|
const dir = globalMediaDir();
|
|
@@ -108,12 +125,9 @@ export function cachePut(filePath, record) {
|
|
|
108
125
|
}
|
|
109
126
|
|
|
110
127
|
export function importFromCache(cacheRecord, projectDir, localId, localPath) {
|
|
128
|
+
if (!validateCacheHit(cacheRecord)) return null;
|
|
111
129
|
const sha = cacheRecord.sha;
|
|
112
|
-
const entryDir = cacheEntryDir(globalMediaDir(), sha);
|
|
113
|
-
if (!isComplete(entryDir)) return null;
|
|
114
|
-
|
|
115
130
|
const cachedFile = cacheRecord.cached_path;
|
|
116
|
-
if (!cachedFile || !existsSync(cachedFile)) return null;
|
|
117
131
|
|
|
118
132
|
mkdirSync(join(projectDir, ".media"), { recursive: true });
|
|
119
133
|
const fullDest = join(projectDir, localPath);
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { test } from "node:test";
|
|
6
|
+
import { appendRecord } from "./manifest.mjs";
|
|
7
|
+
import {
|
|
8
|
+
cacheGet,
|
|
9
|
+
cacheGetByEntity,
|
|
10
|
+
cachePut,
|
|
11
|
+
findGlobalBySha,
|
|
12
|
+
importFromCache,
|
|
13
|
+
readGlobalManifest,
|
|
14
|
+
} from "./cache.mjs";
|
|
15
|
+
|
|
16
|
+
function sandbox(t) {
|
|
17
|
+
const root = mkdtempSync(join(tmpdir(), "mu-cache-recovery-"));
|
|
18
|
+
const previous = process.env.HYPERFRAMES_MEDIA_HOME;
|
|
19
|
+
process.env.HYPERFRAMES_MEDIA_HOME = join(root, "library");
|
|
20
|
+
t.after(() => {
|
|
21
|
+
if (previous === undefined) delete process.env.HYPERFRAMES_MEDIA_HOME;
|
|
22
|
+
else process.env.HYPERFRAMES_MEDIA_HOME = previous;
|
|
23
|
+
rmSync(root, { recursive: true, force: true });
|
|
24
|
+
});
|
|
25
|
+
const source = join(root, "original.wav");
|
|
26
|
+
const bytes = "cache recovery fixture";
|
|
27
|
+
writeFileSync(source, bytes);
|
|
28
|
+
const record = {
|
|
29
|
+
id: "bgm_001",
|
|
30
|
+
type: "bgm",
|
|
31
|
+
entity: "Recovery",
|
|
32
|
+
provenance: { prompt: "cache recovery" },
|
|
33
|
+
};
|
|
34
|
+
return { root, source, bytes, record };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const invalid of [
|
|
38
|
+
"missing file",
|
|
39
|
+
"missing marker",
|
|
40
|
+
"missing directory",
|
|
41
|
+
"empty file",
|
|
42
|
+
"directory",
|
|
43
|
+
]) {
|
|
44
|
+
test(`cachePut repairs a ${invalid} before deduplicating`, (t) => {
|
|
45
|
+
const { root, source, bytes, record } = sandbox(t);
|
|
46
|
+
const first = cachePut(source, record);
|
|
47
|
+
const entry = dirname(first.cached_path);
|
|
48
|
+
if (invalid === "missing marker") rmSync(join(entry, ".hf-complete"));
|
|
49
|
+
else if (invalid === "missing directory") rmSync(entry, { recursive: true });
|
|
50
|
+
else if (invalid === "empty file") writeFileSync(first.cached_path, "");
|
|
51
|
+
else {
|
|
52
|
+
rmSync(first.cached_path);
|
|
53
|
+
if (invalid === "directory") mkdirSync(first.cached_path);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const replacement = join(root, "replacement.wav");
|
|
57
|
+
writeFileSync(replacement, bytes);
|
|
58
|
+
const repaired = cachePut(replacement, record);
|
|
59
|
+
|
|
60
|
+
assert.equal(repaired.deduped, undefined);
|
|
61
|
+
assert.equal(repaired.sha, first.sha);
|
|
62
|
+
assert.equal(readFileSync(repaired.cached_path, "utf8"), bytes);
|
|
63
|
+
const cached = cacheGet(" Cache Recovery ", "bgm");
|
|
64
|
+
assert.equal(cached?.sha, repaired.sha);
|
|
65
|
+
assert.equal(readFileSync(cached.cached_path, "utf8"), bytes);
|
|
66
|
+
assert.equal(cacheGetByEntity("recovery")?.sha, repaired.sha);
|
|
67
|
+
const imported = importFromCache(cached, join(root, "project"), "bgm_002", "restored.wav");
|
|
68
|
+
assert.equal(imported?.provenance.imported_from, first.sha);
|
|
69
|
+
assert.equal(readFileSync(join(root, "project", "restored.wav"), "utf8"), bytes);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
test("an intact cache stays deduplicated without adding a manifest record", (t) => {
|
|
74
|
+
const { source, record } = sandbox(t);
|
|
75
|
+
const first = cachePut(source, record);
|
|
76
|
+
const second = cachePut(source, record);
|
|
77
|
+
|
|
78
|
+
assert.deepEqual(second, { ...first, deduped: true });
|
|
79
|
+
assert.equal(readGlobalManifest().length, 1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("prompt and entity lookups skip a stale entry for another complete match", (t) => {
|
|
83
|
+
const { root, source, record } = sandbox(t);
|
|
84
|
+
const first = cachePut(source, record);
|
|
85
|
+
rmSync(first.cached_path);
|
|
86
|
+
const secondSource = join(root, "second.wav");
|
|
87
|
+
writeFileSync(secondSource, "different reusable bytes");
|
|
88
|
+
const second = cachePut(secondSource, record);
|
|
89
|
+
|
|
90
|
+
assert.equal(cacheGet("cache recovery", "bgm")?.sha, second.sha);
|
|
91
|
+
assert.equal(cacheGetByEntity("RECOVERY")?.sha, second.sha);
|
|
92
|
+
assert.equal(cacheGet("cache recovery", "sfx"), null);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("sha reuse counts distinct content and selects the repaired record", (t) => {
|
|
96
|
+
const { root, source, bytes, record } = sandbox(t);
|
|
97
|
+
const first = cachePut(source, record);
|
|
98
|
+
rmSync(first.cached_path);
|
|
99
|
+
const replacement = join(dirname(first.cached_path), "replacement.wav");
|
|
100
|
+
writeFileSync(replacement, bytes);
|
|
101
|
+
appendRecord(join(root, "library"), {
|
|
102
|
+
...record,
|
|
103
|
+
sha: first.sha,
|
|
104
|
+
reusable: true,
|
|
105
|
+
cached_path: replacement,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
assert.equal(findGlobalBySha(first.sha)?.cached_path, replacement);
|
|
109
|
+
assert.equal(findGlobalBySha(first.sha.slice(0, 16))?.cached_path, replacement);
|
|
110
|
+
});
|
|
@@ -5,7 +5,7 @@ export function speechSpans(meta, { mergeGap = 0.6, offsets, sequential = false,
|
|
|
5
5
|
const merge = Number(mergeGap);
|
|
6
6
|
const lists = wordListsFromMediaMeta(meta);
|
|
7
7
|
const voices = Array.isArray(meta?.voices) ? meta.voices : [];
|
|
8
|
-
if (lists.length > 1 && !offsets && !sequential) {
|
|
8
|
+
if (lists.filter((words) => words.length > 0).length > 1 && !offsets && !sequential) {
|
|
9
9
|
throw new Error(
|
|
10
10
|
"audio_meta has multiple voice lines with file-relative times; pass --sequential or --offsets so spans land at composition time",
|
|
11
11
|
);
|
|
@@ -16,6 +16,7 @@ export function speechSpans(meta, { mergeGap = 0.6, offsets, sequential = false,
|
|
|
16
16
|
const voice = voices[i];
|
|
17
17
|
let offset = 0;
|
|
18
18
|
if (offsets) {
|
|
19
|
+
if (lists[i].length === 0) continue;
|
|
19
20
|
const id = voice?.id ?? String(i);
|
|
20
21
|
if (!(id in offsets)) throw new Error(`--offsets is missing voice "${id}"`);
|
|
21
22
|
offset = Number(offsets[id]) || 0;
|
|
@@ -75,6 +75,63 @@ test("speechSpans returns empty spans for empty input", () => {
|
|
|
75
75
|
assert.deepEqual(speechSpans({ voices: [] }, { mergeGap: 0.6 }), []);
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
test("speechSpans offsets retain voice IDs across empty transcripts", () => {
|
|
79
|
+
const meta = {
|
|
80
|
+
voices: [
|
|
81
|
+
{ id: "silent", duration_s: 2, words: [] },
|
|
82
|
+
{ id: "narration", duration_s: 1, words: [word("w0", "hello", 0.1, 0.9)] },
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
const expected = [{ start: 4.1, end: 4.9 }];
|
|
86
|
+
assert.deepEqual(speechSpans(meta, { offsets: { silent: 0, narration: 4 } }), expected);
|
|
87
|
+
assert.deepEqual(speechSpans(meta, { offsets: { narration: 4 } }), expected);
|
|
88
|
+
assert.throws(() => speechSpans(meta, { offsets: { silent: 0 } }), /missing voice "narration"/);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("speechSpans sequential counts empty line durations and gaps", () => {
|
|
92
|
+
const meta = {
|
|
93
|
+
voices: [
|
|
94
|
+
{ id: "intro", duration_s: 2, words: [] },
|
|
95
|
+
{ id: "first", duration_s: 1, words: [word("w0", "hello", 0.1, 0.9)] },
|
|
96
|
+
{ id: "pause", duration_s: 3, words: [] },
|
|
97
|
+
{ id: "last", duration_s: 1, words: [word("w1", "goodbye", 0.1, 0.9)] },
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
assert.deepEqual(speechSpans(meta, { sequential: true, gap: 0.5 }), [
|
|
101
|
+
{ start: 2.6, end: 3.4 },
|
|
102
|
+
{ start: 7.6, end: 8.4 },
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("speechSpans offsets without IDs retain original voice indices", () => {
|
|
107
|
+
const meta = {
|
|
108
|
+
voices: [{ duration_s: 2 }, { duration_s: 1, words: [word("w0", "hello", 0.1, 0.9)] }],
|
|
109
|
+
};
|
|
110
|
+
assert.deepEqual(speechSpans(meta, { offsets: { 1: 4 } }), [{ start: 4.1, end: 4.9 }]);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("speechSpans keeps normalized-empty transcripts aligned with their voices", () => {
|
|
114
|
+
const meta = {
|
|
115
|
+
voices: [
|
|
116
|
+
{ id: "untimed", duration_s: 2, words: [{ text: "hello" }] },
|
|
117
|
+
{ id: "narration", duration_s: 1, words: [word("w0", "hello", 0.1, 0.9)] },
|
|
118
|
+
],
|
|
119
|
+
};
|
|
120
|
+
assert.deepEqual(speechSpans(meta, { sequential: true }), [{ start: 2.1, end: 2.9 }]);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("speechSpans does not require placement for transcripts without speech", () => {
|
|
124
|
+
const meta = {
|
|
125
|
+
voices: [
|
|
126
|
+
{ id: "a", duration_s: 2, words: [] },
|
|
127
|
+
{ id: "b", duration_s: 1 },
|
|
128
|
+
],
|
|
129
|
+
};
|
|
130
|
+
assert.deepEqual(speechSpans(meta), []);
|
|
131
|
+
assert.deepEqual(speechSpans(meta, { offsets: {} }), []);
|
|
132
|
+
assert.deepEqual(speechSpans(meta, { sequential: true }), []);
|
|
133
|
+
});
|
|
134
|
+
|
|
78
135
|
test("duckKeyframes shapes attack and release from base volume", () => {
|
|
79
136
|
assert.deepEqual(
|
|
80
137
|
duckKeyframes([{ start: 3, end: 5 }], {
|
|
@@ -148,6 +205,36 @@ test("--json spans match --merge-gap semantics exactly", () => {
|
|
|
148
205
|
}
|
|
149
206
|
});
|
|
150
207
|
|
|
208
|
+
for (const [placement, flags, expected] of [
|
|
209
|
+
["sequential", ["--sequential", "--gap", "0.5"], [{ start: 2.6, end: 3.4 }]],
|
|
210
|
+
["offsets", ["--offsets", "narration=4"], [{ start: 4.1, end: 4.9 }]],
|
|
211
|
+
]) {
|
|
212
|
+
test(`audio-duck --json preserves empty voice placement with ${placement}`, (t) => {
|
|
213
|
+
const dir = mkdtempSync(join(tmpdir(), "media-use-duck-empty-"));
|
|
214
|
+
t.after(() => rmSync(dir, { recursive: true, force: true }));
|
|
215
|
+
const metaPath = join(dir, "audio_meta.json");
|
|
216
|
+
writeFileSync(
|
|
217
|
+
metaPath,
|
|
218
|
+
JSON.stringify({
|
|
219
|
+
voices: [
|
|
220
|
+
{ id: "silent", duration_s: 2, words: [] },
|
|
221
|
+
{ id: "narration", duration_s: 1, words: [word("w0", "hello", 0.1, 0.9)] },
|
|
222
|
+
],
|
|
223
|
+
}),
|
|
224
|
+
);
|
|
225
|
+
const result = JSON.parse(
|
|
226
|
+
execFileSync(
|
|
227
|
+
process.execPath,
|
|
228
|
+
[SCRIPT, "--meta", metaPath, "--target", "#bgm", ...flags, "--json"],
|
|
229
|
+
{ encoding: "utf8" },
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
assert.deepEqual(result.spans, expected);
|
|
233
|
+
assert.equal(result.keyframes[0].time, expected[0].start);
|
|
234
|
+
assert.equal(result.lane.lanes[0].points[1].t, expected[0].start);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
151
238
|
function word(id, text, start, end) {
|
|
152
239
|
return { id, text, start, end };
|
|
153
240
|
}
|
|
@@ -3,6 +3,7 @@ import { extname } from "node:path";
|
|
|
3
3
|
import { ffprobeBinary } from "./ff-binaries.mjs";
|
|
4
4
|
|
|
5
5
|
const IMAGE_EXT = new Set([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg", ".ico"]);
|
|
6
|
+
const AUDIO_EXT = new Set([".mp3", ".wav", ".ogg", ".m4a", ".aac"]);
|
|
6
7
|
|
|
7
8
|
export function probe(filePath) {
|
|
8
9
|
const ext = extname(filePath).toLowerCase();
|
|
@@ -18,7 +19,11 @@ export function probe(filePath) {
|
|
|
18
19
|
{ encoding: "utf8", timeout: 5000 },
|
|
19
20
|
);
|
|
20
21
|
const info = JSON.parse(raw);
|
|
21
|
-
const
|
|
22
|
+
const streams = info.streams ?? [];
|
|
23
|
+
const wantedType = AUDIO_EXT.has(ext) ? "audio" : "video";
|
|
24
|
+
const stream =
|
|
25
|
+
streams.find((s) => s.codec_type === wantedType && s.disposition?.attached_pic !== 1) ??
|
|
26
|
+
streams[0];
|
|
22
27
|
const format = info.format;
|
|
23
28
|
|
|
24
29
|
const isImage = IMAGE_EXT.has(ext);
|
|
@@ -4,6 +4,79 @@ import { chmodSync, mkdtempSync, writeFileSync, existsSync, rmSync } from "node:
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { probe } from "./probe.mjs";
|
|
7
|
+
import childProcess from "node:child_process";
|
|
8
|
+
import { syncBuiltinESMExports } from "node:module";
|
|
9
|
+
|
|
10
|
+
function mockProbe(t, streams, format = { duration: "2" }) {
|
|
11
|
+
t.mock.method(childProcess, "execFileSync", () => JSON.stringify({ streams, format }));
|
|
12
|
+
syncBuiltinESMExports();
|
|
13
|
+
t.after(() => {
|
|
14
|
+
t.mock.restoreAll();
|
|
15
|
+
syncBuiltinESMExports();
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const AUDIO_STREAM = { codec_type: "audio", codec_name: "aac", duration: "3" };
|
|
20
|
+
const VIDEO_STREAM = {
|
|
21
|
+
codec_type: "video",
|
|
22
|
+
codec_name: "h264",
|
|
23
|
+
width: 64,
|
|
24
|
+
height: 48,
|
|
25
|
+
duration: "4",
|
|
26
|
+
};
|
|
27
|
+
const COVER_STREAM = {
|
|
28
|
+
codec_type: "video",
|
|
29
|
+
codec_name: "mjpeg",
|
|
30
|
+
width: 100,
|
|
31
|
+
height: 100,
|
|
32
|
+
disposition: { attached_pic: 1 },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
for (const [name, streams] of [
|
|
36
|
+
["audio before video", [AUDIO_STREAM, VIDEO_STREAM]],
|
|
37
|
+
["video before audio", [VIDEO_STREAM, AUDIO_STREAM]],
|
|
38
|
+
["cover art before video", [COVER_STREAM, AUDIO_STREAM, VIDEO_STREAM]],
|
|
39
|
+
]) {
|
|
40
|
+
test(`probe selects the video stream with ${name}`, (t) => {
|
|
41
|
+
mockProbe(t, streams);
|
|
42
|
+
assert.deepEqual(probe("clip.mp4"), {
|
|
43
|
+
duration: 2,
|
|
44
|
+
width: 64,
|
|
45
|
+
height: 48,
|
|
46
|
+
codec: "h264",
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
test("probe selects audio metadata for an audio file with cover art", (t) => {
|
|
52
|
+
mockProbe(t, [COVER_STREAM, AUDIO_STREAM]);
|
|
53
|
+
assert.deepEqual(probe("song.m4a"), { duration: 2, width: null, height: null, codec: "aac" });
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("probe uses the selected video's duration when the container has none", (t) => {
|
|
57
|
+
mockProbe(t, [AUDIO_STREAM, VIDEO_STREAM], {});
|
|
58
|
+
assert.equal(probe("clip.mp4").duration, 4);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("probe selects image dimensions without reporting an image duration", (t) => {
|
|
62
|
+
mockProbe(t, [AUDIO_STREAM, VIDEO_STREAM]);
|
|
63
|
+
assert.deepEqual(probe("frame.png"), {
|
|
64
|
+
duration: null,
|
|
65
|
+
width: 64,
|
|
66
|
+
height: 48,
|
|
67
|
+
codec: "h264",
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("probe retains audio-only metadata and the empty-stream fallback", (t) => {
|
|
72
|
+
mockProbe(t, [AUDIO_STREAM]);
|
|
73
|
+
assert.deepEqual(probe("clip.mp4"), { duration: 2, width: null, height: null, codec: "aac" });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("probe retains the container duration when no streams are reported", (t) => {
|
|
77
|
+
mockProbe(t, []);
|
|
78
|
+
assert.deepEqual(probe("clip.mp4"), { duration: 2, width: null, height: null, codec: null });
|
|
79
|
+
});
|
|
7
80
|
|
|
8
81
|
// Regression for the shell-injection fix: probe() must pass the path as a literal
|
|
9
82
|
// argv entry, never through a shell. A filename containing shell metacharacters
|
|
@@ -14,5 +14,5 @@ export function normalizeWords(input) {
|
|
|
14
14
|
export function wordListsFromMediaMeta(input) {
|
|
15
15
|
if (Array.isArray(input) || Array.isArray(input?.words)) return [normalizeWords(input)];
|
|
16
16
|
if (!Array.isArray(input?.voices)) return [];
|
|
17
|
-
return input.voices.map((voice) => normalizeWords(voice))
|
|
17
|
+
return input.voices.map((voice) => normalizeWords(voice));
|
|
18
18
|
}
|
|
@@ -9,8 +9,8 @@ import {
|
|
|
9
9
|
} from "./chunk-6BEFEWFB.js";
|
|
10
10
|
import {
|
|
11
11
|
trackSkillsInstallSkipped
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-RG3QWWIE.js";
|
|
13
|
+
import "./chunk-XU5GGV6R.js";
|
|
14
14
|
import {
|
|
15
15
|
diag
|
|
16
16
|
} from "./chunk-VWG7OQKU.js";
|
|
@@ -39,15 +39,15 @@ import {
|
|
|
39
39
|
isCoreSkill,
|
|
40
40
|
presentSkills,
|
|
41
41
|
pruneOrphanedLockEntries
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-5HJ343GH.js";
|
|
43
43
|
import {
|
|
44
44
|
withMeta
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-P6WH6QTV.js";
|
|
46
46
|
import "./chunk-K4WIOZFP.js";
|
|
47
47
|
import "./chunk-XUXGIA7H.js";
|
|
48
48
|
import "./chunk-WERLJGIC.js";
|
|
49
49
|
import "./chunk-ISN5GRYI.js";
|
|
50
|
-
import "./chunk-
|
|
50
|
+
import "./chunk-4OC7MQNB.js";
|
|
51
51
|
import "./chunk-KNU5COPB.js";
|
|
52
52
|
import {
|
|
53
53
|
c
|
|
@@ -180,8 +180,8 @@ function pathsOverlap(left, right) {
|
|
|
180
180
|
}
|
|
181
181
|
function sameExistingNode(left, right) {
|
|
182
182
|
try {
|
|
183
|
-
const leftStat = statSync(left);
|
|
184
|
-
const rightStat = statSync(right);
|
|
183
|
+
const leftStat = statSync(left, { bigint: true });
|
|
184
|
+
const rightStat = statSync(right, { bigint: true });
|
|
185
185
|
return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
|
|
186
186
|
} catch {
|
|
187
187
|
return false;
|
|
@@ -9,13 +9,13 @@ import {
|
|
|
9
9
|
printSkillsUpdateNotice,
|
|
10
10
|
refreshSkillsCache,
|
|
11
11
|
skillsCheckDue
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-5HJ343GH.js";
|
|
13
|
+
import "./chunk-P6WH6QTV.js";
|
|
14
14
|
import "./chunk-K4WIOZFP.js";
|
|
15
15
|
import "./chunk-XUXGIA7H.js";
|
|
16
16
|
import "./chunk-WERLJGIC.js";
|
|
17
17
|
import "./chunk-ISN5GRYI.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-4OC7MQNB.js";
|
|
19
19
|
import "./chunk-KNU5COPB.js";
|
|
20
20
|
import "./chunk-M2WBOQ2Q.js";
|
|
21
21
|
export {
|