textmode.export.js 1.7.2 → 1.8.0-beta.2

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.
@@ -2,7 +2,7 @@
2
2
  * TypeScript augmentation for `textmode.export.js`.
3
3
  */
4
4
  import type { TextmodeExportAPI } from '../types';
5
- declare module 'textmode.js' {
6
- interface Textmodifier extends TextmodeExportAPI {
5
+ declare module 'textmode.js/addon' {
6
+ interface TextmodifierExtensions extends TextmodeExportAPI {
7
7
  }
8
8
  }
@@ -0,0 +1,20 @@
1
+ import type { VideoBitrateMode, VideoCodec, VideoContentHint, VideoEncodingPlan, VideoGenerationOptions } from './types';
2
+ export declare const MAX_SAFE_IN_MEMORY_VIDEO_BYTES = 100000000;
3
+ export declare const ULTRA_FALLBACK_BITS_PER_PIXEL_PER_FRAME = 0.5;
4
+ export type VideoQualityDescriptor = {
5
+ kind: 'named';
6
+ level: 'medium' | 'high' | 'very-high';
7
+ } | {
8
+ kind: 'bitrate';
9
+ bitrate: number;
10
+ bitrateMode: VideoBitrateMode;
11
+ } | {
12
+ kind: 'ultra';
13
+ bitrate: number;
14
+ };
15
+ export declare function resolveUltraFallbackBitrate(width: number, height: number, frameRate: number): number;
16
+ export declare function estimateVideoOutputBytes(bitrate: number, frameCount: number, frameRate: number): number;
17
+ export declare function getVideoQualityDescriptor(plan: VideoEncodingPlan): VideoQualityDescriptor;
18
+ export declare function normalizeVideoContentHint(value: VideoContentHint | undefined): VideoContentHint;
19
+ export declare function createVideoEncodingPlan(options: VideoGenerationOptions, codec?: VideoCodec): VideoEncodingPlan;
20
+ export declare function assertVideoOutputFitsMemory(plan: VideoEncodingPlan, allowLargeInMemory: boolean): void;
@@ -1,30 +1,27 @@
1
1
  import type { Textmodifier } from 'textmode.js';
2
2
  import { type PostDrawSubscription } from './VideoFrameDriver';
3
3
  import type { VideoExportOptions } from './types';
4
- /**
5
- * Main video exporter for the textmode.js library.
6
- */
4
+ /** Main video exporter for the textmode.js library. */
7
5
  export declare class VideoExporter {
8
6
  private readonly _recorder;
9
7
  private readonly _textmodifier;
10
8
  private readonly _registerPostDrawHook;
11
9
  constructor(textmodifier: Textmodifier, registerPostDrawHook: PostDrawSubscription);
12
- /**
13
- * Captures deterministic frames and saves them as a video file.
14
- *
15
- * @param options Export options
16
- */
10
+ /** Captures deterministic frames and saves them as a video file. */
17
11
  $saveVideo(options?: VideoExportOptions): Promise<void>;
18
12
  /** Generates a deterministic video without initiating a download. */
19
13
  $generateVideoBlob(options?: VideoExportOptions): Promise<Blob>;
14
+ private _record;
20
15
  private _applyDefaultOptions;
21
16
  private _positiveInteger;
22
17
  private _positiveNumber;
23
18
  private _bitrate;
24
19
  private _bitrateMode;
20
+ private _contentHint;
25
21
  private _latencyMode;
26
22
  private _hardwareAcceleration;
27
23
  private _keyFrameInterval;
28
24
  private _currentPixelDensity;
25
+ private _filePicker;
29
26
  private _withExtension;
30
27
  }
@@ -1,16 +1,19 @@
1
+ import { type StreamTargetChunk } from 'mediabunny';
1
2
  import type { VideoExportProgress, VideoFrameDriverLike, VideoGenerationOptions } from './types';
2
- /**
3
- * Records deterministic textmode frames through WebCodecs and muxes them with Mediabunny.
4
- */
3
+ export type VideoOutputDestination = {
4
+ kind: 'blob';
5
+ allowLargeInMemory: boolean;
6
+ } | {
7
+ kind: 'stream';
8
+ writable: WritableStream<StreamTargetChunk>;
9
+ };
10
+ /** Records deterministic textmode frames through WebCodecs and muxes them with Mediabunny. */
5
11
  export declare class VideoRecorder {
6
- $record(options: VideoGenerationOptions, frameDriver: VideoFrameDriverLike, onProgress?: (progress: VideoExportProgress) => void): Promise<Blob>;
7
- private _drainIfNeeded;
8
- private _drainEncodes;
9
- private _shouldYieldAfterCaptureFrame;
10
- private _yieldToBrowser;
12
+ $record(options: VideoGenerationOptions, frameDriver: VideoFrameDriverLike, onProgress?: (progress: VideoExportProgress) => void, destination?: VideoOutputDestination): Promise<Blob | undefined>;
11
13
  private _createEncodingPlan;
14
+ private _createQuality;
12
15
  private _assertWebCodecsAvailable;
13
- private _resolveBitrate;
16
+ private _awaitWithAbort;
14
17
  private _throwIfAborted;
15
18
  private _emitProgress;
16
19
  private _normalizeError;
@@ -5,5 +5,6 @@ export declare class VideoExportError extends Error {
5
5
  constructor(code: VideoExportErrorCode, message: string, cause?: unknown);
6
6
  }
7
7
  export declare function createAbortError(): VideoExportError;
8
+ export declare function normalizeFilePickerError(error: unknown): VideoExportError | undefined;
8
9
  export declare function createTimeoutError(message: string, cause?: unknown): VideoExportError;
9
10
  export declare function isAbortError(error: unknown): boolean;
@@ -1,5 +1,2 @@
1
- export { VideoExporter } from './VideoExporter';
2
- export { VideoFrameDriver } from './VideoFrameDriver';
3
- export { VideoRecorder } from './VideoRecorder';
4
1
  export { VideoExportError } from './errors';
5
- export type { VideoBitrateMode, VideoBitratePreset, VideoEncodingPlan, VideoExportErrorCode, VideoExportFormat, VideoExportOptions, VideoExportPhase, VideoFrameDriverLike, VideoHardwareAcceleration, VideoLatencyMode, VideoExportProgress, VideoGenerationOptions, VideoRecordingState, VideoRenderFrameOptions, } from './types';
2
+ export type { VideoBitrateMode, VideoBitratePreset, VideoContentHint, VideoEncodingPlan, VideoExportErrorCode, VideoExportFormat, VideoExportOptions, VideoExportPhase, VideoFrameDriverLike, VideoHardwareAcceleration, VideoLatencyMode, VideoExportProgress, VideoGenerationOptions, VideoRecordingState, VideoRenderFrameOptions, } from './types';
@@ -16,20 +16,26 @@ export type VideoRecordingState = 'idle' | 'recording' | 'encoding' | 'completed
16
16
  *
17
17
  * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportPhase | VideoExportPhase API reference}
18
18
  */
19
- export type VideoExportPhase = 'probing' | 'rendering' | 'capturing' | 'encoding' | 'draining' | 'finalizing';
19
+ export type VideoExportPhase = 'probing' | 'rendering' | 'capturing' | 'encoding' | 'draining' | 'writing' | 'finalizing';
20
20
  export type VideoCodec = 'vp8' | 'vp9' | 'avc' | (string & {});
21
21
  /**
22
22
  * Subjective bitrate preset used when an exact bits-per-second value is not supplied.
23
23
  *
24
- * Higher presets produce larger files with more detail. Each preset maps to a fixed bits-per-pixel budget
25
- * (bits per pixel per second), so a given preset resolves to the same bitrate regardless of the export
26
- * frame rate. For exact control, pass a numeric `bitrate` value in bits per second instead.
24
+ * Higher presets request higher constant perceptual quality from Mediabunny. The `ultra` preset requests quantizer
25
+ * zero with a frame-rate-aware bitrate fallback and should be described as near-lossless, never mathematically
26
+ * lossless. For exact bitrate control, pass a numeric `bitrate` value in bits per second instead.
27
27
  *
28
28
  * @category Animation export
29
29
  *
30
30
  * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoBitratePreset | VideoBitratePreset API reference}
31
31
  */
32
- export type VideoBitratePreset = 'low' | 'medium' | 'high';
32
+ export type VideoBitratePreset = 'low' | 'medium' | 'high' | 'ultra';
33
+ /**
34
+ * Hint for the encoder's content-aware rate control.
35
+ *
36
+ * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoContentHint | VideoContentHint API reference}
37
+ */
38
+ export type VideoContentHint = '' | 'motion' | 'detail' | 'text';
33
39
  /**
34
40
  * Video container format written by `saveVideo`.
35
41
  *
@@ -77,7 +83,7 @@ export type VideoLatencyMode = 'quality' | 'realtime';
77
83
  * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoHardwareAcceleration | VideoHardwareAcceleration API reference}
78
84
  */
79
85
  export type VideoHardwareAcceleration = 'no-preference' | 'prefer-hardware' | 'prefer-software';
80
- export type VideoExportErrorCode = 'VIDEO_EXPORT_UNSUPPORTED' | 'VIDEO_CODEC_UNSUPPORTED' | 'VIDEO_EXPORT_ABORTED' | 'VIDEO_EXPORT_TIMEOUT' | 'VIDEO_EXPORT_FAILED' | 'VIDEO_TRANSPARENCY_UNSUPPORTED';
86
+ export type VideoExportErrorCode = 'VIDEO_EXPORT_UNSUPPORTED' | 'VIDEO_CODEC_UNSUPPORTED' | 'VIDEO_EXPORT_ABORTED' | 'VIDEO_EXPORT_TIMEOUT' | 'VIDEO_EXPORT_FAILED' | 'VIDEO_TRANSPARENCY_UNSUPPORTED' | 'VIDEO_DIMENSIONS_UNSUPPORTED' | 'VIDEO_OUTPUT_TOO_LARGE';
81
87
  /**
82
88
  * Progress information emitted during the video export process.
83
89
  *
@@ -128,6 +134,18 @@ export type VideoExportProgress = {
128
134
  * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportProgress#message | VideoExportProgress.message API reference}
129
135
  */
130
136
  message?: string;
137
+ /**
138
+ * The rate-control path selected by the browser, when known.
139
+ *
140
+ * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportProgress#ratecontrol | VideoExportProgress.rateControl API reference}
141
+ */
142
+ rateControl?: 'quantizer' | 'bitrate-fallback' | 'bitrate';
143
+ /**
144
+ * Conservative output-size estimate in bytes, when available.
145
+ *
146
+ * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportProgress#estimatedbytes | VideoExportProgress.estimatedBytes API reference}
147
+ */
148
+ estimatedBytes?: number;
131
149
  };
132
150
  /**
133
151
  * Options for exporting the textmode content to video format.
@@ -164,16 +182,15 @@ export type VideoExportOptions = {
164
182
  /**
165
183
  * Target bitrate in bits per second or a quality preset. Defaults to `'medium'`.
166
184
  *
167
- * Bitrate controls how much encoded data is available per second of video. Higher values can preserve more detail
168
- * in noisy or fast-changing sketches, but create larger files. Presets resolve to a fixed bits-per-pixel budget
169
- * from the export dimensions only, so a given preset targets the same bitrate regardless of the frame rate.
170
- * Numeric values are passed directly to the encoder.
185
+ * String values request constant-quality encoding through Mediabunny. `low`, `medium`, and `high` map to the
186
+ * upstream `medium`, `high`, and `very-high` quality levels; `ultra` requests quantizer zero and may create very
187
+ * large files. Numeric values remain exact bits-per-second targets and are wrapped in Mediabunny `Quality`.
171
188
  *
172
189
  * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportOptions#bitrate | VideoExportOptions.bitrate API reference}
173
190
  */
174
191
  bitrate?: number | VideoBitratePreset;
175
192
  /**
176
- * Encoder bitrate allocation mode. Defaults to `'variable'`.
193
+ * Bitrate allocation mode for numeric bitrates and quality fallback paths. Defaults to `'variable'`.
177
194
  *
178
195
  * Use `'variable'` for most exports so simple frames can compress efficiently and complex frames can receive more
179
196
  * bits. Use `'constant'` only when a steadier data rate is more important than compression efficiency.
@@ -181,6 +198,12 @@ export type VideoExportOptions = {
181
198
  * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportOptions#bitratemode | VideoExportOptions.bitrateMode API reference}
182
199
  */
183
200
  bitrateMode?: VideoBitrateMode;
201
+ /**
202
+ * Content hint passed to the native video encoder. Defaults to `'text'`.
203
+ *
204
+ * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportOptions#contenthint | VideoExportOptions.contentHint API reference}
205
+ */
206
+ contentHint?: VideoContentHint;
184
207
  /**
185
208
  * Encoder latency mode. Defaults to `'quality'`.
186
209
  *
@@ -210,7 +233,7 @@ export type VideoExportOptions = {
210
233
  */
211
234
  keyFrameInterval?: number;
212
235
  /**
213
- * Pixel density used during export. Defaults to `1` so video dimensions match the live canvas.
236
+ * Pixel density used during export. Defaults to `1` so video dimensions match the logical canvas size.
214
237
  *
215
238
  * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportOptions#pixeldensity | VideoExportOptions.pixelDensity API reference}
216
239
  */
@@ -245,6 +268,12 @@ export type VideoExportOptions = {
245
268
  * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportOptions#debuglogging | VideoExportOptions.debugLogging API reference}
246
269
  */
247
270
  debugLogging?: boolean;
271
+ /**
272
+ * Allows `toVideoBlob()` to retain its in-memory contract above the safe 100 MB default.
273
+ *
274
+ * @see {@link https://code.textmode.art/api/textmode.export.js/type-aliases/VideoExportOptions#allowlargeinmemory | VideoExportOptions.allowLargeInMemory API reference}
275
+ */
276
+ allowLargeInMemory?: boolean;
248
277
  };
249
278
  export interface VideoGenerationOptions {
250
279
  filename?: string;
@@ -253,6 +282,7 @@ export interface VideoGenerationOptions {
253
282
  frameCount: number;
254
283
  bitrate: number | VideoBitratePreset;
255
284
  bitrateMode: VideoBitrateMode;
285
+ contentHint?: VideoContentHint;
256
286
  latencyMode: VideoLatencyMode;
257
287
  hardwareAcceleration: VideoHardwareAcceleration;
258
288
  keyFrameInterval: number;
@@ -261,6 +291,7 @@ export interface VideoGenerationOptions {
261
291
  height: number;
262
292
  transparent: boolean;
263
293
  debugLogging: boolean;
294
+ allowLargeInMemory?: boolean;
264
295
  signal?: AbortSignal;
265
296
  prepareFrame?: PrepareExportFrame;
266
297
  }
@@ -271,6 +302,11 @@ export interface VideoEncodingPlan {
271
302
  codec: VideoCodec;
272
303
  bitrate: number;
273
304
  bitrateMode: VideoBitrateMode;
305
+ qualityPreset: VideoBitratePreset | null;
306
+ rateControlIntent: 'bitrate' | 'constant-quality' | 'ultra';
307
+ qualityLevel?: 'medium' | 'high' | 'very-high';
308
+ contentHint: VideoContentHint;
309
+ estimatedBytes: number;
274
310
  latencyMode: VideoLatencyMode;
275
311
  hardwareAcceleration: VideoHardwareAcceleration;
276
312
  keyFrameInterval: number;
@@ -24,7 +24,7 @@ export type { SVGExportOptions } from './exporters/svg';
24
24
  export type { TXTExportOptions } from './exporters/txt';
25
25
  export type { JSONExportColorMode, JSONExportOptions, JSONExportTarget, TextmodeDocumentJSON } from './exporters/json';
26
26
  export type { GIFExportOptions, GIFExportProgress } from './exporters/gif';
27
- export type { VideoBitrateMode, VideoBitratePreset, VideoExportFormat, VideoExportOptions, VideoExportPhase, VideoExportProgress, VideoHardwareAcceleration, VideoLatencyMode, VideoRecordingState, } from './exporters/video';
27
+ export type { VideoBitrateMode, VideoBitratePreset, VideoContentHint, VideoExportFormat, VideoExportOptions, VideoExportPhase, VideoExportProgress, VideoHardwareAcceleration, VideoLatencyMode, VideoRecordingState, } from './exporters/video';
28
28
  export type { LayerExportOptions } from './exporters/base';
29
29
  /**
30
30
  * Default export plugin instance for the standard textmode.js workflow.
@@ -16,6 +16,11 @@ export interface BladeConfig<TOptions> {
16
16
  format: ExportFormat;
17
17
  defaultOptions: TOptions;
18
18
  layerTargetProvider?: LayerTargetProvider;
19
+ videoDimensionsProvider?: () => {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ videoDimensionsTarget?: Element;
19
24
  }
20
25
  export declare abstract class Blade<TOptions> extends Component<void> {
21
26
  readonly capabilities: BladeCapabilities;
@@ -12,7 +12,9 @@ export declare class VideoBlade extends Blade<VideoExportOptions> {
12
12
  private keyFrameIntervalInput;
13
13
  private transparencyInput;
14
14
  private status;
15
+ private estimate;
15
16
  private recordingState;
17
+ private resizeObserver?;
16
18
  constructor(config: BladeConfig<VideoExportOptions>);
17
19
  render(): HTMLElement;
18
20
  getOptions(): VideoExportOptions;
@@ -26,6 +28,12 @@ export declare class VideoBlade extends Blade<VideoExportOptions> {
26
28
  private resolvePlannedFrameCount;
27
29
  private applyDefaults;
28
30
  private resolveBitratePreset;
31
+ private readonly handleBitrateChange;
32
+ private readonly handleEstimateChange;
33
+ private syncReadyStatus;
29
34
  private readonly handleFormatChange;
35
+ private syncOutputEstimate;
36
+ private formatBytes;
37
+ protected _onUnmount(): void;
30
38
  private syncTransparencyAvailability;
31
39
  }
@@ -3,4 +3,7 @@ import type { FormatDefinition } from './models/FormatDefinition';
3
3
  import type { LayerTargetProvider } from '../exporters/base';
4
4
  import type { ExportDefaults } from './types';
5
5
  export type ExportDefaultsProvider = <TFormat extends ExportFormat>(format: TFormat) => ExportDefaults[TFormat];
6
- export declare function getExportFormatDefinitions(layerTargetProvider?: LayerTargetProvider, getDefaults?: ExportDefaultsProvider): ReadonlyArray<FormatDefinition<ExportFormat>>;
6
+ export declare function getExportFormatDefinitions(layerTargetProvider?: LayerTargetProvider, getDefaults?: ExportDefaultsProvider, videoDimensionsProvider?: () => {
7
+ width: number;
8
+ height: number;
9
+ }, videoDimensionsTarget?: Element): ReadonlyArray<FormatDefinition<ExportFormat>>;
@@ -0,0 +1,24 @@
1
+ import type { Textmodifier } from 'textmode.js';
2
+ import type { TextmodeExportAPI } from '../types';
3
+ export declare const EXPORT_API_METHOD_KEYS: ReadonlyArray<Exclude<keyof TextmodeExportAPI, 'exportOverlay'>>;
4
+ type PostDrawSubscription = (callback: () => void) => () => void;
5
+ /**
6
+ * Deep export implementation for one installed plugin instance.
7
+ *
8
+ * The host adapter registers this controller's API as extensions. The controller owns
9
+ * DOM resources, post-draw subscriptions, and abort controllers for asynchronous capture.
10
+ */
11
+ export declare class TextmodeExportController {
12
+ readonly api: TextmodeExportAPI;
13
+ private readonly _registerPostDrawHook;
14
+ private readonly _activeOperations;
15
+ private readonly _overlayController;
16
+ private readonly _stopOverlayRefresh;
17
+ private _disposed;
18
+ constructor(textmodifier: Textmodifier, registerPostDrawHook: PostDrawSubscription);
19
+ dispose(): void;
20
+ private _createOverlayAPI;
21
+ private _runOperation;
22
+ private _assertLive;
23
+ }
24
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "textmode.export.js",
3
- "version": "1.7.2",
3
+ "version": "1.8.0-beta.2",
4
4
  "description": "Export plugin for textmode.js - Save your creations as images, GIFs, videos, SVG, JSON, and plain text.",
5
5
  "type": "module",
6
6
  "devEngines": {
@@ -33,6 +33,8 @@
33
33
  "dist"
34
34
  ],
35
35
  "scripts": {
36
+ "prebuild": "npm run clean",
37
+ "clean": "rm -rf dist",
36
38
  "dev": "textmode-build dev --port 5174 --host",
37
39
  "build:bundle": "textmode-build build",
38
40
  "build:types": "textmode-types build",
@@ -58,20 +60,20 @@
58
60
  "release": "textmode-release release"
59
61
  },
60
62
  "peerDependencies": {
61
- "textmode.js": "^0.17.2"
63
+ "textmode.js": "^0.18.0-beta.3"
62
64
  },
63
65
  "dependencies": {
64
66
  "gifenc": "^1.0.1",
65
- "mediabunny": "^1.45.4"
67
+ "mediabunny": "^1.52.3"
66
68
  },
67
69
  "devDependencies": {
68
70
  "@textmode/build": "^0.1.0",
69
71
  "@textmode/docs": "^0.3.0",
70
72
  "@textmode/lint": "^0.1.0",
71
73
  "@textmode/release": "^0.2.0",
72
- "@textmode/tsconfig": "^0.2.0",
74
+ "@textmode/tsconfig": "^0.2.1",
73
75
  "@types/node": "^24.0.0",
74
- "textmode.js": "^0.17.2",
76
+ "textmode.js": "^0.18.0-beta.3",
75
77
  "typescript": "^6.0.3",
76
78
  "vite-plugin-css-injected-by-js": "^5.0.2",
77
79
  "vitest": "^4.1.10"