diff --git a/Creation/README.md b/Creation/README.md new file mode 100644 index 0000000..a507c76 --- /dev/null +++ b/Creation/README.md @@ -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 +``` diff --git a/Creation/edhrec_deck.py b/Creation/edhrec_deck.py new file mode 100644 index 0000000..f49b712 --- /dev/null +++ b/Creation/edhrec_deck.py @@ -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() diff --git a/Creation/flubs-the-fool_edh.txt b/Creation/flubs-the-fool_edh.txt new file mode 100644 index 0000000..6cb9b1e --- /dev/null +++ b/Creation/flubs-the-fool_edh.txt @@ -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 diff --git a/Imagery/README.md b/Imagery/README.md new file mode 100644 index 0000000..6c63894 --- /dev/null +++ b/Imagery/README.md @@ -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) \ No newline at end of file diff --git a/Imagery/art_image_dl.py b/Imagery/art_image_dl.py new file mode 100644 index 0000000..f81fde3 --- /dev/null +++ b/Imagery/art_image_dl.py @@ -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.") diff --git a/Imagery/artcrop_images/blc_Flubs--The-Fool_art_crop.jpg b/Imagery/artcrop_images/blc_Flubs--The-Fool_art_crop.jpg new file mode 100644 index 0000000..5a34a5f Binary files /dev/null and b/Imagery/artcrop_images/blc_Flubs--The-Fool_art_crop.jpg differ diff --git a/Imagery/artpaper.jpg b/Imagery/artpaper.jpg new file mode 100644 index 0000000..7192813 Binary files /dev/null and b/Imagery/artpaper.jpg differ diff --git a/Imagery/cards.csv b/Imagery/cards.csv new file mode 100644 index 0000000..d2ad733 --- /dev/null +++ b/Imagery/cards.csv @@ -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 diff --git a/Imagery/create_wallpaper.py b/Imagery/create_wallpaper.py new file mode 100644 index 0000000..351d93c --- /dev/null +++ b/Imagery/create_wallpaper.py @@ -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() diff --git a/Imagery/downloaded_images/sld_The-Scorpion-God.jpg b/Imagery/downloaded_images/sld_The-Scorpion-God.jpg new file mode 100644 index 0000000..92d445a Binary files /dev/null and b/Imagery/downloaded_images/sld_The-Scorpion-God.jpg differ diff --git a/Imagery/image_dl.py b/Imagery/image_dl.py new file mode 100644 index 0000000..b0534ac --- /dev/null +++ b/Imagery/image_dl.py @@ -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.") diff --git a/Imagery/interactive_cards_with_dl.py b/Imagery/interactive_cards_with_dl.py new file mode 100644 index 0000000..7a0db7d --- /dev/null +++ b/Imagery/interactive_cards_with_dl.py @@ -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) diff --git a/Imagery/wallpaper.jpg b/Imagery/wallpaper.jpg new file mode 100644 index 0000000..471e8df Binary files /dev/null and b/Imagery/wallpaper.jpg differ diff --git a/Inventory/README.md b/Inventory/README.md new file mode 100644 index 0000000..6b6cb50 --- /dev/null +++ b/Inventory/README.md @@ -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): +``` \ No newline at end of file diff --git a/Inventory/cards.csv b/Inventory/cards.csv new file mode 100644 index 0000000..d8d0775 --- /dev/null +++ b/Inventory/cards.csv @@ -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 diff --git a/Inventory/cli_cards.py b/Inventory/cli_cards.py new file mode 100644 index 0000000..03ce441 --- /dev/null +++ b/Inventory/cli_cards.py @@ -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() diff --git a/Inventory/interactive_cards.py b/Inventory/interactive_cards.py new file mode 100644 index 0000000..9773c51 --- /dev/null +++ b/Inventory/interactive_cards.py @@ -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) diff --git a/README.md b/README.md index 0e596da..b0c47c8 100644 --- a/README.md +++ b/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. -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. \ No newline at end of file