fix: use UUID for podcast episode directory names

Podcast episode names with spaces or special characters caused
filesystem errors when used directly as directory names.
Use UUID-based directory names instead, keeping the original
episode name in the database for display purposes.

Closes #663
This commit is contained in:
Luis Novo 2026-03-11 17:00:00 -05:00
parent d0479b956c
commit 0619fa4d3b
2 changed files with 79 additions and 3 deletions

View file

@ -1,4 +1,5 @@
import time
import uuid
from pathlib import Path
from typing import Optional
@ -220,8 +221,9 @@ async def generate_podcast_command(
logger.info(f"Generated briefing (length: {len(briefing)} chars)")
# 7. Create output directory
output_dir = Path(f"{DATA_FOLDER}/podcasts/episodes/{input_data.episode_name}")
# 7. Create output directory using UUID for filesystem-safe paths
episode_dir_name = str(uuid.uuid4())
output_dir = Path(f"{DATA_FOLDER}/podcasts/episodes/{episode_dir_name}")
output_dir.mkdir(parents=True, exist_ok=True)
logger.info(f"Created output directory: {output_dir}")
@ -232,7 +234,7 @@ async def generate_podcast_command(
result = await create_podcast(
content=input_data.content,
briefing=briefing,
episode_name=input_data.episode_name,
episode_name=episode_dir_name,
output_dir=str(output_dir),
speaker_config=speaker_profile.name,
episode_profile=episode_profile.name,

View file

@ -0,0 +1,74 @@
"""
Tests for podcast episode directory path generation.
Verifies that episode output directories use UUID-based names
instead of raw episode names, preventing filesystem issues with
spaces and special characters (GitHub issue #663).
"""
import uuid
from pathlib import Path, PurePosixPath
def _build_episode_output_dir(data_folder: str) -> tuple[str, Path]:
"""Replicate the directory naming logic from generate_podcast_command."""
episode_dir_name = str(uuid.uuid4())
output_dir = Path(f"{data_folder}/podcasts/episodes/{episode_dir_name}")
return episode_dir_name, output_dir
class TestPodcastEpisodeDirectory:
"""Verify that episode directories are always filesystem-safe."""
def test_directory_uses_uuid_format(self):
dir_name, _ = _build_episode_output_dir("/data")
# Should be a valid UUID
parsed = uuid.UUID(dir_name)
assert str(parsed) == dir_name
def test_directory_path_is_valid(self):
_, output_dir = _build_episode_output_dir("/data")
# Path should have exactly the expected structure
assert str(output_dir).startswith("/data/podcasts/episodes/")
# Directory name should be the last component
assert len(output_dir.name) == 36 # UUID string length
def test_no_collision_between_calls(self):
dir1, _ = _build_episode_output_dir("/data")
dir2, _ = _build_episode_output_dir("/data")
assert dir1 != dir2
def test_path_has_no_spaces_or_special_chars(self):
"""Regardless of episode name, path should be clean."""
problematic_names = [
"My Episode Name",
"Episode: Part 1",
'test "quotes"',
"path/traversal",
"dots..and...more",
"café résumé",
" spaces ",
"",
"?*<>|",
]
for name in problematic_names:
dir_name, output_dir = _build_episode_output_dir("/data")
# UUID path is independent of the episode name
path_str = str(output_dir)
assert " " not in path_str, f"Space found in path for name: {name}"
for char in ['<', '>', ':', '"', '|', '?', '*']:
assert char not in path_str, (
f"Unsafe char '{char}' in path for name: {name}"
)
def test_path_works_on_posix(self):
_, output_dir = _build_episode_output_dir("/data")
posix = PurePosixPath(str(output_dir))
assert posix.parts == ("/", "data", "podcasts", "episodes", output_dir.name)
def test_uuid_directory_can_be_created(self, tmp_path):
"""Actually create the directory to verify it works on the real filesystem."""
dir_name, output_dir = _build_episode_output_dir(str(tmp_path))
output_dir.mkdir(parents=True, exist_ok=True)
assert output_dir.exists()
assert output_dir.is_dir()