Created
April 1, 2023 09:52
-
-
Save schickling/586fdc6e260b52d22f083553b0f602cf to your computer and use it in GitHub Desktop.
blob <> file[] encoding
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// export const encodeFilesToBlob = (files: File[]): Promise<Blob> => { | |
// const formData = new FormData() | |
// files.forEach((file) => { | |
// formData.append('files[]', file, file.name) | |
// }) | |
// return new Response(formData).blob() | |
// } | |
// export const decodeBlobToFiles = async (blob: Blob): Promise<File[]> => { | |
// const resp = new Response(blob, { headers: { 'Content-Type': blob.type } }) | |
// const formData = await resp.formData() | |
// const formFiles = formData.getAll('files[]') as File[] | |
// return formFiles | |
// } | |
// export const encodeFilesToBlob = (files: File[]): Blob => { | |
// const blob = new Blob(files, { type: 'application/octet-stream' }) | |
// return blob | |
// } | |
/** | |
* Structure: | |
* First 4 bytes: number of files | |
* Middle section: Array of file sizes (in bytes) relative to the order of the `files` array | |
* End section: Array of file name lengths (in bytes) relative to the order of the `files` array | |
* | |
* Array of file sizes (in bytes) relative to the order of the `files` array with the first element being the number of files. | |
* Encoded as a Uint32Array (so each element has to be less than 2^32 ~ 4GB) | |
* | |
* e.g. for files = [ | |
* file-a (size 300b, filename = 'dog.jpg') | |
* file-b (size 200b, filename = 'human.jpg') | |
* file-c (size 400b, filename = 'fish.jpg') | |
* ] => [3, 300, 200, 400, 7, 9, 8] | |
*/ | |
type EncodedOffsetInfo = Uint32Array | |
const getEncodedOffsetInfo = (files: readonly File[], fileNameSizes: number[]): EncodedOffsetInfo => { | |
const encodedOffsetInfo = new Uint32Array(files.length * 2 + 1) | |
encodedOffsetInfo[0] = files.length | |
// eslint-disable-next-line unicorn/no-for-loop | |
for (let i = 0; i < files.length; i++) { | |
encodedOffsetInfo[i + 1] = files[i]!.size | |
} | |
// eslint-disable-next-line unicorn/no-for-loop | |
for (let i = 0; i < files.length; i++) { | |
encodedOffsetInfo[i + files.length + 1] = fileNameSizes[i]! | |
} | |
return encodedOffsetInfo | |
} | |
/** | |
* Structure: | |
* Header: EncodedOffsetInfo | |
* Main section: Array of tuples: [file, file name] | |
*/ | |
type EncodedBlob = Blob | |
/** Note that the file type is not preserved. */ | |
export const encodeFilesToBlob = (files: readonly File[]): EncodedBlob => { | |
const textEncoder = new TextEncoder() | |
const encodedFileNames = files.map((file) => textEncoder.encode(file.name)) | |
const fileNameSizes = encodedFileNames.map((encodedFileName) => encodedFileName.length) | |
const encodedOffsetInfo = getEncodedOffsetInfo(files, fileNameSizes) | |
const encodedOffsetInfoBlob = new Blob([encodedOffsetInfo], { type: 'application/octet-stream' }) | |
const blobParts: BlobPart[] = [encodedOffsetInfoBlob] | |
// eslint-disable-next-line unicorn/no-for-loop | |
for (let i = 0; i < files.length; i++) { | |
blobParts.push(files[i]!, encodedFileNames[i]!) | |
} | |
return new Blob(blobParts, { type: 'application/octet-stream' }) | |
} | |
export const decodeBlobToFiles = async (blob: EncodedBlob): Promise<File[]> => { | |
// debugger | |
const arrayBuffer = await blob.arrayBuffer() | |
const dataView = new DataView(arrayBuffer) | |
const textDecoder = new TextDecoder() | |
const numberOfFiles = dataView.getUint32(0, true) | |
const fileSizes = [] | |
for (let i = 0; i < numberOfFiles; i++) { | |
fileSizes.push(dataView.getUint32(4 * (i + 1), true)) | |
} | |
const fileNameSizes = [] | |
for (let i = 0; i < numberOfFiles; i++) { | |
fileNameSizes.push(dataView.getUint32(4 * (numberOfFiles + i + 1), true)) | |
} | |
let offset = 4 * (numberOfFiles * 2 + 1) | |
const files = [] | |
for (let i = 0; i < numberOfFiles; i++) { | |
const fileSize = fileSizes[i]! | |
const fileData = new Uint8Array(arrayBuffer, offset, fileSize) | |
offset += fileSize | |
const fileNameSize = fileNameSizes[i]! | |
const fileNameData = new Uint8Array(arrayBuffer, offset, fileNameSize) | |
const fileName = textDecoder.decode(fileNameData) | |
offset += fileNameSize | |
const file = new File([fileData], fileName) | |
files.push(file) | |
} | |
return files | |
} | |
// const getFilesOffsetInfo | |
// export const decodeBlobToFiles = (blob: Blob): Promise<File[]> => { | |
// return new Promise((resolve, reject) => { | |
// const reader = new FileReader(); | |
// reader.onload = function() { | |
// const arrayBuffer = reader.result; | |
// const files = []; | |
// const dataView = new DataView(arrayBuffer); | |
// let offset = 0; | |
// while (offset < arrayBuffer.byteLength) { | |
// const length = dataView.getUint32(offset, true); | |
// offset += 4; | |
// const name = new TextDecoder().decode(new Uint8Array(arrayBuffer.slice(offset, offset + length))); | |
// offset += length; | |
// const typeLength = dataView.getUint32(offset, true); | |
// offset += 4; | |
// const type = new TextDecoder().decode(new Uint8Array(arrayBuffer.slice(offset, offset + typeLength))); | |
// offset += typeLength; | |
// const blob = new Blob([arrayBuffer.slice(offset, offset + dataView.getUint32(offset, true))], { type: type }); | |
// offset += 4; | |
// files.push(new File([blob], name, { type: type })); | |
// } | |
// console.log(files); // array of File objects | |
// }; | |
// reader.readAsArrayBuffer(blob); | |
// }) | |
// } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment