summaryrefslogtreecommitdiff
path: root/Biz
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-11-22 09:19:44 -0500
committerBen Sima <ben@bensima.com>2025-11-22 09:20:24 -0500
commit26b8a657d00e76d1e54eeaf78246b1bf4b03fb43 (patch)
treefd4728d499d3ca820da6cee61ea9abbfdd2a8b70 /Biz
parent96607ecb2a84bd2dccd203aa8860d43fc942bf66 (diff)
parentff3b744a05924c13cfd7c268d87e72ee6bdd0538 (diff)
Merge task t-1rf15iH: Audio crossfading
Diffstat (limited to 'Biz')
-rw-r--r--Biz/PodcastItLater/UI.py23
-rw-r--r--Biz/PodcastItLater/Worker.py28
2 files changed, 43 insertions, 8 deletions
diff --git a/Biz/PodcastItLater/UI.py b/Biz/PodcastItLater/UI.py
index 00cf5e3..6825fd7 100644
--- a/Biz/PodcastItLater/UI.py
+++ b/Biz/PodcastItLater/UI.py
@@ -408,10 +408,27 @@ class AccountPage(Component[AnyChildren, AccountPageAttrs]):
# User Info Section
html.div(
html.h5("Profile", classes=["mb-3"]),
- html.p(
+ html.div(
html.strong("Email: "),
- user.get("email", ""),
- classes=["mb-2"],
+ html.span(user.get("email", "")),
+ html.button(
+ "Change",
+ classes=[
+ "btn",
+ "btn-sm",
+ "btn-outline-secondary",
+ "ms-2",
+ "py-0",
+ ],
+ hx_get="/settings/email/edit",
+ hx_target="closest div",
+ hx_swap="outerHTML",
+ ),
+ classes=[
+ "mb-2",
+ "d-flex",
+ "align-items-center",
+ ],
),
html.p(
html.strong("Member since: "),
diff --git a/Biz/PodcastItLater/Worker.py b/Biz/PodcastItLater/Worker.py
index 3245fdd..251f614 100644
--- a/Biz/PodcastItLater/Worker.py
+++ b/Biz/PodcastItLater/Worker.py
@@ -60,6 +60,8 @@ MAX_RETRIES = 3
TTS_MODEL = "tts-1"
TTS_VOICE = "alloy"
MEMORY_THRESHOLD = 80 # Percentage threshold for memory usage
+CROSSFADE_DURATION = 500 # ms for crossfading segments
+PAUSE_DURATION = 1000 # ms for silence between segments
class ShutdownHandler:
@@ -358,7 +360,7 @@ class ArticleProcessor:
content_audio: bytes,
outro_audio: bytes,
) -> bytes:
- """Combine intro, content, and outro with 1-second pauses.
+ """Combine intro, content, and outro with crossfades.
Args:
intro_audio: MP3 bytes for intro
@@ -373,11 +375,27 @@ class ArticleProcessor:
content = AudioSegment.from_mp3(io.BytesIO(content_audio))
outro = AudioSegment.from_mp3(io.BytesIO(outro_audio))
- # Create 1-second silence
- pause = AudioSegment.silent(duration=1000) # milliseconds
+ # Create bridge silence (pause + 2 * crossfade to account for overlap)
+ bridge = AudioSegment.silent(duration=PAUSE_DURATION + 2 * CROSSFADE_DURATION)
- # Combine segments with pauses
- combined = intro + pause + content + pause + outro
+ def safe_append(seg1: AudioSegment, seg2: AudioSegment, crossfade: int) -> AudioSegment:
+ if len(seg1) < crossfade or len(seg2) < crossfade:
+ logger.warning(
+ "Segment too short for crossfade (%dms vs %dms/%dms), using concatenation",
+ crossfade,
+ len(seg1),
+ len(seg2),
+ )
+ return seg1 + seg2
+ return seg1.append(seg2, crossfade=crossfade)
+
+ # Combine segments with crossfades
+ # Intro -> Bridge -> Content -> Bridge -> Outro
+ # This effectively fades out the previous segment and fades in the next one
+ combined = safe_append(intro, bridge, CROSSFADE_DURATION)
+ combined = safe_append(combined, content, CROSSFADE_DURATION)
+ combined = safe_append(combined, bridge, CROSSFADE_DURATION)
+ combined = safe_append(combined, outro, CROSSFADE_DURATION)
# Export to bytes
output = io.BytesIO()