mirror of
https://github.com/navidrome/navidrome.git
synced 2026-11-04 01:08:54 +01:00
Backend - Add podcast data model (PodcastChannel, PodcastEpisode) with migrations - Implement Subsonic API endpoints: getPodcasts, getNewestPodcasts, createPodcastChannel, refreshPodcasts, deletePodcastChannel, deletePodcastEpisode, downloadPodcastEpisode, getPodcastEpisode - Add native REST API endpoints: GET/DELETE /api/podcast, GET /api/podcast/preview (feed info without creating channel) - Inject events.Broker into podcast service for SSE support - Emit PodcastEpisodeProgress SSE events during download (every 512 KB) and on completion/error with status field - Use HTTP Content-Length as fallback when RSS feed omits enclosure size - Write ID3 tags (title, album, genre=Podcast) to downloaded files via ffmpeg so the library scanner reads correct metadata - Set MediaFile fields (Title, Album, AlbumID=channelId, AlbumArtist, Genre) on episode registration - Add duplicate URL check in AddChannel - Add ExistsByURL to PodcastChannelRepository Frontend - Podcast list - Add grid/list view toggle (Redux podcastViewReducer) matching album list - New PodcastGridView component with responsive column count (2-6 cols) - Cover image 100px in table view - Remove Feed URL column; add inline copy-to-clipboard button Frontend - Podcast show (episode list) - Real-time download progress (%) in Status column via SSE, no polling - Spinner only before first SSE event; N% once data arrives - Size column removed; Downloading badge replaced with progress - Completed episodes play on row click; separate play button removed - Play / Shuffle / Play Next / Add to Queue buttons above episode list (only shown when completed episodes exist) - On download completion, reload episodes to obtain streamId for immediate playback without page refresh - Clicking album name in AudioTitle navigates to podcast channel page Frontend - Podcast create - Full-width URL input with Fetch Feed Info button and Enter key support - Preview card (cover image, title, episode count, description) before committing channel creation - Add Channel button appears only after preview; shows already-registered message if channel URL exists Frontend - Playlist - Album link navigates to podcast channel page for podcast tracks (identified by genre=Podcast) - Artist column shows '-' for podcast tracks with empty artist field Closes #5420 Signed-off-by: ji-ho lee <search5@gmail.com>
260 lines
11 KiB
Go
260 lines
11 KiB
Go
// Code generated by Wire. DO NOT EDIT.
|
|
|
|
//go:generate go run -mod=mod github.com/google/wire/cmd/wire gen -tags "netgo sqlite_fts5"
|
|
//go:build !wireinject
|
|
// +build !wireinject
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"context"
|
|
"github.com/google/wire"
|
|
"github.com/navidrome/navidrome/adapters/lastfm"
|
|
"github.com/navidrome/navidrome/adapters/listenbrainz"
|
|
"github.com/navidrome/navidrome/core"
|
|
"github.com/navidrome/navidrome/core/agents"
|
|
"github.com/navidrome/navidrome/core/artwork"
|
|
"github.com/navidrome/navidrome/core/external"
|
|
"github.com/navidrome/navidrome/core/ffmpeg"
|
|
"github.com/navidrome/navidrome/core/lyrics"
|
|
"github.com/navidrome/navidrome/core/matcher"
|
|
"github.com/navidrome/navidrome/core/metrics"
|
|
"github.com/navidrome/navidrome/core/playback"
|
|
"github.com/navidrome/navidrome/core/podcasts"
|
|
"github.com/navidrome/navidrome/core/playlists"
|
|
"github.com/navidrome/navidrome/core/scrobbler"
|
|
"github.com/navidrome/navidrome/core/sonic"
|
|
"github.com/navidrome/navidrome/core/stream"
|
|
"github.com/navidrome/navidrome/db"
|
|
"github.com/navidrome/navidrome/model"
|
|
"github.com/navidrome/navidrome/persistence"
|
|
"github.com/navidrome/navidrome/plugins"
|
|
"github.com/navidrome/navidrome/scanner"
|
|
"github.com/navidrome/navidrome/server"
|
|
"github.com/navidrome/navidrome/server/events"
|
|
"github.com/navidrome/navidrome/server/jellyfin"
|
|
"github.com/navidrome/navidrome/server/nativeapi"
|
|
"github.com/navidrome/navidrome/server/public"
|
|
"github.com/navidrome/navidrome/server/subsonic"
|
|
)
|
|
|
|
import (
|
|
_ "github.com/navidrome/navidrome/adapters/deezer"
|
|
_ "github.com/navidrome/navidrome/adapters/gotaglib"
|
|
_ "github.com/navidrome/navidrome/adapters/lastfm"
|
|
_ "github.com/navidrome/navidrome/adapters/listenbrainz"
|
|
)
|
|
|
|
// Injectors from wire_injectors.go:
|
|
|
|
func CreateDataStore() model.DataStore {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
return dataStore
|
|
}
|
|
|
|
func CreateServer() *server.Server {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
broker := events.GetBroker()
|
|
insights := metrics.GetInstance(dataStore)
|
|
serverServer := server.New(dataStore, broker, insights)
|
|
return serverServer
|
|
}
|
|
|
|
func CreateNativeAPIRouter(ctx context.Context) *nativeapi.Router {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
share := core.NewShare(dataStore)
|
|
uploader := artwork.NewUploader(dataStore)
|
|
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
|
insights := metrics.GetInstance(dataStore)
|
|
broker := events.GetBroker()
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
modelScanner := scanner.GetInstance(ctx, dataStore, broker, playlistsPlaylists, metricsMetrics)
|
|
watcher := scanner.GetWatcher(dataStore, modelScanner)
|
|
manager := plugins.GetManager(dataStore, broker, metricsMetrics)
|
|
library := core.NewLibrary(dataStore, modelScanner, watcher, broker, manager)
|
|
user := core.NewUser(dataStore, manager)
|
|
maintenance := core.NewMaintenance(dataStore)
|
|
agentsAgents := agents.GetAgents(dataStore, manager)
|
|
matcherMatcher := matcher.New(dataStore)
|
|
provider := external.NewProvider(dataStore, agentsAgents, matcherMatcher, broker)
|
|
router := nativeapi.New(dataStore, share, playlistsPlaylists, insights, library, user, maintenance, manager, uploader, provider)
|
|
return router
|
|
}
|
|
|
|
func CreateSubsonicAPIRouter(ctx context.Context) *subsonic.Router {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
fileCache := artwork.GetImageCache()
|
|
imageStore := artwork.GetImageStore()
|
|
fFmpeg := ffmpeg.New()
|
|
artworkArtwork := artwork.NewArtwork(dataStore, fileCache, imageStore, fFmpeg)
|
|
transcodingCache := stream.GetTranscodingCache()
|
|
mediaStreamer := stream.NewMediaStreamer(dataStore, fFmpeg, transcodingCache)
|
|
share := core.NewShare(dataStore)
|
|
archiver := core.NewArchiver(mediaStreamer, dataStore, share)
|
|
players := core.NewPlayers(dataStore)
|
|
broker := events.GetBroker()
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
manager := plugins.GetManager(dataStore, broker, metricsMetrics)
|
|
agentsAgents := agents.GetAgents(dataStore, manager)
|
|
matcherMatcher := matcher.New(dataStore)
|
|
provider := external.NewProvider(dataStore, agentsAgents, matcherMatcher, broker)
|
|
uploader := artwork.NewUploader(dataStore)
|
|
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
|
modelScanner := scanner.GetInstance(ctx, dataStore, broker, playlistsPlaylists, metricsMetrics)
|
|
playTracker := scrobbler.GetPlayTracker(dataStore, broker, manager)
|
|
playbackServer := playback.GetInstance(dataStore)
|
|
lyricsLyrics := lyrics.NewLyrics(dataStore, manager)
|
|
transcodeDecider := stream.NewTranscodeDecider(dataStore, fFmpeg)
|
|
podcastService := podcasts.NewPodcastService(dataStore, fFmpeg, broker)
|
|
sonicSonic := sonic.New(dataStore, manager, matcherMatcher)
|
|
router := subsonic.New(dataStore, artworkArtwork, mediaStreamer, archiver, players, provider, modelScanner, broker, playlistsPlaylists, playTracker, share, playbackServer, metricsMetrics, lyricsLyrics, transcodeDecider, podcastService, sonicSonic)
|
|
return router
|
|
}
|
|
|
|
func CreateJellyfinAPIRouter(ctx context.Context) *jellyfin.Router {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
fileCache := artwork.GetImageCache()
|
|
imageStore := artwork.GetImageStore()
|
|
fFmpeg := ffmpeg.New()
|
|
artworkArtwork := artwork.NewArtwork(dataStore, fileCache, imageStore, fFmpeg)
|
|
transcodingCache := stream.GetTranscodingCache()
|
|
mediaStreamer := stream.NewMediaStreamer(dataStore, fFmpeg, transcodingCache)
|
|
transcodeDecider := stream.NewTranscodeDecider(dataStore, fFmpeg)
|
|
players := core.NewPlayers(dataStore)
|
|
broker := events.GetBroker()
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
manager := plugins.GetManager(dataStore, broker, metricsMetrics)
|
|
playTracker := scrobbler.GetPlayTracker(dataStore, broker, manager)
|
|
uploader := artwork.NewUploader(dataStore)
|
|
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
|
agentsAgents := agents.GetAgents(dataStore, manager)
|
|
matcherMatcher := matcher.New(dataStore)
|
|
provider := external.NewProvider(dataStore, agentsAgents, matcherMatcher, broker)
|
|
sonicSonic := sonic.New(dataStore, manager, matcherMatcher)
|
|
lyricsLyrics := lyrics.NewLyrics(dataStore, manager)
|
|
router := jellyfin.New(dataStore, artworkArtwork, mediaStreamer, transcodeDecider, players, playTracker, playlistsPlaylists, provider, sonicSonic, lyricsLyrics, broker)
|
|
return router
|
|
}
|
|
|
|
func CreatePublicRouter() *public.Router {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
fileCache := artwork.GetImageCache()
|
|
imageStore := artwork.GetImageStore()
|
|
fFmpeg := ffmpeg.New()
|
|
artworkArtwork := artwork.NewArtwork(dataStore, fileCache, imageStore, fFmpeg)
|
|
transcodingCache := stream.GetTranscodingCache()
|
|
mediaStreamer := stream.NewMediaStreamer(dataStore, fFmpeg, transcodingCache)
|
|
share := core.NewShare(dataStore)
|
|
archiver := core.NewArchiver(mediaStreamer, dataStore, share)
|
|
router := public.New(dataStore, artworkArtwork, mediaStreamer, share, archiver)
|
|
return router
|
|
}
|
|
|
|
func CreateLastFMRouter() *lastfm.Router {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
router := lastfm.NewRouter(dataStore)
|
|
return router
|
|
}
|
|
|
|
func CreateListenBrainzRouter() *listenbrainz.Router {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
router := listenbrainz.NewRouter(dataStore)
|
|
return router
|
|
}
|
|
|
|
func CreateInsights() metrics.Insights {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
insights := metrics.GetInstance(dataStore)
|
|
return insights
|
|
}
|
|
|
|
func CreatePrometheus() metrics.Metrics {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
return metricsMetrics
|
|
}
|
|
|
|
func CreateScanner(ctx context.Context) model.Scanner {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
broker := events.GetBroker()
|
|
uploader := artwork.NewUploader(dataStore)
|
|
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
modelScanner := scanner.GetInstance(ctx, dataStore, broker, playlistsPlaylists, metricsMetrics)
|
|
return modelScanner
|
|
}
|
|
|
|
func CreateScanWatcher(ctx context.Context) scanner.Watcher {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
broker := events.GetBroker()
|
|
uploader := artwork.NewUploader(dataStore)
|
|
playlistsPlaylists := playlists.NewPlaylists(dataStore, uploader)
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
modelScanner := scanner.GetInstance(ctx, dataStore, broker, playlistsPlaylists, metricsMetrics)
|
|
watcher := scanner.GetWatcher(dataStore, modelScanner)
|
|
return watcher
|
|
}
|
|
|
|
func GetPlaybackServer() playback.PlaybackServer {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
playbackServer := playback.GetInstance(dataStore)
|
|
return playbackServer
|
|
}
|
|
|
|
func CreateArtworkWorker() *artwork.Worker {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
imageStore := artwork.GetImageStore()
|
|
broker := events.GetBroker()
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
manager := plugins.GetManager(dataStore, broker, metricsMetrics)
|
|
agentsAgents := agents.GetAgents(dataStore, manager)
|
|
fFmpeg := ffmpeg.New()
|
|
fileCache := artwork.GetImageCache()
|
|
worker := artwork.NewWorker(dataStore, imageStore, agentsAgents, fFmpeg, broker, fileCache)
|
|
return worker
|
|
}
|
|
|
|
func CreateArtworkResolver(trace *artwork.ChainTrace, live bool) *artwork.TracingResolver {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
broker := events.GetBroker()
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
manager := plugins.GetManager(dataStore, broker, metricsMetrics)
|
|
agentsAgents := agents.GetAgents(dataStore, manager)
|
|
fFmpeg := ffmpeg.New()
|
|
tracingResolver := artwork.NewTracingResolver(dataStore, agentsAgents, fFmpeg, trace, live)
|
|
return tracingResolver
|
|
}
|
|
|
|
func getPluginManager() *plugins.Manager {
|
|
sqlDB := db.Db()
|
|
dataStore := persistence.New(sqlDB)
|
|
broker := events.GetBroker()
|
|
metricsMetrics := metrics.GetPrometheusInstance(dataStore)
|
|
manager := plugins.GetManager(dataStore, broker, metricsMetrics)
|
|
return manager
|
|
}
|
|
|
|
// wire_injectors.go:
|
|
|
|
var allProviders = wire.NewSet(core.Set, artwork.Set, server.New, subsonic.New, jellyfin.New, nativeapi.New, public.New, persistence.New, lastfm.NewRouter, listenbrainz.NewRouter, events.GetBroker, scanner.GetInstance, scanner.GetWatcher, metrics.GetPrometheusInstance, db.Db, plugins.GetManager, sonic.New, wire.Bind(new(agents.PluginLoader), new(*plugins.Manager)), wire.Bind(new(scrobbler.PluginLoader), new(*plugins.Manager)), wire.Bind(new(lyrics.PluginLoader), new(*plugins.Manager)), wire.Bind(new(sonic.PluginLoader), new(*plugins.Manager)), wire.Bind(new(sonic.Engine), new(*sonic.Sonic)), wire.Bind(new(nativeapi.PluginManager), new(*plugins.Manager)), wire.Bind(new(core.PluginUnloader), new(*plugins.Manager)), wire.Bind(new(plugins.PluginMetricsRecorder), new(metrics.Metrics)), wire.Bind(new(core.Watcher), new(scanner.Watcher)), wire.Bind(new(playlists.ImageUploadService), new(artwork.Uploader)))
|
|
|
|
func GetPluginManager(ctx context.Context) *plugins.Manager {
|
|
manager := getPluginManager()
|
|
manager.SetSubsonicRouter(CreateSubsonicAPIRouter(ctx))
|
|
return manager
|
|
}
|