import { DataSample } from '@things-factory/dataset' /** * Media URL Extractor * * Extracts media URLs (image, video, audio) from DataSample for Label Studio tasks */ export interface MediaUrls { [fieldName: string]: string | string[] } export interface AttachmentInfo { id: string mimetype: string name: string fullpath: string } /** * Extract all media URLs from a DataSample * Returns an object with field names as keys and URLs as values * * @param sample - DataSample containing media attachments * @param baseUrl - Base URL for converting relative paths to absolute URLs (e.g., 'https://tf.example.com') * @param mediaFields - Optional array of field names to extract. If not provided, extracts all fields. * @returns Object with media URLs keyed by field name */ export function extractMediaUrls( sample: DataSample, baseUrl: string, mediaFields?: string[] ): MediaUrls { const urls: MediaUrls = {} if (!sample.data || typeof sample.data !== 'object') { return urls } const fieldsToExtract = mediaFields || Object.keys(sample.data) for (const fieldName of fieldsToExtract) { const fieldValue = sample.data[fieldName] if (!fieldValue) continue // Handle array of attachments if (Array.isArray(fieldValue)) { const extractedUrls = extractUrlsFromAttachmentArray(fieldValue, baseUrl) if (extractedUrls.length > 0) { urls[fieldName] = extractedUrls.length === 1 ? extractedUrls[0] : extractedUrls } } // Handle single URL string else if (typeof fieldValue === 'string') { const url = normalizeUrl(fieldValue, baseUrl) if (url) { urls[fieldName] = url } } // Handle single attachment object else if (typeof fieldValue === 'object' && 'fullpath' in fieldValue) { const url = normalizeUrl(fieldValue.fullpath, baseUrl) if (url) { urls[fieldName] = url } } } return urls } /** * Extract URL from DataSample for a specific field * Handles both single values and arrays */ export function extractMediaUrl( sample: DataSample, fieldName: string, baseUrl: string ): string | string[] | null { if (!sample.data || typeof sample.data !== 'object') { return null } const fieldValue = sample.data[fieldName] if (!fieldValue) return null // Handle array of attachments if (Array.isArray(fieldValue)) { const urls = extractUrlsFromAttachmentArray(fieldValue, baseUrl) return urls.length > 0 ? (urls.length === 1 ? urls[0] : urls) : null } // Handle single URL string if (typeof fieldValue === 'string') { return normalizeUrl(fieldValue, baseUrl) } // Handle single attachment object if (typeof fieldValue === 'object' && 'fullpath' in fieldValue) { return normalizeUrl(fieldValue.fullpath, baseUrl) } return null } /** * Extract URLs from an array of attachments * Handles nested arrays created by create-data-sample.ts */ function extractUrlsFromAttachmentArray(arr: any[], baseUrl: string): string[] { const urls: string[] = [] for (const item of arr) { if (Array.isArray(item)) { // Nested array - recursively extract urls.push(...extractUrlsFromAttachmentArray(item, baseUrl)) } else if (item && typeof item === 'object' && 'fullpath' in item) { // Attachment object const url = normalizeUrl(item.fullpath, baseUrl) if (url) urls.push(url) } else if (typeof item === 'string') { // Direct URL string const url = normalizeUrl(item, baseUrl) if (url) urls.push(url) } } return urls } /** * Normalize URL - convert relative paths to absolute URLs * * @param path - Relative path (e.g., '/attachments/2024/01/image.jpg') or absolute URL * @param baseUrl - Base URL (e.g., 'https://tf.example.com') * @returns Absolute URL or null if invalid */ function normalizeUrl(path: string | undefined, baseUrl: string): string | null { if (!path) return null // Already absolute URL if (path.startsWith('http://') || path.startsWith('https://')) { return path } // Relative path - convert to absolute if (path.startsWith('/')) { const cleanBaseUrl = baseUrl.replace(/\/+$/, '') // Remove trailing slashes return `${cleanBaseUrl}${path}` } // Invalid format return null } /** * Build Label Studio task data from DataSample * Automatically extracts all media fields and converts to absolute URLs * * @param sample - DataSample with media attachments * @param baseUrl - Base URL for the Things-Factory server * @param mediaFields - Optional list of media field names to include * @returns Object ready for Label Studio task creation */ export function buildLabelStudioTaskData( sample: DataSample, baseUrl: string, mediaFields?: string[] ): Record { return extractMediaUrls(sample, baseUrl, mediaFields) } /** * Helper: Get base URL from context or environment * Falls back to environment variable or localhost */ export function getBaseUrl(context?: any): string { // Try from context (request) if (context?.state?.domain?.systemUrl) { return context.state.domain.systemUrl } // Try from environment if (process.env.THINGS_FACTORY_URL) { return process.env.THINGS_FACTORY_URL } // Fallback return 'http://localhost:3000' }