Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 32 additions & 15 deletions BeatPrints/poster.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ def track(
accent: bool = False,
theme: ThemesSelector.Options = "Light",
pcover: Optional[str] = None,
) -> None:
return_image: bool = False,
) -> Optional[Image.Image]:
"""
Generates a poster for a track, which includes lyrics.

Expand All @@ -101,6 +102,10 @@ def track(
accent (bool, optional): Adds an accent at the bottom of the poster. Defaults to False.
theme (ThemesSelector.Options, optional): Specifies the theme to use. Must be one of "Light", "Dark", "Catppuccin", "Gruvbox", "Nord", "RosePine", or "Everforest". Defaults to "Light".
pcover (Optional[str]): Path to a custom cover image. Defaults to None.
return_image (bool, optional): If True, returns the PIL Image object instead of saving. Defaults to False.

Returns:
Optional[Image.Image]: The PIL Image object of the poster if return_image is True, otherwise None.
"""

# Check if the theme is valid or not
Expand Down Expand Up @@ -148,13 +153,16 @@ def track(
anchor="lt",
)

# Save the generated poster with a unique filename
name = filename(metadata.name, metadata.artist)
poster.save(os.path.join(self.save_to, name))

print(
f"✨ Poster for {metadata.name} by {metadata.artist} saved to {self.save_to}"
)
if return_image:
return poster
else:
# Save the generated poster with a unique filename
name = filename(metadata.name, metadata.artist)
poster.save(os.path.join(self.save_to, name))
print(
f"✨ Poster for {metadata.name} by {metadata.artist} saved to {self.save_to}"
)
return None

def album(
self,
Expand All @@ -163,7 +171,8 @@ def album(
accent: bool = False,
theme: ThemesSelector.Options = "Light",
pcover: Optional[str] = None,
) -> None:
return_image: bool = False,
) -> Optional[Image.Image]:
"""
Generates a poster for an album, which includes track listing.

Expand All @@ -173,6 +182,10 @@ def album(
accent (bool, optional): Add an accent at the bottom of the poster. Defaults to False.
theme (ThemesSelector.Options, optional): Specifies the theme to use. Must be one of "Light", "Dark", "Catppuccin", "Gruvbox", "Nord", "RosePine", or "Everforest". Defaults to "Light".
pcover (Optional[str]): Path to a custom cover image. Defaults to None.
return_image (bool, optional): If True, returns the PIL Image object instead of saving. Defaults to False.

Returns:
Optional[Image.Image]: The PIL Image object of the poster if return_image is True, otherwise None.
"""

# Check if the theme mentioned is valid or not
Expand Down Expand Up @@ -223,9 +236,13 @@ def album(
)
x += column_width + s.SPACING # Adjust x for next column

# Save the generated album poster with a unique filename
name = filename(metadata.name, metadata.artist)
poster.save(os.path.join(self.save_to, name))
print(
f"✨ Album poster for {metadata.name} by {metadata.artist} saved to {self.save_to}"
)
if return_image:
return poster
else:
# Save the generated album poster with a unique filename
name = filename(metadata.name, metadata.artist)
poster.save(os.path.join(self.save_to, name))
print(
f"✨ Album poster for {metadata.name} by {metadata.artist} saved to {self.save_to}"
)
return None
82 changes: 82 additions & 0 deletions BeatPrints/wallpaper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
from PIL import Image, ImageDraw, ImageFilter

def draw_drop_shadow(base_image, offset=(10, 10), shadow_color=(0, 0, 0, 128), blur_radius=10):
"""Draws a drop shadow behind the given image."""
shadow = Image.new('RGBA', base_image.size, (0, 0, 0, 0))
shadow_draw = ImageDraw.Draw(shadow)
shadow_draw.rectangle((0, 0, base_image.width, base_image.height), fill=shadow_color)

blurred_shadow = shadow.filter(ImageFilter.GaussianBlur(radius=blur_radius))

shadow_offset = Image.new('RGBA', (base_image.width + abs(offset[0]) * 2, base_image.height + abs(offset[1]) * 2), (0, 0, 0, 0))
shadow_offset.paste(blurred_shadow, (abs(offset[0]), abs(offset[1])))

#Can probably be dropped
final_shadow = Image.new('RGBA', shadow_offset.size, (0, 0, 0, 0))
final_shadow.paste(shadow_offset, (0, 0))

return final_shadow

def generate_wallpaper(resolution, image_paths, bg_color):
width_res, height_res = resolution
num_images = len(image_paths)

if not 1 <= num_images <= 10:
raise ValueError("Number of images must be between 1 and 10.")
# Math... Basically take the Wallpaper Width/Height, calculate what the appropriate size of the posters should be
# Horizontal gaps between images are set to be 1/16 the size of the images
# Gaps between the images and the edges of the wallpaper are set to be at least double the size of the inner gaps
# At least one side has to give. The vertical or the horizontal edge gaps
# The vertical_gap was chosen arbitrairly as the baseline for comparison

if num_images > 0:
inner_gap_horizontal_ver = width_res / (3 + (17 * num_images))
vertical_gap_horizontal_ver = 2 * inner_gap_horizontal_ver
vertical_gap_vertical_ver = height_res/(2 + (8*26/19))

if vertical_gap_horizontal_ver < vertical_gap_vertical_ver:
inner_gap = inner_gap_horizontal_ver
image_width = inner_gap * 16
vertical_gap = vertical_gap_horizontal_ver
image_height = (29/19) * image_width

