UNPKG

exif-ai

Version:

A powerful Node.js CLI and library that uses AI providers (OpenAI, Google Gemini, Anthropic Claude, Mistral, Ollama, Amazon Bedrock, Azure OpenAI, DeepInfra, Fireworks, XAI, OpenRouter, and more) to intelligently write image descriptions and tags to EXIF

130 lines 4.07 kB
import { objectFromEntries } from "ts-extras"; /** * Determines the appropriate comma separator based on the content */ function determineCommaSeparator(text) { if (text.includes(",")) { return ","; } if (text.includes(",")) { return ","; } return "\n"; } /** * Counts the number of spaces in a string */ function countSpaces(text) { return (text.match(/\s/g) ?? []).length; } function formatTags(tags) { if (typeof tags !== "string") { return tags ?? []; } // Split the text by newlines first to avoid regex backtracking issues const lines = tags.split(/[\n\r]+/); const numberedLines = lines.filter((line) => /^\d+/.test(line)); const hasMultipleNumberedLines = numberedLines.length > 1; if (hasMultipleNumberedLines) { return numberedLines.map((s) => { return s .replaceAll(/tag\d+/g, "") .replaceAll(/[[\].{}<>/*'"()。]/g, "") .replace(/^\d+/, "") // Remove leading digits .replaceAll(/[::]*/g, "") .trim(); }); } const cleanedTags = tags .replaceAll(/tag\d+/g, "") .replaceAll(/[[\].{}<>/*'"()。]/g, ""); // Determine separators const colonSeparator = cleanedTags.includes(":") ? ":" : ":"; const commaSeparator = determineCommaSeparator(cleanedTags); return (cleanedTags .split(colonSeparator) .at(-1) ?.split(commaSeparator) .map((s) => s .trim() .replaceAll(/\n$/g, "") .replaceAll(/^\d+\s+/g, "") .replaceAll(/[::]*/g, "")) .filter((s) => s.length > 0 && s !== "\n" && countSpaces(s) <= 1) ?? []); } /** * Fetches tags from the provider */ async function fetchTags({ module, buffer, model, prompt, providerArgs, path, file_id, provider, verbose, repeat = 0, }) { let tags = []; for (let index = 0; index < repeat + 1; index++) { try { const result = await module.getTags?.({ buffer, model, prompt, providerArgs, path, file_id, provider, // Pass the provider name to the AI SDK }); tags = formatTags(result); } catch (error) { if (verbose) console.error("Failed to get tags from provider:", error); } if (tags.length > 1) break; } return tags; } /** * Creates a record of tag entries */ function createTagsRecord(formatted, tagTags, existingTags) { if (formatted.length === 0) { return {}; } if (!existingTags) { return objectFromEntries(tagTags.map((key) => [key, formatted])); } return objectFromEntries(tagTags.map((key) => { const existingTag = existingTags[key]; let combinedTags; if (existingTag == null) { combinedTags = formatted; } else if (Array.isArray(existingTag)) { combinedTags = existingTag.concat(formatted); } else { combinedTags = formatted.concat(existingTag.trim()); } return [key, Array.from(new Set(combinedTags))]; })); } export async function getTags({ buffer, model, prompt, providerModule, providerArgs, verbose = false, tagTags, existingTags, additionalTags, path, file_id, repeat, provider, }) { // Get tags from provider let tags = []; if (providerModule && typeof providerModule === "object") { const module = providerModule; tags = await fetchTags({ module, buffer, model, prompt, providerArgs, path, file_id, provider, verbose, repeat, }); } const formatted = tags.concat(additionalTags ?? []); if (verbose) console.log("Tags are:", formatted); return createTagsRecord(formatted, tagTags, existingTags); } //# sourceMappingURL=tags.js.map