Files
beyond-obs/generate.py

212 lines
7.7 KiB
Python

from PIL import Image, ImageDraw, ImageFont
import requests, os
from time import sleep
DNDB_BASE_URL = "https://www.dndbeyond.com/character/"
DNDB_CHARS = [
{
"characterId": 69760590,
"characterName": "Вевель",
"characterClass": "острожник 10",
"portraitName": "vevel",
"characterUrl": "/profile/megavenik/characters/69760590",
"avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/31479/654/150/154/1581111423-69760590.jpeg",
},
{
"characterId": 69976266,
"characterName": "Милош",
"characterClass": "помазанник",
"portraitName": "milosh",
"characterUrl": "/profile/EdgarBrin/characters/69976266",
"avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/31479/725/150/155/1581111423-69976266.jpeg",
},
{
"characterId": 69976392,
"characterName": "Милица",
"characterClass": "голос",
"portraitName": "militsa",
"characterUrl": "/profile/tritiumTino/characters/69976392",
"avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/31479/709/150/154/1581111423-69976392.jpeg",
},
{
"characterId": 90463225,
"characterName": "Нивенна",
"characterClass": "чаровница",
"portraitName": "nivenna",
"characterUrl": "/profile/Maslinium/characters/90463225",
"avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/31479/748/160/150/1581111423-90463225.jpeg",
},
{
"characterId": 99072742,
"characterName": "Дарпа",
"characterClass": "кухарь",
"portraitName": "darpa",
"characterUrl": "/profile/Forfattare/characters/99072742",
"avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/34003/953/150/153/1581111423-99072742.jpeg",
},
{
"characterId": 99483409,
"characterName": "Деян",
"characterClass": "",
"portraitName": "deyan",
"characterUrl": "/profile/SantaKosh/characters/99483409",
"avatarUrl": "https://www.dndbeyond.com/avatars/thumbnails/34005/197/157/150/1581111423-99483409.jpeg",
}
]
class DataModifierService:
def getModifiersByType(self, groupedModifiers: dict, type: str, subType: str = "") -> list:
matching = []
for __, modifiers in groupedModifiers.items():
filtered = list(filter(lambda item: 'type' in item and type == item['type'], modifiers))
if subType:
filtered = list(filter(lambda item: 'subType' in item and subType == item['subType'], filtered))
matching += filtered
return matching
def getTotalModifierValue(self, groupedModifiers: dict, type: str, subType: str) -> int:
modifiers = self.getModifiersByType(groupedModifiers, type, subType)
modifier_values = [modifier['value'] for modifier in modifiers]
return sum(modifier_values)
def get_stat_modifier(stat):
import math
return math.floor((stat - 10) / 2)
def calculate_coordinates(object_width, object_height, center_point):
"""
Calculates the coordinates to put the center point of an object at a specified point.
Args:
object_width (int): The width of the object.
object_height (int): The height of the object.
center_point (tuple): The (x, y) coordinates of the point where the center of the object should be placed.
Returns:
tuple: The (x, y) coordinates of the top-left corner of the object.
"""
x = center_point[0] - object_width // 2
y = center_point[1] - object_height // 2
return (x, y)
def get_correct_text_sizes(draw, text: str, font: ImageFont.FreeTypeFont):
label_width, label_height = draw.textsize(text, font=font)
# to fix text placing we need to get offsets
# https://stackoverflow.com/questions/59008322/pillow-imagedraw-text-coordinates-to-center
offset_x, offset_y = font.getoffset(text)
label_width += offset_x
label_height += offset_y
return (label_width, label_height)
def generate_main(char: dict):
STATS_DICT = {
1: {
"name": "strength",
"coords": (684, 304)
},
2: {
"name": "dexterity",
"coords": (684, 381)
},
3: {
"name": "constitution",
"coords": (684, 457)
},
4: {
"name": "intelligence",
"coords": (767, 304)
},
5: {
"name": "wisdom",
"coords": (767, 381)
},
6: {
"name": "charisma",
"coords": (767, 457)
},
}
headers = {
'Content-Type': 'text/json',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
'Referer': 'https://www.dndbeyond.com/'
}
for i in range(3):
response = requests.get(f"{DNDB_BASE_URL}/{char['characterId']}/json", headers=headers)
if response.status_code == 200:
data = response.json()
print(data["stats"])
break
sleep(1)
else:
print("Error: Failed to retrieve data from URL.")
return False
# Load the background image
background = Image.open("parts/background-main.png")
# Load the image to be placed on top
foreground = Image.open(f"parts/{char['portraitName']}-main.png")
# Paste the foreground image onto the background
background.paste(foreground, (0, 0), foreground)
# Create a drawing context
draw = ImageDraw.Draw(background)
draw_width, _ = draw.im.size # pyright: ignore[reportGeneralTypeIssues]
for stat in data["stats"]:
modifiers = data.get('modifiers', {})
modifier_service = DataModifierService()
total_modifier_value = modifier_service.getTotalModifierValue(modifiers, 'bonus', STATS_DICT[stat['id']]['name'] + '-score')
stat["value"] += total_modifier_value
final_stat = get_stat_modifier(stat["value"])
# Define the font and font size for the text labels
stats_font = ImageFont.truetype("fonts/NotoSerif-Regular.ttf", 30)
# Add text labels on top of the image
stats_label = f"+{final_stat}" if final_stat > 0 else f"{final_stat}"
print(f"{STATS_DICT[stat['id']]['name']}: {stats_label}")
label_width, label_height = get_correct_text_sizes(draw, stats_label, stats_font)
draw.text(calculate_coordinates(label_width, label_height, STATS_DICT[stat["id"]]["coords"]), stats_label, font=stats_font, fill=(255, 255, 255))
name_font = ImageFont.truetype("fonts/alaruss.ttf", 96)
name_label = char['characterName'].upper()
label_width, label_height = get_correct_text_sizes(draw, name_label, name_font)
draw.text((draw_width-label_width-20, 120), name_label, font=name_font, fill=(255,255,255))
class_font = ImageFont.truetype("fonts/NotoSerif-Regular.ttf", 32)
class_label = char['characterClass'].lower()
label_width, label_height = get_correct_text_sizes(draw, class_label, class_font)
draw.text((draw_width-label_width-15, 203), class_label, font=class_font, fill=(255,255,255))
results_dir = "results"
if not os.path.exists(results_dir):
os.makedirs(results_dir)
background.save(f"results/{char['portraitName']}-main.png")
return True
# Save the resulting image
# generate_main(DNDB_CHARS[0])
for char in DNDB_CHARS:
generate_main(char)