else:
vertical_gap = vertical_gap_vertical_ver
inner_gap = vertical_gap / 2
image_height = vertical_gap * 26 * 8 / 19
image_width = image_height * 19 / 29

wallpaper = Image.new('RGB', resolution, bg_color)
shadow_offset = (int(inner_gap / 8), int(inner_gap / 8))
shadow_color = (0, 0, 0, 128)
blur_radius = int(inner_gap / 8)

if num_images > 0:
total_images_width = num_images * image_width
total_inner_gaps_width = (num_images - 1) * inner_gap
horizontal_available_space = width_res - total_images_width - total_inner_gaps_width
horizontal_outer_gap = horizontal_available_space / 2
vertical_available_space = height_res - image_height
vertical_outer_gap = vertical_available_space / 2

y_position = vertical_outer_gap
x_position = horizontal_outer_gap

for path in image_paths:
try:
img = Image.open(path).convert("RGBA")
resized_img = img.resize((int(image_width), int(image_height)))

# Draw drop shadow
shadow = draw_drop_shadow(resized_img, offset=shadow_offset, shadow_color=shadow_color, blur_radius=blur_radius)
wallpaper.paste(shadow, (int(x_position) + shadow_offset[0], int(y_position) + shadow_offset[1]), shadow)

# Paste the actual image
wallpaper.paste(resized_img, (int(x_position), int(y_position)), resized_img)

x_position += image_width + inner_gap
except FileNotFoundError:
print(f"Error: Image not found at {path}")
# Handle error as needed

return wallpaper
100 changes: 95 additions & 5 deletions cli/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
from rich import print

from cli import conf, exutils, validate
from BeatPrints import lyrics, spotify, poster, errors
from BeatPrints import lyrics, spotify, poster, errors, wallpaper
import os

# Initialize components
ly = lyrics.Lyrics()
Expand Down Expand Up @@ -220,7 +221,7 @@ def poster_features():
return theme, accent, image_path


def create_poster():
def create_poster(return_image=False):
"""
Create a poster based on user input.
"""
Expand All @@ -236,6 +237,8 @@ def create_poster():
# Clear the screen
exutils.clear()

generated_image = None

# Generate posters
if poster_type == "Track Poster":
track = select_track(conf.SEARCH_LIMIT)
Expand All @@ -244,19 +247,106 @@ def create_poster():
lyrics = handle_lyrics(track)

exutils.clear()
ps.track(track, lyrics, accent, theme, image)
generated_image = ps.track(track, lyrics, accent, theme, image, return_image=return_image)
else:
album = select_album(conf.SEARCH_LIMIT)

if album:
ps.album(*album, accent, theme, image)
generated_image = ps.album(*album, accent, theme, image, return_image=return_image)

return generated_image


def main():
exutils.clear()

try:
create_poster()
creation_type = questionary.select(
"• What would you like to create?",
choices=["Poster", "Wallpaper"],
style=exutils.lavish,
qmark="✨",
).unsafe_ask()

if creation_type == "Poster":
create_poster()
elif creation_type == "Wallpaper":
num_posters = questionary.text(
"• How many posters (1-10) would you like in your wallpaper?",
validate=validate.NumericValidator(limit=10),
style=exutils.lavish,
qmark="🖼️",
).unsafe_ask()
num_posters = int(num_posters)

if 1 <= num_posters <= 10:
poster_images = []
temp_poster_paths = []
for i in range(num_posters):
print(f"\nCreating poster {i+1} for the wallpaper:")
poster_image = create_poster(return_image=True)
if poster_image:
poster_images.append(poster_image)
# Optionally save to temporary files if memory becomes an issue
temp_path = f"temp_poster_{i+1}.png"
poster_image.save(temp_path)
temp_poster_paths.append(temp_path)
else:
print("Error creating a poster. Wallpaper creation aborted.")
# Clean up any created temporary files
for path in temp_poster_paths:
try:
os.remove(path)
except FileNotFoundError:
pass
return

wallpaper_width = questionary.text(
"• Enter the desired wallpaper width:",
validate=validate.NumericValidator(limit=9999),
style=exutils.lavish,
qmark="📏",
).unsafe_ask()
wallpaper_height = questionary.text(
"• Enter the desired wallpaper height:",
validate=validate.NumericValidator(limit=9999),
style=exutils.lavish,
qmark="📐",
).unsafe_ask()
wallpaper_resolution = (int(wallpaper_width), int(wallpaper_height))
wallpaper_bg_color = questionary.text(
"• Enter the background color for the wallpaper (e.g., slategrey, #RRGGBB):",
style=exutils.lavish,
qmark="🎨",
).unsafe_ask()

try:
wallpaper_image = wallpaper.generate_wallpaper(
wallpaper_resolution,
temp_poster_paths, # Pass the list of temporary file paths
wallpaper_bg_color
)
# Need to come up with a scheme for naming generated files
wallpaper_save_path = os.path.join(conf.POSTERS_DIR, "generated_wallpaper.png")
wallpaper_image.save(wallpaper_save_path)
print(f"\nWallpaper created successfully and saved as {wallpaper_save_path}")

# Clean up temporary poster files
for path in temp_poster_paths:
try:
os.remove(path)
except FileNotFoundError:
pass

except ValueError as e:
print(f"Error during wallpaper generation: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
else:
print("Invalid number of posters.")
else:
print("Invalid choice.")

except KeyboardInterrupt:
exutils.clear()
print("👋 Alright, no problem! See you next time.")
Expand Down