Skip to content

Commit 966cb1d

Browse files
committed
Added ASS rendering fontsdir config with test, to make fonts work in different environments
1 parent 896a035 commit 966cb1d

File tree

3 files changed

+111
-5
lines changed

3 files changed

+111
-5
lines changed

lyrics_transcriber/output/video.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def __init__(
3131
self.output_dir = output_dir
3232
self.cache_dir = cache_dir
3333
self.video_resolution = video_resolution
34+
self.styles = styles
3435
self.logger = logger or logging.getLogger(__name__)
3536

3637
# Get background settings from styles, with defaults
@@ -199,6 +200,21 @@ def _resize_background_image(self, input_path: str) -> str:
199200
self.logger.error(f"Failed to resize background image: {e.output}")
200201
raise
201202

203+
def _build_ass_filter(self, ass_path: str) -> str:
204+
"""Build ASS filter with font directory support."""
205+
ass_filter = f"ass={ass_path}"
206+
207+
# Get font path from styles configuration
208+
karaoke_styles = self.styles.get("karaoke", {})
209+
font_path = karaoke_styles.get("font_path")
210+
211+
if font_path and os.path.isfile(font_path):
212+
font_dir = os.path.dirname(font_path)
213+
ass_filter += f":fontsdir={font_dir}"
214+
self.logger.info(f"Returning ASS filter with fonts dir: {ass_filter}")
215+
216+
return ass_filter
217+
202218
def _build_ffmpeg_command(self, ass_path: str, audio_path: str, output_path: str) -> List[str]:
203219
"""Build FFmpeg command for video generation with optimized settings."""
204220
width, height = self.video_resolution
@@ -230,11 +246,10 @@ def _build_ffmpeg_command(self, ass_path: str, audio_path: str, output_path: str
230246
"-i", f"color=c={self.background_color}:s={width}x{height}:r=30"
231247
])
232248

233-
# Add audio input and subtitle overlay
234249
cmd.extend([
235250
"-i", audio_path,
236251
"-c:a", "flac", # Re-encode audio as FLAC
237-
"-vf", f"ass={ass_path}", # Add subtitles
252+
"-vf", self._build_ass_filter(ass_path), # Add subtitles with font directories
238253
"-c:v", self._get_video_codec(),
239254
# Video quality settings
240255
"-preset", "fast", # Better compression efficiency
@@ -284,12 +299,11 @@ def _build_preview_ffmpeg_command(self, ass_path: str, audio_path: str, output_p
284299
"-i", f"color=c={self.background_color}:s={width}x{height}:r=30"
285300
])
286301

287-
# Add audio input and subtitle overlay
288302
cmd.extend([
289303
"-i", audio_path,
290304
"-c:a", "aac", # Use AAC for audio
291305
"-b:a", "128k", # Audio bitrate
292-
"-vf", f"ass={ass_path}", # Add subtitles
306+
"-vf", self._build_ass_filter(ass_path), # Add subtitles with font directories
293307
"-c:v", "libx264", # Use H.264 codec
294308
"-profile:v", "baseline", # Most compatible H.264 profile
295309
"-level", "3.0", # Compatibility level

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "lyrics-transcriber"
3-
version = "0.57.1"
3+
version = "0.58.0"
44
description = "Automatically create synchronised lyrics files in ASS and MidiCo LRC formats with word-level timestamps, using Whisper and lyrics from Genius and Spotify"
55
authors = ["Andrew Beveridge <[email protected]>"]
66
license = "MIT"

tests/unit/output/test_video.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import json
55
import subprocess
66
from unittest.mock import patch, Mock, call
7+
import logging
78

89
from lyrics_transcriber.output.video import VideoGenerator
910

@@ -203,3 +204,94 @@ def test_invalid_video_resolution():
203204
styles = {"karaoke": {"background_color": "black"}}
204205
with pytest.raises(ValueError):
205206
VideoGenerator(output_dir="test", cache_dir="test", video_resolution=(0, 0), styles=styles)
207+
208+
209+
def test_build_ass_filter_no_font_path(video_generator):
210+
"""Test _build_ass_filter with no font path configured."""
211+
ass_path = "test.ass"
212+
result = video_generator._build_ass_filter(ass_path)
213+
assert result == "ass=test.ass"
214+
215+
216+
def test_build_ass_filter_with_valid_font_path(video_generator, tmp_path):
217+
"""Test _build_ass_filter with valid font path."""
218+
# Create a test font file
219+
font_dir = tmp_path / "fonts"
220+
font_dir.mkdir()
221+
font_file = font_dir / "test.ttf"
222+
font_file.touch()
223+
224+
# Update styles to include font path
225+
video_generator.styles["karaoke"]["font_path"] = str(font_file)
226+
227+
ass_path = "test.ass"
228+
result = video_generator._build_ass_filter(ass_path)
229+
expected = f"ass=test.ass:fontsdir={str(font_dir)}"
230+
assert result == expected
231+
232+
233+
def test_build_ass_filter_with_invalid_font_path(video_generator):
234+
"""Test _build_ass_filter with invalid font path (file doesn't exist)."""
235+
# Set non-existent font path
236+
video_generator.styles["karaoke"]["font_path"] = "/nonexistent/font.ttf"
237+
238+
ass_path = "test.ass"
239+
result = video_generator._build_ass_filter(ass_path)
240+
assert result == "ass=test.ass"
241+
242+
243+
def test_build_ass_filter_with_empty_font_path(video_generator):
244+
"""Test _build_ass_filter with empty font path."""
245+
# Set empty font path
246+
video_generator.styles["karaoke"]["font_path"] = ""
247+
248+
ass_path = "test.ass"
249+
result = video_generator._build_ass_filter(ass_path)
250+
assert result == "ass=test.ass"
251+
252+
253+
def test_build_ass_filter_with_none_font_path(video_generator):
254+
"""Test _build_ass_filter with None font path."""
255+
# Set None font path
256+
video_generator.styles["karaoke"]["font_path"] = None
257+
258+
ass_path = "test.ass"
259+
result = video_generator._build_ass_filter(ass_path)
260+
assert result == "ass=test.ass"
261+
262+
263+
def test_build_ass_filter_no_karaoke_section(tmp_path):
264+
"""Test _build_ass_filter with no karaoke section in styles."""
265+
styles = {} # No karaoke section
266+
generator = VideoGenerator(
267+
output_dir=str(tmp_path / "output"),
268+
cache_dir=str(tmp_path / "cache"),
269+
video_resolution=(1920, 1080),
270+
styles=styles,
271+
)
272+
273+
ass_path = "test.ass"
274+
result = generator._build_ass_filter(ass_path)
275+
assert result == "ass=test.ass"
276+
277+
278+
def test_build_ass_filter_logging(video_generator, tmp_path, caplog):
279+
"""Test _build_ass_filter logging behavior."""
280+
# Set log level to INFO to capture the logs
281+
caplog.set_level(logging.INFO)
282+
283+
# Create a test font file
284+
font_dir = tmp_path / "fonts"
285+
font_dir.mkdir()
286+
font_file = font_dir / "test.ttf"
287+
font_file.touch()
288+
289+
# Update styles to include font path
290+
video_generator.styles["karaoke"]["font_path"] = str(font_file)
291+
292+
ass_path = "test.ass"
293+
result = video_generator._build_ass_filter(ass_path)
294+
295+
# Check that info log was generated
296+
assert "Returning ASS filter with fonts dir:" in caplog.text
297+
assert str(font_dir) in caplog.text

0 commit comments

Comments
 (0)