From 98c1dfa597885bd30e602d9008a6c8c9a529bd62 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sun, 28 Sep 2025 01:11:44 -0400 Subject: [PATCH 1/3] Add support for downloading all songs in albums --- src/components/itemContextMenu.js | 52 ++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 87655c718..b05622021 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -1,3 +1,5 @@ +import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; + import browser from '../scripts/browser'; import { copy } from '../scripts/clipboard'; import dom from '../utils/dom'; @@ -10,9 +12,15 @@ import itemHelper, { canEditPlaylist } from './itemHelper'; import { playbackManager } from './playback/playbackmanager'; import toast from './toast/toast'; import * as userSettings from '../scripts/settings/userSettings'; -import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind'; import { AppFeature } from 'constants/appFeature'; +/** Item types that support downloading all children. */ +const DOWNLOAD_ALL_TYPES = [ + BaseItemKind.MusicAlbum, + BaseItemKind.Season, + BaseItemKind.Series +]; + function getDeleteLabel(type) { switch (type) { case BaseItemKind.Series: @@ -172,7 +180,7 @@ export async function getCommands(options) { if (appHost.supports(AppFeature.FileDownload)) { // CanDownload should probably be updated to return true for these items? - if (user.Policy.EnableContentDownloading && (item.Type === 'Season' || item.Type == 'Series')) { + if (user.Policy.EnableContentDownloading && DOWNLOAD_ALL_TYPES.includes(item.Type)) { commands.push({ name: globalize.translate('DownloadAll'), id: 'downloadall', @@ -415,17 +423,17 @@ function executeCommand(item, id, options) { }); break; case 'downloadall': { - const downloadEpisodes = episodes => { + const downloadItems = items => { import('../scripts/fileDownloader').then((fileDownloader) => { - const downloads = episodes.map(episode => { - const downloadHref = apiClient.getItemDownloadUrl(episode.Id); + const downloads = items.map(item => { + const downloadHref = apiClient.getItemDownloadUrl(item.Id); return { url: downloadHref, - item: episode, - itemId: episode.Id, - serverId: serverId, - title: episode.Name, - filename: episode.Path.replace(/^.*[\\/]/, '') + item, + itemId: item.Id, + serverId, + title: item.Name, + filename: item.Path.replace(/^.*[\\/]/, '') }; }); @@ -441,17 +449,25 @@ function executeCommand(item, id, options) { }); } )).then(seasonData => { - downloadEpisodes(seasonData.map(season => season.Items).flat()); + downloadItems(seasonData.map(season => season.Items).flat()); }); }; - if (item.Type === 'Season') { - downloadSeasons([item]); - } else if (item.Type === 'Series') { - apiClient.getSeasons(item.Id, { - userId: options.user.Id, - Fields: 'ItemCounts' - }).then(seasons => downloadSeasons(seasons.Items)); + switch (item.Type) { + case BaseItemKind.MusicAlbum: + apiClient.getItems(options.user.Id, { + ParentId: item.Id, + Fields: 'CanDownload,Path' + }).then(({ Items }) => downloadItems(Items)); + break; + case BaseItemKind.Season: + downloadSeasons([item]); + break; + case BaseItemKind.Series: + apiClient.getSeasons(item.Id, { + userId: options.user.Id, + Fields: 'ItemCounts' + }).then(seasons => downloadSeasons(seasons.Items)); } getResolveFunction(getResolveFunction(resolve, id), id)(); From 6c03684db5bbf658887dc34c835e6552a1f43c13 Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Sun, 28 Sep 2025 01:35:18 -0400 Subject: [PATCH 2/3] Add support for download all for collections --- src/components/itemContextMenu.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index b05622021..15f414ebd 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -16,6 +16,7 @@ import { AppFeature } from 'constants/appFeature'; /** Item types that support downloading all children. */ const DOWNLOAD_ALL_TYPES = [ + BaseItemKind.BoxSet, BaseItemKind.MusicAlbum, BaseItemKind.Season, BaseItemKind.Series @@ -454,6 +455,7 @@ function executeCommand(item, id, options) { }; switch (item.Type) { + case BaseItemKind.BoxSet: case BaseItemKind.MusicAlbum: apiClient.getItems(options.user.Id, { ParentId: item.Id, From 47889a5789c207703a1f9654c07596f905a8b29a Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Tue, 30 Sep 2025 12:21:48 -0400 Subject: [PATCH 3/3] Only download supported items --- src/components/itemContextMenu.js | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/components/itemContextMenu.js b/src/components/itemContextMenu.js index 15f414ebd..7f07c203e 100644 --- a/src/components/itemContextMenu.js +++ b/src/components/itemContextMenu.js @@ -426,17 +426,19 @@ function executeCommand(item, id, options) { case 'downloadall': { const downloadItems = items => { import('../scripts/fileDownloader').then((fileDownloader) => { - const downloads = items.map(item => { - const downloadHref = apiClient.getItemDownloadUrl(item.Id); - return { - url: downloadHref, - item, - itemId: item.Id, - serverId, - title: item.Name, - filename: item.Path.replace(/^.*[\\/]/, '') - }; - }); + const downloads = items + .filter(i => i.CanDownload) + .map(i => { + const downloadHref = apiClient.getItemDownloadUrl(i.Id); + return { + url: downloadHref, + item: i, + itemId: i.Id, + serverId, + title: i.Name, + filename: i.Path.replace(/^.*[\\/]/, '') + }; + }); fileDownloader.download(downloads); });