Skip to content

Commit e890554

Browse files
authored
Merge pull request #6 from shellord/dev
fix: s3 file upload title issue
2 parents d848ab2 + 4a1353f commit e890554

11 files changed

Lines changed: 85 additions & 46 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,7 @@ docker exec infra-k3s-server-1 kubectl get pvc -n notepod
264264
# Check notepod-ai logs
265265
kubectl -n notepod logs -f -l app=notepod-ai --all-containers --prefix --tail=1000
266266
```
267+
268+
### SSH port forward for rds
269+
270+
ssh -L 5432:notepod-postgres.cnmw0e08ykfs.ap-south-1.rds.amazonaws.com:5432 -i ~/.ssh/notepod-key.pem ubuntu@notepod.saheen.dev

mobile/src/app/(home)/index.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,13 @@ const PodcastList = ({
6868
);
6969

7070
export default function HomeScreen() {
71-
const { currentPodcast, stopPlayback, togglePlayback, isPlaying } =
72-
usePlayer();
71+
const {
72+
currentPodcast,
73+
stopPlayback,
74+
togglePlayback,
75+
isPlaying,
76+
currentTime,
77+
} = usePlayer();
7378
const { data: podcastsResponse, isLoading, error } = useGetPodcasts();
7479
const deletePodcastMutation = useDeletePodcast();
7580
const router = useRouter();
@@ -166,6 +171,7 @@ export default function HomeScreen() {
166171
onClosePress={handleClose}
167172
isPlaying={isPlaying}
168173
onTogglePlay={togglePlayback}
174+
currentTime={currentTime}
169175
/>
170176
)}
171177
<BlurView

mobile/src/components/NowPlaying.tsx

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@ import PlayButton from "./PlayButton";
55
import { router } from "expo-router";
66
import { Podcast } from "~/lib/api";
77
import { typeIcon } from "./PodcastIcon";
8+
import { formatTime } from "~/lib/utils";
89

910
type Props = {
1011
podcast: Podcast;
1112
onClosePress: () => void;
1213
isPlaying?: boolean;
1314
onTogglePlay: () => void;
15+
currentTime: number;
1416
};
1517

1618
const NowPlaying = ({
1719
podcast,
1820
onClosePress,
1921
isPlaying,
2022
onTogglePlay,
23+
currentTime = 0,
2124
}: Props) => {
2225
return (
2326
<Pressable
@@ -35,18 +38,23 @@ const NowPlaying = ({
3538
</View>
3639

3740
<View className="flex-row items-center gap-4">
38-
<PlayButton
39-
onPress={onTogglePlay}
40-
size={32}
41-
iconSize={16}
42-
isPlaying={isPlaying}
43-
/>
44-
<Pressable
41+
<View className="flex-row items-center">
42+
<Font className="text-sm text-gray-400">
43+
{formatTime(currentTime)}
44+
</Font>
45+
<PlayButton
46+
onPress={onTogglePlay}
47+
size={32}
48+
iconSize={16}
49+
isPlaying={isPlaying}
50+
/>
51+
</View>
52+
{/* <Pressable
4553
onPress={onClosePress}
4654
className="w-8 h-8 items-center justify-center"
4755
>
4856
<Ionicons name="close" size={20} color="white" />
49-
</Pressable>
57+
</Pressable> */}
5058
</View>
5159
</View>
5260
</Pressable>

mobile/src/components/PlayButton.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const PlayButton = ({
1717
iconSize = 20,
1818
className = "",
1919
isPlaying = false,
20+
color = "white",
2021
}: PlayButtonProps) => {
2122
return (
2223
<Button
@@ -27,7 +28,7 @@ const PlayButton = ({
2728
<Ionicons
2829
name={isPlaying ? "pause" : "play"}
2930
size={iconSize}
30-
color="white"
31+
color={color}
3132
/>
3233
</Button>
3334
);

mobile/src/components/PodcastCard.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const PodcastCard = ({ podcast, onDelete }: Props) => {
3737
const shimmerOpacity = useSharedValue(0.3);
3838

3939
// Determine if podcast is playable (completed and has audio)
40-
const isPlayable = podcast.status === "COMPLETED" && podcast.audioUrl;
40+
const isPlayable = podcast.status === "COMPLETED" && !!podcast.audioUrl;
4141

4242
// Determine if podcast can be deleted (not currently processing)
4343
const isDeletable =
@@ -229,7 +229,7 @@ const PodcastCard = ({ podcast, onDelete }: Props) => {
229229
{/* Main Card */}
230230
<GestureDetector gesture={panGesture}>
231231
<Animated.View style={cardStyle}>
232-
<Button onPress={handleCardPress}>
232+
<Button onPress={handleCardPress} disabled={!isPlayable}>
233233
<View className="flex-row items-center py-4 bg-white/5 rounded-xl h-16">
234234
{/* TYPE ICON */}
235235
<View className="w-16 h-16 bg-white/10 items-center justify-center mr-4">
@@ -267,9 +267,13 @@ const PodcastCard = ({ podcast, onDelete }: Props) => {
267267
isPlaying={playing}
268268
/>
269269
) : (
270-
<View className="w-10 h-10 rounded-full bg-white/10 items-center justify-center">
271-
<Ionicons name="play" size={16} color="#ffffff40" />
272-
</View>
270+
<PlayButton
271+
onPress={handlePlay}
272+
iconSize={20}
273+
size={40}
274+
isPlaying={playing}
275+
color="gray"
276+
/>
273277
)}
274278
</View>
275279
</Button>

mobile/src/components/Slider.tsx

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { View, Animated, PanResponder } from "react-native";
22
import React, { useRef, useState, useEffect } from "react";
3+
import { formatTime } from "~/lib/utils";
34

45
interface SliderProps {
56
currentTime?: number;
@@ -12,14 +13,6 @@ const Slider: React.FC<SliderProps> = ({
1213
totalDuration = 296,
1314
onTimeChange,
1415
}) => {
15-
const formatTime = (seconds: number) => {
16-
if (isNaN(seconds) || seconds < 0) seconds = 0;
17-
const totalSeconds = Math.floor(seconds);
18-
const mins = Math.floor(totalSeconds / 60);
19-
const secs = totalSeconds % 60;
20-
return `${mins}:${secs.toString().padStart(2, "0")}`;
21-
};
22-
2316
const [sliderWidth, setSliderWidth] = useState(0);
2417
const [isDragging, setIsDragging] = useState(false);
2518
const [internalTime, setInternalTime] = useState(currentTime);

mobile/src/hooks/usePlayer.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ export const PlayerProvider = ({ children }: PlayerProviderProps) => {
7777
useEffect(() => {
7878
if (audioPlayerStatus.didJustFinish) {
7979
setIsPlaying(false);
80+
// Reset to beginning when audio finishes
81+
player.seekTo(0);
8082
}
81-
}, [audioPlayerStatus.didJustFinish]);
83+
}, [audioPlayerStatus.didJustFinish, player]);
8284

8385
const playPodcast = useCallback((podcast: Podcast) => {
8486
if (!podcast.audioUrl) return;
@@ -89,8 +91,14 @@ export const PlayerProvider = ({ children }: PlayerProviderProps) => {
8991

9092
const togglePlayback = useCallback(() => {
9193
if (!currentPodcast) return;
94+
95+
// If audio finished playing, reset to beginning before playing
96+
if (!isPlaying && currentTime >= duration && duration > 0) {
97+
player.seekTo(0);
98+
}
99+
92100
setIsPlaying((prev) => !prev);
93-
}, [currentPodcast]);
101+
}, [currentPodcast, isPlaying, currentTime, duration, player]);
94102

95103
const stopPlayback = useCallback(() => {
96104
setIsPlaying(false);

mobile/src/lib/utils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const formatTime = (seconds: number) => {
2+
if (isNaN(seconds) || seconds < 0) seconds = 0;
3+
const totalSeconds = Math.floor(seconds);
4+
const mins = Math.floor(totalSeconds / 60);
5+
const secs = totalSeconds % 60;
6+
return `${mins}:${secs.toString().padStart(2, "0")}`;
7+
};
8+
9+
export { formatTime };

podcast-ai/app.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ async def handle_podcast_generation(data: Dict[str, Any]) -> None:
9393
duration=request.duration,
9494
language=request.language,
9595
start_page=start_page,
96-
end_page=end_page
96+
end_page=end_page,
97+
podcast_id=request.id
9798
)
9899

99100
# Step 3: Publish COMPLETED status with results
@@ -165,7 +166,8 @@ async def generate_podcast(request: PodcastRequest):
165166
duration=request.duration,
166167
language=request.language,
167168
start_page=start_page,
168-
end_page=end_page
169+
end_page=end_page,
170+
podcast_id=request.id
169171
)
170172
return PodcastResponse(
171173
id=request.id,

podcast-ai/services/podcast.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def _duration_to_minutes(self, duration: PodcastDuration) -> int:
4949
}
5050
return duration_mapping[duration]
5151

52-
def generate_podcast_from_pdf(self, pdf_url: str, speakers: list[SpeakerWithVoiceId], duration: PodcastDuration = PodcastDuration.SHORT, language: str = "english", start_page: Optional[int] = None, end_page: Optional[int] = None) -> Podcast:
52+
def generate_podcast_from_pdf(self, pdf_url: str, speakers: list[SpeakerWithVoiceId], duration: PodcastDuration = PodcastDuration.SHORT, language: str = "english", start_page: Optional[int] = None, end_page: Optional[int] = None, podcast_id: Optional[str] = None) -> Podcast:
5353
"""
5454
Generate a podcast for a given PDF file and speakers.
5555
@@ -106,11 +106,11 @@ def generate_podcast_from_pdf(self, pdf_url: str, speakers: list[SpeakerWithVoic
106106
elif end_page:
107107
title += f" (Up to Page {end_page})"
108108

109-
url = self._s3_storage_service.upload_podcast_audio(audio, title)
109+
url = self._s3_storage_service.upload_podcast_audio(audio, title, podcast_id)
110110

111111
return Podcast(title=title, duration_minutes=self.calculate_audio_duration(audio), audio_url=url, script=script_response.script)
112112

113-
def generate_podcast_from_image(self, image_url: str, speakers: list[SpeakerWithVoiceId], duration: PodcastDuration = PodcastDuration.SHORT, language: str = "english") -> Podcast:
113+
def generate_podcast_from_image(self, image_url: str, speakers: list[SpeakerWithVoiceId], duration: PodcastDuration = PodcastDuration.SHORT, language: str = "english", podcast_id: Optional[str] = None) -> Podcast:
114114
"""
115115
Generate a podcast for a given image and speakers.
116116
@@ -164,11 +164,11 @@ def generate_podcast_from_image(self, image_url: str, speakers: list[SpeakerWith
164164

165165
# Use AI-generated title or create a fallback
166166
title = script_response.title if script_response.title else "Image Analysis Podcast"
167-
url = self._s3_storage_service.upload_podcast_audio(audio, title)
167+
url = self._s3_storage_service.upload_podcast_audio(audio, title, podcast_id)
168168

169169
return Podcast(title=title, duration_minutes=self.calculate_audio_duration(audio), audio_url=url, script=script_response.script)
170170

171-
def generate_podcast_from_topic(self, topic: str, speakers: list[SpeakerWithVoiceId], duration: PodcastDuration = PodcastDuration.SHORT, language: str = "english") -> Podcast:
171+
def generate_podcast_from_topic(self, topic: str, speakers: list[SpeakerWithVoiceId], duration: PodcastDuration = PodcastDuration.SHORT, language: str = "english", podcast_id: Optional[str] = None) -> Podcast:
172172
"""
173173
Generate a podcast for a given topic and speakers.
174174
"""
@@ -195,11 +195,11 @@ def generate_podcast_from_topic(self, topic: str, speakers: list[SpeakerWithVoic
195195
))
196196

197197
audio = self._elevenlabs_client.generate_podcast_audio(conversations)
198-
url = self._s3_storage_service.upload_podcast_audio(audio, topic)
198+
url = self._s3_storage_service.upload_podcast_audio(audio, topic, podcast_id)
199199

200200
return Podcast(title=topic, duration_minutes=self.calculate_audio_duration(audio), audio_url=url, script=script.script)
201201

202-
def generate_podcast_from_website(self, website_url: str, speakers: list[SpeakerWithVoiceId], duration: PodcastDuration = PodcastDuration.SHORT, language: str = "english") -> Podcast:
202+
def generate_podcast_from_website(self, website_url: str, speakers: list[SpeakerWithVoiceId], duration: PodcastDuration = PodcastDuration.SHORT, language: str = "english", podcast_id: Optional[str] = None) -> Podcast:
203203
"""
204204
Generate a podcast for a given website and speakers.
205205
@@ -245,7 +245,7 @@ def generate_podcast_from_website(self, website_url: str, speakers: list[Speaker
245245

246246
# Use the website URL as title
247247
title = website_url
248-
url = self._s3_storage_service.upload_podcast_audio(audio, title)
248+
url = self._s3_storage_service.upload_podcast_audio(audio, title, podcast_id)
249249

250250
return Podcast(title=title, duration_minutes=self.calculate_audio_duration(audio), audio_url=url, script=script.script)
251251

@@ -255,15 +255,18 @@ def generate_podcast(self,
255255
duration: PodcastDuration = PodcastDuration.SHORT,
256256
language: str = "english",
257257
start_page: Optional[int] = None,
258-
end_page: Optional[int] = None) -> Podcast:
258+
end_page: Optional[int] = None,
259+
podcast_id: Optional[str] = None) -> Podcast:
259260
if type == PodcastType.TOPIC:
260-
return self.generate_podcast_from_topic(source_content, speakers, duration, language)
261+
return self.generate_podcast_from_topic(source_content, speakers, duration, language, podcast_id)
261262
elif type == PodcastType.PDF_DOCUMENT:
262-
return self.generate_podcast_from_pdf(source_content, speakers, duration, language, start_page, end_page)
263+
return self.generate_podcast_from_pdf(source_content, speakers, duration, language, start_page, end_page, podcast_id)
263264
elif type == PodcastType.PHOTO:
264-
return self.generate_podcast_from_image(source_content, speakers, duration, language)
265+
return self.generate_podcast_from_image(source_content, speakers, duration, language, podcast_id)
265266
elif type == PodcastType.WEBSITE_LINK:
266-
return self.generate_podcast_from_website(source_content, speakers, duration, language)
267+
return self.generate_podcast_from_website(source_content, speakers, duration, language, podcast_id)
268+
else:
269+
raise ValueError(f"Unsupported podcast type: {type}")
267270

268271
def calculate_audio_duration(self, audio: bytes) -> int:
269272
"""

0 commit comments

Comments
 (0)