diff --git a/src/renderer/components/item-card/item-card.tsx b/src/renderer/components/item-card/item-card.tsx
index 8b00e7a55..1c4cb65db 100644
--- a/src/renderer/components/item-card/item-card.tsx
+++ b/src/renderer/components/item-card/item-card.tsx
@@ -27,6 +27,7 @@ import {
formatDurationString,
formatRating,
} from '/@/renderer/utils/format';
+import { SEPARATOR_STRING } from '/@/shared/api/utils';
import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon';
import { Separator } from '/@/shared/components/separator/separator';
@@ -1054,7 +1055,17 @@ export const getDataRows = (type?: 'compact' | 'default' | 'poster'): DataRow[]
{
format: (data) => {
if ('releaseYear' in data && data.releaseYear !== null) {
- return String(data.releaseYear);
+ const releaseYear = data.releaseYear;
+ const originalYear =
+ 'originalYear' in data && data.originalYear !== null
+ ? data.originalYear
+ : null;
+
+ if (originalYear !== null && originalYear !== releaseYear) {
+ return `♫ ${originalYear}${SEPARATOR_STRING}${releaseYear}`;
+ }
+
+ return String(releaseYear);
}
return '';
},
@@ -1063,7 +1074,15 @@ export const getDataRows = (type?: 'compact' | 'default' | 'poster'): DataRow[]
{
format: (data) => {
if ('releaseDate' in data && data.releaseDate) {
- return formatDateAbsoluteUTC(data.releaseDate);
+ if (
+ 'originalDate' in data &&
+ data.originalDate &&
+ data.originalDate !== data.releaseDate
+ ) {
+ return `♫ ${formatDateAbsoluteUTC(data.originalDate)}${SEPARATOR_STRING}${formatDateAbsoluteUTC(data.releaseDate)}`;
+ }
+
+ return `${formatDateAbsoluteUTC(data.releaseDate)}`;
}
return '';
},
diff --git a/src/renderer/components/item-list/item-table-list/columns/date-column.tsx b/src/renderer/components/item-list/item-table-list/columns/date-column.tsx
index 09bb7ce74..e0d662229 100644
--- a/src/renderer/components/item-list/item-table-list/columns/date-column.tsx
+++ b/src/renderer/components/item-list/item-table-list/columns/date-column.tsx
@@ -10,9 +10,11 @@ import {
formatDateRelative,
formatHrDateTime,
} from '/@/renderer/utils/format';
+import { SEPARATOR_STRING } from '/@/shared/api/utils';
import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text';
import { Tooltip } from '/@/shared/components/tooltip/tooltip';
+import { TableColumn } from '/@/shared/types/types';
const getDateTooltipLabel = (utcString: string) => {
return (
@@ -54,6 +56,47 @@ export const AbsoluteDateColumn = (props: ItemTableListInnerColumn) => {
props.columns[props.columnIndex].id
];
+ if (props.type === TableColumn.RELEASE_DATE) {
+ const item = (props.data as (any | undefined)[])[props.rowIndex];
+ if (item && 'releaseDate' in item && item.releaseDate) {
+ const releaseDate = item.releaseDate;
+ const originalDate =
+ 'originalDate' in item && item.originalDate && item.originalDate !== releaseDate
+ ? item.originalDate
+ : null;
+
+ if (originalDate) {
+ const formattedOriginalDate = formatDateAbsoluteUTC(originalDate);
+ const formattedReleaseDate = formatDateAbsoluteUTC(releaseDate);
+ const displayText = `♫ ${formattedOriginalDate}${SEPARATOR_STRING}${formattedReleaseDate}`;
+
+ return (
+
+
+ {displayText}
+
+
+ );
+ }
+
+ if (typeof releaseDate === 'string' && releaseDate) {
+ return (
+
+
+ {formatDateAbsoluteUTC(releaseDate)}
+
+
+ );
+ }
+ }
+
+ if (row === null) {
+ return ;
+ }
+
+ return ;
+ }
+
if (typeof row === 'string' && row) {
return (
diff --git a/src/renderer/components/item-list/item-table-list/columns/year-column.tsx b/src/renderer/components/item-list/item-table-list/columns/year-column.tsx
new file mode 100644
index 000000000..69acf6c5b
--- /dev/null
+++ b/src/renderer/components/item-list/item-table-list/columns/year-column.tsx
@@ -0,0 +1,41 @@
+import {
+ ColumnNullFallback,
+ ColumnSkeletonFixed,
+ ItemTableListInnerColumn,
+ TableColumnTextContainer,
+} from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
+import { SEPARATOR_STRING } from '/@/shared/api/utils';
+
+export const YearColumn = (props: ItemTableListInnerColumn) => {
+ const item = (props.data as (any | undefined)[])[props.rowIndex];
+
+ if (item && 'releaseYear' in item && item.releaseYear !== null) {
+ const releaseYear = item.releaseYear;
+ const originalYear =
+ 'originalYear' in item && item.originalYear !== null ? item.originalYear : null;
+
+ if (originalYear !== null && originalYear !== releaseYear) {
+ return (
+
+ ♫ {originalYear}
+ {SEPARATOR_STRING}
+ {releaseYear}
+
+ );
+ }
+
+ if (typeof releaseYear === 'number') {
+ return {releaseYear};
+ }
+ }
+
+ const row: number | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[
+ props.columns[props.columnIndex].id
+ ];
+
+ if (row === null) {
+ return ;
+ }
+
+ return ;
+};
diff --git a/src/renderer/components/item-list/item-table-list/default-columns.ts b/src/renderer/components/item-list/item-table-list/default-columns.ts
index e5c058719..b2233f5d4 100644
--- a/src/renderer/components/item-list/item-table-list/default-columns.ts
+++ b/src/renderer/components/item-list/item-table-list/default-columns.ts
@@ -110,7 +110,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
label: i18n.t('table.config.label.year', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.YEAR,
- width: 100,
+ width: 200,
},
{
align: 'center',
@@ -119,7 +119,7 @@ export const SONG_TABLE_COLUMNS: DefaultTableColumn[] = [
label: i18n.t('table.config.label.releaseDate', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.RELEASE_DATE,
- width: 120,
+ width: 240,
},
{
align: 'center',
@@ -376,7 +376,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
label: i18n.t('table.config.label.year', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.YEAR,
- width: 100,
+ width: 200,
},
{
align: 'center',
@@ -385,7 +385,7 @@ export const ALBUM_TABLE_COLUMNS: DefaultTableColumn[] = [
label: i18n.t('table.config.label.releaseDate', { postProcess: 'titleCase' }),
pinned: null,
value: TableColumn.RELEASE_DATE,
- width: 120,
+ width: 240,
},
{
align: 'center',
diff --git a/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx b/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
index b3832773a..f09b67974 100644
--- a/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
+++ b/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
@@ -47,6 +47,7 @@ import { SizeColumn } from '/@/renderer/components/item-list/item-table-list/col
import { TextColumn } from '/@/renderer/components/item-list/item-table-list/columns/text-column';
import { TitleColumn } from '/@/renderer/components/item-list/item-table-list/columns/title-column';
import { TitleCombinedColumn } from '/@/renderer/components/item-list/item-table-list/columns/title-combined-column';
+import { YearColumn } from '/@/renderer/components/item-list/item-table-list/columns/year-column';
import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
import { ItemControls, ItemListItem } from '/@/renderer/components/item-list/types';
import { eventEmitter } from '/@/renderer/events/event-emitter';
@@ -487,9 +488,11 @@ export const ItemTableListColumn = (props: ItemTableListColumn) => {
case TableColumn.DISC_NUMBER:
case TableColumn.SAMPLE_RATE:
case TableColumn.TRACK_NUMBER:
- case TableColumn.YEAR:
return ;
+ case TableColumn.YEAR:
+ return ;
+
case TableColumn.DATE_ADDED:
return ;
diff --git a/src/shared/api/jellyfin/jellyfin-normalize.ts b/src/shared/api/jellyfin/jellyfin-normalize.ts
index 5411d95e7..eb441e553 100644
--- a/src/shared/api/jellyfin/jellyfin-normalize.ts
+++ b/src/shared/api/jellyfin/jellyfin-normalize.ts
@@ -238,7 +238,7 @@ const normalizeSong = (
peak: null,
playCount: (item.UserData && item.UserData.PlayCount) || 0,
playlistItemId: item.PlaylistItemId,
- releaseDate: item.PremiereDate ? item.PremiereDate : null,
+ releaseDate: item.PremiereDate || null,
releaseYear: item.ProductionYear || null,
sampleRate,
size,
@@ -302,7 +302,8 @@ const normalizeAlbum = (
lastPlayedAt: null,
mbzId: item.ProviderIds?.MusicBrainzAlbum || null,
name: item.Name,
- originalDate: null,
+ originalDate: item.PremiereDate || null,
+ originalYear: item.ProductionYear || null,
participants: getPeople(item),
playCount: item.UserData?.PlayCount || 0,
recordLabels: item.Studios?.map((entry) => entry.Name) || [],
diff --git a/src/shared/api/navidrome/navidrome-normalize.ts b/src/shared/api/navidrome/navidrome-normalize.ts
index e970117e9..c52891a85 100644
--- a/src/shared/api/navidrome/navidrome-normalize.ts
+++ b/src/shared/api/navidrome/navidrome-normalize.ts
@@ -49,6 +49,26 @@ const normalizeReleaseDate = (item: { date?: string; releaseDate?: string }) =>
return null;
};
+const normalizeOriginalDate = (item: {
+ date?: string;
+ originalDate?: string;
+ releaseDate?: string;
+}) => {
+ if (item.originalDate && matchesFullDate(item.originalDate)) {
+ return item.originalDate;
+ }
+
+ if (item.releaseDate && matchesFullDate(item.releaseDate)) {
+ return item.releaseDate;
+ }
+
+ if (item.date && matchesFullDate(item.date)) {
+ return item.date;
+ }
+
+ return null;
+};
+
const getArtists = (
item:
| z.infer
@@ -282,6 +302,11 @@ const normalizeAlbum = (
pathReplace?: string,
pathReplaceWith?: string,
): Album => {
+ const releaseDate = normalizeReleaseDate(item);
+ const releaseYear = releaseDate ? parseInt(releaseDate.split('-')[0]) : null;
+ const originalDate = normalizeOriginalDate(item);
+ const originalYear = originalDate ? parseInt(originalDate.split('-')[0]) : null;
+
return {
...parseAlbumTags(item),
...getArtists(item, false),
@@ -316,11 +341,12 @@ const normalizeAlbum = (
lastPlayedAt: normalizePlayDate(item),
mbzId: item.mbzAlbumId || null,
name: item.name,
- originalDate: item.originalDate || null,
+ originalDate,
+ originalYear,
playCount: item.playCount || 0,
- releaseDate: normalizeReleaseDate(item),
+ releaseDate,
releaseType: item.mbzAlbumType || null,
- releaseYear: item.maxYear || null,
+ releaseYear,
size: item.size,
songCount: item.songCount,
songs: item.songs
diff --git a/src/shared/api/navidrome/navidrome-types.ts b/src/shared/api/navidrome/navidrome-types.ts
index 404fd989e..c2a935877 100644
--- a/src/shared/api/navidrome/navidrome-types.ts
+++ b/src/shared/api/navidrome/navidrome-types.ts
@@ -458,17 +458,18 @@ const album = z.object({
libraryId: z.number(),
libraryName: z.string(),
libraryPath: z.string(),
+ maxOriginalYear: z.number().optional(),
maxYear: z.number(),
mbzAlbumArtistId: z.string().optional(),
mbzAlbumId: z.string().optional(),
mbzAlbumType: z.string().optional(),
mbzReleaseGroupId: z.string().optional(),
+ minOriginalYear: z.number().optional(),
minYear: z.number(),
name: z.string(),
orderAlbumArtistName: z.string(),
orderAlbumName: z.string(),
originalDate: z.string().optional(),
- originalYear: z.number().optional(),
participants: z.optional(participants),
playCount: z.number().optional(),
playDate: z.string().optional(),
diff --git a/src/shared/api/subsonic/subsonic-normalize.ts b/src/shared/api/subsonic/subsonic-normalize.ts
index 9e9f2964b..8c4308e9f 100644
--- a/src/shared/api/subsonic/subsonic-normalize.ts
+++ b/src/shared/api/subsonic/subsonic-normalize.ts
@@ -265,6 +265,14 @@ const normalizeAlbum = (
pathReplace?: string,
pathReplaceWith?: string,
): Album => {
+ const releaseDate =
+ item.releaseDate &&
+ typeof item.releaseDate.year === 'number' &&
+ typeof item.releaseDate.month === 'number' &&
+ typeof item.releaseDate.day === 'number'
+ ? `${item.releaseDate.year}-${item.releaseDate.month}-${item.releaseDate.day}`
+ : null;
+
return {
_itemType: LibraryItem.ALBUM,
_serverId: server?.id || 'unknown',
@@ -289,17 +297,12 @@ const normalizeAlbum = (
lastPlayedAt: null,
mbzId: null,
name: item.name,
- originalDate: null,
+ originalDate: releaseDate,
+ originalYear: item.year || null,
participants: getParticipants(item),
playCount: null,
recordLabels: item.recordLabels?.map((item) => item.name) || [],
- releaseDate:
- item.releaseDate &&
- typeof item.releaseDate.year === 'number' &&
- typeof item.releaseDate.month === 'number' &&
- typeof item.releaseDate.day === 'number'
- ? `${item.releaseDate.year}-${item.releaseDate.month}-${item.releaseDate.day}`
- : null,
+ releaseDate,
releaseType: getReleaseType(item),
releaseTypes: item.releaseTypes || [],
releaseYear: item.year || null,
diff --git a/src/shared/api/utils.ts b/src/shared/api/utils.ts
index f1335f8b9..cfb3f04d7 100644
--- a/src/shared/api/utils.ts
+++ b/src/shared/api/utils.ts
@@ -139,7 +139,7 @@ export const getClientType = (): string => {
}
};
-export const SEPARATOR_STRING = ' · ';
+export const SEPARATOR_STRING = ' • ';
export const sortSongList = (songs: Song[], sortBy: SongListSort, sortOrder: SortOrder) => {
let results: Song[] = songs;
@@ -419,13 +419,13 @@ export const sortAlbumList = (albums: Album[], sortBy: AlbumListSort, sortOrder:
results,
[
(v) => {
- if (v.releaseDate) {
- return new Date(v.releaseDate).getTime();
+ if (v.originalDate) {
+ return new Date(v.originalDate).getTime();
}
// Fallback to the first day of the release year
- if (v.releaseYear) {
- return new Date(v.releaseYear, 0, 1).getTime();
+ if (v.originalYear) {
+ return new Date(v.originalYear, 0, 1).getTime();
}
return 0;
},
diff --git a/src/shared/types/domain-types.ts b/src/shared/types/domain-types.ts
index 564449795..2bee07f35 100644
--- a/src/shared/types/domain-types.ts
+++ b/src/shared/types/domain-types.ts
@@ -182,6 +182,7 @@ export type Album = {
mbzId: null | string;
name: string;
originalDate: null | string;
+ originalYear: null | number;
participants: null | Record;
playCount: null | number;
recordLabels: string[];