File size: 6,308 Bytes
7312c42 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | import { uuid } from '@/lib/uuid'
import { mediaSlotNumber } from '@/lib/media-url'
import type { MultiTrack, MultiTrackSegment, MultiTrackSourceType, MultiTrackTaskImage } from '@/types/multitrack'
export const MAX_TASK_IMAGES = 9
export const MULTIPLE_MEDIA_SEPARATOR = '|MULTIPLE|'
export function taskImagesFromContent(images: MultiTrackTaskImage[] | undefined): MultiTrackTaskImage[] {
return Array.isArray(images) ? images : []
}
export function activeTaskImages(images: MultiTrackTaskImage[] | undefined): MultiTrackTaskImage[] {
return taskImagesFromContent(images).filter((image) => image.muted !== true)
}
export function taskImageReferenceIndex(
images: MultiTrackTaskImage[],
imageId: string,
offset = 0,
): number | null {
const imageIndex = images.findIndex((image) => image.id === imageId)
if (imageIndex < 0 || images[imageIndex].muted === true) return null
return offset + images.slice(0, imageIndex + 1).filter((image) => image.muted !== true).length - 1
}
export function taskImageSlotNumber(image: MultiTrackTaskImage, fallbackIndex: number): number {
return mediaSlotNumber(image.slot_name, fallbackIndex)
}
export function taskImageIdentity(image: MultiTrackTaskImage): string {
const sourceType = image.source_type ?? 'input'
const path = sourceType === 'slot'
? image.slot_name
: image.file_path ?? image.local_path ?? image.url ?? image.file_name
return `${sourceType}:${path ?? image.id}`
}
export function canEnableSharedTaskImage(
segments: MultiTrackSegment[],
image: MultiTrackTaskImage,
): boolean {
const identity = taskImageIdentity(image)
return segments.every((segment) => {
const images = taskImagesFromContent(segment.content.images)
return images.some((item) => taskImageIdentity(item) === identity)
|| images.length < MAX_TASK_IMAGES
})
}
export function sharedTaskImageUpdates(
segments: MultiTrackSegment[],
image: MultiTrackTaskImage,
enabled: boolean,
): Array<{ segmentId: string; images: MultiTrackTaskImage[] }> {
const identity = taskImageIdentity(image)
return segments.map((segment) => {
const images = taskImagesFromContent(segment.content.images)
const matchingImage = images.find((item) => taskImageIdentity(item) === identity)
if (enabled) {
const sharedImage = matchingImage
? { ...matchingImage, shared_reference: true }
: {
...image,
id: uuid(),
shared_reference: true,
shared_reference_copy: true,
}
return {
segmentId: segment.id,
images: [
...images.filter((item) => item.shared_reference === true && taskImageIdentity(item) !== identity),
sharedImage,
...images.filter((item) => item.shared_reference !== true && taskImageIdentity(item) !== identity),
].slice(0, MAX_TASK_IMAGES),
}
}
return {
segmentId: segment.id,
images: images.flatMap((item) => {
if (taskImageIdentity(item) !== identity) return [item]
if (item.shared_reference_copy === true) return []
const localImage = { ...item, shared_reference: false }
delete localImage.shared_reference_copy
return [localImage]
}),
}
})
}
export function synchronizeSharedTaskImages(tracks: MultiTrack[]): MultiTrack[] {
const taskSegments = tracks
.filter((track) => track.type === 'task')
.flatMap((track) => track.segments)
const sharedImages = new Map<string, MultiTrackTaskImage>()
for (const segment of taskSegments) {
for (const image of taskImagesFromContent(segment.content.images)) {
if (image.shared_reference !== true) continue
const identity = taskImageIdentity(image)
if (!sharedImages.has(identity)) sharedImages.set(identity, image)
}
}
if (sharedImages.size === 0) return tracks
return tracks.map((track) => {
if (track.type !== 'task') return track
return {
...track,
segments: track.segments.map((segment) => {
const images = taskImagesFromContent(segment.content.images)
const common = Array.from(sharedImages.entries()).map(([identity, sharedImage]) => {
const existing = images.find((item) => taskImageIdentity(item) === identity)
return existing
? { ...existing, shared_reference: true }
: {
...sharedImage,
id: uuid(),
shared_reference: true,
shared_reference_copy: true,
}
})
const local = images.filter((item) => !sharedImages.has(taskImageIdentity(item)))
return {
...segment,
content: {
...segment.content,
images: [...common, ...local].slice(0, MAX_TASK_IMAGES),
},
}
}),
}
})
}
export function createTaskImage(filePath: string, source: MultiTrackSourceType): MultiTrackTaskImage {
const normalizedSource = filePath.startsWith('__slot__:') ? 'slot' : source
const slotName = normalizedSource === 'slot' ? filePath.replace(/^__slot__:/, '') : undefined
const fileName = slotName ?? filePath.split(/[\\/]/).pop() ?? filePath
return {
id: uuid(),
source_type: normalizedSource,
file_path: normalizedSource === 'input' || normalizedSource === 'output' ? filePath : undefined,
local_path: normalizedSource === 'local' ? filePath : undefined,
url: normalizedSource === 'url' ? filePath : undefined,
slot_name: slotName,
file_name: fileName,
}
}
export function splitSelectedTaskMedia(value: string): string[] {
return value.split(MULTIPLE_MEDIA_SEPARATOR).filter((item) => item.length > 0)
}
export async function uploadTaskImageFile(file: File): Promise<MultiTrackTaskImage> {
const formData = new FormData()
formData.append('image', file)
const response = await fetch('/upload/image', {
method: 'POST',
body: formData,
})
if (!response.ok) {
throw new Error(`Failed to upload image: ${file.name}`)
}
const result = await response.json() as { name?: string; subfolder?: string }
const name = result.name ?? file.name
const subfolder = result.subfolder ?? ''
return {
id: uuid(),
source_type: 'input',
file_path: subfolder ? `${subfolder}/${name}` : name,
file_name: name,
}
}
|