diff --git a/web/packages/media/live-photo.ts b/web/packages/media/live-photo.ts index 755bb40be..96be4f613 100644 --- a/web/packages/media/live-photo.ts +++ b/web/packages/media/live-photo.ts @@ -1,4 +1,4 @@ -import { nameAndExtension } from "@/next/file"; +import { fileNameFromComponents, nameAndExtension } from "@/next/file"; import JSZip from "jszip"; class LivePhoto { @@ -8,18 +8,6 @@ class LivePhoto { videoNameTitle: string; } -export function getFileNameWithoutExtension(filename: string) { - const lastDotPosition = filename.lastIndexOf("."); - if (lastDotPosition === -1) return filename; - else return filename.slice(0, lastDotPosition); -} - -export function getFileExtensionWithDot(filename: string) { - const lastDotPosition = filename.lastIndexOf("."); - if (lastDotPosition === -1) return ""; - else return filename.slice(lastDotPosition); -} - /** * Convert a binary serialized representation of a live photo to an in-memory * {@link LivePhoto}. @@ -42,12 +30,12 @@ export const decodeLivePhoto = async (fileName: string, zipBlob: Blob) => { const livePhoto = new LivePhoto(); for (const zipFileName in zip.files) { if (zipFileName.startsWith("image")) { - livePhoto.imageNameTitle = - name + getFileExtensionWithDot(zipFileName); + const [, imageExt] = nameAndExtension(zipFileName); + livePhoto.imageNameTitle = fileNameFromComponents([name, imageExt]); livePhoto.image = await zip.files[zipFileName].async("uint8array"); } else if (zipFileName.startsWith("video")) { - livePhoto.videoNameTitle = - name + getFileExtensionWithDot(zipFileName); + const [, videoExt] = nameAndExtension(zipFileName); + livePhoto.videoNameTitle = fileNameFromComponents([name, videoExt]); livePhoto.video = await zip.files[zipFileName].async("uint8array"); } } @@ -69,7 +57,7 @@ export const encodeLivePhoto = async (livePhoto: LivePhoto) => { const [, videoExt] = nameAndExtension(livePhoto.videoNameTitle); const zip = new JSZip(); - zip.file(["image", imageExt].filter((x) => !!x).join("."), livePhoto.image); - zip.file(["video", videoExt].filter((x) => !!x).join("."), livePhoto.video); + zip.file(fileNameFromComponents(["image", imageExt]), livePhoto.image); + zip.file(fileNameFromComponents(["video", videoExt]), livePhoto.video); return await zip.generateAsync({ type: "uint8array" }); }; diff --git a/web/packages/next/file.ts b/web/packages/next/file.ts index b69fece50..fae9a6d00 100644 --- a/web/packages/next/file.ts +++ b/web/packages/next/file.ts @@ -1,19 +1,34 @@ import type { ElectronFile } from "./types/file"; +/** + * The two parts of a file name - the name itself, and an (optional) extension. + * + * The extension does not include the dot. + */ +type FileNameComponents = [name: string, extension: string | undefined]; + /** * Split a filename into its components - the name itself, and the extension (if * any) - returning both. The dot is not included in either. * * For example, `foo-bar.png` will be split into ["foo-bar", "png"]. + * + * See {@link fileNameFromComponents} for the inverse operation. */ -export const nameAndExtension = ( - fileName: string, -): [string, string | undefined] => { +export const nameAndExtension = (fileName: string): FileNameComponents => { const i = fileName.lastIndexOf("."); if (i == -1) return [fileName, undefined]; else return [fileName.slice(0, i), fileName.slice(i + 1)]; }; +/** + * Construct a file name from its components (name and extension). + * + * Inverse of {@link nameAndExtension}. + */ +export const fileNameFromComponents = (components: FileNameComponents) => + components.filter((x) => !!x).join("."); + export function getFileNameSize(file: File | ElectronFile) { return `${file.name}_${convertBytesToHumanReadable(file.size)}`; }