Updated layout and added README.md for each
This commit is contained in:
parent
8f217b4536
commit
1058f14404
23
Creation/README.md
Normal file
23
Creation/README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Creation
|
||||||
|
|
||||||
|
`edhrec_deck.py` Input Example
|
||||||
|
```
|
||||||
|
python edhrec_deck.py
|
||||||
|
Enter the commander card name: Flubs, The Fool
|
||||||
|
Fetching EDHREC data for Flubs, The Fool...
|
||||||
|
Extracting UUIDs and quantities from Archidekt section...
|
||||||
|
Fetching card names from Scryfall...
|
||||||
|
Deck list saved to flubs-the-fool_edh.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Example Moxfield Output:
|
||||||
|
```
|
||||||
|
1x Flubs, the Fool
|
||||||
|
1x Abundance
|
||||||
|
1x Amphibian Downpour
|
||||||
|
1x Ancient Grudge
|
||||||
|
1x Birgi, God of Storytelling // Harnfel, Horn of Bounty
|
||||||
|
*clipped for brevity*
|
||||||
|
1x Worldly Tutor
|
||||||
|
1x Yavimaya Coast
|
||||||
|
```
|
80
Creation/edhrec_deck.py
Normal file
80
Creation/edhrec_deck.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
def refactor_commander_name(name):
|
||||||
|
# Replace spaces, commas, and apostrophes with dashes, and convert to lowercase
|
||||||
|
name = name.replace(" ", "-").replace(",", "").replace("'", "")
|
||||||
|
return name.lower()
|
||||||
|
|
||||||
|
def fetch_edhrec_data(commander_name):
|
||||||
|
url = f"https://json.edhrec.com/pages/commanders/{commander_name}.json"
|
||||||
|
response = requests.get(url)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print(f"Failed to fetch data for {commander_name}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def fetch_card_name_from_scryfall(uuid):
|
||||||
|
scryfall_url = f"https://api.scryfall.com/cards/{uuid}"
|
||||||
|
response = requests.get(scryfall_url)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
data = response.json()
|
||||||
|
return data['name']
|
||||||
|
else:
|
||||||
|
print(f"Failed to fetch card name from Scryfall for UUID: {uuid}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_uuids_and_quantities_from_archidekt(edhrec_data):
|
||||||
|
card_info = []
|
||||||
|
|
||||||
|
if 'archidekt' in edhrec_data:
|
||||||
|
for idx, deck_section in enumerate(edhrec_data['archidekt']):
|
||||||
|
if isinstance(deck_section, dict) and 'u' in deck_section and 'q' in deck_section:
|
||||||
|
uuid = deck_section['u']
|
||||||
|
quantity = deck_section['q']
|
||||||
|
card_info.append((uuid, quantity))
|
||||||
|
else:
|
||||||
|
print("No Archidekt section found in the EDHREC data.")
|
||||||
|
|
||||||
|
return card_info
|
||||||
|
|
||||||
|
def fetch_card_names_from_uuids(card_info):
|
||||||
|
card_entries = []
|
||||||
|
for uuid, quantity in card_info:
|
||||||
|
card_name = fetch_card_name_from_scryfall(uuid)
|
||||||
|
if card_name:
|
||||||
|
card_entries.append(f"{quantity}x {card_name}")
|
||||||
|
return card_entries
|
||||||
|
|
||||||
|
def create_moxfield_deck_file(commander_name, card_entries):
|
||||||
|
filename = f"{commander_name}_edh.txt"
|
||||||
|
with open(filename, 'w') as file:
|
||||||
|
for entry in card_entries:
|
||||||
|
file.write(f"{entry}\n")
|
||||||
|
print(f"Deck list saved to {filename}")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
commander_name = input("Enter the commander card name: ")
|
||||||
|
refactored_name = refactor_commander_name(commander_name)
|
||||||
|
|
||||||
|
print(f"Fetching EDHREC data for {commander_name}...")
|
||||||
|
edhrec_data = fetch_edhrec_data(refactored_name)
|
||||||
|
|
||||||
|
if edhrec_data:
|
||||||
|
print(f"Extracting UUIDs and quantities from Archidekt section...")
|
||||||
|
card_info = extract_uuids_and_quantities_from_archidekt(edhrec_data)
|
||||||
|
|
||||||
|
if card_info:
|
||||||
|
print(f"Fetching card names from Scryfall...")
|
||||||
|
card_entries = fetch_card_names_from_uuids(card_info)
|
||||||
|
if card_entries:
|
||||||
|
create_moxfield_deck_file(refactored_name, card_entries)
|
||||||
|
else:
|
||||||
|
print("No card names found for the given UUIDs.")
|
||||||
|
else:
|
||||||
|
print("No UUIDs found in the Archidekt decks.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
89
Creation/flubs-the-fool_edh.txt
Normal file
89
Creation/flubs-the-fool_edh.txt
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
1x Flubs, the Fool
|
||||||
|
1x Abundance
|
||||||
|
1x Amphibian Downpour
|
||||||
|
1x Ancient Grudge
|
||||||
|
1x Anger
|
||||||
|
1x Arcane Signet
|
||||||
|
1x Avenger of Zendikar
|
||||||
|
1x Azusa, Lost but Seeking
|
||||||
|
1x Beast Within
|
||||||
|
1x Birds of Paradise
|
||||||
|
1x Birgi, God of Storytelling // Harnfel, Horn of Bounty
|
||||||
|
1x Blasphemous Act
|
||||||
|
1x Boseiju, Who Endures
|
||||||
|
1x Bountiful Landscape
|
||||||
|
1x Brain Freeze
|
||||||
|
1x Breeding Pool
|
||||||
|
1x Case of the Locked Hothouse
|
||||||
|
1x Chaos Warp
|
||||||
|
1x Command Tower
|
||||||
|
1x Conduit of Worlds
|
||||||
|
1x Conspiracy Theorist
|
||||||
|
1x Containment Construct
|
||||||
|
1x Courser of Kruphix
|
||||||
|
1x Crop Rotation
|
||||||
|
1x Crucible of Worlds
|
||||||
|
1x Cultivate
|
||||||
|
1x Currency Converter
|
||||||
|
1x Doc Aurlock, Grizzled Genius
|
||||||
|
1x Dreamroot Cascade
|
||||||
|
1x Druid Class
|
||||||
|
1x Dryad of the Ilysian Grove
|
||||||
|
1x Ensnaring Bridge
|
||||||
|
1x Eruth, Tormented Prophet
|
||||||
|
1x Eternal Witness
|
||||||
|
1x Exotic Orchard
|
||||||
|
1x Exploration
|
||||||
|
1x Explore
|
||||||
|
1x Faithless Looting
|
||||||
|
1x Farseek
|
||||||
|
1x Fblthp, Lost on the Range
|
||||||
|
8x Forest
|
||||||
|
1x Frontier Bivouac
|
||||||
|
1x Glint-Horn Buccaneer
|
||||||
|
1x Growth Spiral
|
||||||
|
1x Hinterland Harbor
|
||||||
|
1x Hugs, Grisly Guardian
|
||||||
|
1x Inti, Seneschal of the Sun
|
||||||
|
2x Island
|
||||||
|
1x Jeska's Will
|
||||||
|
1x Ketria Triome
|
||||||
|
1x Library of Leng
|
||||||
|
1x Life from the Loam
|
||||||
|
1x Lotus Cobra
|
||||||
|
1x Lotus Petal
|
||||||
|
1x Mishra's Bauble
|
||||||
|
1x Misty Rainforest
|
||||||
|
4x Mountain
|
||||||
|
1x Nature's Lore
|
||||||
|
1x Noxious Revival
|
||||||
|
1x Oracle of Mul Daya
|
||||||
|
1x Party Thrasher
|
||||||
|
1x Pongify
|
||||||
|
1x Ramunap Excavator
|
||||||
|
1x Rejuvenating Springs
|
||||||
|
1x Rootbound Crag
|
||||||
|
1x Scalding Tarn
|
||||||
|
1x Scute Swarm
|
||||||
|
1x Sensei's Divining Top
|
||||||
|
1x Shifting Woodland
|
||||||
|
1x Six
|
||||||
|
1x Sol Ring
|
||||||
|
1x Song of Creation
|
||||||
|
1x Spire Garden
|
||||||
|
1x Springheart Nantuko
|
||||||
|
1x Steam Vents
|
||||||
|
1x Stomping Ground
|
||||||
|
1x Sulfur Falls
|
||||||
|
1x Summer Bloom
|
||||||
|
1x Surly Badgersaur
|
||||||
|
1x Tatyova, Benthic Druid
|
||||||
|
1x Three Visits
|
||||||
|
1x Tireless Provisioner
|
||||||
|
1x Training Center
|
||||||
|
1x Underworld Breach
|
||||||
|
1x Valakut Exploration
|
||||||
|
1x Wayward Swordtooth
|
||||||
|
1x Wooded Foothills
|
||||||
|
1x Worldly Tutor
|
||||||
|
1x Yavimaya Coast
|
29
Imagery/README.md
Normal file
29
Imagery/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Creation
|
||||||
|
|
||||||
|
`art_image_dl.py` Output Example
|
||||||
|
|
||||||
|
![Flubs, The Fool Art Clip](artcrop_images/blc_Flubs--The-Fool_art_crop.jpg)
|
||||||
|
|
||||||
|
`image_dl.py` Output Example
|
||||||
|
|
||||||
|
![The Scorpion God](downloaded_images/sld_The-Scorpion-God.jpg)
|
||||||
|
|
||||||
|
`create_wallpaper.py` Input/Output examples. These are randomized.
|
||||||
|
|
||||||
|
```
|
||||||
|
Enter the directory containing JPG files: downloaded_images
|
||||||
|
Choose a screen size:
|
||||||
|
1: 1920x1080 (Full HD)
|
||||||
|
2: 2560x1440 (2K)
|
||||||
|
3: 3840x2160 (4K)
|
||||||
|
4: 1280x720 (HD)
|
||||||
|
5: 1600x900 (HD+)
|
||||||
|
Enter the number corresponding to your choice: 1
|
||||||
|
Enter the name of the output wallpaper file (e.g., wallpaper.jpg): wallpaper.jpg
|
||||||
|
Wallpaper saved as wallpaper.jpg
|
||||||
|
```
|
||||||
|
Full card output (will work to fix this)
|
||||||
|
![wallpaper.jpg](wallpaper.jpg)
|
||||||
|
|
||||||
|
Artcrop output
|
||||||
|
![artpaper.jpg](artpaper.jpg)
|
81
Imagery/art_image_dl.py
Normal file
81
Imagery/art_image_dl.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import requests
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Function to fetch card details from Scryfall using the set code and collector number
|
||||||
|
def fetch_card_images_by_set_and_number(set_code, collector_number):
|
||||||
|
url = f"https://api.scryfall.com/cards/{set_code}/{collector_number}"
|
||||||
|
response = requests.get(url)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
card_data = response.json()
|
||||||
|
images = []
|
||||||
|
|
||||||
|
# Handle single-sided or multi-sided cards
|
||||||
|
if 'image_uris' in card_data:
|
||||||
|
images.append((card_data['image_uris']['art_crop'], card_data['name']))
|
||||||
|
if 'card_faces' in card_data:
|
||||||
|
for face in card_data['card_faces']:
|
||||||
|
if 'image_uris' in face:
|
||||||
|
images.append((face['image_uris']['art_crop'], face['name']))
|
||||||
|
return images
|
||||||
|
else:
|
||||||
|
print(f"Error fetching card with set code {set_code} and collector number {collector_number}: {response.status_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Function to download and save an image
|
||||||
|
def download_image(image_url, save_path):
|
||||||
|
response = requests.get(image_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(save_path, 'wb') as file:
|
||||||
|
file.write(response.content)
|
||||||
|
else:
|
||||||
|
print(f"Failed to download image from {image_url}")
|
||||||
|
|
||||||
|
# Function to process the CSV and download images
|
||||||
|
def download_images_from_csv(csv_path, output_folder):
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
if not os.path.exists(output_folder):
|
||||||
|
os.makedirs(output_folder)
|
||||||
|
|
||||||
|
# Read the CSV file
|
||||||
|
df = pd.read_csv(csv_path)
|
||||||
|
|
||||||
|
# Assuming the CSV has columns 'Card Name', 'Set Code', and 'Collector Number'
|
||||||
|
for index, row in df.iterrows():
|
||||||
|
card_name = row['Card Name']
|
||||||
|
set_code = row['Set Code']
|
||||||
|
collector_number = row['Collector Number']
|
||||||
|
|
||||||
|
# Naming convention: set_code_card_name_face_name.jpg
|
||||||
|
image_base_name = f"{set_code}_{card_name.replace('/', '-').replace(':', '').replace(' ', '-').replace(',', '-')}"
|
||||||
|
|
||||||
|
print(f"Processing {card_name} from set {set_code} with collector number {collector_number}...")
|
||||||
|
|
||||||
|
images = fetch_card_images_by_set_and_number(set_code, collector_number)
|
||||||
|
if images:
|
||||||
|
for image_url, face_name in images:
|
||||||
|
# Sanitize file name by removing invalid characters
|
||||||
|
image_file_name = f"{image_base_name}_art_crop.jpg"
|
||||||
|
save_path = os.path.join(output_folder, image_file_name)
|
||||||
|
|
||||||
|
# Skip downloading if the image already exists
|
||||||
|
if os.path.exists(save_path):
|
||||||
|
print(f"Image already exists for {card_name} ({face_name}), skipping download.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
download_image(image_url, save_path)
|
||||||
|
time.sleep(0.1) # Sleep to avoid hitting API rate limits
|
||||||
|
else:
|
||||||
|
print(f"No images found for {card_name} from set {set_code} with collector number {collector_number}")
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Define the path to the input CSV and output folder
|
||||||
|
csv_path = 'cards.csv' # Replace with your CSV file path
|
||||||
|
output_folder = 'artcrop_images'
|
||||||
|
|
||||||
|
# Start the download process
|
||||||
|
download_images_from_csv(csv_path, output_folder)
|
||||||
|
print("Download completed.")
|
BIN
Imagery/artcrop_images/blc_Flubs--The-Fool_art_crop.jpg
Normal file
BIN
Imagery/artcrop_images/blc_Flubs--The-Fool_art_crop.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 69 KiB |
BIN
Imagery/artpaper.jpg
Normal file
BIN
Imagery/artpaper.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 474 KiB |
48
Imagery/cards.csv
Normal file
48
Imagery/cards.csv
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
Card Name,Set Code,Collector Number,Quantity,Foil
|
||||||
|
"Flubs, The Fool",blc,356,1,True
|
||||||
|
"Flubs, The Fool",blc,356,1,False
|
||||||
|
Skemfar Shadowsage,sld,759,3,True
|
||||||
|
Pollenbright Druid,sld,776,1,True
|
||||||
|
Timberwatch Elf,sld,780,2,True
|
||||||
|
Frilled Mystic,sld,786,1,True
|
||||||
|
Coveted Jewel,sld,799,1,False
|
||||||
|
The Scorpion God,sld,904,1,True
|
||||||
|
"Linda, Kandarian Queen",sld,1355,1,True
|
||||||
|
Felidar Guardian,sld,1487,1,False
|
||||||
|
Peregrine Drake,sld,1488,1,False
|
||||||
|
Serpent of Yawning Depths,sld,1489,1,False
|
||||||
|
Scourage of Valkas,sld,1490,1,False
|
||||||
|
Voracious Hydra,sld,1491,1,False
|
||||||
|
Wall of Omens,sld,1518,1,False
|
||||||
|
Circular Logic,sld,1519,1,False
|
||||||
|
Scheming Symmentry,sld,1520,1,False
|
||||||
|
Price of Progress,sld,1521,1,False
|
||||||
|
Eternal Witness,sld,1522,1,False
|
||||||
|
Sakashima of a Thousand Faces,sld,1541,1,False
|
||||||
|
"Yargle, Glutton of Urborg",sld,1542,1,False
|
||||||
|
"Krark, The Thumbless",sld,1543,1,False
|
||||||
|
"Adrix and Nev, Twincasters",sld,1544,1,False
|
||||||
|
Arcance Denial,sld,1545,1,False
|
||||||
|
Nightscape Familiar,sld,1546,1,False
|
||||||
|
Rain of Filth,sld,1547,1,False
|
||||||
|
Simian Spirit Guide,sld,1548,1,False
|
||||||
|
Prince of Thralls,sld,1549,1,False
|
||||||
|
"Gonti, Lord of Luxury",sld,1566,1,False
|
||||||
|
"Vilis, Broker of Blood",sld,1567,1,False
|
||||||
|
"Anowon, the Ruin Thief",sld,1568,1,False
|
||||||
|
"Grenzo, Dungeon Warden",sld,1569,1,False
|
||||||
|
Reconnasissance,sld,1575,1,False
|
||||||
|
"Jace, Wielder of Mysteries",sld,1576,1,False
|
||||||
|
Black Market,sld,1577,1,False
|
||||||
|
Dire Undercurrents,sld,1578,1,False
|
||||||
|
"Obeka, Brute Chronolgoist",sld,1579,1,False
|
||||||
|
Sorin Markov,sld,1698,1,False
|
||||||
|
Sorin Markov,sld,1698,1,True
|
||||||
|
"Huatli, Radiant Champion",sld,1699,1,False
|
||||||
|
"Huatli, Radiant Champion",sld,1699,1,True
|
||||||
|
"Kiora, Behemoth Beckoner",sld,1700,1,False
|
||||||
|
"Kiora, Behemoth Beckoner",sld,1700,1,True
|
||||||
|
"Tezzeret, Master of the Bridge",sld,1701,1,False
|
||||||
|
"Tezzeret, Master of the Bridge",sld,1701,1,True
|
||||||
|
"Vraska, Golgari Queen",sld,1702,1,False
|
||||||
|
"Vraska, Golgari Queen",sld,1702,1,True
|
|
102
Imagery/create_wallpaper.py
Normal file
102
Imagery/create_wallpaper.py
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import os
|
||||||
|
import random
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Function to load, resize, and crop JPG images from a directory
|
||||||
|
def load_resize_and_crop_images(directory, target_size):
|
||||||
|
images = []
|
||||||
|
for filename in os.listdir(directory):
|
||||||
|
if filename.lower().endswith((".jpg", ".jpeg")):
|
||||||
|
try:
|
||||||
|
img = Image.open(os.path.join(directory, filename))
|
||||||
|
|
||||||
|
# Calculate the aspect ratios for scaling
|
||||||
|
img_aspect = img.width / img.height
|
||||||
|
target_aspect = target_size[0] / target_size[1]
|
||||||
|
|
||||||
|
if img_aspect > target_aspect:
|
||||||
|
# Image is wider than the target: scale height and crop width
|
||||||
|
img = img.resize((int(img_aspect * target_size[1]), target_size[1]), Image.Resampling.LANCZOS)
|
||||||
|
left = (img.width - target_size[0]) / 2
|
||||||
|
img = img.crop((left, 0, left + target_size[0], target_size[1]))
|
||||||
|
else:
|
||||||
|
# Image is taller than the target: scale width and crop height
|
||||||
|
img = img.resize((target_size[0], int(target_size[0] / img_aspect)), Image.Resampling.LANCZOS)
|
||||||
|
top = (img.height - target_size[1]) / 2
|
||||||
|
img = img.crop((0, top, target_size[0], top + target_size[1]))
|
||||||
|
|
||||||
|
images.append(img)
|
||||||
|
except IOError:
|
||||||
|
print(f"Warning: Unable to open image {filename}. It may be corrupted or not a valid image file.")
|
||||||
|
return images
|
||||||
|
|
||||||
|
# Function to create a wallpaper from images
|
||||||
|
def create_wallpaper(images, screen_size, output_file):
|
||||||
|
wallpaper = Image.new('RGB', screen_size)
|
||||||
|
|
||||||
|
random.shuffle(images) # Randomize the order of images
|
||||||
|
|
||||||
|
x_offset = 0
|
||||||
|
y_offset = 0
|
||||||
|
|
||||||
|
for img in images:
|
||||||
|
img_width, img_height = img.size
|
||||||
|
|
||||||
|
if x_offset + img_width > screen_size[0]: # Move to next row if needed
|
||||||
|
x_offset = 0
|
||||||
|
y_offset += img_height
|
||||||
|
|
||||||
|
if y_offset + img_height > screen_size[1]: # Stop if out of vertical space
|
||||||
|
break
|
||||||
|
|
||||||
|
wallpaper.paste(img, (x_offset, y_offset))
|
||||||
|
x_offset += img_width
|
||||||
|
|
||||||
|
wallpaper.save(output_file, format='JPEG')
|
||||||
|
print(f"Wallpaper saved as {output_file}")
|
||||||
|
|
||||||
|
# Function to prompt user for screen size
|
||||||
|
def get_screen_size():
|
||||||
|
sizes = {
|
||||||
|
"1": (1920, 1080),
|
||||||
|
"2": (2560, 1440),
|
||||||
|
"3": (3840, 2160),
|
||||||
|
"4": (1280, 720),
|
||||||
|
"5": (1600, 900)
|
||||||
|
}
|
||||||
|
|
||||||
|
print("Choose a screen size:")
|
||||||
|
print("1: 1920x1080 (Full HD)")
|
||||||
|
print("2: 2560x1440 (2K)")
|
||||||
|
print("3: 3840x2160 (4K)")
|
||||||
|
print("4: 1280x720 (HD)")
|
||||||
|
print("5: 1600x900 (HD+)")
|
||||||
|
|
||||||
|
choice = input("Enter the number corresponding to your choice: ")
|
||||||
|
|
||||||
|
return sizes.get(choice, (1920, 1080)) # Default to 1920x1080 if invalid choice
|
||||||
|
|
||||||
|
def main():
|
||||||
|
directory = input("Enter the directory containing JPG files: ")
|
||||||
|
screen_size = get_screen_size()
|
||||||
|
|
||||||
|
# Determine the target size for each image based on the screen size
|
||||||
|
# Here, we assume a grid of 5 images across and as many rows as needed
|
||||||
|
target_size = (screen_size[0] // 5, screen_size[1] // 5)
|
||||||
|
|
||||||
|
output_file = input("Enter the name of the output wallpaper file (e.g., wallpaper.jpg): ")
|
||||||
|
|
||||||
|
# Ensure the output file has a .jpg extension
|
||||||
|
if not output_file.lower().endswith('.jpg'):
|
||||||
|
output_file += '.jpg'
|
||||||
|
|
||||||
|
images = load_resize_and_crop_images(directory, target_size)
|
||||||
|
|
||||||
|
if not images:
|
||||||
|
print("No JPG files found in the directory.")
|
||||||
|
return
|
||||||
|
|
||||||
|
create_wallpaper(images, screen_size, output_file)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
BIN
Imagery/downloaded_images/sld_The-Scorpion-God.jpg
Normal file
BIN
Imagery/downloaded_images/sld_The-Scorpion-God.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 120 KiB |
81
Imagery/image_dl.py
Normal file
81
Imagery/image_dl.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import requests
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Function to fetch card details from Scryfall using the set code and collector number
|
||||||
|
def fetch_card_images_by_set_and_number(set_code, collector_number):
|
||||||
|
url = f"https://api.scryfall.com/cards/{set_code}/{collector_number}"
|
||||||
|
response = requests.get(url)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
card_data = response.json()
|
||||||
|
images = []
|
||||||
|
|
||||||
|
# Handle single-sided or multi-sided cards
|
||||||
|
if 'image_uris' in card_data:
|
||||||
|
images.append((card_data['image_uris']['normal'], card_data['name']))
|
||||||
|
if 'card_faces' in card_data:
|
||||||
|
for face in card_data['card_faces']:
|
||||||
|
if 'image_uris' in face:
|
||||||
|
images.append((face['image_uris']['normal'], face['name']))
|
||||||
|
return images
|
||||||
|
else:
|
||||||
|
print(f"Error fetching card with set code {set_code} and collector number {collector_number}: {response.status_code}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Function to download and save an image
|
||||||
|
def download_image(image_url, save_path):
|
||||||
|
response = requests.get(image_url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
with open(save_path, 'wb') as file:
|
||||||
|
file.write(response.content)
|
||||||
|
else:
|
||||||
|
print(f"Failed to download image from {image_url}")
|
||||||
|
|
||||||
|
# Function to process the CSV and download images
|
||||||
|
def download_images_from_csv(csv_path, output_folder):
|
||||||
|
# Create output directory if it doesn't exist
|
||||||
|
if not os.path.exists(output_folder):
|
||||||
|
os.makedirs(output_folder)
|
||||||
|
|
||||||
|
# Read the CSV file
|
||||||
|
df = pd.read_csv(csv_path)
|
||||||
|
|
||||||
|
# Assuming the CSV has columns 'Card Name', 'Set Code', and 'Collector Number'
|
||||||
|
for index, row in df.iterrows():
|
||||||
|
card_name = row['Card Name']
|
||||||
|
set_code = row['Set Code']
|
||||||
|
collector_number = row['Collector Number']
|
||||||
|
|
||||||
|
# Naming convention: set_code_card_name_face_name.jpg
|
||||||
|
image_base_name = f"{set_code}_{card_name.replace('/', '-').replace(':', '').replace(' ', '-').replace(',', '-')}"
|
||||||
|
|
||||||
|
print(f"Processing {card_name} from set {set_code} with collector number {collector_number}...")
|
||||||
|
|
||||||
|
images = fetch_card_images_by_set_and_number(set_code, collector_number)
|
||||||
|
if images:
|
||||||
|
for image_url, face_name in images:
|
||||||
|
# Sanitize file name by removing invalid characters
|
||||||
|
image_file_name = f"{image_base_name}.jpg"
|
||||||
|
save_path = os.path.join(output_folder, image_file_name)
|
||||||
|
|
||||||
|
# Skip downloading if the image already exists
|
||||||
|
if os.path.exists(save_path):
|
||||||
|
print(f"Image already exists for {card_name} ({face_name}), skipping download.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
download_image(image_url, save_path)
|
||||||
|
time.sleep(0.2) # Sleep to avoid hitting API rate limits
|
||||||
|
else:
|
||||||
|
print(f"No images found for {card_name} from set {set_code} with collector number {collector_number}")
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Define the path to the input CSV and output folder
|
||||||
|
csv_path = 'cards.csv' # Replace with your CSV file path
|
||||||
|
output_folder = 'downloaded_images'
|
||||||
|
|
||||||
|
# Start the download process
|
||||||
|
download_images_from_csv(csv_path, output_folder)
|
||||||
|
print("Download complete.")
|
96
Imagery/interactive_cards_with_dl.py
Normal file
96
Imagery/interactive_cards_with_dl.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
|
# Function to append a card entry to the CSV file
|
||||||
|
def add_card_to_csv(csv_path, card_name, set_code, collector_number, quantity, foil):
|
||||||
|
file_exists = os.path.isfile(csv_path)
|
||||||
|
|
||||||
|
with open(csv_path, mode='a', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
|
||||||
|
# Write header if the file doesn't exist
|
||||||
|
if not file_exists:
|
||||||
|
writer.writerow(['Card Name', 'Set Code', 'Collector Number', 'Quantity', 'Foil'])
|
||||||
|
|
||||||
|
# Append the card data
|
||||||
|
writer.writerow([card_name, set_code, collector_number, quantity, foil])
|
||||||
|
|
||||||
|
print(f"Added {quantity}x {card_name} from set {set_code} with collector number {collector_number} (Foil: {foil}) to {csv_path}.")
|
||||||
|
|
||||||
|
# Function to download the full card image
|
||||||
|
def download_full_card_image(set_code, collector_number, card_name, foil):
|
||||||
|
url = f"https://api.scryfall.com/cards/{set_code}/{collector_number}"
|
||||||
|
response = requests.get(url)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
card_data = response.json()
|
||||||
|
|
||||||
|
# Determine the image URL (normal)
|
||||||
|
image_url = card_data['image_uris']['normal'] if 'image_uris' in card_data else None
|
||||||
|
if not image_url and 'card_faces' in card_data: # Check for double-sided cards
|
||||||
|
image_url = card_data['card_faces'][0]['image_uris']['normal']
|
||||||
|
|
||||||
|
if image_url:
|
||||||
|
# Create the full_card directory if it doesn't exist
|
||||||
|
directory = 'full_card'
|
||||||
|
if not os.path.exists(directory):
|
||||||
|
os.makedirs(directory)
|
||||||
|
|
||||||
|
# Create a filename for the image
|
||||||
|
foil_text = "_foil" if foil else ""
|
||||||
|
file_name = f"{card_name.replace('/', '-').replace(':', '').replace(' ', '_')}_{set_code}_{collector_number}{foil_text}.jpg"
|
||||||
|
file_path = os.path.join(directory, file_name)
|
||||||
|
|
||||||
|
# Download and save the image
|
||||||
|
image_response = requests.get(image_url)
|
||||||
|
with open(file_path, 'wb') as file:
|
||||||
|
file.write(image_response.content)
|
||||||
|
|
||||||
|
print(f"Downloaded full card image for {card_name} (Set: {set_code}, Collector Number: {collector_number}, Foil: {foil})")
|
||||||
|
else:
|
||||||
|
print(f"No full card image found for {card_name} (Set: {set_code}, Collector Number: {collector_number})")
|
||||||
|
else:
|
||||||
|
print(f"Failed to fetch data for {card_name} (Set: {set_code}, Collector Number: {collector_number}) from Scryfall.")
|
||||||
|
|
||||||
|
# Function to sort the CSV file by Set Code and Collector Number
|
||||||
|
def sort_csv_file(csv_path):
|
||||||
|
with open(csv_path, mode='r') as file:
|
||||||
|
reader = csv.reader(file)
|
||||||
|
header = next(reader) # Read the header
|
||||||
|
sorted_rows = sorted(reader, key=lambda row: (row[1], int(row[2]))) # Sort by Set Code (row[1]) and Collector Number (row[2]))
|
||||||
|
|
||||||
|
# Write the sorted data back to the CSV
|
||||||
|
with open(csv_path, mode='w', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerow(header) # Write the header back
|
||||||
|
writer.writerows(sorted_rows) # Write the sorted rows
|
||||||
|
|
||||||
|
print(f"CSV file sorted by Set Code and Collector Number.")
|
||||||
|
|
||||||
|
# Function to get user input and add multiple cards interactively
|
||||||
|
def interactive_card_entry(csv_path):
|
||||||
|
while True:
|
||||||
|
# Get the card details from the user
|
||||||
|
card_name = input("Enter the card name (or type 'exit' to quit): ").strip()
|
||||||
|
if card_name.lower() == 'exit':
|
||||||
|
print("Exiting the program.")
|
||||||
|
break
|
||||||
|
|
||||||
|
set_code = input("Enter the set code: ").strip()
|
||||||
|
collector_number = input("Enter the collector number: ").strip()
|
||||||
|
quantity = int(input("Enter the quantity: ").strip())
|
||||||
|
foil = input("Is this card a foil? (yes/no): ").strip().lower() == 'yes'
|
||||||
|
|
||||||
|
# Add the card to the CSV file
|
||||||
|
add_card_to_csv(csv_path, card_name, set_code, collector_number, quantity, foil)
|
||||||
|
|
||||||
|
# Download the full card image
|
||||||
|
download_full_card_image(set_code, collector_number, card_name, foil)
|
||||||
|
|
||||||
|
# Sort the CSV file after each entry
|
||||||
|
sort_csv_file(csv_path)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
csv_path = input("Enter the path to the CSV file: ").strip()
|
||||||
|
interactive_card_entry(csv_path)
|
BIN
Imagery/wallpaper.jpg
Normal file
BIN
Imagery/wallpaper.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 521 KiB |
29
Inventory/README.md
Normal file
29
Inventory/README.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Inventory
|
||||||
|
|
||||||
|
`cli_cards.py` Help Section
|
||||||
|
|
||||||
|
```
|
||||||
|
Add one or more Magic: The Gathering cards to a CSV file.
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
csv_path The path to the CSV file where the card data will be stored. If the file does not exist, it will be created.
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--card Card_Name Set_Code Collector_Number Quantity Foil
|
||||||
|
Add a card to the CSV file. Each card entry should be specified in the following format: Card_Name Set_Code Collector_Number Quantity Foil. Foil should be 'True' or 'False' indicating if the card is foil or not.
|
||||||
|
This option can be used multiple times to add multiple cards in one command.
|
||||||
|
```
|
||||||
|
|
||||||
|
`interactive_cards.py` Input Example
|
||||||
|
```
|
||||||
|
Enter the path to the CSV file: cards.csv
|
||||||
|
Enter the card name (or type 'exit' to quit): Evercoat Ursine
|
||||||
|
Enter the set code: blc
|
||||||
|
Enter the collector number: 64
|
||||||
|
Enter the quantity: 1
|
||||||
|
Is this card a foil? (yes/no): no
|
||||||
|
Added 1x Evercoat Ursine from set blc with collector number 64 (Foil: False) to cards.csv.
|
||||||
|
CSV file sorted by Set Code and Collector Number.
|
||||||
|
Enter the card name (or type 'exit' to quit):
|
||||||
|
```
|
49
Inventory/cards.csv
Normal file
49
Inventory/cards.csv
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
Card Name,Set Code,Collector Number,Quantity,Foil
|
||||||
|
Evercoat Ursine,blc,64,1,False
|
||||||
|
"Flubs, The Fool",blc,356,1,True
|
||||||
|
"Flubs, The Fool",blc,356,1,False
|
||||||
|
Skemfar Shadowsage,sld,759,3,True
|
||||||
|
Pollenbright Druid,sld,776,1,True
|
||||||
|
Timberwatch Elf,sld,780,2,True
|
||||||
|
Frilled Mystic,sld,786,1,True
|
||||||
|
Coveted Jewel,sld,799,1,False
|
||||||
|
The Scorpion God,sld,904,1,True
|
||||||
|
"Linda, Kandarian Queen",sld,1355,1,True
|
||||||
|
Felidar Guardian,sld,1487,1,False
|
||||||
|
Peregrine Drake,sld,1488,1,False
|
||||||
|
Serpent of Yawning Depths,sld,1489,1,False
|
||||||
|
Scourage of Valkas,sld,1490,1,False
|
||||||
|
Voracious Hydra,sld,1491,1,False
|
||||||
|
Wall of Omens,sld,1518,1,False
|
||||||
|
Circular Logic,sld,1519,1,False
|
||||||
|
Scheming Symmentry,sld,1520,1,False
|
||||||
|
Price of Progress,sld,1521,1,False
|
||||||
|
Eternal Witness,sld,1522,1,False
|
||||||
|
Sakashima of a Thousand Faces,sld,1541,1,False
|
||||||
|
"Yargle, Glutton of Urborg",sld,1542,1,False
|
||||||
|
"Krark, The Thumbless",sld,1543,1,False
|
||||||
|
"Adrix and Nev, Twincasters",sld,1544,1,False
|
||||||
|
Arcance Denial,sld,1545,1,False
|
||||||
|
Nightscape Familiar,sld,1546,1,False
|
||||||
|
Rain of Filth,sld,1547,1,False
|
||||||
|
Simian Spirit Guide,sld,1548,1,False
|
||||||
|
Prince of Thralls,sld,1549,1,False
|
||||||
|
"Gonti, Lord of Luxury",sld,1566,1,False
|
||||||
|
"Vilis, Broker of Blood",sld,1567,1,False
|
||||||
|
"Anowon, the Ruin Thief",sld,1568,1,False
|
||||||
|
"Grenzo, Dungeon Warden",sld,1569,1,False
|
||||||
|
Reconnasissance,sld,1575,1,False
|
||||||
|
"Jace, Wielder of Mysteries",sld,1576,1,False
|
||||||
|
Black Market,sld,1577,1,False
|
||||||
|
Dire Undercurrents,sld,1578,1,False
|
||||||
|
"Obeka, Brute Chronolgoist",sld,1579,1,False
|
||||||
|
Sorin Markov,sld,1698,1,False
|
||||||
|
Sorin Markov,sld,1698,1,True
|
||||||
|
"Huatli, Radiant Champion",sld,1699,1,False
|
||||||
|
"Huatli, Radiant Champion",sld,1699,1,True
|
||||||
|
"Kiora, Behemoth Beckoner",sld,1700,1,False
|
||||||
|
"Kiora, Behemoth Beckoner",sld,1700,1,True
|
||||||
|
"Tezzeret, Master of the Bridge",sld,1701,1,False
|
||||||
|
"Tezzeret, Master of the Bridge",sld,1701,1,True
|
||||||
|
"Vraska, Golgari Queen",sld,1702,1,False
|
||||||
|
"Vraska, Golgari Queen",sld,1702,1,True
|
|
60
Inventory/cli_cards.py
Normal file
60
Inventory/cli_cards.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import csv
|
||||||
|
import os
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
# Function to append a card entry to the CSV file
|
||||||
|
def add_card_to_csv(csv_path, card_name, set_code, collector_number, quantity, foil):
|
||||||
|
file_exists = os.path.isfile(csv_path)
|
||||||
|
|
||||||
|
with open(csv_path, mode='a', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
|
||||||
|
# Write header if the file doesn't exist
|
||||||
|
if not file_exists:
|
||||||
|
writer.writerow(['Card Name', 'Set Code', 'Collector Number', 'Quantity', 'Foil'])
|
||||||
|
|
||||||
|
# Append the card data
|
||||||
|
writer.writerow([card_name, set_code, collector_number, quantity, foil])
|
||||||
|
|
||||||
|
print(f"Added {quantity}x {card_name} from set {set_code} with collector number {collector_number} (Foil: {foil}) to {csv_path}.")
|
||||||
|
|
||||||
|
# Function to parse and add multiple cards
|
||||||
|
def add_multiple_cards(csv_path, card_entries):
|
||||||
|
for entry in card_entries:
|
||||||
|
card_name, set_code, collector_number, quantity, foil = entry
|
||||||
|
add_card_to_csv(csv_path, card_name, set_code, collector_number, quantity, foil)
|
||||||
|
|
||||||
|
# Main function to handle command-line arguments
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Add one or more Magic: The Gathering cards to a CSV file.")
|
||||||
|
|
||||||
|
parser.add_argument('csv_path',
|
||||||
|
help="The path to the CSV file where the card data will be stored. If the file does not exist, it will be created.")
|
||||||
|
|
||||||
|
# Allow multiple --card entries
|
||||||
|
parser.add_argument('--card',
|
||||||
|
action='append',
|
||||||
|
nargs=5,
|
||||||
|
metavar=('Card_Name', 'Set_Code', 'Collector_Number', 'Quantity', 'Foil'),
|
||||||
|
help=("Add a card to the CSV file. "
|
||||||
|
"Each card entry should be specified in the following format: "
|
||||||
|
"Card_Name Set_Code Collector_Number Quantity Foil. "
|
||||||
|
"Foil should be 'True' or 'False' indicating if the card is foil or not. "
|
||||||
|
"This option can be used multiple times to add multiple cards in one command. "))
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.card:
|
||||||
|
card_entries = []
|
||||||
|
for card in args.card:
|
||||||
|
card_name, set_code, collector_number, quantity, foil = card
|
||||||
|
quantity = int(quantity)
|
||||||
|
foil = foil.lower() == 'true' # Convert the foil input to a boolean
|
||||||
|
card_entries.append((card_name, set_code, collector_number, quantity, foil))
|
||||||
|
|
||||||
|
add_multiple_cards(args.csv_path, card_entries)
|
||||||
|
else:
|
||||||
|
print("No cards provided. Use the --card option to add cards.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
57
Inventory/interactive_cards.py
Normal file
57
Inventory/interactive_cards.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import csv
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Function to append a card entry to the CSV file
|
||||||
|
def add_card_to_csv(csv_path, card_name, set_code, collector_number, quantity, foil):
|
||||||
|
file_exists = os.path.isfile(csv_path)
|
||||||
|
|
||||||
|
with open(csv_path, mode='a', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
|
||||||
|
# Write header if the file doesn't exist
|
||||||
|
if not file_exists:
|
||||||
|
writer.writerow(['Card Name', 'Set Code', 'Collector Number', 'Quantity', 'Foil'])
|
||||||
|
|
||||||
|
# Append the card data
|
||||||
|
writer.writerow([card_name, set_code, collector_number, quantity, foil])
|
||||||
|
|
||||||
|
print(f"Added {quantity}x {card_name} from set {set_code} with collector number {collector_number} (Foil: {foil}) to {csv_path}.")
|
||||||
|
|
||||||
|
# Function to sort the CSV file by Set Code and Collector Number
|
||||||
|
def sort_csv_file(csv_path):
|
||||||
|
with open(csv_path, mode='r') as file:
|
||||||
|
reader = csv.reader(file)
|
||||||
|
header = next(reader) # Read the header
|
||||||
|
sorted_rows = sorted(reader, key=lambda row: (row[1], int(row[2]))) # Sort by Set Code (row[1]) and Collector Number (row[2])
|
||||||
|
|
||||||
|
# Write the sorted data back to the CSV
|
||||||
|
with open(csv_path, mode='w', newline='') as file:
|
||||||
|
writer = csv.writer(file)
|
||||||
|
writer.writerow(header) # Write the header back
|
||||||
|
writer.writerows(sorted_rows) # Write the sorted rows
|
||||||
|
|
||||||
|
print(f"CSV file sorted by Set Code and Collector Number.")
|
||||||
|
|
||||||
|
# Function to get user input and add multiple cards interactively
|
||||||
|
def interactive_card_entry(csv_path):
|
||||||
|
while True:
|
||||||
|
# Get the card details from the user
|
||||||
|
card_name = input("Enter the card name (or type 'exit' to quit): ").strip()
|
||||||
|
if card_name.lower() == 'exit':
|
||||||
|
print("Exiting the program.")
|
||||||
|
break
|
||||||
|
|
||||||
|
set_code = input("Enter the set code: ").strip()
|
||||||
|
collector_number = input("Enter the collector number: ").strip()
|
||||||
|
quantity = int(input("Enter the quantity: ").strip())
|
||||||
|
foil = input("Is this card a foil? (yes/no): ").strip().lower() == 'yes'
|
||||||
|
|
||||||
|
# Add the card to the CSV file
|
||||||
|
add_card_to_csv(csv_path, card_name, set_code, collector_number, quantity, foil)
|
||||||
|
|
||||||
|
# Sort the CSV file after each entry
|
||||||
|
sort_csv_file(csv_path)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
csv_path = input("Enter the path to the CSV file: ").strip()
|
||||||
|
interactive_card_entry(csv_path)
|
11
README.md
11
README.md
@ -2,4 +2,13 @@
|
|||||||
|
|
||||||
I *really* love [Magic: The Gathering](https://en.wikipedia.org/wiki/Magic:_The_Gathering) and wanted to make a set of tools for me to use to create an inventory of my collection, download the card images or art crops of my collection, and create a wallpaper from those images.
|
I *really* love [Magic: The Gathering](https://en.wikipedia.org/wiki/Magic:_The_Gathering) and wanted to make a set of tools for me to use to create an inventory of my collection, download the card images or art crops of my collection, and create a wallpaper from those images.
|
||||||
|
|
||||||
Will be adding more tools along the way!
|
# Creation
|
||||||
|
In the [Creation Directory](Creation/), we have an EDH deck creation tool that takes a Commander's name and looks on EDHRec through the Archidekt section, pulls the UUID and quanities of the cards, parses the UUID's on Scryfall for the card's name, then outputs it into a text file that's setup for use on Moxfield (or any other site that utilizes the same deck list style).
|
||||||
|
|
||||||
|
# Imagery
|
||||||
|
If you're more into images of your inventory, the tools inside [Imagery](Imagery/) will be more up your alley. These tools will allow you to input your cards using the [interactive_cards_with_dl.py](Imagery/interactive_cards_with_dl.py) script which will output the cards into a CSV file. The [image_dl.py](Imagery/image_dl.py) and [art_image_dl.py](Imagery/art_image_dl.py) will download the card imagery from Scryfall and place them into their correct folders.
|
||||||
|
|
||||||
|
Once you've completed that, if you want to make a wallpaper out of those images, you can use [create_wallpaper.py](Imagery/create_wallpaper.py) to create a wallpaper with your downloaded card images.
|
||||||
|
|
||||||
|
# Inventory
|
||||||
|
The age old question, "How can I inventory my cards?" These tools will allow you to do so.
|
Loading…
Reference in New Issue
Block a user