Skip to content
Open
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,3 +164,8 @@ cython_debug/
# Added by TrueMyst
!{example}.env
posters/*

#Added by DamnUI
.github/instructions/ins.instructions.md
w.py
w2.py
Binary file added BeatPrints/assets/templates/a4_catppuccin.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BeatPrints/assets/templates/a4_dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BeatPrints/assets/templates/a4_everforest.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BeatPrints/assets/templates/a4_gruvbox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BeatPrints/assets/templates/a4_light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BeatPrints/assets/templates/a4_nord.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BeatPrints/assets/templates/a4_nord_corrected.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BeatPrints/assets/templates/a4_rosepine.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions BeatPrints/consts.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,38 @@ class Size:
PL_HEIGHT = 2325


class SizeA4:
# A4 Canvas Size (300 DPI) - Native Resolution
A4_WIDTH = 2480
A4_HEIGHT = 3508

# Heading's Width (Max) - Scaled for A4: 1760 * 1.0877 = 1914
HEADING_WIDTH = 1914

# Resolution Size - Scaled for A4
COVER = (2219, 2062) # 2040 * 1.0877 = 2219
SCANCODE = (718, 171) # 660 * 1.0877 = 718, 170 * 1.0080 = 171

# Track/Album Metadata - Font sizes scaled for A4
TRACKS = 76 # 70 * 1.0877 = 76
HEADING = 174 # 160 * 1.0877 = 174
ARTIST = 131 # 120 * 1.0877 = 131
DURATION = 98 # 90 * 1.0877 = 98
LYRICS = 91 # 84 * 1.0877 = 91
LABEL = 65 # 60 * 1.0877 = 65

# Album's Tracklist
MAX_ROWS = 5
MAX_WIDTH = 2219 # 2040 * 1.0877 = 2219

# Space between texts - Scaled for A4
SPACING = 76 # 70 * 1.0877 = 76

# Color Palette - Scaled for A4
PL_WIDTH = 370 # 340 * 1.0877 = 370
PL_HEIGHT = 2344 # 2325 * 1.0080 = 2344


class Position:
COVER = (120, 120)
HEADING = (120, 2550)
Expand All @@ -51,6 +83,19 @@ class Position:
ACCENT = (0, 3440, 2280, 3480)
SCANCODE = (90, 3220)

class PositionA4:
# All positions scaled for A4 canvas (X * 1.0877, Y * 1.0080)
COVER = (131, 121) # (120 * 1.0877, 120 * 1.0080)
HEADING = (131, 2570) # (120 * 1.0877, 2550 * 1.0080)
ARTIST = (131, 2722) # (120 * 1.0877, 2700 * 1.0080)
LYRICS = (131, 2812) # (120 * 1.0877, 2790 * 1.0080)
TRACKS = (131, 2802) # (120 * 1.0877, 2780 * 1.0080)
LABEL = (2349, 3257) # (2160 * 1.0877, 3230 * 1.0080)
DURATION = (2349, 2570) # (2160 * 1.0877, 2550 * 1.0080)
PALETTE = (131, 2258) # (120 * 1.0877, 2240 * 1.0080)
ACCENT = (0, 3468, 2480, 3508) # (0, 3440 * 1.0080, 2280 * 1.0877, 3480 * 1.0080)
SCANCODE = (98, 3246) # (90 * 1.0877, 3220 * 1.0080)


class Color:
# Default Themes
Expand Down
45 changes: 37 additions & 8 deletions BeatPrints/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from Pylette import extract_colors
from PIL import Image, ImageDraw, ImageEnhance
from BeatPrints.consts import Size, Position, Color, ThemesSelector, FilePath
from BeatPrints.consts import Size, Position, Color, ThemesSelector, FilePath, SizeA4, PositionA4

# Initialize the components
s = Size()
Expand Down Expand Up @@ -47,7 +47,7 @@ def get_palette(image: Image.Image) -> List[Tuple]:


def draw_palette(
draw: ImageDraw.ImageDraw, image: Image.Image, accent: bool = False
draw: ImageDraw.ImageDraw, image: Image.Image, accent: bool = False, A4: bool = False
) -> None:
"""
Draws a color palette on the given image.
Expand All @@ -59,6 +59,13 @@ def draw_palette(
"""
palette = get_palette(image)

if A4:
s = SizeA4()
p = PositionA4()
else:
s = Size()
p = Position()

# Render each color from the palette as a rectangle
for index in range(6):
x, y = p.PALETTE
Expand Down Expand Up @@ -128,6 +135,7 @@ def scannable(
id: str,
theme: ThemesSelector.Options = "Light",
item: Literal["track", "album"] = "track",
A4: bool = False,
) -> Image.Image:
"""
Generates a Spotify scannable code for a track or album.
Expand All @@ -141,6 +149,13 @@ def scannable(
Image.Image: The resized scannable code image.
"""

if A4:
s = SizeA4()
p = PositionA4()
else:
s = Size()
p = Position()

variant = t.THEMES[theme]

# URL to fetch the scannable code
Expand All @@ -167,7 +182,7 @@ def scannable(
return scan_code.resize(s.SCANCODE, Image.Resampling.BICUBIC)


def cover(url: str, path: Optional[str]) -> Image.Image:
def cover(url: str, path: Optional[str], A4: bool = False) -> Image.Image:
"""
Fetches and processes an image from a URL or local path.

Expand All @@ -182,6 +197,13 @@ def cover(url: str, path: Optional[str]) -> Image.Image:
Raises:
FileNotFoundError: If the provided local image path does not exist.
"""
if A4:
s = SizeA4()
p = PositionA4()
else:
s = Size()
p = Position()


if path:
path_ = Path(path).expanduser().resolve()
Expand All @@ -198,18 +220,25 @@ def cover(url: str, path: Optional[str]) -> Image.Image:
return magicify(img.resize(s.COVER))


def get_theme(theme: ThemesSelector.Options = "Light") -> Tuple[tuple, str]:
def get_theme(theme: ThemesSelector.Options = "Light", A4: bool = False) -> Tuple[tuple, str]:
"""
Returns theme-related properties based on the selected theme.

Args:
theme (ThemesSelector.Options, optional): The selected theme. Defaults to "Light".

Returns:
Tuple[tuple, str]: A tuple containing the theme color and the template path.
Tuple[tuple, str]: A tuple containing the theme color and A4 template path.
"""

variant = t.THEMES[theme]
template = os.path.join(f.TEMPLATES, f"{theme.lower()}.png")

# Use the new A4-sized template files that preserve original visual elements
if A4:
a4_template_path = os.path.join(f.TEMPLATES, f"a4_{theme.lower()}.png")
else:
a4_template_path = os.path.join(f.TEMPLATES, f"{theme.lower()}.png")

return variant, a4_template_path



return variant, template
112 changes: 74 additions & 38 deletions BeatPrints/poster.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

from BeatPrints.errors import ThemeNotFoundError
from BeatPrints.spotify import TrackMetadata, AlbumMetadata
from BeatPrints.consts import Size, Position, ThemesSelector
from BeatPrints.consts import Size, Position, ThemesSelector, SizeA4, PositionA4

# Initialize the components


s = Size()
p = Position()
t = ThemesSelector()
Expand All @@ -43,6 +45,7 @@ def _draw_template(
draw: ImageDraw.ImageDraw,
metadata: Union[TrackMetadata, AlbumMetadata],
color: Tuple[int, int, int],
A4: bool = False,
):
"""
Adds text like title, artist, and label info.
Expand All @@ -52,6 +55,13 @@ def _draw_template(
metadata (Union[TrackMetadata, AlbumMetadata]): Metadata containing the required text.
color (Tuple[int, int, int]): Text color.
"""
if A4:
s = SizeA4()
p = PositionA4()
elif not A4:
s = Size()
p = Position()

# Add heading (track or album name) in bold
write.heading(
draw,
Expand Down Expand Up @@ -91,6 +101,7 @@ def track(
accent: bool = False,
theme: ThemesSelector.Options = "Light",
pcover: Optional[str] = None,
a4: bool = False,
) -> None:
"""
Generates a poster for a track, which includes lyrics.
Expand All @@ -101,34 +112,43 @@ 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.
a4 (bool, optional): Generate poster in A4 size (2480x3508). Defaults to True.
"""
# Initialize size and position constants based on poster format
if a4:
s = SizeA4()
p = PositionA4()
else:
s = Size()
p = Position()

# Check if the theme is valid or not
if theme not in t.THEMES:
raise ThemeNotFoundError

# Get theme and template for the poster
color, template = image.get_theme(theme)
# Get theme and A4 template path for the poster
color, template_path = image.get_theme(theme, A4=a4)
# Adjust constants based on poster size
# Get cover art and scancode sized for A4
cover = image.cover(metadata.image, pcover, A4=a4)
scannable = image.scannable(metadata.id, theme, "track", A4=a4)

# Get cover art and scancode
cover = image.cover(metadata.image, pcover)
scannable = image.scannable(metadata.id, theme, "track")

with Image.open(template) as poster:
# Load the A4 template with beautiful original design elements
with Image.open(template_path) as poster:
poster = poster.convert("RGB")
draw = ImageDraw.Draw(poster)

# Paste the cover and scancode
# Paste the cover and scancode at A4-native positions
poster.paste(cover, p.COVER)
poster.paste(scannable, p.SCANCODE, scannable)

# Add an accent at the bottom if True
image.draw_palette(draw, cover, accent)
image.draw_palette(draw, cover, accent, A4=a4)

# Add the track's metadata
self._draw_template(draw, metadata, color)
# Add the track's metadata using A4-native sizes and positions
self._draw_template(draw, metadata, color, A4=a4)

# Add the track's duration and lyrics to the poster
# Add the track's duration and lyrics using A4-native sizes
write.text(
draw,
p.DURATION,
Expand All @@ -148,13 +168,18 @@ def track(
anchor="lt",
)

# Save the generated poster with a unique filename
# Save the A4 poster with preserved original design elements
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 a4:
print(
f"✨ Poster for {metadata.name} by {metadata.artist} saved to {self.save_to} (A4: 2480×3508)"
)
else:
print(
f"✨ Poster for {metadata.name} by {metadata.artist} saved to {self.save_to} 2280×3480"
)

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

Args:
metadata (AlbumMetadata): Metadata containing details about the album.
indexing (bool, optional): Add index numbers to the tracks. Defaults to False.
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.
"""
# Initialize size and position constants based on poster format
if a4:
s = SizeA4()
p = PositionA4()
elif not a4:
s = Size()
p = Position()

# Check if the theme mentioned is valid or not
if theme not in t.THEMES:
raise ThemeNotFoundError

# Get theme colors and template for the poster
color, template = image.get_theme(theme)
# Get theme colors and A4 template path for the poster
color, template_path = image.get_theme(theme, A4=a4)

# Get cover art and spotify scannable code
cover = image.cover(metadata.image, pcover)
scannable = image.scannable(metadata.id, theme, "album")
# Get cover art and spotify scannable code sized for A4
cover = image.cover(metadata.image, pcover, A4=a4)
scannable = image.scannable(metadata.id, theme, "album", A4=a4)

with Image.open(template) as poster:
# Load the A4 template with beautiful original design elements
with Image.open(template_path) as poster:
poster = poster.convert("RGB")
draw = ImageDraw.Draw(poster)

# Paste the album cover and scannable Spotify code
# Paste the album cover and scannable Spotify code at A4-native positions
poster.paste(cover, p.COVER)
poster.paste(scannable, p.SCANCODE, scannable)

# Optionally add a color palette or design accents
image.draw_palette(draw, cover, accent)
image.draw_palette(draw, cover, accent, A4=a4)

# Add album information (name, artist, etc.)
self._draw_template(draw, metadata, color)
# Add album information using A4-native sizes and positions
self._draw_template(draw, metadata, color, A4=a4)

# Album's Tracks
tracks = metadata.tracks

# Organize the tracklist and render it on the poster
# Organize the tracklist and render it on the poster using A4-native sizing
tracklist, track_widths = organize_tracks(tracks, indexing)

# Starting Position
# Starting Position (A4-native)
x, y = p.TRACKS

# Render the tracklist, adjusting the position for each column
# Render the tracklist using A4-native font sizes and spacing
for track_column, column_width in zip(tracklist, track_widths):
write.text(
draw,
Expand All @@ -221,11 +252,16 @@ def album(
anchor="lt",
spacing=2,
)
x += column_width + s.SPACING # Adjust x for next column
x += column_width + s.SPACING # A4-native spacing

# Save the generated album poster with a unique filename
# Save the A4 album poster with preserved original design elements
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 a4:
print(
f"✨ Poster for {metadata.name} by {metadata.artist} saved to {self.save_to} (A4: 2480×3508)"
)
else:
print(
f"✨ Poster for {metadata.name} by {metadata.artist} saved to {self.save_to} 2280×3480"
)
Loading