Skip to content

Commit

Permalink
Improved title screen generation, though still not perfect
Browse files Browse the repository at this point in the history
  • Loading branch information
beveradb committed Dec 18, 2023
1 parent 10554f1 commit f3ebaf5
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 55 deletions.
115 changes: 65 additions & 50 deletions karaoke_prep/karaoke_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ def __init__(
intro_background_color="#000000",
intro_background_image=None,
intro_font="AvenirNext-Bold.ttf",
intro_artist_color="#ff7acc",
intro_title_color="#ffdf6b",
intro_artist_color="#ffffff",
intro_title_color="#ff7acc",
):
self.logger = logging.getLogger(__name__)
self.logger.setLevel(log_level)
Expand Down Expand Up @@ -60,11 +60,15 @@ def __init__(
self.normalization_enabled = normalization_enabled
self.denoise_enabled = denoise_enabled
self.create_track_subfolders = create_track_subfolders
self.intro_background_color = intro_background_color
self.intro_background_image = intro_background_image
self.intro_font = intro_font
self.intro_artist_color = intro_artist_color
self.intro_title_color = intro_title_color

self.title_format = {
"background_color": intro_background_color,
"background_image": intro_background_image,
"artist_font": intro_font,
"title_font": intro_font,
"artist_color": intro_artist_color,
"title_color": intro_title_color,
}

self.logger.debug(f"KaraokePrep output_format: {self.output_format}")

Expand Down Expand Up @@ -230,58 +234,67 @@ def setup_output_paths(self, artist, title):

return track_output_dir, artist_title

def create_intro_video(self, artist, title, output_filename):
def calculate_text_size_and_position(self, draw, text, font_path, start_size, resolution, padding):
font_size = start_size
font = ImageFont.truetype(font_path, size=font_size) if os.path.exists(font_path) else ImageFont.load_default()

# Initial position for calculating the text bounding box
temp_position = (padding, padding)
bbox = draw.textbbox(temp_position, text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]

while text_width + 2 * padding > resolution[0] or text_height + 2 * padding > resolution[1]:
font_size -= 10
if font_size <= 0:
raise ValueError("Cannot fit text within screen bounds.")
font = ImageFont.truetype(font_path, size=font_size) if os.path.exists(font_path) else ImageFont.load_default()
bbox = draw.textbbox(temp_position, text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]

text_position = ((resolution[0] - text_width) // 2, (resolution[1] - text_height) // 2)
return font, text_position

def create_intro_video(self, artist, title, format, output_image_filepath, output_video_filepath):
duration = 5 # Duration in seconds
resolution = (3840, 2160) # 4K resolution
font_size = 400 # Initial font size
line_spacing = 50 # Adjust as needed
padding = 300 # Padding from edges

# Load or create background image
if self.intro_background_image:
background = Image.open(self.intro_background_image)
if format["background_image"] and os.path.exists(format["background_image"]):
self.logger.debug(f"Title screen background image file found: {format['background_image']}")
background = Image.open(format["background_image"])
else:
background = Image.new("RGB", resolution, color=self.hex_to_rgb(self.intro_background_color))
background = Image.new("RGB", resolution, color=self.hex_to_rgb(format["background_color"]))

# Resize background to match resolution
background = background.resize(resolution)

# Create an ImageDraw instance
draw = ImageDraw.Draw(background)

# Function to calculate text width
def calculate_text_width(font):
artist_text_width = draw.textlength(artist, font=font)
title_text_width = draw.textlength(title, font=font)
return max(artist_text_width, title_text_width)
title = title.upper()
artist = artist.upper()

# Adjust font size if necessary
while True:
if os.path.exists(self.intro_font):
font = ImageFont.truetype(self.intro_font, size=font_size)
else:
self.logger.warning(f"Font file not found: {self.intro_font}. Using default font.")
font = ImageFont.load_default(size=font_size)

max_text_width = calculate_text_width(font)
if max_text_width + padding * 2 > resolution[0]:
font_size -= 10 # Reduce font size
if font_size <= 0:
raise ValueError("Cannot fit text within screen bounds.")
else:
break
initial_font_size = 500
title_padding = 400
artist_padding = 700
second_row_vertical_offset = 450

# Calculate text positions
artist_text_position = ((resolution[0] - calculate_text_width(font)) // 2, resolution[1] // 2 - font_size - line_spacing)
title_text_position = ((resolution[0] - calculate_text_width(font)) // 2, resolution[1] // 2 + line_spacing)
title_font, title_text_position = self.calculate_text_size_and_position(
draw, title, format["title_font"], initial_font_size, resolution, title_padding
)
artist_font, artist_text_position = self.calculate_text_size_and_position(
draw, artist, format["artist_font"], initial_font_size, resolution, artist_padding
)

artist_text_position = (artist_text_position[0], title_text_position[1] + second_row_vertical_offset)

# Draw text
draw.text(artist_text_position, artist, fill=self.intro_artist_color, font=font)
draw.text(title_text_position, title, fill=self.intro_title_color, font=font)
draw.text(title_text_position, title, fill=format["title_color"], font=title_font)
draw.text(artist_text_position, artist, fill=format["artist_color"], font=artist_font)

# Save to a temporary image file
temp_image_path = "/tmp/temp_background.png"
background.save(temp_image_path)
# Save static background image
background.save(output_image_filepath)

# Use ffmpeg to create video
ffmpeg_command = [
Expand All @@ -290,7 +303,7 @@ def calculate_text_width(font):
"-loop",
"1",
"-i",
temp_image_path,
output_image_filepath,
"-c:v",
"libx264",
"-t",
Expand All @@ -299,11 +312,10 @@ def calculate_text_width(font):
"yuv420p",
"-vf",
f"scale={resolution[0]}:{resolution[1]}",
output_filename,
output_video_filepath,
]

subprocess.run(ffmpeg_command)
os.remove(temp_image_path)

def hex_to_rgb(self, hex_color):
"""Convert hex color to RGB tuple."""
Expand All @@ -326,6 +338,10 @@ def prep_single_track(self):
"title": title,
}

processed_track["title_image"] = os.path.join(track_output_dir, f"{artist_title} (Title).png")
processed_track["title_video"] = os.path.join(track_output_dir, f"{artist_title} (Title).mov")
self.create_intro_video(artist, title, self.title_format, processed_track["title_image"], processed_track["title_video"])

yt_webm_filename_pattern = os.path.join(track_output_dir, f"{artist_title} (YouTube *.webm")
yt_webm_glob = glob.glob(yt_webm_filename_pattern)

Expand Down Expand Up @@ -379,9 +395,6 @@ def prep_single_track(self):

processed_track["lyrics"] = lyrics_file

intro_filename = os.path.join(track_output_dir, f"{artist_title} (Intro).mp4")
self.create_intro_video(artist, title, intro_filename)

self.logger.info(f"Separating audio twice for track: {title} by {artist}")

instrumental_path = os.path.join(track_output_dir, f"{artist_title} (Instrumental {self.model_name}).{self.output_format}")
Expand Down Expand Up @@ -440,7 +453,9 @@ def process_playlist(self):
def process(self):
if self.is_playlist_url():
self.persistent_artist = self.artist
self.logger.info(f"Provided YouTube URL is a playlist, beginning batch operation with persistent artist: {self.persistent_artist}")
self.logger.info(
f"Provided YouTube URL is a playlist, beginning batch operation with persistent artist: {self.persistent_artist}"
)
return self.process_playlist()
else:
self.logger.info(f"Provided YouTube URL is NOT a playlist, processing single track")
Expand Down
8 changes: 4 additions & 4 deletions karaoke_prep/utils/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,14 @@ def main():

parser.add_argument(
"--intro_artist_color",
default="#ff7acc",
help="Optional: Font color for intro video artist text (default: #ff7acc). Example: --intro_artist_color=#123456",
default="#ffdf6b",
help="Optional: Font color for intro video artist text (default: #ffdf6b). Example: --intro_artist_color=#123456",
)

parser.add_argument(
"--intro_title_color",
default="#ffdf6b",
help="Optional: Font color for intro video title text (default: #ffdf6b). Example: --intro_title_color=#123456",
default="#ffffff",
help="Optional: Font color for intro video title text (default: #ffffff). Example: --intro_title_color=#123456",
)

args = parser.parse_args()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "karaoke-prep"
version = "0.4.6"
version = "0.4.7"
description = "Prepare for karaoke video creation, by downloading audio and lyrics for a specified song or youtube playlist and separatung audio stems."
authors = ["Andrew Beveridge <andrew@beveridge.uk>"]
license = "MIT"
Expand Down

0 comments on commit f3ebaf5

Please sign in to comment.