From aff8574356b876fad6041229bd057896893f842d Mon Sep 17 00:00:00 2001 From: Henry Hobbs Date: Mon, 20 May 2024 18:47:54 -0400 Subject: [PATCH] initial commit --- .gitignore | 1 + README.md | 11 +++++ card-search.js | 18 ++++++++ generate-card-captions.js | 91 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 card-search.js create mode 100644 generate-card-captions.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..7d14c30 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# MTG Visual Search Scripts + +This repository contains scripts to search for Magic: The Gathering cards by image contents. + +## generate-card-captions.js + +This script takes a JSON file from scryfall.com containing card data. It generates a JSON file containing card names, IDs, and captions for each card. The captions are generated using Azure Cognitive Services Computer Vision API. + +## search-cards.js + +This script takes the output of generate-card-captions.js and searches the card captions for keywords provided and outputs another JSON file containing the results. diff --git a/card-search.js b/card-search.js new file mode 100644 index 0000000..9caaa90 --- /dev/null +++ b/card-search.js @@ -0,0 +1,18 @@ +//TODO: Use args for search strings, input, and output files + +const fs = require("fs") +var cardArray = JSON.parse(fs.readFileSync("output.json", "utf8")) + +var searchStrings = ["chair", "throne", "seat"] + +var searchResults = cardArray.filter((card) => { + return ( + card.caption && + searchStrings.some((searchString) => + card.caption.toLowerCase().includes(searchString) + ) + ) +}) + +console.log(`Found ${searchResults.length} cards with search strings`) +fs.writeFileSync("search-results.json", JSON.stringify(searchResults), "utf8") diff --git a/generate-card-captions.js b/generate-card-captions.js new file mode 100644 index 0000000..101e73a --- /dev/null +++ b/generate-card-captions.js @@ -0,0 +1,91 @@ +//TODO: +// Error handling for bad args +// Error handling for read/write errors +// Retry on failed requests (toggle with flag?) + +const args = { + modelVersion: "latest", // -m --model-version + apiVersion: "2023-10-01", // -a --api-version + input: "unique-artwork-20240519090229.json", // -i --input + output: "output.json", // -o --output +} + +const cliArgs = process.argv.slice(2) +while (cliArgs.length) { + const arg = cliArgs.shift() + if (arg === "-m" || arg === "--model-version") { + args.modelVersion = cliArgs.shift() + continue + } + if (arg === "-a" || arg === "--api-version") { + args.apiVersion = cliArgs.shift() + continue + } + if (arg === "-i" || arg === "--input") { + args.input = cliArgs.shift() + continue + } + if (arg === "-o" || arg === "--output") { + args.output = cliArgs.shift() + } +} +console.log("Running with args: ", args) + +const fs = require("fs") +var cardArray = JSON.parse(fs.readFileSync(args.input, "utf8")) +console.log(`Read ${cardArray.length} cards from ${args.input}`) + +const largeImages = cardArray.filter((card) => card.image_uris?.large) +let simpleCardArray = largeImages.map((card) => { + return { + imageUrl: card.image_uris.large, + name: card.name, + id: card.id, + oracleId: card.oracle_id, + url: card.scryfall_uri, + caption: null, + } +}) +console.log(`Filtered ${simpleCardArray.length} cards with large images`) + +const getCaptionForImageUrl = async (url) => { + try { + const response = await fetch( + `${AZURE_VISION_RESOURCE_URL}/computervision/imageanalysis:analyze?features=denseCaptions&model-version=${modelVersion}&language=en&gender-neutral-caption=false&api-version=${apiVersion}`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + "Ocp-Apim-Subscription-Key": + process.env.AZURE_VISION_SUBSCRIPTION_KEY, + }, + body: JSON.stringify({ + url, + }), + } + ) + const data = await response.json() + return data.denseCaptionsResult.values + .map((caption) => caption.text) + .join("|") + } catch (error) { + console.error("Error fetching caption for card: ", url) + console.error(error) + return null + } +} + +const addCaptionsToCards = async (cards) => { + fs.writeFileSync(args.output, "[", "utf8") + for (const card of cards) { + const caption = await getCaptionForImageUrl(card.imageUrl) + console.log(`Card ${card.id} caption: ${caption}`) + card.caption = caption + fs.appendFileSync(args.output, JSON.stringify(card), "utf8") + fs.appendFileSync(args.output, ",\n", "utf8") + } + fs.appendFileSync(args.output, "]", "utf8") + return cards +} + +addCaptionsToCards(simpleCardArray)