fix(now-playing): episode as title, podcast as artist on both paths
The card put the podcast name in the title slot ('Podcast — Episode'
prefix truncating on long names) with an empty artist slot for local
playback — the downloaded files carry no artist tag. Now:
- mediaTitle is the episode title only (UI + CLI); streams keep their
artist from stream tags, matching Apple Podcasts' title/artist layout.
- downloads are tagged at completion (ffmpeg -c copy, atomic rename):
title=episode, artist=podcast — verified on a real 72MB file in 1.5s.
This commit is contained in:
@@ -348,7 +348,7 @@ async function play(episode: Episode): Promise<void> {
|
|||||||
volume: vol,
|
volume: vol,
|
||||||
speed: spd,
|
speed: spd,
|
||||||
startPosition: startPos > 0 ? startPos : undefined,
|
startPosition: startPos > 0 ? startPos : undefined,
|
||||||
mediaTitle: podcastTitle ? `${podcastTitle} — ${episode.title}` : episode.title,
|
mediaTitle: episode.title,
|
||||||
coverArtPath: coverArtPath ?? undefined,
|
coverArtPath: coverArtPath ?? undefined,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -444,9 +444,7 @@ async function load(episode: Episode): Promise<void> {
|
|||||||
volume: volume(),
|
volume: volume(),
|
||||||
speed: storeSpeed || speed(),
|
speed: storeSpeed || speed(),
|
||||||
startPosition: pos > 0 ? pos : undefined,
|
startPosition: pos > 0 ? pos : undefined,
|
||||||
mediaTitle: podcastTitle
|
mediaTitle: episode.title,
|
||||||
? `${podcastTitle} — ${episode.title}`
|
|
||||||
: episode.title,
|
|
||||||
coverArtPath: coverArtPath ?? undefined,
|
coverArtPath: coverArtPath ?? undefined,
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
@@ -620,9 +618,7 @@ async function switchBackend(name: BackendName): Promise<void> {
|
|||||||
startPosition: pos,
|
startPosition: pos,
|
||||||
volume: vol,
|
volume: vol,
|
||||||
speed: spd,
|
speed: spd,
|
||||||
mediaTitle: podcastTitle
|
mediaTitle: ep.title,
|
||||||
? `${podcastTitle} — ${ep.title}`
|
|
||||||
: ep.title,
|
|
||||||
coverArtPath: coverArtPath ?? undefined,
|
coverArtPath: coverArtPath ?? undefined,
|
||||||
});
|
});
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ async function handlePlay(feeds: Feed[], arg: string): Promise<void> {
|
|||||||
? await fetchCoverArt(coverUrl)
|
? await fetchCoverArt(coverUrl)
|
||||||
: null
|
: null
|
||||||
await backend.play(episodeResult.audioUrl, {
|
await backend.play(episodeResult.audioUrl, {
|
||||||
mediaTitle: `${feedResult.podcast.title} — ${episodeResult.title}`,
|
mediaTitle: episodeResult.title,
|
||||||
coverArtPath: coverArtPath ?? undefined,
|
coverArtPath: coverArtPath ?? undefined,
|
||||||
})
|
})
|
||||||
console.log("Playback started (use the UI to control)")
|
console.log("Playback started (use the UI to control)")
|
||||||
|
|||||||
@@ -269,6 +269,39 @@ function createDownloadStore() {
|
|||||||
.exited.catch(() => {});
|
.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 {
|
} else {
|
||||||
updateDownload(item.episodeId, {
|
updateDownload(item.episodeId, {
|
||||||
status: DownloadStatus.FAILED,
|
status: DownloadStatus.FAILED,
|
||||||
|
|||||||
Reference in New Issue
Block a user