@@ -0,0 +1,26 @@ | |||
import WaveSurfer from 'wavesurfer.js'; | |||
import { AudioMetadata } from './common'; | |||
export const getMetadataFromUrl = (audioUrl: string) => new Promise<Partial<AudioMetadata>>((resolve, reject) => { | |||
try { | |||
const dummyContainer = window.document.createElement('div'); | |||
// TODO remove wavesurfer dependency | |||
const waveSurferInstance = WaveSurfer.create({ | |||
container: dummyContainer, | |||
}); | |||
waveSurferInstance.on('ready', () => { | |||
const metadata = { | |||
duration: waveSurferInstance.getDuration(), | |||
}; | |||
waveSurferInstance.destroy(); | |||
dummyContainer.remove(); | |||
resolve(metadata); | |||
}); | |||
void waveSurferInstance.load(audioUrl); | |||
} catch (err) { | |||
reject(err); | |||
} | |||
}); |
@@ -0,0 +1,3 @@ | |||
export interface AudioMetadata { | |||
duration?: number; | |||
} |
@@ -1,39 +1,17 @@ | |||
import WaveSurfer from 'wavesurfer.js'; | |||
import {AudioMetadata} from './common'; | |||
export interface AudioMetadata { | |||
duration?: number; | |||
} | |||
export const getAudioMetadata = (audioUrl?: string, fileType?: string) => new Promise<AudioMetadata>(async (resolve, reject) => { | |||
// TODO server side | |||
if (fileType === 'audio/mid') { | |||
resolve({}); | |||
return; | |||
export const getMetadataFromUrl = async (audioUrl?: string) => { | |||
if (typeof audioUrl === 'undefined') { | |||
return {} as Partial<AudioMetadata>; | |||
} | |||
try { | |||
const dummyContainer = window.document.createElement('div'); | |||
const waveSurferInstance = WaveSurfer.create({ | |||
container: dummyContainer, | |||
}); | |||
waveSurferInstance.on('ready', async () => { | |||
const metadata = { | |||
duration: waveSurferInstance.getDuration(), | |||
}; | |||
waveSurferInstance.destroy(); | |||
dummyContainer.remove(); | |||
resolve(metadata); | |||
}); | |||
if (typeof window !== 'undefined') { | |||
const { getMetadataFromUrl: client } = await import('./client'); | |||
return client(audioUrl); | |||
} | |||
if (audioUrl === undefined) { | |||
resolve({}); | |||
return; | |||
} | |||
// TODO server side | |||
return {} as Partial<AudioMetadata>; | |||
}; | |||
await waveSurferInstance.load(audioUrl); | |||
} catch (err) { | |||
reject(err); | |||
} | |||
}); | |||
export * from './common'; |
@@ -1,7 +1,7 @@ | |||
import ColorThief from 'colorthief'; | |||
import {DEFAULT_PALETTE_COLOR_COUNT, GetImageMetadataOptions, ImageMetadata} from './common'; | |||
const doGetImageMetadata = async (thisImage: HTMLImageElement, options = {} as GetImageMetadataOptions) => { | |||
const doGetImageMetadata = async (thisImage: HTMLImageElement, options = {} as GetImageMetadataOptions): Promise<Partial<ImageMetadata>> => { | |||
const { paletteColorCount = DEFAULT_PALETTE_COLOR_COUNT } = options; | |||
const colorThief = new ColorThief(); | |||
const palette = await colorThief.getPalette(thisImage, paletteColorCount); | |||
@@ -12,9 +12,9 @@ const doGetImageMetadata = async (thisImage: HTMLImageElement, options = {} as G | |||
}; | |||
}; | |||
export const getMetadataFromElement = (thisImage: HTMLImageElement, options?: GetImageMetadataOptions): Promise<ImageMetadata> => doGetImageMetadata(thisImage, options); | |||
export const getMetadataFromElement = (thisImage: HTMLImageElement, options?: GetImageMetadataOptions): Promise<Partial<ImageMetadata>> => doGetImageMetadata(thisImage, options); | |||
export const getMetadataFromUrl = (imageUrl: string, options = {} as GetImageMetadataOptions): Promise<ImageMetadata> => new Promise((resolve, reject) => { | |||
export const getMetadataFromUrl = (imageUrl: string, options = {} as GetImageMetadataOptions): Promise<Partial<ImageMetadata>> => new Promise((resolve, reject) => { | |||
const image = new Image(); | |||
image.addEventListener('load', async (imageLoadEvent) => { | |||
@@ -25,14 +25,9 @@ export const getMetadataFromUrl = (imageUrl: string, options = {} as GetImageMet | |||
}); | |||
image.addEventListener('error', () => { | |||
reject(new Error('Could not load file as image')); | |||
reject(new Error('Could not load URL as image')); | |||
image.remove(); | |||
}); | |||
if (imageUrl === undefined) { | |||
resolve({}); | |||
return; | |||
} | |||
image.src = imageUrl; | |||
}); |
@@ -1,6 +1,8 @@ | |||
import {ImageMetadata} from './common'; | |||
export const getMetadataFromUrl = async (imageUrl?: string) => { | |||
if (imageUrl === undefined) { | |||
return {}; | |||
if (typeof imageUrl === 'undefined') { | |||
return {} as Partial<ImageMetadata>; | |||
} | |||
if (typeof window !== 'undefined') { | |||
@@ -1,6 +1,6 @@ | |||
import {DEFAULT_PALETTE_COLOR_COUNT, GetImageMetadataOptions, ImageMetadata} from './common'; | |||
const doGetImageMetadata = async (input: string | Buffer, options = {} as GetImageMetadataOptions) => { | |||
const doGetImageMetadata = async (input: string | Buffer, options = {} as GetImageMetadataOptions): Promise<Partial<ImageMetadata>> => { | |||
const { paletteColorCount = DEFAULT_PALETTE_COLOR_COUNT } = options; | |||
const { imageSize, disableFS } = await import('image-size'); | |||
disableFS(true); | |||
@@ -14,6 +14,6 @@ const doGetImageMetadata = async (input: string | Buffer, options = {} as GetIma | |||
}; | |||
}; | |||
export const getMetadataFromUrl = (imageUrl: string, options?: GetImageMetadataOptions): Promise<ImageMetadata> => doGetImageMetadata(imageUrl, options); | |||
export const getMetadataFromUrl = (imageUrl: string, options?: GetImageMetadataOptions): Promise<Partial<ImageMetadata>> => doGetImageMetadata(imageUrl, options); | |||
export const getMetadataFromBuffer = (buffer: Buffer, options?: GetImageMetadataOptions): Promise<ImageMetadata> => doGetImageMetadata(buffer, options); | |||
export const getMetadataFromBuffer = (buffer: Buffer, options?: GetImageMetadataOptions): Promise<Partial<ImageMetadata>> => doGetImageMetadata(buffer, options); |
@@ -50,7 +50,7 @@ const countLinesOfCode = (lines: string[], scheme: string): number => { | |||
//return lines.filter((line) => !line.trim().startsWith('//')).length; | |||
return lines.filter((line) => line.trim().length > 0).length; | |||
} | |||
}; | |||
export const getTextMetadata = (contents: string, filename?: string): Promise<TextMetadata> => { | |||
const lineNormalizedContents = contents.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); | |||
@@ -65,7 +65,7 @@ export const getTextMetadata = (contents: string, filename?: string): Promise<Te | |||
scheme: value.aliasOf, | |||
schemeTitle: value.title, | |||
linesOfCode: countLinesOfCode(lines, value.aliasOf), | |||
} | |||
}; | |||
} | |||
return { | |||
@@ -81,7 +81,7 @@ export const getTextMetadata = (contents: string, filename?: string): Promise<Te | |||
{ | |||
contents, | |||
lineCount, | |||
} as TextMetadata | |||
} as TextMetadata, | |||
); | |||
if (typeof metadata.scheme !== 'string') { | |||
@@ -90,4 +90,4 @@ export const getTextMetadata = (contents: string, filename?: string): Promise<Te | |||
} | |||
return Promise.resolve(metadata); | |||
} | |||
}; |
@@ -0,0 +1,25 @@ | |||
import {VideoMetadata} from './common'; | |||
export const getMetadataFromUrl = async (videoUrl: string) => new Promise<Partial<VideoMetadata>>((resolve, reject) => { | |||
const video = window.document.createElement('video'); | |||
const source = window.document.createElement('source'); | |||
video.addEventListener('loadedmetadata', (videoLoadEvent) => { | |||
const thisVideo = videoLoadEvent.currentTarget as HTMLVideoElement; | |||
const metadata = { | |||
width: thisVideo.videoWidth, | |||
height: thisVideo.videoHeight, | |||
duration: thisVideo.duration, | |||
}; | |||
video.remove(); | |||
resolve(metadata); | |||
}); | |||
video.addEventListener('error', () => { | |||
reject(new Error('Could not load file as video')); | |||
video.remove(); | |||
}); | |||
source.src = videoUrl; | |||
video.appendChild(source); | |||
}); |
@@ -0,0 +1,5 @@ | |||
export interface VideoMetadata { | |||
width?: number; | |||
height?: number; | |||
duration?: number; | |||
} |
@@ -1,35 +1,15 @@ | |||
export interface VideoFileMetadata { | |||
width?: number; | |||
height?: number; | |||
duration?: number; | |||
} | |||
export const getVideoMetadata = (videoUrl?: string) => new Promise<VideoFileMetadata>((resolve, reject) => { | |||
// TODO server side | |||
const video = window.document.createElement('video'); | |||
const source = window.document.createElement('source'); | |||
video.addEventListener('loadedmetadata', (videoLoadEvent) => { | |||
const thisVideo = videoLoadEvent.currentTarget as HTMLVideoElement; | |||
const metadata = { | |||
width: thisVideo.videoWidth, | |||
height: thisVideo.videoHeight, | |||
duration: thisVideo.duration, | |||
}; | |||
video.remove(); | |||
resolve(metadata); | |||
}); | |||
video.addEventListener('error', () => { | |||
reject(new Error('Could not load file as video')); | |||
video.remove(); | |||
}); | |||
export const getMetadataFromUrl = async (videoUrl?: string) => { | |||
if (typeof videoUrl === 'undefined') { | |||
resolve({}); | |||
return; | |||
return {}; | |||
} | |||
source.src = videoUrl; | |||
video.appendChild(source); | |||
}); | |||
if (typeof window !== 'undefined') { | |||
const { getMetadataFromUrl: client } = await import('./client'); | |||
return client(videoUrl); | |||
} | |||
// TODO server side | |||
return {}; | |||
}; | |||
export * from './common'; |
@@ -16,6 +16,7 @@ export const SpectrogramCanvas = React.forwardRef<SpectrogramCanvasDerivedElemen | |||
className, | |||
children, | |||
controls, | |||
// TODO organize props for color | |||
waveColor, | |||
progressColor, | |||
cursorColor, | |||
@@ -1,8 +1,8 @@ | |||
import * as mimeTypes from 'mime-types'; | |||
import {getTextMetadata, TextMetadata} from '@modal-soft/text-utils'; | |||
import {getMetadataFromUrl, ImageMetadata} from '@modal-soft/image-utils'; | |||
import {getAudioMetadata, AudioMetadata} from '@modal-soft/audio-utils'; | |||
import {getVideoMetadata, VideoFileMetadata} from '@modal-soft/video-utils'; | |||
import {getMetadataFromUrl as getImageMetadataFromUrl, ImageMetadata} from '@modal-soft/image-utils'; | |||
import {getMetadataFromUrl as getAudioMetadataFromUrl, AudioMetadata} from '@modal-soft/audio-utils'; | |||
import {getMetadataFromUrl as getVideoMetadataFromUrl, VideoMetadata} from '@modal-soft/video-utils'; | |||
const MIME_TYPE_DESCRIPTIONS = { | |||
'image/gif': 'GIF Image', | |||
@@ -164,7 +164,7 @@ export interface ImageFile extends FileWithResolvedContentType { | |||
export const augmentImageFile = async <T extends Partial<FileWithDataUrl>>(file: T): Promise<ImageFile> => { | |||
const fileMutable = file as unknown as Record<string, ImageMetadata>; | |||
fileMutable.metadata = await getMetadataFromUrl(file.url); | |||
fileMutable.metadata = await getImageMetadataFromUrl(file.url); | |||
return fileMutable as unknown as ImageFile; | |||
}; | |||
@@ -175,7 +175,11 @@ export interface AudioFile extends FileWithResolvedContentType { | |||
export const augmentAudioFile = async <T extends Partial<FileWithDataUrl>>(file: T): Promise<AudioFile> => { | |||
const fileMutable = file as unknown as Record<string, AudioMetadata>; | |||
fileMutable.metadata = await getAudioMetadata(file.url, file.type); | |||
if (file.type === 'audio/mid') { | |||
fileMutable.metadata = {}; | |||
} else { | |||
fileMutable.metadata = await getAudioMetadataFromUrl(file.url); | |||
} | |||
return fileMutable as unknown as AudioFile; | |||
}; | |||
@@ -200,11 +204,11 @@ export const augmentBinaryFile = async <T extends Partial<FileWithDataUrl>>(file | |||
export interface VideoFile extends FileWithResolvedContentType { | |||
resolvedType: ContentType.VIDEO; | |||
metadata?: VideoFileMetadata; | |||
metadata?: VideoMetadata; | |||
} | |||
export const augmentVideoFile = async <T extends Partial<FileWithDataUrl>>(file: T): Promise<VideoFile> => { | |||
const fileMutable = file as unknown as Record<string, AudioMetadata>; | |||
fileMutable.metadata = await getVideoMetadata(file.url); | |||
fileMutable.metadata = await getVideoMetadataFromUrl(file.url); | |||
return fileMutable as unknown as VideoFile; | |||
}; |