Updated layout and added README.md for each

This commit is contained in:
Caleb Fultz 2024-08-13 18:53:13 -04:00
parent 8f217b4536
commit 1058f14404
18 changed files with 834 additions and 1 deletions

23
Creation/README.md Normal file
View 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
View 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()

View 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
View 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
View 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.")

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

BIN
Imagery/artpaper.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 474 KiB

48
Imagery/cards.csv Normal file
View 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
1 Card Name Set Code Collector Number Quantity Foil
2 Flubs, The Fool blc 356 1 True
3 Flubs, The Fool blc 356 1 False
4 Skemfar Shadowsage sld 759 3 True
5 Pollenbright Druid sld 776 1 True
6 Timberwatch Elf sld 780 2 True
7 Frilled Mystic sld 786 1 True
8 Coveted Jewel sld 799 1 False
9 The Scorpion God sld 904 1 True
10 Linda, Kandarian Queen sld 1355 1 True
11 Felidar Guardian sld 1487 1 False
12 Peregrine Drake sld 1488 1 False
13 Serpent of Yawning Depths sld 1489 1 False
14 Scourage of Valkas sld 1490 1 False
15 Voracious Hydra sld 1491 1 False
16 Wall of Omens sld 1518 1 False
17 Circular Logic sld 1519 1 False
18 Scheming Symmentry sld 1520 1 False
19 Price of Progress sld 1521 1 False
20 Eternal Witness sld 1522 1 False
21 Sakashima of a Thousand Faces sld 1541 1 False
22 Yargle, Glutton of Urborg sld 1542 1 False
23 Krark, The Thumbless sld 1543 1 False
24 Adrix and Nev, Twincasters sld 1544 1 False
25 Arcance Denial sld 1545 1 False
26 Nightscape Familiar sld 1546 1 False
27 Rain of Filth sld 1547 1 False
28 Simian Spirit Guide sld 1548 1 False
29 Prince of Thralls sld 1549 1 False
30 Gonti, Lord of Luxury sld 1566 1 False
31 Vilis, Broker of Blood sld 1567 1 False
32 Anowon, the Ruin Thief sld 1568 1 False
33 Grenzo, Dungeon Warden sld 1569 1 False
34 Reconnasissance sld 1575 1 False
35 Jace, Wielder of Mysteries sld 1576 1 False
36 Black Market sld 1577 1 False
37 Dire Undercurrents sld 1578 1 False
38 Obeka, Brute Chronolgoist sld 1579 1 False
39 Sorin Markov sld 1698 1 False
40 Sorin Markov sld 1698 1 True
41 Huatli, Radiant Champion sld 1699 1 False
42 Huatli, Radiant Champion sld 1699 1 True
43 Kiora, Behemoth Beckoner sld 1700 1 False
44 Kiora, Behemoth Beckoner sld 1700 1 True
45 Tezzeret, Master of the Bridge sld 1701 1 False
46 Tezzeret, Master of the Bridge sld 1701 1 True
47 Vraska, Golgari Queen sld 1702 1 False
48 Vraska, Golgari Queen sld 1702 1 True

102
Imagery/create_wallpaper.py Normal file
View 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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

81
Imagery/image_dl.py Normal file
View 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.")

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

29
Inventory/README.md Normal file
View 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
View 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
1 Card Name Set Code Collector Number Quantity Foil
2 Evercoat Ursine blc 64 1 False
3 Flubs, The Fool blc 356 1 True
4 Flubs, The Fool blc 356 1 False
5 Skemfar Shadowsage sld 759 3 True
6 Pollenbright Druid sld 776 1 True
7 Timberwatch Elf sld 780 2 True
8 Frilled Mystic sld 786 1 True
9 Coveted Jewel sld 799 1 False
10 The Scorpion God sld 904 1 True
11 Linda, Kandarian Queen sld 1355 1 True
12 Felidar Guardian sld 1487 1 False
13 Peregrine Drake sld 1488 1 False
14 Serpent of Yawning Depths sld 1489 1 False
15 Scourage of Valkas sld 1490 1 False
16 Voracious Hydra sld 1491 1 False
17 Wall of Omens sld 1518 1 False
18 Circular Logic sld 1519 1 False
19 Scheming Symmentry sld 1520 1 False
20 Price of Progress sld 1521 1 False
21 Eternal Witness sld 1522 1 False
22 Sakashima of a Thousand Faces sld 1541 1 False
23 Yargle, Glutton of Urborg sld 1542 1 False
24 Krark, The Thumbless sld 1543 1 False
25 Adrix and Nev, Twincasters sld 1544 1 False
26 Arcance Denial sld 1545 1 False
27 Nightscape Familiar sld 1546 1 False
28 Rain of Filth sld 1547 1 False
29 Simian Spirit Guide sld 1548 1 False
30 Prince of Thralls sld 1549 1 False
31 Gonti, Lord of Luxury sld 1566 1 False
32 Vilis, Broker of Blood sld 1567 1 False
33 Anowon, the Ruin Thief sld 1568 1 False
34 Grenzo, Dungeon Warden sld 1569 1 False
35 Reconnasissance sld 1575 1 False
36 Jace, Wielder of Mysteries sld 1576 1 False
37 Black Market sld 1577 1 False
38 Dire Undercurrents sld 1578 1 False
39 Obeka, Brute Chronolgoist sld 1579 1 False
40 Sorin Markov sld 1698 1 False
41 Sorin Markov sld 1698 1 True
42 Huatli, Radiant Champion sld 1699 1 False
43 Huatli, Radiant Champion sld 1699 1 True
44 Kiora, Behemoth Beckoner sld 1700 1 False
45 Kiora, Behemoth Beckoner sld 1700 1 True
46 Tezzeret, Master of the Bridge sld 1701 1 False
47 Tezzeret, Master of the Bridge sld 1701 1 True
48 Vraska, Golgari Queen sld 1702 1 False
49 Vraska, Golgari Queen sld 1702 1 True

60
Inventory/cli_cards.py Normal file
View 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()

View 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)

View File

@ -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.
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.