diff --git a/docs/api-reference/encoder/README.md b/docs/api-reference/encoder/README.md index de2079ec..f728d2b6 100644 --- a/docs/api-reference/encoder/README.md +++ b/docs/api-reference/encoder/README.md @@ -13,8 +13,8 @@ Encoders are used to capture image frames of an HTML `` and encode them | [`WebMEncoder`](/docs/api-reference/encoder/webm-encoder) | Video | Encodes `.webm` video using [Whammy](https://antimatter15.com/2012/08/whammy-a-real-time-javascript-webm-encoder/). | | [`GIFEncoder`](/docs/api-reference/encoder/gif-encoder) | Animated Image | Encodes `.gif` images using `gifshot.js`. | | [`StreamEncoder`](/docs/api-reference/encoder/stream-encoder) | Video | Encodes `.webm` rough previews, but drops frames. | -| [`PNGSequenceEncoder`](/docs/api-reference/encoder/png-sequence-encoder) | Image Sequence | Encodes video frames as loseless `.png` contained in a `.tar`. | -| [`JPEGSequenceEncoder`](/docs/api-reference/encoder/jpeg-sequence-encoder) | Image Sequence | Encodes video frames as compressed `.jpeg` contained in a `.tar`. | +| [`PNGSequenceEncoder`](/docs/api-reference/encoder/png-sequence-encoder) | Image Sequence | Encodes video frames as loseless `.png` contained in a `.zip`. | +| [`JPEGSequenceEncoder`](/docs/api-reference/encoder/jpeg-sequence-encoder) | Image Sequence | Encodes video frames as compressed `.jpeg` contained in a `.zip`. | ### Attributions diff --git a/docs/api-reference/encoder/jpeg-sequence-encoder.md b/docs/api-reference/encoder/jpeg-sequence-encoder.md index 9c818db0..549031ae 100644 --- a/docs/api-reference/encoder/jpeg-sequence-encoder.md +++ b/docs/api-reference/encoder/jpeg-sequence-encoder.md @@ -1,6 +1,6 @@ # JPEGSequenceEncoder -A photo sequence encoder that inherits [FrameEncoder](/docs/api-reference/encoder/frame-encoder). Saves each frame as a photo contained in a `".tar"` archive. +A photo sequence encoder that inherits [FrameEncoder](/docs/api-reference/encoder/frame-encoder). Saves each frame as a photo contained in a `".zip"` archive. ## Constructor @@ -12,7 +12,7 @@ In addition to the [FrameEncoder](/docs/api-reference/encoder/frame-encoder) set * `quality` - See member note. Defaults to 1.0. -* `archive` - `zip` or `tar`. Defaults to `tar`. +* `archive` - `zip`. ## Members diff --git a/docs/api-reference/encoder/png-sequence-encoder.md b/docs/api-reference/encoder/png-sequence-encoder.md index 3f048d48..27e7fee7 100644 --- a/docs/api-reference/encoder/png-sequence-encoder.md +++ b/docs/api-reference/encoder/png-sequence-encoder.md @@ -1,6 +1,6 @@ # PNGSequenceEncoder -A photo sequence encoder that inherits [FrameEncoder](/docs/api-reference/encoder/frame-encoder). Saves each frame as a photo contained in a `".tar"` archive. +A photo sequence encoder that inherits [FrameEncoder](/docs/api-reference/encoder/frame-encoder). Saves each frame as a photo contained in a `".zip"` archive. ## Constructor @@ -10,7 +10,7 @@ Construction of the encoder class is not required. Refer to [DeckAdapter.render] In addition to the [FrameEncoder](/docs/api-reference/encoder/frame-encoder) settings, these settings are available under the `png` namespace. -* `archive` - `zip` or `tar`. Defaults to `tar`. +* `archive` - `zip`. **Notes:** diff --git a/docs/table-of-contents.json b/docs/table-of-contents.json index 8d5d0a4e..0d717ec8 100644 --- a/docs/table-of-contents.json +++ b/docs/table-of-contents.json @@ -30,7 +30,8 @@ "type": "category", "label": "Animations", "items": [ - "api-reference/animations/animation-manager" + "api-reference/animations/animation-manager", + "api-reference/animations/deck-animation" ] }, { diff --git a/examples/website/basic-basemap-mapbox-legacy/app.jsx b/examples/website/basic-basemap-mapbox-legacy/app.jsx index 0742f85c..e8ba0fcd 100644 --- a/examples/website/basic-basemap-mapbox-legacy/app.jsx +++ b/examples/website/basic-basemap-mapbox-legacy/app.jsx @@ -52,11 +52,8 @@ const formatConfigs = { webm: { quality: 0.8 }, - png: { - archive: 'zip' - }, + png: {}, jpeg: { - archive: 'zip', quality: 0.8 }, gif: { @@ -73,17 +70,16 @@ const timecode = { }; const DeckGLOverlay = forwardRef((props, ref) => { - // MapboxOverlay handles a variety of props differently than the Deck class. - // https://deck.gl/docs/api-reference/mapbox/mapbox-overlay#constructor - const deck = useControl(() => new MapboxOverlay({...props, interleaved: true})); + // MapboxOverlay handles a variety of props differently than the Deck class. + // https://deck.gl/docs/api-reference/mapbox/mapbox-overlay#constructor + const deck = useControl(() => new MapboxOverlay({...props, interleaved: true})); - deck.setProps(props); + deck.setProps(props); - // @ts-expect-error private property - setRef(ref, deck._deck); - return null; - } -); + // @ts-expect-error private property + setRef(ref, deck._deck); + return null; +}); const Container = ({children}) => (
- +
{ // @ts-expect-error private property setRef(ref, deck._deck); return null; -} -); +}); const Container = ({children}) => (
- +
[ Math.floor(Math.random() * 255) ]; -export default function App({mapStyle = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json'}) { +export default function App({ + mapStyle = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json' +}) { const deckRef = useRef(null); const mapRef = useRef(null); const [busy, setBusy] = useState(false); @@ -170,10 +169,7 @@ export default function App({mapStyle = 'https://basemaps.cartocdn.com/gl/positr mapLib={maplibregl} // Note: 'reuseMap' prop with gatsby and mapbox extension causes stale reference error. > - + Uint8Array) { - const buffer = utils.clean(512); - let offset = 0; - - structure.forEach(value => { - const str = data[value.field] || ''; - let i: number; - let length: number; - - for (i = 0, length = str.length; i < length; i += 1) { - buffer[offset] = str.charCodeAt(i); - offset += 1; - } - - // space it out with nulls - offset += value.length - i; - }); - - if (typeof cb === 'function') { - return cb(buffer, offset); - } - return buffer; -} diff --git a/modules/core/src/encoders/tar/tar-builder.ts b/modules/core/src/encoders/tar/tar-builder.ts deleted file mode 100644 index ba571441..00000000 --- a/modules/core/src/encoders/tar/tar-builder.ts +++ /dev/null @@ -1,45 +0,0 @@ -// hubble.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - -import Tar from './tar'; - -type TarBuilderOptions = { - recordsPerBlock: number; -}; - -const TAR_BUILDER_OPTIONS: TarBuilderOptions = { - recordsPerBlock: 20 -}; - -export default class TARBuilder { - tape: Tar; - count: number; - options: TarBuilderOptions; - - static get properties() { - return { - id: 'tar', - name: 'TAR', - extensions: ['tar'], - mimeType: 'application/x-tar', - builder: TARBuilder, - options: TAR_BUILDER_OPTIONS - }; - } - - constructor(options: Partial) { - this.options = {...TAR_BUILDER_OPTIONS, ...options}; - this.tape = new Tar(this.options.recordsPerBlock); - this.count = 0; - } - - addFile(buffer: ArrayBuffer, filename: string) { - this.tape.append(filename, new Uint8Array(buffer)); - this.count++; - } - - async build(): Promise { - return new Response(this.tape.save()).arrayBuffer(); - } -} diff --git a/modules/core/src/encoders/tar/tar.ts b/modules/core/src/encoders/tar/tar.ts deleted file mode 100644 index 0545efd8..00000000 --- a/modules/core/src/encoders/tar/tar.ts +++ /dev/null @@ -1,141 +0,0 @@ -// hubble.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - -import * as utils from './utils'; -import * as header from './header'; - -let blockSize: number; -const recordSize = 512; - -type Block = { - header: any; - input: Uint8Array; - headerLength: number; - inputLength: number; -}; - -class Tar { - blocks: Block[]; - written: number; - length: number; - out: Uint8Array; - - constructor(recordsPerBlock: number) { - this.written = 0; - blockSize = (recordsPerBlock || 20) * recordSize; - this.out = utils.clean(blockSize); - this.blocks = []; - this.length = 0; - this.save = this.save.bind(this); - this.clear = this.clear.bind(this); - this.append = this.append.bind(this); - } - - append( - filepath: string, - input: string | Uint8Array, - opts: {mode?: number; mtime?: number; uid?: any; gid?: number; owner?: any; group?: any} = {} - ) { - let checksum: number; - - if (typeof input === 'string') { - input = utils.stringToUint8(input); - } else if (input?.constructor && input.constructor !== Uint8Array.prototype.constructor) { - const errorInput = input.constructor - .toString() - .match(/function\s*([$A-Za-z_][0-9A-Za-z_]*)\s*\(/) - ?.at(1); - const errorMessage = `Invalid input type. You gave me: ${errorInput}`; - throw errorMessage; - } - - const mode = opts.mode || parseInt('777', 8) & 0xfff; - const mtime = opts.mtime || Math.floor(Number(new Date()) / 1000); - const uid = opts.uid || 0; - const gid = opts.gid || 0; - - const data = { - fileName: filepath, - fileMode: utils.pad(mode, 7), - uid: utils.pad(uid, 7), - gid: utils.pad(gid, 7), - fileSize: utils.pad(input.length, 11), - mtime: utils.pad(mtime, 11), - checksum: ' ', - // 0 = just a file - type: '0', - ustar: 'ustar ', - owner: opts.owner || '', - group: opts.group || '' - }; - - // calculate the checksum - checksum = 0; - Object.keys(data).forEach(key => { - let i: number; - const value = data[key]; - let length: number; - - for (i = 0, length = value.length; i < length; i += 1) { - checksum += value.charCodeAt(i); - } - }); - - data.checksum = `${utils.pad(checksum, 6)}\u0000 `; - - const headerArr = header.format(data); - - const headerLength = Math.ceil(headerArr.length / recordSize) * recordSize; - const inputLength = Math.ceil(input.length / recordSize) * recordSize; - - this.blocks.push({ - header: headerArr, - input, - headerLength, - inputLength - }); - } - - save() { - const buffers: Uint8Array[] = []; - const chunks: {blocks: Block[]; length: number}[] = []; - let length = 0; - const max = Math.pow(2, 20); - - let chunk: Block[] = []; - this.blocks.forEach(b => { - if (length + b.headerLength + b.inputLength > max) { - chunks.push({blocks: chunk, length}); - chunk = []; - length = 0; - } - chunk.push(b); - length += b.headerLength + b.inputLength; - }); - chunks.push({blocks: chunk, length}); - - chunks.forEach(c => { - const buffer = new Uint8Array(c.length); - let written = 0; - c.blocks.forEach(b => { - buffer.set(b.header, written); - written += b.headerLength; - buffer.set(b.input, written); - written += b.inputLength; - }); - buffers.push(buffer); - }); - - buffers.push(new Uint8Array(2 * recordSize)); - - return new Blob(buffers, {type: 'octet/stream'}); - } - - clear() { - this.written = 0; - this.out = utils.clean(blockSize); - } -} - -export default Tar; diff --git a/modules/core/src/encoders/tar/utils.ts b/modules/core/src/encoders/tar/utils.ts deleted file mode 100644 index e15c61a5..00000000 --- a/modules/core/src/encoders/tar/utils.ts +++ /dev/null @@ -1,144 +0,0 @@ -// hubble.gl -// SPDX-License-Identifier: MIT -// Copyright (c) vis.gl contributors - -const lookup = [ - 'A', - 'B', - 'C', - 'D', - 'E', - 'F', - 'G', - 'H', - 'I', - 'J', - 'K', - 'L', - 'M', - 'N', - 'O', - 'P', - 'Q', - 'R', - 'S', - 'T', - 'U', - 'V', - 'W', - 'X', - 'Y', - 'Z', - 'a', - 'b', - 'c', - 'd', - 'e', - 'f', - 'g', - 'h', - 'i', - 'j', - 'k', - 'l', - 'm', - 'n', - 'o', - 'p', - 'q', - 'r', - 's', - 't', - 'u', - 'v', - 'w', - 'x', - 'y', - 'z', - '0', - '1', - '2', - '3', - '4', - '5', - '6', - '7', - '8', - '9', - '+', - '/' -]; -export function clean(length: number) { - let i: number; - const buffer = new Uint8Array(length); - for (i = 0; i < length; i += 1) { - buffer[i] = 0; - } - return buffer; -} - -export function extend(orig: any, length: number, addLength: number, multipleOf: number) { - const newSize = length + addLength; - const buffer = clean((Math.floor(newSize / multipleOf) + 1) * multipleOf); - - buffer.set(orig); - - return buffer; -} - -export function pad(num: number, bytes: number, base = 8) { - const numStr = num.toString(base); - return '000000000000'.substr(numStr.length + 12 - bytes) + num; -} - -export function stringToUint8(input: string, out?: Uint8Array, offset: number = 0) { - let i: number; - let length: number; - - out = out || clean(input.length); - - for (i = 0, length = input.length; i < length; i += 1) { - out[offset] = input.charCodeAt(i); - offset += 1; - } - - return out; -} - -export function uint8ToBase64(uint8: Uint8Array) { - let i: number; - // if we have 1 byte left, pad 2 bytes - const extraBytes = uint8.length % 3; - let output = ''; - let temp: number; - let length: number; - - function tripletToBase64(num) { - return ( - lookup[(num >> 18) & 0x3f] + - lookup[(num >> 12) & 0x3f] + - lookup[(num >> 6) & 0x3f] + - lookup[num & 0x3f] - ); - } - - // go through the array every three bytes, we'll deal with trailing stuff later - for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) { - temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + uint8[i + 2]; - output += tripletToBase64(temp); - } - - // this prevents an ERR_INVALID_URL in Chrome (Firefox okay) - switch (output.length % 4) { - case 1: - output += '='; - break; - case 2: - output += '=='; - break; - default: - break; - } - - return output; -} diff --git a/modules/core/src/encoders/video/jpeg-sequence-encoder.ts b/modules/core/src/encoders/video/jpeg-sequence-encoder.ts index 79a53818..84135870 100644 --- a/modules/core/src/encoders/video/jpeg-sequence-encoder.ts +++ b/modules/core/src/encoders/video/jpeg-sequence-encoder.ts @@ -4,18 +4,13 @@ import type {FrameEncoderSettings} from '../frame-encoder'; import FrameEncoder from '../frame-encoder'; -import TARBuilder from '../tar/tar-builder'; import {pad, canvasToArrayBuffer} from '../utils/index'; import {encode} from '@loaders.gl/core'; import {ZipWriter} from '@loaders.gl/zip'; -const TAR = 'tar'; -const ZIP = 'zip'; - class JPEGSequenceEncoder extends FrameEncoder { - tarBuilder: TARBuilder | null = null; filemap: {[filename: string]: ArrayBuffer} = {}; - options: {quality: number; archive?: 'tar' | 'zip'} = {quality: 1, archive: TAR}; + options: {quality: number} = {quality: 1}; constructor(settings: FrameEncoderSettings) { super(settings); @@ -25,27 +20,12 @@ class JPEGSequenceEncoder extends FrameEncoder { } this.options.quality = this.options.quality || 1.0; - this.options.archive = this.options.archive || TAR; - switch (this.options.archive) { - case TAR: { - this.mimeType = TARBuilder.properties.mimeType; - this.extension = `.${TARBuilder.properties.extensions[0]}`; - break; - } - case ZIP: { - this.mimeType = ZipWriter.mimeTypes?.[0] || ''; - this.extension = `.${ZipWriter.extensions?.[0]}`; - break; - } - default: { - throw new Error(`Unsupported archive type [zip, tar]: ${this.options.archive}`); - } - } + this.mimeType = ZipWriter.mimeTypes?.[0] || ''; + this.extension = `.${ZipWriter.extensions?.[0]}`; } start() { - this.tarBuilder = new TARBuilder({}); this.filemap = {}; } @@ -53,44 +33,13 @@ class JPEGSequenceEncoder extends FrameEncoder { const mimeType = 'image/jpeg'; const extension = '.jpg'; const buffer = await canvasToArrayBuffer(canvas, mimeType, this.options.quality); - switch (this.options.archive) { - case TAR: { - if (!this.tarBuilder) { - break; - } - const filename = pad(this.tarBuilder.count) + extension; - this.tarBuilder.addFile(buffer, filename); - break; - } - case ZIP: { - const filename = pad(Object.keys(this.filemap).length) + extension; - this.filemap[filename] = buffer; - break; - } - default: { - throw new Error(`Unsupported archive type [zip, tar]: ${this.options.archive}`); - } - } + const filename = pad(Object.keys(this.filemap).length) + extension; + this.filemap[filename] = buffer; } async save() { - switch (this.options.archive) { - case TAR: { - if (!this.tarBuilder) { - break; - } - const arrayBuffer = await this.tarBuilder.build(); - return new Blob([arrayBuffer], {type: TARBuilder.properties.mimeType}); - } - case ZIP: { - const arrayBuffer = await encode(this.filemap, ZipWriter); - return new Blob([arrayBuffer], {type: ZipWriter.mimeTypes?.[0]}); - } - default: { - throw new Error(`Unsupported archive type [zip, tar]: ${this.options.archive}`); - } - } - return null; + const arrayBuffer = await encode(this.filemap, ZipWriter); + return new Blob([arrayBuffer], {type: ZipWriter.mimeTypes?.[0]}); } } diff --git a/modules/core/src/encoders/video/png-sequence-encoder.ts b/modules/core/src/encoders/video/png-sequence-encoder.ts index 1248875e..c4a7ce18 100644 --- a/modules/core/src/encoders/video/png-sequence-encoder.ts +++ b/modules/core/src/encoders/video/png-sequence-encoder.ts @@ -2,20 +2,15 @@ // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors +import {encode} from '@loaders.gl/core'; +import {ZipWriter} from '@loaders.gl/zip'; import type {FrameEncoderSettings} from '../frame-encoder'; import FrameEncoder from '../frame-encoder'; -import TARBuilder from '../tar/tar-builder'; import {pad, canvasToArrayBuffer} from '../utils/index'; -import {encode} from '@loaders.gl/core'; -import {ZipWriter} from '@loaders.gl/zip'; - -const TAR = 'tar'; -const ZIP = 'zip'; class PNGSequenceEncoder extends FrameEncoder { - tarBuilder: TARBuilder | null = null; filemap: {[filename: string]: ArrayBuffer} = {}; - options: {archive?: 'tar' | 'zip'} = {archive: TAR}; + options: {} = {}; constructor(settings: FrameEncoderSettings) { super(settings); @@ -24,27 +19,11 @@ class PNGSequenceEncoder extends FrameEncoder { this.options = {...settings.png}; } - this.options.archive = this.options.archive || TAR; - - switch (this.options.archive) { - case TAR: { - this.mimeType = TARBuilder.properties.mimeType; - this.extension = `.${TARBuilder.properties.extensions[0]}`; - break; - } - case ZIP: { - this.mimeType = ZipWriter.mimeTypes?.[0] || ''; - this.extension = `.${ZipWriter.extensions?.[0]}`; - break; - } - default: { - throw new Error(`Unsupported archive type [zip, tar]: ${this.options.archive}`); - } - } + this.mimeType = ZipWriter.mimeTypes[0] || ''; + this.extension = `.${ZipWriter.extensions?.[0]}`; } start() { - this.tarBuilder = new TARBuilder({}); this.filemap = {}; } @@ -52,47 +31,16 @@ class PNGSequenceEncoder extends FrameEncoder { const mimeType = 'image/png'; const extension = '.png'; const buffer = await canvasToArrayBuffer(canvas, mimeType); - switch (this.options.archive) { - case TAR: { - if (!this.tarBuilder) { - break; - } - const filename = pad(this.tarBuilder.count) + extension; - this.tarBuilder.addFile(buffer, filename); - break; - } - case ZIP: { - const filename = pad(Object.keys(this.filemap).length) + extension; - this.filemap[filename] = buffer; - break; - } - default: { - throw new Error(`Unsupported archive type [zip, tar]: ${this.options.archive}`); - } - } + const filename = pad(Object.keys(this.filemap).length) + extension; + this.filemap[filename] = buffer; } /** * @return {Promise} */ async save() { - switch (this.options.archive) { - case TAR: { - if (!this.tarBuilder) { - break; - } - const arrayBuffer = await this.tarBuilder.build(); - return new Blob([arrayBuffer], {type: TARBuilder.properties.mimeType}); - } - case ZIP: { - const arrayBuffer = await encode(this.filemap, ZipWriter); - return new Blob([arrayBuffer], {type: ZipWriter.mimeTypes?.[0]}); - } - default: { - throw new Error(`Unsupported archive type [zip, tar]: ${this.options.archive}`); - } - } - return null; + const arrayBuffer = await encode(this.filemap, ZipWriter); + return new Blob([arrayBuffer], {type: ZipWriter.mimeTypes?.[0]}); } } diff --git a/modules/react/src/components/export-video/export-video-panel-container.tsx b/modules/react/src/components/export-video/export-video-panel-container.tsx index 4b81d2fa..8c3be053 100644 --- a/modules/react/src/components/export-video/export-video-panel-container.tsx +++ b/modules/react/src/components/export-video/export-video-panel-container.tsx @@ -167,12 +167,9 @@ export class ExportVideoPanelContainer extends Component< quality: 0.8 }, jpeg: { - archive: 'zip', quality: 0.8 }, - png: { - archive: 'zip' - }, + png: {}, gif: { sampleInterval: 1000, width, diff --git a/modules/react/src/components/quick-animation.tsx b/modules/react/src/components/quick-animation.tsx index c402c640..1767de91 100644 --- a/modules/react/src/components/quick-animation.tsx +++ b/modules/react/src/components/quick-animation.tsx @@ -5,8 +5,8 @@ import React, {useState, useRef, useMemo} from 'react'; import DeckGL, {DeckGLRef} from '@deck.gl/react/typed'; import BasicControls from './basic-controls'; import {useDeckAdapter, useNextFrame} from '../hooks'; -import type {MapViewState} from '@deck.gl/core/typed'; -import {FormatConfigs} from '@hubble.gl/core'; +import type {DeckProps, MapViewState} from '@deck.gl/core/typed'; +import type {DeckAnimation, FormatConfigs, Timecode} from '@hubble.gl/core'; export const QuickAnimation = ({ initialViewState, @@ -15,6 +15,13 @@ export const QuickAnimation = ({ resolution = {width: 640, height: 480}, formatConfigs = {}, deckProps = {} +}: { + initialViewState: MapViewState; + animation: DeckAnimation; + timecode: Timecode; + resolution?: {width: number; height: number}; + formatConfigs?: Partial; + deckProps?: DeckProps; }) => { const deckRef = useRef(null); const deck = useMemo(() => deckRef.current && deckRef.current.deck, [deckRef.current]); @@ -29,11 +36,8 @@ export const QuickAnimation = ({ webm: { quality: 0.8 }, - png: { - archive: 'zip' - }, + png: {}, jpeg: { - archive: 'zip', quality: 0.8 }, gif: {