@bubo-squared/gyroview 0.1.0
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/CHANGELOG.md +32 -0
- package/LICENSE +21 -0
- package/README.md +216 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.js +4 -0
- package/dist/index.d.ts +959 -0
- package/dist/index.js +13 -0
- package/dist/packages/adapters/blob/src/BlobRandomAccessSource.js +37 -0
- package/dist/packages/adapters/blob/src/index.js +2 -0
- package/dist/packages/adapters/fetch/src/HttpRangeSource.js +194 -0
- package/dist/packages/adapters/fetch/src/HttpResourceLocator.js +35 -0
- package/dist/packages/adapters/fetch/src/httpRequest.js +126 -0
- package/dist/packages/adapters/fetch/src/index.js +3 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyAudioSegments.js +133 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyDemuxer.js +63 -0
- package/dist/packages/adapters/mediabunny/src/MediabunnyVideoTrackReader.js +84 -0
- package/dist/packages/adapters/mediabunny/src/SegmentChannel.js +84 -0
- package/dist/packages/adapters/mediabunny/src/bufferSources.js +12 -0
- package/dist/packages/adapters/mediabunny/src/index.js +2 -0
- package/dist/packages/adapters/mse-audio/src/MediaSourceAudioClock.js +139 -0
- package/dist/packages/adapters/mse-audio/src/SourceBufferFeeder.js +174 -0
- package/dist/packages/adapters/mse-audio/src/events.js +26 -0
- package/dist/packages/adapters/mse-audio/src/index.js +2 -0
- package/dist/packages/adapters/mse-audio/src/mediaSourceSupport.js +82 -0
- package/dist/packages/adapters/three/src/ThreeFrameRenderer.js +274 -0
- package/dist/packages/adapters/three/src/compileAndProve.js +14 -0
- package/dist/packages/adapters/three/src/fullscreenPass.js +52 -0
- package/dist/packages/adapters/three/src/index.js +2 -0
- package/dist/packages/adapters/three/src/lensTextures.js +25 -0
- package/dist/packages/adapters/three/src/pictureMaterials.js +44 -0
- package/dist/packages/adapters/three/src/renderInto.js +15 -0
- package/dist/packages/adapters/three/src/rendererUniforms.js +157 -0
- package/dist/packages/adapters/three/src/samplingStrategies.js +26 -0
- package/dist/packages/adapters/three/src/seamMeter/SeamMeterPass.js +79 -0
- package/dist/packages/adapters/three/src/seamMeter/rowMeans.js +38 -0
- package/dist/packages/adapters/three/src/shaderPrograms.js +66 -0
- package/dist/packages/adapters/three/src/shaders/analysis.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/equirectangularRays.js +4 -0
- package/dist/packages/adapters/three/src/shaders/fixedJoin.js +4 -0
- package/dist/packages/adapters/three/src/shaders/fullscreen.vert.js +4 -0
- package/dist/packages/adapters/three/src/shaders/header.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensModels.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensSampling.js +4 -0
- package/dist/packages/adapters/three/src/shaders/lensTextures.js +4 -0
- package/dist/packages/adapters/three/src/shaders/rawLenses.frag.js +4 -0
- package/dist/packages/adapters/three/src/shaders/rectilinearRays.js +4 -0
- package/dist/packages/adapters/three/src/shaders/screenAreas.js +4 -0
- package/dist/packages/adapters/three/src/shaders/stitch.frag.js +4 -0
- package/dist/packages/adapters/three/src/webglRenderer.js +37 -0
- package/dist/packages/adapters/webcodecs/src/WebCodecsVideoDecoderPort.js +135 -0
- package/dist/packages/adapters/webcodecs/src/index.js +2 -0
- package/dist/packages/core/src/application/gainMatching/GainMatching.js +66 -0
- package/dist/packages/core/src/application/gainMatching/GainMatchingFrameSink.js +62 -0
- package/dist/packages/core/src/application/playback/ClockWatch.js +76 -0
- package/dist/packages/core/src/application/playback/DecodePipeline.js +188 -0
- package/dist/packages/core/src/application/playback/DecodeRun.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairQueue.js +93 -0
- package/dist/packages/core/src/application/playback/FramePairer.js +60 -0
- package/dist/packages/core/src/application/playback/PlaybackSession.js +417 -0
- package/dist/packages/core/src/application/playback/SeekOrder.js +36 -0
- package/dist/packages/core/src/application/playback/SessionLifecycle.js +42 -0
- package/dist/packages/core/src/application/playback/StartGate.js +55 -0
- package/dist/packages/core/src/application/playback/TimeUpdates.js +31 -0
- package/dist/packages/core/src/application/playback/WallClock.js +46 -0
- package/dist/packages/core/src/application/playback/keyframeTimeAt.js +15 -0
- package/dist/packages/core/src/application/playback/probeDecoding.js +168 -0
- package/dist/packages/core/src/application/recording/frameTimesOf.js +22 -0
- package/dist/packages/core/src/application/recording/inspectLayout.js +21 -0
- package/dist/packages/core/src/application/recording/inspectRecording.js +124 -0
- package/dist/packages/core/src/application/recording/locateOtherLensFile.js +18 -0
- package/dist/packages/core/src/application/recording/motionOf.js +39 -0
- package/dist/packages/core/src/application/recording/readRecording.js +31 -0
- package/dist/packages/core/src/application/recording/timeRecording.js +57 -0
- package/dist/packages/core/src/application/stabilization/StabilizingFrameSink.js +31 -0
- package/dist/packages/core/src/domain/format/boxes/BoxLayout.js +7 -0
- package/dist/packages/core/src/domain/format/boxes/boxConstants.js +16 -0
- package/dist/packages/core/src/domain/format/boxes/scanBoxes.js +59 -0
- package/dist/packages/core/src/domain/format/calibration/CalibrationVersion.js +13 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/CalibrationStringLayout.js +34 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/LegacyCalibrationLayout.js +44 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/MeiCalibrationLayout.js +45 -0
- package/dist/packages/core/src/domain/format/calibration/layouts/PolynomialCalibrationLayout.js +44 -0
- package/dist/packages/core/src/domain/format/calibration/offsetTokens.js +65 -0
- package/dist/packages/core/src/domain/format/calibration/parseOffsetString.js +52 -0
- package/dist/packages/core/src/domain/format/calibration/selectCalibration.js +46 -0
- package/dist/packages/core/src/domain/format/captureOrigin.js +20 -0
- package/dist/packages/core/src/domain/format/constants.js +33 -0
- package/dist/packages/core/src/domain/format/info/infoFields.js +110 -0
- package/dist/packages/core/src/domain/format/info/parseInfoRecord.js +90 -0
- package/dist/packages/core/src/domain/format/info/readInfoRecord.js +14 -0
- package/dist/packages/core/src/domain/format/layout/detectLensLayout.js +122 -0
- package/dist/packages/core/src/domain/format/naming/RecordingFileName.js +67 -0
- package/dist/packages/core/src/domain/format/records/TrailerRecords.js +83 -0
- package/dist/packages/core/src/domain/format/records/exposure/parseExposureRecord.js +48 -0
- package/dist/packages/core/src/domain/format/records/gyro/FloatGyroSampleLayout.js +39 -0
- package/dist/packages/core/src/domain/format/records/gyro/RawGyroSampleLayout.js +55 -0
- package/dist/packages/core/src/domain/format/records/gyro/SampleColumns.js +82 -0
- package/dist/packages/core/src/domain/format/records/gyro/gyroLayouts.js +12 -0
- package/dist/packages/core/src/domain/format/records/gyro/parseGyroRecord.js +129 -0
- package/dist/packages/core/src/domain/format/trailer/RecordHeader.js +23 -0
- package/dist/packages/core/src/domain/format/trailer/Trailer.js +23 -0
- package/dist/packages/core/src/domain/format/trailer/TrailerFooter.js +27 -0
- package/dist/packages/core/src/domain/format/trailer/locateRecords.js +80 -0
- package/dist/packages/core/src/domain/format/trailer/parseRecordIndex.js +28 -0
- package/dist/packages/core/src/domain/format/trailer/readTrailer.js +32 -0
- package/dist/packages/core/src/domain/motion/exposure/ExposureRecord.js +53 -0
- package/dist/packages/core/src/domain/motion/gyro/GyroTrack.js +60 -0
- package/dist/packages/core/src/domain/motion/gyro/medianStep.js +11 -0
- package/dist/packages/core/src/domain/motion/gyro/repairedTimeline.js +101 -0
- package/dist/packages/core/src/domain/motion/imu/ImuFrame.js +112 -0
- package/dist/packages/core/src/domain/motion/orientation/OrientationTrack.js +146 -0
- package/dist/packages/core/src/domain/motion/orientation/estimateGyroBias.js +64 -0
- package/dist/packages/core/src/domain/motion/orientation/gravity.js +97 -0
- package/dist/packages/core/src/domain/motion/stabilization/Stabilizer.js +13 -0
- package/dist/packages/core/src/domain/motion/stabilization/stabilizers.js +86 -0
- package/dist/packages/core/src/domain/motion/timing/CaptureClock.js +41 -0
- package/dist/packages/core/src/domain/motion/timing/FrameTimes.js +92 -0
- package/dist/packages/core/src/domain/motion/timing/frameTimeSources.js +61 -0
- package/dist/packages/core/src/domain/motion/timing/resolveFrameTimes.js +68 -0
- package/dist/packages/core/src/domain/optics/EquidistantModel.js +39 -0
- package/dist/packages/core/src/domain/optics/MeiModel.js +45 -0
- package/dist/packages/core/src/domain/optics/PolynomialModel.js +44 -0
- package/dist/packages/core/src/domain/optics/gainMatch.js +62 -0
- package/dist/packages/core/src/domain/optics/lensGeometry.js +18 -0
- package/dist/packages/core/src/domain/optics/lensPose.js +35 -0
- package/dist/packages/core/src/domain/optics/opticsConstants.js +5 -0
- package/dist/packages/core/src/domain/playback/PlayerState.js +83 -0
- package/dist/packages/core/src/domain/stitching/LensLayout.js +21 -0
- package/dist/packages/core/src/domain/stitching/StitchingSetup.js +78 -0
- package/dist/packages/core/src/domain/stitching/seamDisparity.js +4 -0
- package/dist/packages/core/src/domain/stitching/seamDisparityField.js +5 -0
- package/dist/packages/core/src/domain/stitching/seamJoin.js +16 -0
- package/dist/packages/core/src/domain/stitching/seamMismatch.js +1 -0
- package/dist/packages/core/src/domain/stitching/seamRing.js +4 -0
- package/dist/packages/core/src/domain/stitching/seamStrip.js +26 -0
- package/dist/packages/core/src/domain/view/Framing.js +31 -0
- package/dist/packages/core/src/domain/view/PictureQuality.js +26 -0
- package/dist/packages/core/src/domain/view/ViewMode.js +9 -0
- package/dist/packages/core/src/domain/view/ViewState.js +48 -0
- package/dist/packages/core/src/domain/view/lensTilesView.js +63 -0
- package/dist/packages/core/src/domain/view/magnification.js +103 -0
- package/dist/packages/core/src/domain/view/normalView.js +29 -0
- package/dist/packages/core/src/domain/view/panoramaView.js +99 -0
- package/dist/packages/core/src/domain/view/rectilinear.js +40 -0
- package/dist/packages/core/src/domain/view/screenLayout.js +113 -0
- package/dist/packages/core/src/domain/view/viewGestures.js +98 -0
- package/dist/packages/core/src/domain/view/viewModes.js +14 -0
- package/dist/packages/core/src/index.js +59 -0
- package/dist/packages/core/src/ports/FramePair.js +6 -0
- package/dist/packages/core/src/shared/async/Deferred.js +42 -0
- package/dist/packages/core/src/shared/async/RunStop.js +40 -0
- package/dist/packages/core/src/shared/binary/ByteRange.js +38 -0
- package/dist/packages/core/src/shared/binary/ByteReader.js +77 -0
- package/dist/packages/core/src/shared/errors/GyroViewError.js +81 -0
- package/dist/packages/core/src/shared/errors/reportLater.js +11 -0
- package/dist/packages/core/src/shared/events/Outbox.js +68 -0
- package/dist/packages/core/src/shared/events/TypedEmitter.js +55 -0
- package/dist/packages/core/src/shared/keysOf.js +10 -0
- package/dist/packages/core/src/shared/lazy.js +11 -0
- package/dist/packages/core/src/shared/math/Matrix3.js +93 -0
- package/dist/packages/core/src/shared/math/Quaternion.js +133 -0
- package/dist/packages/core/src/shared/math/Vector3.js +54 -0
- package/dist/packages/core/src/shared/math/clamp.js +9 -0
- package/dist/packages/core/src/shared/protobuf/ProtobufMessage.js +157 -0
- package/dist/packages/core/src/shared/text/urlPath.js +31 -0
- package/dist/packages/core/src/shared/text/utf8.js +12 -0
- package/dist/packages/core/src/shared/units/angle.js +22 -0
- package/dist/packages/core/src/shared/units/time.js +13 -0
- package/dist/packages/player/src/PlayerSource.js +15 -0
- package/dist/packages/player/src/browserPlayer.js +18 -0
- package/dist/packages/player/src/choices.js +25 -0
- package/dist/packages/player/src/composition/Disposables.js +31 -0
- package/dist/packages/player/src/composition/browserPorts.js +46 -0
- package/dist/packages/player/src/composition/buildPipeline.js +153 -0
- package/dist/packages/player/src/composition/ensureDecodable.js +63 -0
- package/dist/packages/player/src/composition/openInputs.js +179 -0
- package/dist/packages/player/src/composition/openRecording.js +42 -0
- package/dist/packages/player/src/controls/ChoiceMenu.js +113 -0
- package/dist/packages/player/src/controls/PictureMenus.js +47 -0
- package/dist/packages/player/src/controls/SeekBar.js +125 -0
- package/dist/packages/player/src/controls/SoundControls.js +41 -0
- package/dist/packages/player/src/controls/TransportButtons.js +31 -0
- package/dist/packages/player/src/controls/ViewGestures.js +136 -0
- package/dist/packages/player/src/controls/Wording.js +78 -0
- package/dist/packages/player/src/controls/controlParts.js +36 -0
- package/dist/packages/player/src/controls/controls.js +4 -0
- package/dist/packages/player/src/controls/controlsBar.js +23 -0
- package/dist/packages/player/src/controls/controlsMarkup.js +85 -0
- package/dist/packages/player/src/controls/customControls.js +59 -0
- package/dist/packages/player/src/controls/formatTime.js +19 -0
- package/dist/packages/player/src/controls/icons.js +40 -0
- package/dist/packages/player/src/controls/keyboard.js +118 -0
- package/dist/packages/player/src/controls/messages.js +94 -0
- package/dist/packages/player/src/controls/parseMarkup.js +31 -0
- package/dist/packages/player/src/controls/viewButtons.js +14 -0
- package/dist/packages/player/src/element/ElementLoads.js +119 -0
- package/dist/packages/player/src/element/FullscreenToggle.js +39 -0
- package/dist/packages/player/src/element/GyroViewElement.js +313 -0
- package/dist/packages/player/src/element/IdleWatcher.js +71 -0
- package/dist/packages/player/src/element/TypedEventElement.js +22 -0
- package/dist/packages/player/src/element/accessibleRegion.js +19 -0
- package/dist/packages/player/src/element/attributeNames.js +51 -0
- package/dist/packages/player/src/element/attributes.js +101 -0
- package/dist/packages/player/src/element/defineGyroView.js +21 -0
- package/dist/packages/player/src/element/earlyProperties.js +36 -0
- package/dist/packages/player/src/element/elementSource.js +23 -0
- package/dist/packages/player/src/element/liveSettings.js +138 -0
- package/dist/packages/player/src/element/playbackAttributes.js +53 -0
- package/dist/packages/player/src/element/playerEventMirror.js +33 -0
- package/dist/packages/player/src/element/reflectedProperties.js +78 -0
- package/dist/packages/player/src/element/styles.js +4 -0
- package/dist/packages/player/src/element/template.js +47 -0
- package/dist/packages/player/src/index.js +10 -0
- package/dist/packages/player/src/inspectRecording.js +22 -0
- package/dist/packages/player/src/player/DrawingBufferFit.js +77 -0
- package/dist/packages/player/src/player/FrameLoop.js +40 -0
- package/dist/packages/player/src/player/PictureSettings.js +60 -0
- package/dist/packages/player/src/player/Player.js +377 -0
- package/dist/packages/player/src/player/PlayerEvents.js +28 -0
- package/dist/packages/player/src/player/PlayerPhase.js +21 -0
- package/dist/packages/player/src/player/PlayerSound.js +61 -0
- package/dist/packages/player/src/player/PlayerView.js +113 -0
- package/dist/packages/player/src/player/SessionRelay.js +66 -0
- package/dist/packages/player/src/player/ensureFinite.js +26 -0
- package/dist/packages/player/src/player/loadRecording.js +65 -0
- package/dist/packages/player/src/player/transportEvents.js +29 -0
- package/dist/standalone.js +4919 -0
- package/package.json +73 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { hasErrorCode, messageOf } from "../../shared/errors/GyroViewError.js";
|
|
2
|
+
import { Deferred } from "../../shared/async/Deferred.js";
|
|
3
|
+
//#region ../../packages/core/src/application/playback/probeDecoding.ts
|
|
4
|
+
var DECODES = {
|
|
5
|
+
verdict: "decodes",
|
|
6
|
+
detail: void 0
|
|
7
|
+
};
|
|
8
|
+
var NO_KEY_FRAME = {
|
|
9
|
+
verdict: "no-key-frame",
|
|
10
|
+
detail: "the track has no key frame"
|
|
11
|
+
};
|
|
12
|
+
var NO_FRAME_OUT = {
|
|
13
|
+
verdict: "decode-failed",
|
|
14
|
+
detail: "the decoder accepted the first key frame but produced no picture"
|
|
15
|
+
};
|
|
16
|
+
var TIMED_OUT = {
|
|
17
|
+
verdict: "timed-out",
|
|
18
|
+
detail: "the decoder produced no picture before the deadline"
|
|
19
|
+
};
|
|
20
|
+
var KEY_FRAME_LATE = {
|
|
21
|
+
verdict: "key-frame-late",
|
|
22
|
+
detail: "the first key frame did not arrive before the deadline"
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Use case: find out before playback whether this platform decodes the recording, by decoding the
|
|
26
|
+
* first key frame of every frame source. The decoder port's `isSupported` alone is not trusted:
|
|
27
|
+
* platforms answer yes and then fail, and hardware decoders can stall, hence the real decode under
|
|
28
|
+
* a deadline. The host resolves `deadline` once the probe has taken too long (the core has no
|
|
29
|
+
* timers); sources still undecided then report `timed-out`, or `key-frame-late` while their key
|
|
30
|
+
* frame was still being read, and their decoders are closed. A track that cannot be read rejects
|
|
31
|
+
* the probe with its own failure, and the other sources' decoders are closed then, not at the
|
|
32
|
+
* deadline.
|
|
33
|
+
*/
|
|
34
|
+
async function probeDecoding(frameSources, decoderPort, deadline) {
|
|
35
|
+
const probes = frameSources.map((track) => new SourceProbe(track, decoderPort));
|
|
36
|
+
try {
|
|
37
|
+
const sources = await Promise.all(probes.map((probe) => probeFrameSource(probe, deadline)));
|
|
38
|
+
return {
|
|
39
|
+
canDecode: sources.every((source) => source.verdict === "decodes"),
|
|
40
|
+
sources
|
|
41
|
+
};
|
|
42
|
+
} finally {
|
|
43
|
+
for (const probe of probes) probe.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function probeFrameSource(probe, deadline) {
|
|
47
|
+
try {
|
|
48
|
+
const outcome = await Promise.race([probe.run(), afterDeadline(deadline, probe)]);
|
|
49
|
+
return {
|
|
50
|
+
track: probe.track.description,
|
|
51
|
+
...outcome
|
|
52
|
+
};
|
|
53
|
+
} finally {
|
|
54
|
+
probe.close();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
async function afterDeadline(deadline, probe) {
|
|
58
|
+
await deadline.promise;
|
|
59
|
+
return probe.outcomeAtDeadline;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* One frame source's probe: owns the decoder it opens so a deadline can close it from outside.
|
|
63
|
+
* Closed while its decoder works, it settles as timed out; a key frame read that never answers
|
|
64
|
+
* keeps its run pending until the track itself is let go, since track reads take no signal.
|
|
65
|
+
*/
|
|
66
|
+
var SourceProbe = class {
|
|
67
|
+
track;
|
|
68
|
+
decoderPort;
|
|
69
|
+
decoder;
|
|
70
|
+
/**
|
|
71
|
+
* The outcome the open decoder reports into, settled by a close as well.
|
|
72
|
+
*/
|
|
73
|
+
firstOutcome;
|
|
74
|
+
isClosed = false;
|
|
75
|
+
isReadingKeyFrame = false;
|
|
76
|
+
constructor(track, decoderPort) {
|
|
77
|
+
this.track = track;
|
|
78
|
+
this.decoderPort = decoderPort;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* What a deadline passing now says: the network did not bring the key frame in time, or the
|
|
82
|
+
* platform did not answer or decode it in time.
|
|
83
|
+
*/
|
|
84
|
+
get outcomeAtDeadline() {
|
|
85
|
+
return this.isReadingKeyFrame ? KEY_FRAME_LATE : TIMED_OUT;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The decoder's answers make the verdict; a failure to read the track rejects as it is, since
|
|
89
|
+
* a connection that dropped is no codec's fault. A probe closed meanwhile reads no key frame and
|
|
90
|
+
* opens no decoder: decoders are few on some platforms, and the next load may need them.
|
|
91
|
+
*/
|
|
92
|
+
async run() {
|
|
93
|
+
const configuration = await this.track.decoderConfiguration();
|
|
94
|
+
if (!await this.decoderPort.isSupported(configuration)) return unsupported(configuration);
|
|
95
|
+
if (this.isClosed) return TIMED_OUT;
|
|
96
|
+
this.isReadingKeyFrame = true;
|
|
97
|
+
const keyPacket = await this.track.firstKeyPacket();
|
|
98
|
+
this.isReadingKeyFrame = false;
|
|
99
|
+
return keyPacket ? await this.decodeFirst(configuration, keyPacket) : NO_KEY_FRAME;
|
|
100
|
+
}
|
|
101
|
+
close() {
|
|
102
|
+
this.isClosed = true;
|
|
103
|
+
this.decoder?.close();
|
|
104
|
+
this.decoder = void 0;
|
|
105
|
+
this.firstOutcome?.resolve(TIMED_OUT);
|
|
106
|
+
}
|
|
107
|
+
async decodeFirst(configuration, keyPacket) {
|
|
108
|
+
if (this.isClosed) return TIMED_OUT;
|
|
109
|
+
const first = new Deferred();
|
|
110
|
+
this.firstOutcome = first;
|
|
111
|
+
const decoder = await this.openDecoder(configuration, first);
|
|
112
|
+
if (decoder === void 0) return first.promise;
|
|
113
|
+
this.decoder = decoder;
|
|
114
|
+
this.decodeInto(decoder, keyPacket, first);
|
|
115
|
+
return first.promise;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A decoder that reports into `first`; undefined, the outcome reported there, when the port
|
|
119
|
+
* refuses one or the probe was closed while it was being made.
|
|
120
|
+
*/
|
|
121
|
+
async openDecoder(configuration, first) {
|
|
122
|
+
try {
|
|
123
|
+
const decoder = await this.decoderPort.create(configuration, {
|
|
124
|
+
onFrame: (frame) => {
|
|
125
|
+
frame.close();
|
|
126
|
+
first.resolve(DECODES);
|
|
127
|
+
},
|
|
128
|
+
onError: (error) => {
|
|
129
|
+
first.resolve(failedWith(error));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
if (!this.isClosed) return decoder;
|
|
133
|
+
decoder.close();
|
|
134
|
+
first.resolve(TIMED_OUT);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
first.resolve(failedWith(error));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Decodes the key frame and forces its picture out of decoders that buffer. A decoder that
|
|
141
|
+
* refuses the packet (one that is no real key frame), or a flush that ends without a picture,
|
|
142
|
+
* is a failure.
|
|
143
|
+
*/
|
|
144
|
+
async decodeInto(decoder, keyPacket, first) {
|
|
145
|
+
try {
|
|
146
|
+
decoder.decode(keyPacket);
|
|
147
|
+
await decoder.flush();
|
|
148
|
+
first.resolve(NO_FRAME_OUT);
|
|
149
|
+
} catch (error) {
|
|
150
|
+
first.resolve(failedWith(error));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
function unsupported(configuration) {
|
|
155
|
+
return {
|
|
156
|
+
verdict: "unsupported-configuration",
|
|
157
|
+
detail: `${configuration.codec} at ${configuration.codedWidth}x${configuration.codedHeight} is not supported here`
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function failedWith(error) {
|
|
161
|
+
const message = messageOf(error);
|
|
162
|
+
return {
|
|
163
|
+
verdict: hasErrorCode(error, "codec-unsupported") ? "unsupported-configuration" : "decode-failed",
|
|
164
|
+
detail: message
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//#endregion
|
|
168
|
+
export { probeDecoding };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { planFrameTimes } from "../../domain/motion/timing/resolveFrameTimes.js";
|
|
2
|
+
//#region ../../packages/core/src/application/recording/frameTimesOf.ts
|
|
3
|
+
/**
|
|
4
|
+
* Frame times for a frame source. The exposure record costs one small read and is always at
|
|
5
|
+
* hand; the track's own timestamps are fetched only when the domain says the answer depends on
|
|
6
|
+
* them, since walking the sample table is the costly part. Undefined when even nominal spacing
|
|
7
|
+
* is impossible (no frame rate).
|
|
8
|
+
*/
|
|
9
|
+
async function frameTimesOf(recording, frameSource, timeline) {
|
|
10
|
+
const { info } = recording;
|
|
11
|
+
const [frameCount, exposureRecord] = await Promise.all([frameSource.frameCount(), recording.readExposureRecord()]);
|
|
12
|
+
const plan = planFrameTimes({
|
|
13
|
+
...timeline,
|
|
14
|
+
frameCount,
|
|
15
|
+
frameRate: info.frameRate,
|
|
16
|
+
readoutTime: info.readoutTime,
|
|
17
|
+
exposureRecord
|
|
18
|
+
}, info.preferredFrameTimeSource);
|
|
19
|
+
return plan.needsTrackTimestamps ? plan.resolveWith(await frameSource.sampleTimestamps()) : plan.resolved;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { frameTimesOf };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readTrailer } from "../../domain/format/trailer/readTrailer.js";
|
|
2
|
+
import { trailerWrapperOf } from "../../domain/format/boxes/BoxLayout.js";
|
|
3
|
+
import { scanBoxes } from "../../domain/format/boxes/scanBoxes.js";
|
|
4
|
+
//#region ../../packages/core/src/application/recording/inspectLayout.ts
|
|
5
|
+
/**
|
|
6
|
+
* Use case: map out a file's structure, box by box and record by record.
|
|
7
|
+
*/
|
|
8
|
+
async function inspectLayout(source) {
|
|
9
|
+
const fileSize = await source.size();
|
|
10
|
+
const [boxes, trailer] = await Promise.all([scanBoxes(source, fileSize), readTrailer(source, fileSize)]);
|
|
11
|
+
return {
|
|
12
|
+
fileSize,
|
|
13
|
+
boxes,
|
|
14
|
+
trailerWrapper: trailerWrapperOf(boxes),
|
|
15
|
+
trailerVersion: trailer.footer.version,
|
|
16
|
+
trailerPayloadStart: trailer.payloadStart,
|
|
17
|
+
records: trailer.records.toSorted((left, right) => left.id - right.id)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
//#endregion
|
|
21
|
+
export { inspectLayout };
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { hasErrorCode, messageOf } from "../../shared/errors/GyroViewError.js";
|
|
2
|
+
import { microseconds, microsecondsToSeconds } from "../../shared/units/time.js";
|
|
3
|
+
import { magnitudeOf } from "../../shared/math/Vector3.js";
|
|
4
|
+
import { readRecording } from "./readRecording.js";
|
|
5
|
+
import { inspectLayout } from "./inspectLayout.js";
|
|
6
|
+
//#region ../../packages/core/src/application/recording/inspectRecording.ts
|
|
7
|
+
var GRAVITY_SAMPLE_COUNT = 1e3;
|
|
8
|
+
/**
|
|
9
|
+
* Reads what a recording's file holds without playing it: the layout of its boxes and trailer,
|
|
10
|
+
* the info record, the calibration and summaries of the gyro and exposure records. A gyro layout
|
|
11
|
+
* it cannot tell is reported in the result, since telling it is what an inspection is for; a
|
|
12
|
+
* file that is not a recording rejects as `readRecording` does.
|
|
13
|
+
*/
|
|
14
|
+
async function inspectRecording(source) {
|
|
15
|
+
const [layout, recording] = await Promise.all([inspectLayout(source), readRecording(source)]);
|
|
16
|
+
const gyro = await gyroOf(recording);
|
|
17
|
+
const exposure = await exposureOf(recording, gyro);
|
|
18
|
+
return {
|
|
19
|
+
...summarizeStructure(layout),
|
|
20
|
+
info: recording.info,
|
|
21
|
+
calibration: summarizeCalibration(recording),
|
|
22
|
+
calibrationWarnings: recording.calibration.warnings,
|
|
23
|
+
gyro,
|
|
24
|
+
exposure
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function summarizeStructure(layout) {
|
|
28
|
+
return {
|
|
29
|
+
fileSize: layout.fileSize,
|
|
30
|
+
boxes: layout.boxes.map((box) => ({
|
|
31
|
+
type: box.type,
|
|
32
|
+
offset: box.range.offset,
|
|
33
|
+
size: box.range.length
|
|
34
|
+
})),
|
|
35
|
+
trailerWrapper: layout.trailerWrapper,
|
|
36
|
+
trailerVersion: layout.trailerVersion,
|
|
37
|
+
payloadStart: layout.trailerPayloadStart,
|
|
38
|
+
records: layout.records.map((record) => ({
|
|
39
|
+
id: record.id,
|
|
40
|
+
format: record.format,
|
|
41
|
+
offset: record.payload.offset,
|
|
42
|
+
size: record.payload.length
|
|
43
|
+
}))
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function summarizeCalibration(recording) {
|
|
47
|
+
const { calibration } = recording.calibration;
|
|
48
|
+
return calibration === void 0 ? void 0 : {
|
|
49
|
+
version: calibration.version,
|
|
50
|
+
canvas: [calibration.canvas.width, calibration.canvas.height],
|
|
51
|
+
lenses: calibration.lenses.map((lens) => ({
|
|
52
|
+
lensIndex: lens.lensIndex,
|
|
53
|
+
model: lens.model.kind,
|
|
54
|
+
principalPoint: [lens.model.principalPoint.x, lens.model.principalPoint.y],
|
|
55
|
+
orientationDegrees: [
|
|
56
|
+
lens.orientation.yaw,
|
|
57
|
+
lens.orientation.pitch,
|
|
58
|
+
lens.orientation.roll
|
|
59
|
+
],
|
|
60
|
+
translationMetres: lens.translation
|
|
61
|
+
}))
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function summarizeGyro(gyro) {
|
|
65
|
+
const { track, layout, strayBytes, damagedSamples, mendedStamps } = gyro;
|
|
66
|
+
const leading = Math.min(GRAVITY_SAMPLE_COUNT, track.length);
|
|
67
|
+
let magnitudeSum = 0;
|
|
68
|
+
for (let index = 0; index < leading; index += 1) magnitudeSum += magnitudeOf(track.sampleAt(index).acceleration);
|
|
69
|
+
const span = track.isEmpty ? 0 : track.sampleAt(track.length - 1).captureTime - track.sampleAt(0).captureTime;
|
|
70
|
+
return {
|
|
71
|
+
layout,
|
|
72
|
+
samples: track.length,
|
|
73
|
+
strayBytes,
|
|
74
|
+
damagedSamples,
|
|
75
|
+
mendedStamps,
|
|
76
|
+
spanSeconds: microsecondsToSeconds(microseconds(span)),
|
|
77
|
+
meanIntervalUs: track.meanSampleInterval,
|
|
78
|
+
meanAccelerationMagnitudeG: leading > 0 ? magnitudeSum / leading : void 0
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function summarizeExposure(exposure, firstEncodedFrame) {
|
|
82
|
+
let shutterSum = 0;
|
|
83
|
+
for (let index = 0; index < exposure.length; index += 1) shutterSum += exposure.entryAt(index).shutterTime;
|
|
84
|
+
const hasEntries = exposure.length > 0;
|
|
85
|
+
return {
|
|
86
|
+
entries: exposure.length,
|
|
87
|
+
firstCaptureTimeUs: hasEntries ? exposure.entryAt(0).captureTime : void 0,
|
|
88
|
+
lastCaptureTimeUs: hasEntries ? exposure.entryAt(exposure.length - 1).captureTime : void 0,
|
|
89
|
+
meanShutterTimeSeconds: hasEntries ? shutterSum / exposure.length : void 0,
|
|
90
|
+
firstEncodedFrameEntry: firstEncodedFrame
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The gyro summary, or why the record cannot be read: a camera the inspector has not seen is the
|
|
95
|
+
* case it exists for, so an undecidable layout is reported, not fatal.
|
|
96
|
+
*/
|
|
97
|
+
async function gyroOf(recording) {
|
|
98
|
+
try {
|
|
99
|
+
const gyro = await recording.readGyroRecord();
|
|
100
|
+
return gyro === void 0 ? void 0 : summarizeGyro(gyro);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (!hasErrorCode(error, "unsupported-gyro-record")) throw error;
|
|
103
|
+
return { unreadable: messageOf(error) };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* The exposure summary; damaged when the trailer lists a record that did not parse, and not read
|
|
108
|
+
* where the gyro layout cannot be told (its stamps are in the gyro's unit).
|
|
109
|
+
*/
|
|
110
|
+
async function exposureOf(recording, gyro) {
|
|
111
|
+
if (!recording.listsExposureRecord) return void 0;
|
|
112
|
+
if (gyro !== void 0 && "unreadable" in gyro) return { unread: "gyro layout unknown" };
|
|
113
|
+
const exposure = await recording.readExposureRecord();
|
|
114
|
+
return exposure ? summarizeExposure(exposure, await firstEncodedFrameEntry(exposure, recording)) : { damaged: true };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The exposure entry of the first encoded frame; unknown without a capture clock.
|
|
118
|
+
*/
|
|
119
|
+
async function firstEncodedFrameEntry(exposure, recording) {
|
|
120
|
+
const clock = await recording.captureClock();
|
|
121
|
+
return clock === void 0 ? void 0 : exposure.indexAtOrAfter(clock.firstFrameCaptureTime);
|
|
122
|
+
}
|
|
123
|
+
//#endregion
|
|
124
|
+
export { inspectRecording };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RecordingFileName } from "../../domain/format/naming/RecordingFileName.js";
|
|
2
|
+
import { fileNameOfUrl, splitUrl } from "../../shared/text/urlPath.js";
|
|
3
|
+
//#region ../../packages/core/src/application/recording/locateOtherLensFile.ts
|
|
4
|
+
/**
|
|
5
|
+
* Use case: the URL of the other lens's file of a split-file recording, if the server has one.
|
|
6
|
+
*/
|
|
7
|
+
async function locateOtherLensFile(recordingUrl, locator) {
|
|
8
|
+
const name = RecordingFileName.parse(fileNameOfUrl(recordingUrl));
|
|
9
|
+
if (!name) return void 0;
|
|
10
|
+
const candidate = siblingUrl(recordingUrl, name.otherLensName());
|
|
11
|
+
return await locator.exists(candidate) ? candidate : void 0;
|
|
12
|
+
}
|
|
13
|
+
function siblingUrl(url, fileName) {
|
|
14
|
+
const { path, suffix } = splitUrl(url);
|
|
15
|
+
return `${path.slice(0, path.lastIndexOf("/") + 1)}${encodeURIComponent(fileName)}${suffix}`;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { locateOtherLensFile };
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { imuFrameFor } from "../../domain/motion/imu/ImuFrame.js";
|
|
2
|
+
import { OrientationTrack } from "../../domain/motion/orientation/OrientationTrack.js";
|
|
3
|
+
//#region ../../packages/core/src/application/recording/motionOf.ts
|
|
4
|
+
var NO_GYRO_WARNING = "the recording has no gyro record; stabilization is unavailable";
|
|
5
|
+
/**
|
|
6
|
+
* Integrates the gyro record when there is one. A camera whose IMU frame has never been measured
|
|
7
|
+
* still gets stabilization, with a warning, because the aligned guess is right for some cameras
|
|
8
|
+
* and the ranking test can confirm it later.
|
|
9
|
+
*/
|
|
10
|
+
async function motionOf(recording, clock) {
|
|
11
|
+
const gyro = await recording.readGyroRecord();
|
|
12
|
+
if (!gyro) return {
|
|
13
|
+
setup: void 0,
|
|
14
|
+
warnings: [NO_GYRO_WARNING]
|
|
15
|
+
};
|
|
16
|
+
const imuFrame = imuFrameFor(recording.info);
|
|
17
|
+
const orientations = OrientationTrack.integrate({
|
|
18
|
+
gyro: gyro.track,
|
|
19
|
+
clock,
|
|
20
|
+
frame: imuFrame
|
|
21
|
+
});
|
|
22
|
+
const warnings = [...unverifiedWarningsOf(imuFrame, recording), ...damageWarningsOf(gyro)];
|
|
23
|
+
return {
|
|
24
|
+
setup: {
|
|
25
|
+
orientations,
|
|
26
|
+
imuFrame
|
|
27
|
+
},
|
|
28
|
+
warnings
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function damageWarningsOf(gyro) {
|
|
32
|
+
return [...gyro.damagedSamples > 0 ? [`damaged gyro samples left out of stabilization: ${gyro.damagedSamples}`] : [], ...gyro.mendedStamps > 0 ? [`gyro stamps off their neighbours mended: ${gyro.mendedStamps}`] : []];
|
|
33
|
+
}
|
|
34
|
+
function unverifiedWarningsOf(imuFrame, recording) {
|
|
35
|
+
if (imuFrame.isVerified) return [];
|
|
36
|
+
return [`the IMU frame of ${recording.info.model ?? "this camera"} has not been verified on a recording; stabilization may misbehave`];
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { motionOf };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { selectCalibration } from "../../domain/format/calibration/selectCalibration.js";
|
|
2
|
+
import { readInfoRecord } from "../../domain/format/info/readInfoRecord.js";
|
|
3
|
+
import { TrailerRecords } from "../../domain/format/records/TrailerRecords.js";
|
|
4
|
+
import { readTrailer } from "../../domain/format/trailer/readTrailer.js";
|
|
5
|
+
import { CaptureClock } from "../../domain/motion/timing/CaptureClock.js";
|
|
6
|
+
//#region ../../packages/core/src/application/recording/readRecording.ts
|
|
7
|
+
/**
|
|
8
|
+
* Use case: open a source and read everything needed to describe the recording, with the
|
|
9
|
+
* minimum of I/O: one size lookup, the trailer's table of contents, then the info record. The
|
|
10
|
+
* file's box structure is left to `inspectLayout`. A missing calibration is reported, not
|
|
11
|
+
* fatal; stitching checks for it. The large records are read when asked for.
|
|
12
|
+
*/
|
|
13
|
+
async function readRecording(source) {
|
|
14
|
+
const fileSize = await source.size();
|
|
15
|
+
const trailer = await readTrailer(source, fileSize);
|
|
16
|
+
const info = await readInfoRecord(source, trailer);
|
|
17
|
+
const records = new TrailerRecords(source, trailer, info);
|
|
18
|
+
return {
|
|
19
|
+
info,
|
|
20
|
+
calibration: selectCalibration(info.calibration),
|
|
21
|
+
listsExposureRecord: records.listsExposure,
|
|
22
|
+
captureClock: async () => {
|
|
23
|
+
const origin = await records.firstFrameCaptureTime();
|
|
24
|
+
return origin === void 0 ? void 0 : new CaptureClock(origin, info.gyroOffset);
|
|
25
|
+
},
|
|
26
|
+
readGyroRecord: () => records.readGyro(),
|
|
27
|
+
readExposureRecord: () => records.readExposure()
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
//#endregion
|
|
31
|
+
export { readRecording };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { hasErrorCode, messageOf } from "../../shared/errors/GyroViewError.js";
|
|
2
|
+
import { seconds } from "../../shared/units/time.js";
|
|
3
|
+
import { frameTimesOf } from "./frameTimesOf.js";
|
|
4
|
+
import { motionOf } from "./motionOf.js";
|
|
5
|
+
//#region ../../packages/core/src/application/recording/timeRecording.ts
|
|
6
|
+
var TIMING_UNAVAILABLE = "frame timing and stabilization are unavailable";
|
|
7
|
+
var NO_CLOCK_WARNING = `the recording has no capture clock; ${TIMING_UNAVAILABLE}`;
|
|
8
|
+
var NO_FRAME_SOURCE_WARNING = `the recording has no frame source to time; ${TIMING_UNAVAILABLE}`;
|
|
9
|
+
var NO_FRAME_TIMES_WARNING = "no frame timing source is usable; frames are timed by their track timestamps";
|
|
10
|
+
/**
|
|
11
|
+
* Relates the recording to video time: the capture clock, the frame times of its first frame
|
|
12
|
+
* source (all sources share the camera clock) and the camera's orientation for stabilization.
|
|
13
|
+
*/
|
|
14
|
+
async function timeRecording(recording, frameSource) {
|
|
15
|
+
try {
|
|
16
|
+
return await timeByTheClock(recording, frameSource);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (!hasErrorCode(error, "unsupported-gyro-record")) throw error;
|
|
19
|
+
return withoutTiming([`${messageOf(error)}; ${TIMING_UNAVAILABLE}`]);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function timeByTheClock(recording, frameSource) {
|
|
23
|
+
const recordingClock = await recording.captureClock();
|
|
24
|
+
if (!recordingClock) return withoutTiming([NO_CLOCK_WARNING]);
|
|
25
|
+
if (!frameSource) return withoutTiming([NO_FRAME_SOURCE_WARNING]);
|
|
26
|
+
const timeline = await timelineOf(recordingClock, frameSource);
|
|
27
|
+
const [frames, motion] = await Promise.all([frameTimesOf(recording, frameSource, timeline), motionOf(recording, timeline.clock)]);
|
|
28
|
+
return {
|
|
29
|
+
frameTimes: frames?.frameTimes,
|
|
30
|
+
frameTimeSource: frames?.source,
|
|
31
|
+
motion: motion.setup,
|
|
32
|
+
warnings: [...frames?.warnings ?? [NO_FRAME_TIMES_WARNING], ...motion.warnings]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Where the frame source's frames lie in time: the first shows where the first key packet does
|
|
37
|
+
* (a track without one does not decode), and the rest follow at that packet's duration, the
|
|
38
|
+
* cameras recording at a constant rate.
|
|
39
|
+
*/
|
|
40
|
+
async function timelineOf(recordingClock, frameSource) {
|
|
41
|
+
const firstKey = await frameSource.firstKeyPacket();
|
|
42
|
+
const hasDuration = firstKey !== void 0 && firstKey.duration > 0;
|
|
43
|
+
return {
|
|
44
|
+
clock: recordingClock.withFirstFrameAt(firstKey?.timestamp ?? seconds(0)),
|
|
45
|
+
frameDuration: hasDuration ? firstKey.duration : void 0
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function withoutTiming(warnings) {
|
|
49
|
+
return {
|
|
50
|
+
frameTimes: void 0,
|
|
51
|
+
frameTimeSource: void 0,
|
|
52
|
+
motion: void 0,
|
|
53
|
+
warnings
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
//#endregion
|
|
57
|
+
export { timeRecording };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { OffStabilization } from "../../domain/motion/stabilization/stabilizers.js";
|
|
2
|
+
//#region ../../packages/core/src/application/stabilization/StabilizingFrameSink.ts
|
|
3
|
+
/**
|
|
4
|
+
* Use case: before every presentation, looks up the camera's orientation at the frame's
|
|
5
|
+
* exposure and tells the sink how to turn the picture for the current stabilization mode.
|
|
6
|
+
*/
|
|
7
|
+
var StabilizingFrameSink = class {
|
|
8
|
+
parts;
|
|
9
|
+
stabilizer = new OffStabilization();
|
|
10
|
+
constructor(parts) {
|
|
11
|
+
this.parts = parts;
|
|
12
|
+
}
|
|
13
|
+
setStabilizer(stabilizer) {
|
|
14
|
+
this.stabilizer = stabilizer;
|
|
15
|
+
}
|
|
16
|
+
present(presentation) {
|
|
17
|
+
const time = this.exposureTimeOf(presentation);
|
|
18
|
+
const orientation = this.parts.orientations.orientationAt(time);
|
|
19
|
+
this.parts.sink.setStabilization(this.stabilizer.nextRotation(orientation, time));
|
|
20
|
+
this.parts.sink.present(presentation);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The frame's mid-exposure time, or its track timestamp when there are no frame times to say.
|
|
24
|
+
*/
|
|
25
|
+
exposureTimeOf(presentation) {
|
|
26
|
+
const { timestamp } = presentation.pair;
|
|
27
|
+
return this.parts.frameTimes?.midExposureAt(timestamp) ?? timestamp;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { StabilizingFrameSink };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BoxType } from "./boxConstants.js";
|
|
2
|
+
//#region ../../packages/core/src/domain/format/boxes/BoxLayout.ts
|
|
3
|
+
function trailerWrapperOf(boxes) {
|
|
4
|
+
return boxes.some((box) => box.type === BoxType.Insta360Trailer) ? "inst-box" : "bare";
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { trailerWrapperOf };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region ../../packages/core/src/domain/format/boxes/boxConstants.ts
|
|
2
|
+
/**
|
|
3
|
+
* The box types the player looks for. `inst` is Insta360's wrapper around the trailer on newer
|
|
4
|
+
* firmware; older firmware appends the trailer bare, outside any box.
|
|
5
|
+
*/
|
|
6
|
+
var BoxType = { Insta360Trailer: "inst" };
|
|
7
|
+
/**
|
|
8
|
+
* Four printable ASCII characters, which is what every real box type looks like. Anything else
|
|
9
|
+
* at a box boundary means the boxes have ended and raw trailer bytes follow.
|
|
10
|
+
*/
|
|
11
|
+
var PLAUSIBLE_BOX_TYPE = /^[ -~]{4}$/u;
|
|
12
|
+
function isPlausibleBoxType(type) {
|
|
13
|
+
return PLAUSIBLE_BOX_TYPE.test(type);
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { BoxType, isPlausibleBoxType };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { hasErrorCode } from "../../../shared/errors/GyroViewError.js";
|
|
2
|
+
import { ByteReader } from "../../../shared/binary/ByteReader.js";
|
|
3
|
+
import { ByteRange } from "../../../shared/binary/ByteRange.js";
|
|
4
|
+
import { isPlausibleBoxType } from "./boxConstants.js";
|
|
5
|
+
//#region ../../packages/core/src/domain/format/boxes/scanBoxes.ts
|
|
6
|
+
/**
|
|
7
|
+
* Walks the top-level boxes of a file with one small read per box, and stops at the first bytes
|
|
8
|
+
* that do not form a box header, where a bare trailer begins. `fileSize` is passed in so the
|
|
9
|
+
* caller can share one size lookup between several readers of the same source.
|
|
10
|
+
*/
|
|
11
|
+
async function scanBoxes(source, fileSize) {
|
|
12
|
+
const boxes = [];
|
|
13
|
+
let offset = 0;
|
|
14
|
+
while (offset + 8 <= fileSize) {
|
|
15
|
+
const box = await readBoxAt(source, offset, fileSize);
|
|
16
|
+
if (!box) break;
|
|
17
|
+
boxes.push(box);
|
|
18
|
+
offset = box.range.end;
|
|
19
|
+
}
|
|
20
|
+
return boxes;
|
|
21
|
+
}
|
|
22
|
+
async function readBoxAt(source, offset, fileSize) {
|
|
23
|
+
const headerLength = Math.min(16, fileSize - offset);
|
|
24
|
+
const header = new ByteReader(await source.read(ByteRange.of(offset, headerLength)));
|
|
25
|
+
const type = header.asciiAt(4, 4);
|
|
26
|
+
if (!isPlausibleBoxType(type)) return void 0;
|
|
27
|
+
const resolved = resolveSize(header, offset, fileSize);
|
|
28
|
+
if (!resolved) return void 0;
|
|
29
|
+
const { size, headerSize } = resolved;
|
|
30
|
+
return size >= headerSize && offset + size <= fileSize ? {
|
|
31
|
+
type,
|
|
32
|
+
range: ByteRange.of(offset, size)
|
|
33
|
+
} : void 0;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Undefined when the header declares a large size that is not a safe integer: real boxes never
|
|
37
|
+
* do, so those bytes are trailer data, not a box.
|
|
38
|
+
*/
|
|
39
|
+
function resolveSize(header, offset, fileSize) {
|
|
40
|
+
const declared = header.uint32BeAt(0);
|
|
41
|
+
if (declared === 1 && header.length >= 16) return largeSize(header);
|
|
42
|
+
return {
|
|
43
|
+
size: declared === 0 ? fileSize - offset : declared,
|
|
44
|
+
headerSize: 8
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function largeSize(header) {
|
|
48
|
+
try {
|
|
49
|
+
return {
|
|
50
|
+
size: header.uint64BeAt(8),
|
|
51
|
+
headerSize: 16
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
if (hasErrorCode(error, "binary-unsafe-integer")) return void 0;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { scanBoxes };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region ../../packages/core/src/domain/format/calibration/CalibrationVersion.ts
|
|
2
|
+
/**
|
|
3
|
+
* Which calibration string a set was read from, in increasing order of fidelity: `offset` (1),
|
|
4
|
+
* `offset_v2` (2) and `offset_v3` (3). The versioned strings declare theirs in the high bits of
|
|
5
|
+
* their version word (the X5 recordings; insta360-rs docs); the legacy one declares none.
|
|
6
|
+
*/
|
|
7
|
+
var CalibrationVersion = {
|
|
8
|
+
Legacy: 1,
|
|
9
|
+
Polynomial: 2,
|
|
10
|
+
Mei: 3
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { CalibrationVersion };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { degrees } from "../../../../shared/units/angle.js";
|
|
2
|
+
//#region ../../packages/core/src/domain/format/calibration/layouts/CalibrationStringLayout.ts
|
|
3
|
+
function eulerDegrees(yaw, pitch, roll) {
|
|
4
|
+
return {
|
|
5
|
+
yaw: degrees(yaw),
|
|
6
|
+
pitch: degrees(pitch),
|
|
7
|
+
roll: degrees(roll)
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* For the versioned strings (v2, v3): what is wrong with a version word that does not declare
|
|
12
|
+
* `expected` in its high bits, if anything.
|
|
13
|
+
*/
|
|
14
|
+
function versionWordMismatch(versionWord, expected) {
|
|
15
|
+
const declared = versionWord >>> 16;
|
|
16
|
+
return declared === expected ? void 0 : `declares version ${declared}`;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* For the versioned strings: the canvas the first lens block declares at the given token
|
|
20
|
+
* positions; an empty canvas when there is no block.
|
|
21
|
+
*/
|
|
22
|
+
function canvasOfFirstBlock(blocks, widthToken, heightToken) {
|
|
23
|
+
const [first] = blocks;
|
|
24
|
+
return first ? {
|
|
25
|
+
width: first(widthToken),
|
|
26
|
+
height: first(heightToken)
|
|
27
|
+
} : EMPTY_CANVAS;
|
|
28
|
+
}
|
|
29
|
+
var EMPTY_CANVAS = {
|
|
30
|
+
width: 0,
|
|
31
|
+
height: 0
|
|
32
|
+
};
|
|
33
|
+
//#endregion
|
|
34
|
+
export { canvasOfFirstBlock, eulerDegrees, versionWordMismatch };
|