diff --git a/frontend/src/Series/Index/Menus/SeriesIndexSortMenu.tsx b/frontend/src/Series/Index/Menus/SeriesIndexSortMenu.tsx
index 29805a9bd..d99645c8a 100644
--- a/frontend/src/Series/Index/Menus/SeriesIndexSortMenu.tsx
+++ b/frontend/src/Series/Index/Menus/SeriesIndexSortMenu.tsx
@@ -154,6 +154,15 @@ function SeriesIndexSortMenu(props: SeriesIndexSortMenuProps) {
{translate('SizeOnDisk')}
+
+ {translate('AverageSizePerEpisode')}
+
+
0 ? sizeOnDisk / totalEpisodeCount : 0;
+
+ return (
+
+ {averageSize ? formatBytes(averageSize) : null}
+
+ );
+ }
+
if (name === 'genres') {
const joinedGenres = genres.join(', ');
diff --git a/frontend/src/Series/Index/Table/SeriesIndexTableHeader.css b/frontend/src/Series/Index/Table/SeriesIndexTableHeader.css
index df574576e..a37cb5081 100644
--- a/frontend/src/Series/Index/Table/SeriesIndexTableHeader.css
+++ b/frontend/src/Series/Index/Table/SeriesIndexTableHeader.css
@@ -92,6 +92,12 @@
flex: 0 0 120px;
}
+.averageSizePerEpisode {
+ composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
+
+ flex: 0 0 160px;
+}
+
.ratings {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
diff --git a/frontend/src/Series/Index/Table/SeriesIndexTableHeader.css.d.ts b/frontend/src/Series/Index/Table/SeriesIndexTableHeader.css.d.ts
index f30a9e786..1b566399d 100644
--- a/frontend/src/Series/Index/Table/SeriesIndexTableHeader.css.d.ts
+++ b/frontend/src/Series/Index/Table/SeriesIndexTableHeader.css.d.ts
@@ -3,6 +3,7 @@
interface CssExports {
'actions': string;
'added': string;
+ 'averageSizePerEpisode': string;
'banner': string;
'bannerGrow': string;
'certification': string;
diff --git a/frontend/src/Series/seriesOptionsStore.ts b/frontend/src/Series/seriesOptionsStore.ts
index 022bcb90d..b71c1cbb9 100644
--- a/frontend/src/Series/seriesOptionsStore.ts
+++ b/frontend/src/Series/seriesOptionsStore.ts
@@ -191,6 +191,12 @@ const { useOptions, useOption, setOptions, setOption, setSort, getOptions } =
isSortable: true,
isVisible: false,
},
+ {
+ name: 'averageSizePerEpisode',
+ label: () => translate('AverageSize'),
+ isSortable: true,
+ isVisible: false,
+ },
{
name: 'genres',
label: () => translate('Genres'),
diff --git a/frontend/src/Series/useSeries.ts b/frontend/src/Series/useSeries.ts
index 63497121d..b41428f2f 100644
--- a/frontend/src/Series/useSeries.ts
+++ b/frontend/src/Series/useSeries.ts
@@ -113,6 +113,14 @@ const SORT_PREDICATES = {
return item.statistics?.sizeOnDisk ?? 0;
},
+ averageSizePerEpisode: (item: Series, _direction: SortDirection) => {
+ const totalEpisodeCount = item.statistics?.totalEpisodeCount ?? 0;
+
+ return totalEpisodeCount > 0
+ ? (item.statistics?.sizeOnDisk ?? 0) / totalEpisodeCount
+ : 0;
+ },
+
network: (item: Series, _direction: SortDirection) => {
const network = item.network;
@@ -258,6 +266,20 @@ const FILTER_PREDICATES = {
return predicate(sizeOnDisk, filterValue);
},
+ averageSizePerEpisode: (
+ item: Series,
+ filterValue: number,
+ type: FilterType
+ ) => {
+ const predicate = getFilterTypePredicate(type);
+ const totalEpisodeCount = item.statistics?.totalEpisodeCount ?? 0;
+ const averageSize =
+ totalEpisodeCount > 0
+ ? (item.statistics?.sizeOnDisk ?? 0) / totalEpisodeCount
+ : 0;
+ return predicate(averageSize, filterValue);
+ },
+
hasMissingSeason: (item: Series, filterValue: boolean, type: FilterType) => {
const predicate = getFilterTypePredicate(type);
const seasons = item.seasons ?? [];
@@ -444,6 +466,12 @@ export const FILTER_BUILDER: FilterBuilderProp[] = [
type: filterBuilderTypes.NUMBER,
valueType: filterBuilderValueTypes.BYTES,
},
+ {
+ name: 'averageSizePerEpisode',
+ label: () => translate('AverageSizePerEpisode'),
+ type: filterBuilderTypes.NUMBER,
+ valueType: filterBuilderValueTypes.BYTES,
+ },
{
name: 'genres',
label: () => translate('Genres'),
diff --git a/src/NzbDrone.Core/Localization/Core/en.json b/src/NzbDrone.Core/Localization/Core/en.json
index 51d7e3c78..5d0d58a94 100644
--- a/src/NzbDrone.Core/Localization/Core/en.json
+++ b/src/NzbDrone.Core/Localization/Core/en.json
@@ -148,6 +148,8 @@
"AutomaticAdd": "Automatic Add",
"AutomaticSearch": "Automatic Search",
"AutomaticUpdatesDisabledDocker": "Automatic updates are not directly supported when using the Docker update mechanism. You will need to update the container image outside of {appName} or use a script",
+ "AverageSize": "Average Size",
+ "AverageSizePerEpisode": "Average Size per Episode",
"Backup": "Backup",
"BackupFolderHelpText": "Relative paths will be under {appName}'s AppData directory",
"BackupIntervalHelpText": "Interval between automatic backups",