diff --git a/src/hooks/useAudio.ts b/src/hooks/useAudio.ts index d8441ad..a51f4f0 100644 --- a/src/hooks/useAudio.ts +++ b/src/hooks/useAudio.ts @@ -348,7 +348,7 @@ async function play(episode: Episode): Promise { volume: vol, speed: spd, startPosition: startPos > 0 ? startPos : undefined, - mediaTitle: podcastTitle ? `${podcastTitle} — ${episode.title}` : episode.title, + mediaTitle: episode.title, coverArtPath: coverArtPath ?? undefined, }); @@ -444,9 +444,7 @@ async function load(episode: Episode): Promise { volume: volume(), speed: storeSpeed || speed(), startPosition: pos > 0 ? pos : undefined, - mediaTitle: podcastTitle - ? `${podcastTitle} — ${episode.title}` - : episode.title, +mediaTitle: episode.title, coverArtPath: coverArtPath ?? undefined, }) .catch(() => {}); @@ -620,9 +618,7 @@ async function switchBackend(name: BackendName): Promise { startPosition: pos, volume: vol, speed: spd, - mediaTitle: podcastTitle - ? `${podcastTitle} — ${ep.title}` - : ep.title, +mediaTitle: ep.title, coverArtPath: coverArtPath ?? undefined, }); setIsPlaying(true); diff --git a/src/index.tsx b/src/index.tsx index 9235826..5df82ac 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -195,7 +195,7 @@ async function handlePlay(feeds: Feed[], arg: string): Promise { ? await fetchCoverArt(coverUrl) : null await backend.play(episodeResult.audioUrl, { - mediaTitle: `${feedResult.podcast.title} — ${episodeResult.title}`, + mediaTitle: episodeResult.title, coverArtPath: coverArtPath ?? undefined, }) console.log("Playback started (use the UI to control)") diff --git a/src/stores/download.ts b/src/stores/download.ts index b997dcc..3989463 100644 --- a/src/stores/download.ts +++ b/src/stores/download.ts @@ -269,6 +269,39 @@ function createDownloadStore() { .exited.catch(() => {}); } } + + // Tag the local file (codec-copy, no re-encode) so mpv's Now + // Playing metadata for local playback is title=episode, + // artist=podcast — the source streams carry no usable tags and + // macOS composes "title - artist" from exactly these fields. + // Atomic: ffmpeg writes a temp file, then renames into place. + if (result.filePath && episode) { + const podcastTitle = + feedStore.feeds().find((f) => f.id === item.feedId)?.podcast.title ?? + downloads().get(item.episodeId)?.podcastTitle; + if (podcastTitle) { + const tmp = `${result.filePath}.tag`; + Bun.spawn([ + "ffmpeg", + "-y", + "-i", + result.filePath, + "-c", + "copy", + "-metadata", + `title=${episode.title}`, + "-metadata", + `artist=${podcastTitle}`, + tmp, + ]) + .exited.then(async (code) => { + if (code !== 0) return; + const { renameSync } = await import("node:fs"); + renameSync(tmp, result.filePath); + }) + .catch(() => {}); + } + } } else { updateDownload(item.episodeId, { status: DownloadStatus.FAILED,