Skip to content

Commit

Permalink
thumbnailing data urls now
Browse files Browse the repository at this point in the history
  • Loading branch information
arietrouw committed Nov 1, 2023
1 parent d7de20f commit f37ec70
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@ import { isPayloadOfSchemaType, Payload } from '@xyo-network/payload-model'

import { ImageThumbnailSchema } from '../Schema'

export interface ImageThumbnailMime {
detected?: {
ext?: string
mime?: string
}
invalid?: boolean
returned?: string
type?: string
}

export type ImageThumbnail = Payload<
{
http?: {
code?: string
ipAddress?: string
status?: number
}
mime?: {
detected?: {
ext?: string
mime?: string
}
invalid?: boolean
returned?: string
type?: string
}
mime?: ImageThumbnailMime
sourceHash?: string
sourceUrl: string
url?: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ export type ImageThumbnailWitnessConfigSchema = typeof ImageThumbnailWitnessConf
export type ImageThumbnailEncoding = 'PNG' | 'JPG' | 'GIF'

export type ImageThumbnailWitnessConfig = WitnessConfig<{
dataUrlPassthrough?: boolean
encoding?: ImageThumbnailEncoding
height?: number
ipfsGateway?: string
maxAsyncProcesses?: number
maxCacheBytes?: number
maxCacheEntries?: number
quality?: number
runExclusive?: boolean
schema: ImageThumbnailWitnessConfigSchema
width?: number
}>
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { URL } from '@xylabs/url'
import { AbstractWitness } from '@xyo-network/abstract-witness'
import { axios, AxiosError, AxiosResponse } from '@xyo-network/axios'
import { PayloadHasher } from '@xyo-network/core'
import { ImageThumbnail, ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin'
import { ImageThumbnail, ImageThumbnailMime, ImageThumbnailSchema } from '@xyo-network/image-thumbnail-payload-plugin'
import { UrlPayload, UrlSchema } from '@xyo-network/url-payload-plugin'
import { Semaphore } from 'async-mutex'
import FileType from 'file-type'
Expand Down Expand Up @@ -123,8 +123,8 @@ export class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams =
throw Error('ImageMagick is required for this witness')
}
const urlPayloads = payloads.filter((payload) => payload.schema === UrlSchema)
return await this._semaphore.runExclusive(async () =>
compact(
const process = async () => {
return compact(
await Promise.all(
urlPayloads.map<Promise<ImageThumbnail>>(async ({ url }) => {
let result: ImageThumbnail
Expand All @@ -133,11 +133,18 @@ export class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams =
const dataBuffer = ImageThumbnailWitness.bufferFromDataUrl(url)

if (dataBuffer) {
result = {
schema: ImageThumbnailSchema,
sourceHash: await ImageThumbnailWitness.binaryToSha256(dataBuffer),
sourceUrl: url,
url,
if (this.config.dataUrlPassthrough) {
result = {
schema: ImageThumbnailSchema,
sourceHash: await ImageThumbnailWitness.binaryToSha256(dataBuffer),
sourceUrl: url,
url,
}
} else {
result = await this.processImage(dataBuffer, {
schema: ImageThumbnailSchema,
sourceUrl: url,
})
}
} else {
//if it is ipfs, go through cloud flair
Expand All @@ -147,8 +154,9 @@ export class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams =
return result
}),
),
),
)
)
}
return this.config.runExclusive ? await this._semaphore.runExclusive(() => process()) : process()
}

private async createThumbnailDataUrl(sourceBuffer: Buffer, encoding?: ImageThumbnailEncoding) {
Expand Down Expand Up @@ -236,80 +244,86 @@ export class ImageThumbnailWitness<TParams extends ImageThumbnailWitnessParams =

if (response.status >= 200 && response.status < 300) {
const contentType: string | undefined = response.headers['content-type']?.toString()
const [mediaType, fileType] = contentType?.split('/') ?? ['', '']
result.mime = result.mime ?? {}
result.mime.returned = mediaType
const sourceBuffer = Buffer.from(response.data, 'binary')

try {
result.mime.detected = await FileType.fromBuffer(sourceBuffer)
} catch (ex) {
const error = ex as Error
this.logger?.error(`FileType error: ${error.message}`)
}
return this.processImage(sourceBuffer, result, contentType)
}
return result
}

const processImage = async (encoding?: ImageThumbnailEncoding) => {
result.sourceHash = await ImageThumbnailWitness.binaryToSha256(sourceBuffer)
result.url = await this.createThumbnailDataUrl(sourceBuffer, encoding)
}
private async processImage(sourceBuffer: Buffer, imageThumbnail: ImageThumbnail, contentType?: string): Promise<ImageThumbnail> {
const [mediaType, fileType] = contentType?.split('/') ?? ['', '']
imageThumbnail.mime = imageThumbnail.mime ?? {}
imageThumbnail.mime.returned = mediaType

const processVideo = async () => {
// Gracefully handle the case where ffmpeg is not installed.
// eslint-disable-next-line import/no-named-as-default-member
if (hasbin.sync('ffmpeg')) {
result.sourceHash = await ImageThumbnailWitness.binaryToSha256(sourceBuffer)
result.url = await this.createThumbnailFromVideo(sourceBuffer)
} else {
result.mime = result.mime ?? {}
result.mime.invalid = true
}
}
try {
imageThumbnail.mime.detected = await FileType.fromBuffer(sourceBuffer)
} catch (ex) {
const error = ex as Error
this.logger?.error(`FileType error: ${error.message}`)
}

let encoding: ImageThumbnailEncoding = 'PNG'
const processImage = async (encoding?: ImageThumbnailEncoding) => {
imageThumbnail.sourceHash = await ImageThumbnailWitness.binaryToSha256(sourceBuffer)
imageThumbnail.url = await this.createThumbnailDataUrl(sourceBuffer, encoding)
}

switch (fileType.toUpperCase()) {
case 'GIF':
encoding = 'GIF'
break
case 'JPG':
case 'JPEG':
encoding = 'JPG'
break
const processVideo = async () => {
// Gracefully handle the case where ffmpeg is not installed.
// eslint-disable-next-line import/no-named-as-default-member
if (hasbin.sync('ffmpeg')) {
imageThumbnail.sourceHash = await ImageThumbnailWitness.binaryToSha256(sourceBuffer)
imageThumbnail.url = await this.createThumbnailFromVideo(sourceBuffer)
} else {
imageThumbnail.mime = imageThumbnail.mime ?? {}
imageThumbnail.mime.invalid = true
}
}

switch (mediaType) {
case 'image': {
await processImage(encoding)
result.mime.type = mediaType
break
}
case 'video': {
await processVideo()
result.mime.type = mediaType
break
}
default: {
const [detectedMediaType] = result.mime.detected?.mime?.split('/') ?? ['', '']
switch (detectedMediaType) {
case 'image': {
await processImage()
result.mime.type = result.mime.detected?.mime
break
}
case 'video': {
await processVideo()
result.mime.type = result.mime.detected?.mime
break
}
default: {
result.mime.invalid = true
break
}
let encoding: ImageThumbnailEncoding = 'PNG'

switch (fileType.toUpperCase()) {
case 'GIF':
encoding = 'GIF'
break
case 'JPG':
case 'JPEG':
encoding = 'JPG'
break
}

switch (mediaType) {
case 'image': {
await processImage(encoding)
imageThumbnail.mime.type = mediaType
break
}
case 'video': {
await processVideo()
imageThumbnail.mime.type = mediaType
break
}
default: {
const [detectedMediaType] = imageThumbnail.mime.detected?.mime?.split('/') ?? ['', '']
switch (detectedMediaType) {
case 'image': {
await processImage()
imageThumbnail.mime.type = imageThumbnail.mime.detected?.mime
break
}
case 'video': {
await processVideo()
imageThumbnail.mime.type = imageThumbnail.mime.detected?.mime
break
}
default: {
imageThumbnail.mime.invalid = true
break
}
break
}
break
}
}
return result
return imageThumbnail
}
}

0 comments on commit f37ec70

Please sign in to comment.