0.13.1-6 Series Fix
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -366,6 +366,7 @@ router.post(
|
|||||||
providerId,
|
providerId,
|
||||||
tmdbId,
|
tmdbId,
|
||||||
metadataKind,
|
metadataKind,
|
||||||
|
workflowKind,
|
||||||
seasonNumber,
|
seasonNumber,
|
||||||
seasonName,
|
seasonName,
|
||||||
episodeCount,
|
episodeCount,
|
||||||
@@ -393,6 +394,7 @@ router.post(
|
|||||||
metadataProvider,
|
metadataProvider,
|
||||||
providerId,
|
providerId,
|
||||||
tmdbId,
|
tmdbId,
|
||||||
|
workflowKind,
|
||||||
seasonNumber,
|
seasonNumber,
|
||||||
discNumber
|
discNumber
|
||||||
});
|
});
|
||||||
@@ -411,6 +413,7 @@ router.post(
|
|||||||
providerId,
|
providerId,
|
||||||
tmdbId,
|
tmdbId,
|
||||||
metadataKind,
|
metadataKind,
|
||||||
|
workflowKind,
|
||||||
seasonNumber,
|
seasonNumber,
|
||||||
seasonName,
|
seasonName,
|
||||||
episodeCount,
|
episodeCount,
|
||||||
|
|||||||
@@ -1159,6 +1159,20 @@ function normalizePositiveIntegerOrNull(value) {
|
|||||||
return Math.trunc(parsed);
|
return Math.trunc(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeDvdMetadataWorkflowKind(value) {
|
||||||
|
const raw = String(value || '').trim().toLowerCase();
|
||||||
|
if (!raw) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (['film', 'movie', 'feature'].includes(raw)) {
|
||||||
|
return 'film';
|
||||||
|
}
|
||||||
|
if (['series', 'tv', 'season', 'episode'].includes(raw)) {
|
||||||
|
return 'series';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveSeriesAssignmentEpisodeSpan(assignment = null) {
|
function resolveSeriesAssignmentEpisodeSpan(assignment = null) {
|
||||||
const source = assignment && typeof assignment === 'object' ? assignment : {};
|
const source = assignment && typeof assignment === 'object' ? assignment : {};
|
||||||
const start = normalizePositiveIntegerOrNull(
|
const start = normalizePositiveIntegerOrNull(
|
||||||
@@ -1313,6 +1327,17 @@ function extractSelectedMetadataFromMakemkvInfo(makemkvInfo = {}) {
|
|||||||
function hasSeriesMetadataSignals(selectedMetadata = {}, analyzeContext = {}) {
|
function hasSeriesMetadataSignals(selectedMetadata = {}, analyzeContext = {}) {
|
||||||
const metadata = selectedMetadata && typeof selectedMetadata === 'object' ? selectedMetadata : {};
|
const metadata = selectedMetadata && typeof selectedMetadata === 'object' ? selectedMetadata : {};
|
||||||
const analyze = analyzeContext && typeof analyzeContext === 'object' ? analyzeContext : {};
|
const analyze = analyzeContext && typeof analyzeContext === 'object' ? analyzeContext : {};
|
||||||
|
const workflowKind = normalizeDvdMetadataWorkflowKind(
|
||||||
|
metadata.workflowKind
|
||||||
|
|| analyze.workflowKind
|
||||||
|
|| null
|
||||||
|
);
|
||||||
|
if (workflowKind === 'film') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (workflowKind === 'series') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const metadataKind = String(metadata.metadataKind || analyze.metadataKind || '').trim().toLowerCase();
|
const metadataKind = String(metadata.metadataKind || analyze.metadataKind || '').trim().toLowerCase();
|
||||||
const seasonNumber = normalizePositiveNumberOrNull(
|
const seasonNumber = normalizePositiveNumberOrNull(
|
||||||
metadata.seasonNumber ?? analyze?.seriesLookupHint?.seasonNumber ?? null
|
metadata.seasonNumber ?? analyze?.seriesLookupHint?.seasonNumber ?? null
|
||||||
@@ -5118,6 +5143,7 @@ class HistoryService {
|
|||||||
};
|
};
|
||||||
const requestedProviderRaw = String(payload.metadataProvider || '').trim().toLowerCase();
|
const requestedProviderRaw = String(payload.metadataProvider || '').trim().toLowerCase();
|
||||||
const requestedTmdbId = parseTmdbId(payload?.tmdbId ?? payload?.providerId ?? null);
|
const requestedTmdbId = parseTmdbId(payload?.tmdbId ?? payload?.providerId ?? null);
|
||||||
|
const requestedWorkflowKind = normalizeDvdMetadataWorkflowKind(payload?.workflowKind);
|
||||||
const metadataProvider = requestedProviderRaw === 'themoviedb'
|
const metadataProvider = requestedProviderRaw === 'themoviedb'
|
||||||
? 'tmdb'
|
? 'tmdb'
|
||||||
: (requestedProviderRaw || (requestedTmdbId !== null ? 'tmdb' : 'omdb'));
|
: (requestedProviderRaw || (requestedTmdbId !== null ? 'tmdb' : 'omdb'));
|
||||||
@@ -5337,6 +5363,7 @@ class HistoryService {
|
|||||||
year,
|
year,
|
||||||
imdbId,
|
imdbId,
|
||||||
poster: posterUrl,
|
poster: posterUrl,
|
||||||
|
workflowKind: 'series',
|
||||||
metadataProvider: 'tmdb',
|
metadataProvider: 'tmdb',
|
||||||
providerId,
|
providerId,
|
||||||
tmdbId: effectiveTmdbId,
|
tmdbId: effectiveTmdbId,
|
||||||
@@ -5356,6 +5383,7 @@ class HistoryService {
|
|||||||
: {};
|
: {};
|
||||||
const nextAnalyzeContext = {
|
const nextAnalyzeContext = {
|
||||||
...analyzeContext,
|
...analyzeContext,
|
||||||
|
workflowKind: 'series',
|
||||||
metadataProvider: 'tmdb',
|
metadataProvider: 'tmdb',
|
||||||
metadataKind,
|
metadataKind,
|
||||||
selectedMetadata: {
|
selectedMetadata: {
|
||||||
@@ -5445,24 +5473,68 @@ class HistoryService {
|
|||||||
const imdbId = omdb?.imdbId || imdbIdInput || job.imdb_id || null;
|
const imdbId = omdb?.imdbId || imdbIdInput || job.imdb_id || null;
|
||||||
const posterUrl = omdb?.poster || manualPoster || job.poster_url || null;
|
const posterUrl = omdb?.poster || manualPoster || job.poster_url || null;
|
||||||
const selectedFromOmdb = omdb ? 1 : Number(payload.fromOmdb ? 1 : 0);
|
const selectedFromOmdb = omdb ? 1 : Number(payload.fromOmdb ? 1 : 0);
|
||||||
|
const resolvedJobMediaType = inferMediaType(
|
||||||
|
job,
|
||||||
|
makemkvInfo,
|
||||||
|
job?.mediainfo_info_json,
|
||||||
|
job?.encode_plan_json,
|
||||||
|
job?.handbrake_info_json
|
||||||
|
);
|
||||||
|
const shouldUseFilmWorkflow = resolvedJobMediaType === 'dvd' && metadataProvider === 'omdb';
|
||||||
|
const existingWorkflowKind = normalizeDvdMetadataWorkflowKind(
|
||||||
|
existingSelectedMetadata?.workflowKind
|
||||||
|
|| analyzeContext?.workflowKind
|
||||||
|
|| null
|
||||||
|
);
|
||||||
|
const effectiveWorkflowKind = shouldUseFilmWorkflow
|
||||||
|
? 'film'
|
||||||
|
: (requestedWorkflowKind || existingWorkflowKind);
|
||||||
const nextSelectedMetadata = {
|
const nextSelectedMetadata = {
|
||||||
...(existingSelectedMetadata && typeof existingSelectedMetadata === 'object' ? existingSelectedMetadata : {}),
|
...(existingSelectedMetadata && typeof existingSelectedMetadata === 'object' ? existingSelectedMetadata : {}),
|
||||||
title,
|
title,
|
||||||
year,
|
year,
|
||||||
imdbId,
|
imdbId,
|
||||||
poster: posterUrl,
|
poster: posterUrl,
|
||||||
metadataProvider: 'omdb'
|
metadataProvider: 'omdb',
|
||||||
|
...(effectiveWorkflowKind ? { workflowKind: effectiveWorkflowKind } : {}),
|
||||||
|
...(shouldUseFilmWorkflow
|
||||||
|
? {
|
||||||
|
providerId: null,
|
||||||
|
tmdbId: null,
|
||||||
|
metadataKind: 'movie',
|
||||||
|
seasonNumber: null,
|
||||||
|
seasonName: null,
|
||||||
|
episodeCount: 0,
|
||||||
|
episodes: [],
|
||||||
|
discNumber: null,
|
||||||
|
tmdbDetails: null
|
||||||
|
}
|
||||||
|
: {})
|
||||||
};
|
};
|
||||||
const existingAnalyzeSelected = analyzeContext?.selectedMetadata && typeof analyzeContext.selectedMetadata === 'object'
|
const existingAnalyzeSelected = analyzeContext?.selectedMetadata && typeof analyzeContext.selectedMetadata === 'object'
|
||||||
? analyzeContext.selectedMetadata
|
? analyzeContext.selectedMetadata
|
||||||
: {};
|
: {};
|
||||||
|
const existingSeriesLookupHint = analyzeContext?.seriesLookupHint && typeof analyzeContext.seriesLookupHint === 'object'
|
||||||
|
? analyzeContext.seriesLookupHint
|
||||||
|
: {};
|
||||||
const nextAnalyzeContext = {
|
const nextAnalyzeContext = {
|
||||||
...analyzeContext,
|
...analyzeContext,
|
||||||
|
...(effectiveWorkflowKind ? { workflowKind: effectiveWorkflowKind } : {}),
|
||||||
metadataProvider: 'omdb',
|
metadataProvider: 'omdb',
|
||||||
|
...(shouldUseFilmWorkflow ? { metadataKind: 'movie' } : {}),
|
||||||
selectedMetadata: {
|
selectedMetadata: {
|
||||||
...existingAnalyzeSelected,
|
...existingAnalyzeSelected,
|
||||||
...nextSelectedMetadata
|
...nextSelectedMetadata
|
||||||
}
|
},
|
||||||
|
...(shouldUseFilmWorkflow
|
||||||
|
? {
|
||||||
|
seriesLookupHint: {
|
||||||
|
...existingSeriesLookupHint,
|
||||||
|
seasonNumber: null,
|
||||||
|
discNumber: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
};
|
};
|
||||||
const topLevelSelected = makemkvInfo?.selectedMetadata && typeof makemkvInfo.selectedMetadata === 'object'
|
const topLevelSelected = makemkvInfo?.selectedMetadata && typeof makemkvInfo.selectedMetadata === 'object'
|
||||||
? makemkvInfo.selectedMetadata
|
? makemkvInfo.selectedMetadata
|
||||||
@@ -5483,7 +5555,14 @@ class HistoryService {
|
|||||||
poster_url: posterUrl,
|
poster_url: posterUrl,
|
||||||
omdb_json: omdb?.raw ? JSON.stringify(omdb.raw) : (job.omdb_json || null),
|
omdb_json: omdb?.raw ? JSON.stringify(omdb.raw) : (job.omdb_json || null),
|
||||||
selected_from_omdb: selectedFromOmdb,
|
selected_from_omdb: selectedFromOmdb,
|
||||||
makemkv_info_json: JSON.stringify(nextMakemkvInfo)
|
makemkv_info_json: JSON.stringify(nextMakemkvInfo),
|
||||||
|
...(shouldUseFilmWorkflow
|
||||||
|
? {
|
||||||
|
parent_job_id: null,
|
||||||
|
job_kind: 'dvd',
|
||||||
|
media_type: 'dvd'
|
||||||
|
}
|
||||||
|
: {})
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bild herunterladen, in persistenten Ordner verschieben und poster_url aktualisieren
|
// Bild herunterladen, in persistenten Ordner verschieben und poster_url aktualisieren
|
||||||
|
|||||||
@@ -117,6 +117,20 @@ function normalizePositiveInteger(value) {
|
|||||||
return Math.trunc(parsed);
|
return Math.trunc(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeDvdMetadataWorkflowKind(value) {
|
||||||
|
const raw = String(value || '').trim().toLowerCase();
|
||||||
|
if (!raw) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (['film', 'movie', 'feature'].includes(raw)) {
|
||||||
|
return 'film';
|
||||||
|
}
|
||||||
|
if (['series', 'tv', 'season', 'episode'].includes(raw)) {
|
||||||
|
return 'series';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function resolveSelectedMetadataForJob(job = null, analyzeContext = null, activeContext = null) {
|
function resolveSelectedMetadataForJob(job = null, analyzeContext = null, activeContext = null) {
|
||||||
const analyzeSelectedMetadata = analyzeContext?.selectedMetadata && typeof analyzeContext.selectedMetadata === 'object'
|
const analyzeSelectedMetadata = analyzeContext?.selectedMetadata && typeof analyzeContext.selectedMetadata === 'object'
|
||||||
? analyzeContext.selectedMetadata
|
? analyzeContext.selectedMetadata
|
||||||
@@ -128,6 +142,12 @@ function resolveSelectedMetadataForJob(job = null, analyzeContext = null, active
|
|||||||
...analyzeSelectedMetadata,
|
...analyzeSelectedMetadata,
|
||||||
...activeSelectedMetadata
|
...activeSelectedMetadata
|
||||||
};
|
};
|
||||||
|
const workflowKind = normalizeDvdMetadataWorkflowKind(
|
||||||
|
merged.workflowKind
|
||||||
|
|| analyzeContext?.workflowKind
|
||||||
|
|| activeContext?.workflowKind
|
||||||
|
|| null
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...merged,
|
...merged,
|
||||||
@@ -135,6 +155,7 @@ function resolveSelectedMetadataForJob(job = null, analyzeContext = null, active
|
|||||||
year: Number(merged.year ?? job?.year ?? 0) || null,
|
year: Number(merged.year ?? job?.year ?? 0) || null,
|
||||||
imdbId: String(merged.imdbId || job?.imdb_id || '').trim() || null,
|
imdbId: String(merged.imdbId || job?.imdb_id || '').trim() || null,
|
||||||
poster: String(merged.poster || job?.poster_url || '').trim() || null,
|
poster: String(merged.poster || job?.poster_url || '').trim() || null,
|
||||||
|
...(workflowKind ? { workflowKind } : {}),
|
||||||
metadataProvider: String(
|
metadataProvider: String(
|
||||||
merged.metadataProvider
|
merged.metadataProvider
|
||||||
|| analyzeContext?.metadataProvider
|
|| analyzeContext?.metadataProvider
|
||||||
@@ -152,6 +173,17 @@ function isSeriesDvdMetadataSelection(mediaProfile, selectedMetadata = {}, analy
|
|||||||
const metadata = selectedMetadata && typeof selectedMetadata === 'object'
|
const metadata = selectedMetadata && typeof selectedMetadata === 'object'
|
||||||
? selectedMetadata
|
? selectedMetadata
|
||||||
: {};
|
: {};
|
||||||
|
const workflowKind = normalizeDvdMetadataWorkflowKind(
|
||||||
|
metadata.workflowKind
|
||||||
|
|| analyzeContext?.workflowKind
|
||||||
|
|| null
|
||||||
|
);
|
||||||
|
if (workflowKind === 'film') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (workflowKind === 'series') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
const provider = String(
|
const provider = String(
|
||||||
metadata.metadataProvider
|
metadata.metadataProvider
|
||||||
|| analyzeContext?.metadataProvider
|
|| analyzeContext?.metadataProvider
|
||||||
@@ -13674,6 +13706,7 @@ class PipelineService extends EventEmitter {
|
|||||||
providerId = null,
|
providerId = null,
|
||||||
tmdbId = null,
|
tmdbId = null,
|
||||||
metadataKind = null,
|
metadataKind = null,
|
||||||
|
workflowKind = null,
|
||||||
seasonNumber = null,
|
seasonNumber = null,
|
||||||
seasonName = null,
|
seasonName = null,
|
||||||
episodeCount = null,
|
episodeCount = null,
|
||||||
@@ -13694,6 +13727,7 @@ class PipelineService extends EventEmitter {
|
|||||||
metadataProvider,
|
metadataProvider,
|
||||||
providerId,
|
providerId,
|
||||||
tmdbId,
|
tmdbId,
|
||||||
|
workflowKind,
|
||||||
seasonNumber,
|
seasonNumber,
|
||||||
discNumber
|
discNumber
|
||||||
});
|
});
|
||||||
@@ -14009,28 +14043,46 @@ class PipelineService extends EventEmitter {
|
|||||||
const posterValue = poster === undefined
|
const posterValue = poster === undefined
|
||||||
? (job.poster_url || null)
|
? (job.poster_url || null)
|
||||||
: (poster || null);
|
: (poster || null);
|
||||||
const effectiveMetadataProvider = String(
|
let effectiveMetadataProvider = String(
|
||||||
metadataProvider
|
metadataProvider
|
||||||
|| mkInfo?.analyzeContext?.metadataProvider
|
|| mkInfo?.analyzeContext?.metadataProvider
|
||||||
|| 'omdb'
|
|| 'omdb'
|
||||||
).trim().toLowerCase() || 'omdb';
|
).trim().toLowerCase() || 'omdb';
|
||||||
const effectiveProviderId = String(
|
const requestedWorkflowKind = normalizeDvdMetadataWorkflowKind(workflowKind);
|
||||||
|
const existingWorkflowKind = normalizeDvdMetadataWorkflowKind(
|
||||||
|
mkInfo?.analyzeContext?.selectedMetadata?.workflowKind
|
||||||
|
|| mkInfo?.analyzeContext?.workflowKind
|
||||||
|
|| mkInfo?.selectedMetadata?.workflowKind
|
||||||
|
|| null
|
||||||
|
);
|
||||||
|
const providerWorkflowKind = effectiveMetadataProvider === 'tmdb'
|
||||||
|
? 'series'
|
||||||
|
: (effectiveMetadataProvider === 'omdb' ? 'film' : null);
|
||||||
|
const effectiveWorkflowKind = requestedWorkflowKind || providerWorkflowKind || existingWorkflowKind;
|
||||||
|
if (mediaProfile === 'dvd') {
|
||||||
|
if (effectiveWorkflowKind === 'series') {
|
||||||
|
effectiveMetadataProvider = 'tmdb';
|
||||||
|
} else if (effectiveWorkflowKind === 'film') {
|
||||||
|
effectiveMetadataProvider = 'omdb';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let effectiveProviderId = String(
|
||||||
providerId
|
providerId
|
||||||
|| mkInfo?.analyzeContext?.selectedMetadata?.providerId
|
|| mkInfo?.analyzeContext?.selectedMetadata?.providerId
|
||||||
|| ''
|
|| ''
|
||||||
).trim() || null;
|
).trim() || null;
|
||||||
const effectiveTmdbId = Number(
|
let effectiveTmdbId = Number(
|
||||||
tmdbId
|
tmdbId
|
||||||
|| mkInfo?.analyzeContext?.selectedMetadata?.tmdbId
|
|| mkInfo?.analyzeContext?.selectedMetadata?.tmdbId
|
||||||
|| 0
|
|| 0
|
||||||
) || null;
|
) || null;
|
||||||
const effectiveMetadataKind = String(
|
let effectiveMetadataKind = String(
|
||||||
metadataKind
|
metadataKind
|
||||||
|| mkInfo?.analyzeContext?.selectedMetadata?.metadataKind
|
|| mkInfo?.analyzeContext?.selectedMetadata?.metadataKind
|
||||||
|| ''
|
|| ''
|
||||||
).trim().toLowerCase() || null;
|
).trim().toLowerCase() || null;
|
||||||
const effectiveDiscNumber = normalizePositiveInteger(discNumber);
|
let effectiveDiscNumber = normalizePositiveInteger(discNumber);
|
||||||
const effectiveSeasonNumber = Number(
|
let effectiveSeasonNumber = Number(
|
||||||
seasonNumber
|
seasonNumber
|
||||||
|| mkInfo?.analyzeContext?.selectedMetadata?.seasonNumber
|
|| mkInfo?.analyzeContext?.selectedMetadata?.seasonNumber
|
||||||
|| 0
|
|| 0
|
||||||
@@ -14052,7 +14104,17 @@ class PipelineService extends EventEmitter {
|
|||||||
: []);
|
: []);
|
||||||
let tmdbDetails = null;
|
let tmdbDetails = null;
|
||||||
|
|
||||||
const isDvdTmdbMetadataSelection = mediaProfile === 'dvd' && effectiveMetadataProvider === 'tmdb';
|
const isDvdTmdbMetadataSelection = mediaProfile === 'dvd' && effectiveWorkflowKind === 'series';
|
||||||
|
if (mediaProfile === 'dvd' && effectiveWorkflowKind === 'film') {
|
||||||
|
effectiveProviderId = null;
|
||||||
|
effectiveTmdbId = null;
|
||||||
|
effectiveMetadataKind = 'movie';
|
||||||
|
effectiveDiscNumber = null;
|
||||||
|
effectiveSeasonNumber = null;
|
||||||
|
effectiveSeasonName = null;
|
||||||
|
effectiveEpisodeCount = 0;
|
||||||
|
effectiveEpisodes = [];
|
||||||
|
}
|
||||||
if (isDvdTmdbMetadataSelection && !effectiveDiscNumber) {
|
if (isDvdTmdbMetadataSelection && !effectiveDiscNumber) {
|
||||||
const error = new Error('Serien-DVD erkannt: Disk-Nummer ist Pflicht (Start bei 1).');
|
const error = new Error('Serien-DVD erkannt: Disk-Nummer ist Pflicht (Start bei 1).');
|
||||||
error.statusCode = 400;
|
error.statusCode = 400;
|
||||||
@@ -14159,6 +14221,7 @@ class PipelineService extends EventEmitter {
|
|||||||
imdbId: effectiveImdbId,
|
imdbId: effectiveImdbId,
|
||||||
poster: posterValue,
|
poster: posterValue,
|
||||||
metadataProvider: effectiveMetadataProvider,
|
metadataProvider: effectiveMetadataProvider,
|
||||||
|
...(effectiveWorkflowKind ? { workflowKind: effectiveWorkflowKind } : {}),
|
||||||
providerId: effectiveProviderId,
|
providerId: effectiveProviderId,
|
||||||
tmdbId: effectiveTmdbId,
|
tmdbId: effectiveTmdbId,
|
||||||
metadataKind: effectiveMetadataKind,
|
metadataKind: effectiveMetadataKind,
|
||||||
@@ -14170,7 +14233,10 @@ class PipelineService extends EventEmitter {
|
|||||||
...(tmdbDetails && typeof tmdbDetails === 'object' ? { tmdbDetails } : {})
|
...(tmdbDetails && typeof tmdbDetails === 'object' ? { tmdbDetails } : {})
|
||||||
};
|
};
|
||||||
|
|
||||||
let parentContainerJobId = normalizePositiveInteger(job.parent_job_id || null);
|
const shouldDetachSeriesContainer = mediaProfile === 'dvd' && effectiveWorkflowKind === 'film';
|
||||||
|
let parentContainerJobId = shouldDetachSeriesContainer
|
||||||
|
? null
|
||||||
|
: normalizePositiveInteger(job.parent_job_id || null);
|
||||||
const isDvdSeriesSelection = isDvdTmdbMetadataSelection;
|
const isDvdSeriesSelection = isDvdTmdbMetadataSelection;
|
||||||
if (isDvdSeriesSelection) {
|
if (isDvdSeriesSelection) {
|
||||||
const normalizeEpisodeRows = (rows) => {
|
const normalizeEpisodeRows = (rows) => {
|
||||||
@@ -14464,10 +14530,18 @@ class PipelineService extends EventEmitter {
|
|||||||
selectedPlaylist: playlistDecision.selectedPlaylist || null,
|
selectedPlaylist: playlistDecision.selectedPlaylist || null,
|
||||||
selectedTitleId: playlistDecision.selectedTitleId ?? null,
|
selectedTitleId: playlistDecision.selectedTitleId ?? null,
|
||||||
metadataProvider: effectiveMetadataProvider,
|
metadataProvider: effectiveMetadataProvider,
|
||||||
|
...(effectiveWorkflowKind ? { workflowKind: effectiveWorkflowKind } : {}),
|
||||||
selectedMetadata
|
selectedMetadata
|
||||||
}
|
}
|
||||||
}, mediaProfile);
|
}, mediaProfile);
|
||||||
|
|
||||||
|
const seriesDetachPatch = shouldDetachSeriesContainer
|
||||||
|
? {
|
||||||
|
parent_job_id: null,
|
||||||
|
job_kind: resolveJobKindForMediaProfile(mediaProfile),
|
||||||
|
media_type: mediaProfile
|
||||||
|
}
|
||||||
|
: {};
|
||||||
await historyService.updateJob(jobId, {
|
await historyService.updateJob(jobId, {
|
||||||
title: effectiveTitle,
|
title: effectiveTitle,
|
||||||
year: effectiveYear,
|
year: effectiveYear,
|
||||||
@@ -14479,7 +14553,8 @@ class PipelineService extends EventEmitter {
|
|||||||
last_state: nextStatus,
|
last_state: nextStatus,
|
||||||
raw_path: updatedRawPath,
|
raw_path: updatedRawPath,
|
||||||
makemkv_info_json: JSON.stringify(updatedMakemkvInfo),
|
makemkv_info_json: JSON.stringify(updatedMakemkvInfo),
|
||||||
...(parentContainerJobId ? { parent_job_id: parentContainerJobId, job_kind: 'dvd_series_child', media_type: mediaProfile } : {})
|
...(parentContainerJobId ? { parent_job_id: parentContainerJobId, job_kind: 'dvd_series_child', media_type: mediaProfile } : {}),
|
||||||
|
...seriesDetachPatch
|
||||||
});
|
});
|
||||||
|
|
||||||
// Bild in Cache laden (async, blockiert nicht)
|
// Bild in Cache laden (async, blockiert nicht)
|
||||||
@@ -16698,6 +16773,27 @@ class PipelineService extends EventEmitter {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((row) => Number.isFinite(Number(row?.handBrakeTitleId)) && Number(row.handBrakeTitleId) > 0);
|
.filter((row) => Number.isFinite(Number(row?.handBrakeTitleId)) && Number(row.handBrakeTitleId) > 0);
|
||||||
|
const allTitlesData = manualTitleSourceRows
|
||||||
|
.map((item) => {
|
||||||
|
const titleId = normalizeReviewTitleId(item?.handBrakeTitleId ?? item?.index ?? item?.id);
|
||||||
|
if (!titleId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const durationSeconds = Number(item?.durationSeconds || 0);
|
||||||
|
return {
|
||||||
|
handBrakeTitleId: titleId,
|
||||||
|
durationSeconds,
|
||||||
|
durationMinutes: Number((durationSeconds / 60).toFixed(1)),
|
||||||
|
audioTrackCount: Number(item?.audioTrackCount || 0),
|
||||||
|
subtitleTrackCount: Number(item?.subtitleTrackCount || 0),
|
||||||
|
sizeBytes: Number(item?.sizeBytes || 0)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((row) => Number.isFinite(Number(row?.handBrakeTitleId)) && Number(row.handBrakeTitleId) > 0)
|
||||||
|
.sort((a, b) => a.handBrakeTitleId - b.handBrakeTitleId);
|
||||||
|
const decisionSelectableTitleIds = normalizeReviewTitleIdList([
|
||||||
|
seriesPlayAllDecision.longestCandidateTitleId
|
||||||
|
]);
|
||||||
const preselectedTitleIds = normalizeReviewTitleIdList(
|
const preselectedTitleIds = normalizeReviewTitleIdList(
|
||||||
(Array.isArray(seriesPlayAllDecision.defaultSelectedTitleIds)
|
(Array.isArray(seriesPlayAllDecision.defaultSelectedTitleIds)
|
||||||
? seriesPlayAllDecision.defaultSelectedTitleIds
|
? seriesPlayAllDecision.defaultSelectedTitleIds
|
||||||
@@ -16711,6 +16807,7 @@ class PipelineService extends EventEmitter {
|
|||||||
...enrichedReview,
|
...enrichedReview,
|
||||||
handBrakeTitleDecisionRequired: true,
|
handBrakeTitleDecisionRequired: true,
|
||||||
handBrakeTitleCandidates: candidatesData,
|
handBrakeTitleCandidates: candidatesData,
|
||||||
|
handBrakeAllTitles: allTitlesData,
|
||||||
handBrakeDvdInputPath: dvdScanInputPath,
|
handBrakeDvdInputPath: dvdScanInputPath,
|
||||||
handBrakeDecisionMode: 'series_playall_or_double',
|
handBrakeDecisionMode: 'series_playall_or_double',
|
||||||
handBrakeDecisionSummary: {
|
handBrakeDecisionSummary: {
|
||||||
@@ -16722,6 +16819,7 @@ class PipelineService extends EventEmitter {
|
|||||||
toleranceSeconds: seriesPlayAllDecision.toleranceSeconds,
|
toleranceSeconds: seriesPlayAllDecision.toleranceSeconds,
|
||||||
longestVsEpisodeRatio: seriesPlayAllDecision.longestVsEpisodeRatio
|
longestVsEpisodeRatio: seriesPlayAllDecision.longestVsEpisodeRatio
|
||||||
},
|
},
|
||||||
|
handBrakeDecisionSelectableTitleIds: decisionSelectableTitleIds,
|
||||||
selectedHandBrakeTitleIds: effectivePreselectedTitleIds,
|
selectedHandBrakeTitleIds: effectivePreselectedTitleIds,
|
||||||
selectedHandBrakeTitleId: effectivePreselectedTitleIds[0] || null,
|
selectedHandBrakeTitleId: effectivePreselectedTitleIds[0] || null,
|
||||||
encodeInputPath: null
|
encodeInputPath: null
|
||||||
@@ -16758,9 +16856,11 @@ class PipelineService extends EventEmitter {
|
|||||||
rawPath,
|
rawPath,
|
||||||
handBrakeTitleDecisionRequired: true,
|
handBrakeTitleDecisionRequired: true,
|
||||||
handBrakeTitleCandidates: candidatesData,
|
handBrakeTitleCandidates: candidatesData,
|
||||||
|
handBrakeAllTitles: allTitlesData,
|
||||||
handBrakeDvdInputPath: dvdScanInputPath,
|
handBrakeDvdInputPath: dvdScanInputPath,
|
||||||
handBrakeDecisionMode: 'series_playall_or_double',
|
handBrakeDecisionMode: 'series_playall_or_double',
|
||||||
handBrakeDecisionSummary: enrichedReview.handBrakeDecisionSummary,
|
handBrakeDecisionSummary: enrichedReview.handBrakeDecisionSummary,
|
||||||
|
handBrakeDecisionSelectableTitleIds: decisionSelectableTitleIds,
|
||||||
selectedHandBrakeTitleIds: effectivePreselectedTitleIds,
|
selectedHandBrakeTitleIds: effectivePreselectedTitleIds,
|
||||||
selectedHandBrakeTitleId: effectivePreselectedTitleIds[0] || null,
|
selectedHandBrakeTitleId: effectivePreselectedTitleIds[0] || null,
|
||||||
reviewConfirmed: false,
|
reviewConfirmed: false,
|
||||||
@@ -17023,6 +17123,27 @@ class PipelineService extends EventEmitter {
|
|||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter((row) => Number.isFinite(Number(row?.handBrakeTitleId)) && Number(row.handBrakeTitleId) > 0);
|
.filter((row) => Number.isFinite(Number(row?.handBrakeTitleId)) && Number(row.handBrakeTitleId) > 0);
|
||||||
|
const allTitlesData = allTitles
|
||||||
|
.map((item) => {
|
||||||
|
const titleId = normalizeReviewTitleId(item?.handBrakeTitleId ?? item?.index ?? item?.id);
|
||||||
|
if (!titleId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const durationSeconds = Number(item?.durationSeconds || 0);
|
||||||
|
return {
|
||||||
|
handBrakeTitleId: titleId,
|
||||||
|
durationSeconds,
|
||||||
|
durationMinutes: Number((durationSeconds / 60).toFixed(1)),
|
||||||
|
audioTrackCount: Number(item?.audioTrackCount || 0),
|
||||||
|
subtitleTrackCount: Number(item?.subtitleTrackCount || 0),
|
||||||
|
sizeBytes: Number(item?.sizeBytes || 0)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((row) => Number.isFinite(Number(row?.handBrakeTitleId)) && Number(row.handBrakeTitleId) > 0)
|
||||||
|
.sort((a, b) => a.handBrakeTitleId - b.handBrakeTitleId);
|
||||||
|
const decisionSelectableTitleIds = normalizeReviewTitleIdList([
|
||||||
|
seriesPlayAllDecision.longestCandidateTitleId
|
||||||
|
]);
|
||||||
const preselectedTitleIds = normalizeReviewTitleIdList(
|
const preselectedTitleIds = normalizeReviewTitleIdList(
|
||||||
(Array.isArray(seriesPlayAllDecision.defaultSelectedTitleIds)
|
(Array.isArray(seriesPlayAllDecision.defaultSelectedTitleIds)
|
||||||
? seriesPlayAllDecision.defaultSelectedTitleIds
|
? seriesPlayAllDecision.defaultSelectedTitleIds
|
||||||
@@ -17036,6 +17157,7 @@ class PipelineService extends EventEmitter {
|
|||||||
...enrichedReview,
|
...enrichedReview,
|
||||||
handBrakeTitleDecisionRequired: true,
|
handBrakeTitleDecisionRequired: true,
|
||||||
handBrakeTitleCandidates: candidatesData,
|
handBrakeTitleCandidates: candidatesData,
|
||||||
|
handBrakeAllTitles: allTitlesData,
|
||||||
handBrakeDvdInputPath: dvdScanInputPath,
|
handBrakeDvdInputPath: dvdScanInputPath,
|
||||||
handBrakeDecisionMode: 'series_playall_or_double',
|
handBrakeDecisionMode: 'series_playall_or_double',
|
||||||
handBrakeDecisionSummary: {
|
handBrakeDecisionSummary: {
|
||||||
@@ -17047,6 +17169,7 @@ class PipelineService extends EventEmitter {
|
|||||||
toleranceSeconds: seriesPlayAllDecision.toleranceSeconds,
|
toleranceSeconds: seriesPlayAllDecision.toleranceSeconds,
|
||||||
longestVsEpisodeRatio: seriesPlayAllDecision.longestVsEpisodeRatio
|
longestVsEpisodeRatio: seriesPlayAllDecision.longestVsEpisodeRatio
|
||||||
},
|
},
|
||||||
|
handBrakeDecisionSelectableTitleIds: decisionSelectableTitleIds,
|
||||||
selectedHandBrakeTitleIds: effectivePreselectedTitleIds,
|
selectedHandBrakeTitleIds: effectivePreselectedTitleIds,
|
||||||
selectedHandBrakeTitleId: effectivePreselectedTitleIds[0] || null,
|
selectedHandBrakeTitleId: effectivePreselectedTitleIds[0] || null,
|
||||||
encodeInputPath: null
|
encodeInputPath: null
|
||||||
@@ -17083,9 +17206,11 @@ class PipelineService extends EventEmitter {
|
|||||||
rawPath,
|
rawPath,
|
||||||
handBrakeTitleDecisionRequired: true,
|
handBrakeTitleDecisionRequired: true,
|
||||||
handBrakeTitleCandidates: candidatesData,
|
handBrakeTitleCandidates: candidatesData,
|
||||||
|
handBrakeAllTitles: allTitlesData,
|
||||||
handBrakeDvdInputPath: dvdScanInputPath,
|
handBrakeDvdInputPath: dvdScanInputPath,
|
||||||
handBrakeDecisionMode: 'series_playall_or_double',
|
handBrakeDecisionMode: 'series_playall_or_double',
|
||||||
handBrakeDecisionSummary: enrichedReview.handBrakeDecisionSummary,
|
handBrakeDecisionSummary: enrichedReview.handBrakeDecisionSummary,
|
||||||
|
handBrakeDecisionSelectableTitleIds: decisionSelectableTitleIds,
|
||||||
selectedHandBrakeTitleIds: effectivePreselectedTitleIds,
|
selectedHandBrakeTitleIds: effectivePreselectedTitleIds,
|
||||||
selectedHandBrakeTitleId: effectivePreselectedTitleIds[0] || null,
|
selectedHandBrakeTitleId: effectivePreselectedTitleIds[0] || null,
|
||||||
reviewConfirmed: false,
|
reviewConfirmed: false,
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"primeicons": "^7.0.0",
|
"primeicons": "^7.0.0",
|
||||||
"primereact": "^10.9.2",
|
"primereact": "^10.9.2",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -78,7 +78,10 @@ function stripEpisodePartSuffix(value) {
|
|||||||
if (!source) {
|
if (!source) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const suffixPattern = /(?:\s*[-–—:.,]?\s*)(?:\(?\s*(?:teil|part|pt)\s*(?:\d{1,2}|[ivxlcdm]{1,6})(?:\s*(?:\/|von|of)\s*(?:\d{1,2}|[ivxlcdm]{1,6}))?\s*\)?)\s*$/i;
|
// Output cleanup for multi-episode labels:
|
||||||
|
// remove "Teil/Part" markers and pure numeric/roman suffixes in
|
||||||
|
// parentheses like "(1)" / "(2)".
|
||||||
|
const suffixPattern = /(?:\s*[-–—:.,]?\s*)(?:(?:\(?\s*(?:teil|part|pt)\s*(?:\d{1,2}|[ivxlcdm]{1,6})(?:\s*(?:\/|von|of)\s*(?:\d{1,2}|[ivxlcdm]{1,6}))?\s*\)?)|\(\s*(?:\d{1,2}|[ivxlcdm]{1,6})\s*\))\s*$/i;
|
||||||
let normalized = source;
|
let normalized = source;
|
||||||
let changed = false;
|
let changed = false;
|
||||||
for (let i = 0; i < 2; i += 1) {
|
for (let i = 0; i < 2; i += 1) {
|
||||||
|
|||||||
@@ -14,6 +14,19 @@ export default function MetadataSelectionDialog({
|
|||||||
onSearch,
|
onSearch,
|
||||||
busy
|
busy
|
||||||
}) {
|
}) {
|
||||||
|
const normalizeWorkflowKind = (value) => {
|
||||||
|
const raw = String(value || '').trim().toLowerCase();
|
||||||
|
if (!raw) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (['film', 'movie', 'feature'].includes(raw)) {
|
||||||
|
return 'film';
|
||||||
|
}
|
||||||
|
if (['series', 'tv', 'season', 'episode'].includes(raw)) {
|
||||||
|
return 'series';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
const parseDiscNumber = (rawValue) => {
|
const parseDiscNumber = (rawValue) => {
|
||||||
const text = String(rawValue ?? '').trim();
|
const text = String(rawValue ?? '').trim();
|
||||||
if (!/^\d+$/.test(text)) {
|
if (!/^\d+$/.test(text)) {
|
||||||
@@ -37,7 +50,22 @@ export default function MetadataSelectionDialog({
|
|||||||
const [submitError, setSubmitError] = useState('');
|
const [submitError, setSubmitError] = useState('');
|
||||||
const [seasonFilter, setSeasonFilter] = useState('');
|
const [seasonFilter, setSeasonFilter] = useState('');
|
||||||
const [discValidationError, setDiscValidationError] = useState('');
|
const [discValidationError, setDiscValidationError] = useState('');
|
||||||
const contextMetadataProvider = String(context?.metadataProvider || 'omdb').trim().toLowerCase() || 'omdb';
|
const contextWorkflowKind = normalizeWorkflowKind(
|
||||||
|
context?.selectedMetadata?.workflowKind
|
||||||
|
|| context?.workflowKind
|
||||||
|
|| null
|
||||||
|
);
|
||||||
|
const contextSelectedMetadataProvider = String(
|
||||||
|
context?.selectedMetadata?.metadataProvider
|
||||||
|
|| ''
|
||||||
|
).trim().toLowerCase();
|
||||||
|
const contextMetadataProvider = (
|
||||||
|
contextWorkflowKind === 'series'
|
||||||
|
? 'tmdb'
|
||||||
|
: (contextWorkflowKind === 'film'
|
||||||
|
? 'omdb'
|
||||||
|
: (contextSelectedMetadataProvider || String(context?.metadataProvider || 'omdb').trim().toLowerCase()))
|
||||||
|
) || 'omdb';
|
||||||
const [selectedMetadataProvider, setSelectedMetadataProvider] = useState(contextMetadataProvider);
|
const [selectedMetadataProvider, setSelectedMetadataProvider] = useState(contextMetadataProvider);
|
||||||
const mediaProfile = String(
|
const mediaProfile = String(
|
||||||
context?.mediaProfile
|
context?.mediaProfile
|
||||||
@@ -196,6 +224,7 @@ export default function MetadataSelectionDialog({
|
|||||||
poster: selected.poster && selected.poster !== 'N/A' ? selected.poster : null,
|
poster: selected.poster && selected.poster !== 'N/A' ? selected.poster : null,
|
||||||
fromOmdb: metadataProvider === 'omdb',
|
fromOmdb: metadataProvider === 'omdb',
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
|
workflowKind: metadataProvider === 'tmdb' ? 'series' : 'film',
|
||||||
providerId: selected.providerId || null,
|
providerId: selected.providerId || null,
|
||||||
tmdbId: selected.tmdbId || null,
|
tmdbId: selected.tmdbId || null,
|
||||||
metadataKind: selected.metadataKind || null,
|
metadataKind: selected.metadataKind || null,
|
||||||
@@ -213,6 +242,7 @@ export default function MetadataSelectionDialog({
|
|||||||
poster: null,
|
poster: null,
|
||||||
fromOmdb: false,
|
fromOmdb: false,
|
||||||
metadataProvider,
|
metadataProvider,
|
||||||
|
workflowKind: metadataProvider === 'tmdb' ? 'series' : 'film',
|
||||||
seasonNumber: null,
|
seasonNumber: null,
|
||||||
...(effectiveDiscNumber ? { discNumber: effectiveDiscNumber } : {})
|
...(effectiveDiscNumber ? { discNumber: effectiveDiscNumber } : {})
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -810,7 +810,10 @@ function stripEpisodePartSuffix(value) {
|
|||||||
if (!source) {
|
if (!source) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const suffixPattern = /(?:\s*[-–—:.,]?\s*)(?:\(?\s*(?:teil|part|pt)\s*(?:\d{1,2}|[ivxlcdm]{1,6})(?:\s*(?:\/|von|of)\s*(?:\d{1,2}|[ivxlcdm]{1,6}))?\s*\)?)\s*$/i;
|
// Output cleanup for multi-episode labels:
|
||||||
|
// remove "Teil/Part" markers and pure numeric/roman suffixes in
|
||||||
|
// parentheses like "(1)" / "(2)".
|
||||||
|
const suffixPattern = /(?:\s*[-–—:.,]?\s*)(?:(?:\(?\s*(?:teil|part|pt)\s*(?:\d{1,2}|[ivxlcdm]{1,6})(?:\s*(?:\/|von|of)\s*(?:\d{1,2}|[ivxlcdm]{1,6}))?\s*\)?)|\(\s*(?:\d{1,2}|[ivxlcdm]{1,6})\s*\))\s*$/i;
|
||||||
let normalized = source;
|
let normalized = source;
|
||||||
let changed = false;
|
let changed = false;
|
||||||
for (let i = 0; i < 2; i += 1) {
|
for (let i = 0; i < 2; i += 1) {
|
||||||
@@ -1943,15 +1946,49 @@ export default function PipelineStatusCard({
|
|||||||
? pipeline.context.handBrakeDecisionSummary
|
? pipeline.context.handBrakeDecisionSummary
|
||||||
: null;
|
: null;
|
||||||
const ambiguousLongestTitleId = normalizeTitleId(handBrakeDecisionSummary?.longestCandidateTitleId);
|
const ambiguousLongestTitleId = normalizeTitleId(handBrakeDecisionSummary?.longestCandidateTitleId);
|
||||||
|
const handBrakeDecisionCandidateTitleIds = useMemo(() => normalizeTitleIdList(
|
||||||
|
(Array.isArray(pipeline?.context?.handBrakeTitleCandidates)
|
||||||
|
? pipeline.context.handBrakeTitleCandidates
|
||||||
|
: []
|
||||||
|
).map((item) => item?.handBrakeTitleId)
|
||||||
|
), [pipeline?.context?.handBrakeTitleCandidates]);
|
||||||
|
const handBrakeDecisionSelectableTitleIds = useMemo(() => {
|
||||||
|
const explicit = normalizeTitleIdList(pipeline?.context?.handBrakeDecisionSelectableTitleIds);
|
||||||
|
if (explicit.length > 0) {
|
||||||
|
return explicit;
|
||||||
|
}
|
||||||
|
if (isSeriesPlayAllDecisionMode && ambiguousLongestTitleId) {
|
||||||
|
return [ambiguousLongestTitleId];
|
||||||
|
}
|
||||||
|
return handBrakeDecisionCandidateTitleIds;
|
||||||
|
}, [
|
||||||
|
isSeriesPlayAllDecisionMode,
|
||||||
|
ambiguousLongestTitleId,
|
||||||
|
handBrakeDecisionCandidateTitleIds,
|
||||||
|
pipeline?.context?.handBrakeDecisionSelectableTitleIds
|
||||||
|
]);
|
||||||
|
const handBrakeDecisionSelectableTitleIdSet = useMemo(
|
||||||
|
() => new Set(handBrakeDecisionSelectableTitleIds.map((id) => Number(id))),
|
||||||
|
[handBrakeDecisionSelectableTitleIds]
|
||||||
|
);
|
||||||
|
|
||||||
const waitingHandBrakeTitleRows = useMemo(() => {
|
const waitingHandBrakeTitleRows = useMemo(() => {
|
||||||
const raw = Array.isArray(pipeline?.context?.handBrakeTitleCandidates)
|
const rawCandidates = Array.isArray(pipeline?.context?.handBrakeTitleCandidates)
|
||||||
? pipeline.context.handBrakeTitleCandidates
|
? pipeline.context.handBrakeTitleCandidates
|
||||||
: [];
|
: [];
|
||||||
return raw
|
const rawAllTitles = isSeriesPlayAllDecisionMode && Array.isArray(pipeline?.context?.handBrakeAllTitles)
|
||||||
|
? pipeline.context.handBrakeAllTitles
|
||||||
|
: [];
|
||||||
|
const sourceRows = rawAllTitles.length > 0 ? rawAllTitles : rawCandidates;
|
||||||
|
const candidateIdSet = new Set(
|
||||||
|
normalizeTitleIdList(rawCandidates.map((item) => item?.handBrakeTitleId)).map((id) => Number(id))
|
||||||
|
);
|
||||||
|
return sourceRows
|
||||||
.map((item) => {
|
.map((item) => {
|
||||||
const titleId = Number(item?.handBrakeTitleId);
|
const titleId = normalizeTitleId(item?.handBrakeTitleId);
|
||||||
if (!Number.isFinite(titleId) || titleId <= 0) return null;
|
if (!titleId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const durationSeconds = Number(item?.durationSeconds || 0);
|
const durationSeconds = Number(item?.durationSeconds || 0);
|
||||||
const minutes = Math.floor(durationSeconds / 60);
|
const minutes = Math.floor(durationSeconds / 60);
|
||||||
const seconds = durationSeconds % 60;
|
const seconds = durationSeconds % 60;
|
||||||
@@ -1963,12 +2000,23 @@ export default function PipelineStatusCard({
|
|||||||
durationMinutes: Number(item?.durationMinutes || 0),
|
durationMinutes: Number(item?.durationMinutes || 0),
|
||||||
audioTrackCount: Number(item?.audioTrackCount || 0),
|
audioTrackCount: Number(item?.audioTrackCount || 0),
|
||||||
subtitleTrackCount: Number(item?.subtitleTrackCount || 0),
|
subtitleTrackCount: Number(item?.subtitleTrackCount || 0),
|
||||||
sizeBytes: Number(item?.sizeBytes || 0)
|
sizeBytes: Number(item?.sizeBytes || 0),
|
||||||
|
decisionCandidate: candidateIdSet.has(Number(titleId))
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.sort((a, b) => a.handBrakeTitleId - b.handBrakeTitleId);
|
.sort((a, b) => a.handBrakeTitleId - b.handBrakeTitleId);
|
||||||
}, [pipeline?.context?.handBrakeTitleCandidates]);
|
}, [
|
||||||
|
isSeriesPlayAllDecisionMode,
|
||||||
|
pipeline?.context?.handBrakeAllTitles,
|
||||||
|
pipeline?.context?.handBrakeTitleCandidates
|
||||||
|
]);
|
||||||
|
const visibleWaitingHandBrakeTitleRows = useMemo(() => {
|
||||||
|
if (!isSeriesPlayAllDecisionMode) {
|
||||||
|
return waitingHandBrakeTitleRows;
|
||||||
|
}
|
||||||
|
return waitingHandBrakeTitleRows.filter((row) => row.decisionCandidate);
|
||||||
|
}, [isSeriesPlayAllDecisionMode, waitingHandBrakeTitleRows]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!handBrakeTitleDecisionRequired) {
|
if (!handBrakeTitleDecisionRequired) {
|
||||||
@@ -1991,11 +2039,24 @@ export default function PipelineStatusCard({
|
|||||||
setSelectedHandBrakeTitleIdsState([Math.trunc(singleContextId)]);
|
setSelectedHandBrakeTitleIdsState([Math.trunc(singleContextId)]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const best = waitingHandBrakeTitleRows[0]?.handBrakeTitleId || null;
|
if (isSeriesPlayAllDecisionMode) {
|
||||||
setSelectedHandBrakeTitleIdsState(best ? [best] : []);
|
const lockedDefaultIds = waitingHandBrakeTitleRows
|
||||||
|
.filter((row) => row.decisionCandidate && !handBrakeDecisionSelectableTitleIdSet.has(Number(row.handBrakeTitleId)))
|
||||||
|
.map((row) => row.handBrakeTitleId);
|
||||||
|
if (lockedDefaultIds.length > 0) {
|
||||||
|
setSelectedHandBrakeTitleIdsState(normalizeTitleIdList(lockedDefaultIds));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const firstSelectable = waitingHandBrakeTitleRows.find((row) => (
|
||||||
|
!isSeriesPlayAllDecisionMode || handBrakeDecisionSelectableTitleIdSet.has(Number(row.handBrakeTitleId))
|
||||||
|
))?.handBrakeTitleId || null;
|
||||||
|
setSelectedHandBrakeTitleIdsState(firstSelectable ? [firstSelectable] : []);
|
||||||
}, [
|
}, [
|
||||||
handBrakeTitleDecisionRequired,
|
handBrakeTitleDecisionRequired,
|
||||||
|
isSeriesPlayAllDecisionMode,
|
||||||
retryJobId,
|
retryJobId,
|
||||||
|
handBrakeDecisionSelectableTitleIdSet,
|
||||||
waitingHandBrakeTitleRows,
|
waitingHandBrakeTitleRows,
|
||||||
pipeline?.context?.selectedHandBrakeTitleId,
|
pipeline?.context?.selectedHandBrakeTitleId,
|
||||||
pipeline?.context?.selectedHandBrakeTitleIds
|
pipeline?.context?.selectedHandBrakeTitleIds
|
||||||
@@ -3087,40 +3148,58 @@ export default function PipelineStatusCard({
|
|||||||
<h3>{isSeriesPlayAllDecisionMode ? 'Serien-Grenzfall: PlayAll oder Doppelfolge?' : 'DVD Titel-Auswahl erforderlich'}</h3>
|
<h3>{isSeriesPlayAllDecisionMode ? 'Serien-Grenzfall: PlayAll oder Doppelfolge?' : 'DVD Titel-Auswahl erforderlich'}</h3>
|
||||||
<small>
|
<small>
|
||||||
{isSeriesPlayAllDecisionMode
|
{isSeriesPlayAllDecisionMode
|
||||||
? 'Grenzfall erkannt: Der längste Titel passt zeitlich zur Summe der kürzeren Episoden. Bitte entscheiden, ob er als PlayAll ausgeschlossen oder als Doppelfolge mit encodiert werden soll.'
|
? 'Min. ein Titel passt zeitlich zur Summe der kürzeren Episoden. Bitte durch Auswahl entscheiden, ob PlayAll Multi-Episode (nicht anhaken) oder Doppelfolge (anhaken).'
|
||||||
: 'Mehrere DVD-Titel gefunden, die die Mindestlänge erfüllen. Bitte einen oder mehrere Episoden-Titel auswählen.'}
|
: 'Mehrere DVD-Titel gefunden, die die Mindestlänge erfüllen. Bitte einen oder mehrere Episoden-Titel auswählen.'}
|
||||||
</small>
|
</small>
|
||||||
{waitingHandBrakeTitleRows.length > 0 ? (
|
{visibleWaitingHandBrakeTitleRows.length > 0 ? (
|
||||||
<div className="playlist-decision-list">
|
<div className="playlist-decision-list">
|
||||||
{waitingHandBrakeTitleRows.map((row) => (
|
{visibleWaitingHandBrakeTitleRows.map((row) => {
|
||||||
<div key={row.handBrakeTitleId} className="playlist-decision-item">
|
const isDecisionSelectable = !isSeriesPlayAllDecisionMode
|
||||||
<label className="readonly-check-row">
|
|| handBrakeDecisionSelectableTitleIdSet.has(Number(row.handBrakeTitleId));
|
||||||
<input
|
const isAmbiguousDecisionTitle = isSeriesPlayAllDecisionMode
|
||||||
type="checkbox"
|
&& isDecisionSelectable
|
||||||
checked={selectedHandBrakeTitleIdsState.includes(row.handBrakeTitleId)}
|
&& (
|
||||||
disabled={queueLocked}
|
Number(row.handBrakeTitleId) === Number(ambiguousLongestTitleId)
|
||||||
onChange={() => {
|
|| handBrakeDecisionSelectableTitleIdSet.has(Number(row.handBrakeTitleId))
|
||||||
setSelectedHandBrakeTitleIdsState((prev) => {
|
);
|
||||||
const list = Array.isArray(prev) ? prev : [];
|
const isRecognizedLocked = isSeriesPlayAllDecisionMode
|
||||||
if (list.includes(row.handBrakeTitleId)) {
|
&& row.decisionCandidate
|
||||||
return list.filter((value) => value !== row.handBrakeTitleId);
|
&& !isDecisionSelectable;
|
||||||
|
const isOutOfDecisionScope = isSeriesPlayAllDecisionMode
|
||||||
|
&& !row.decisionCandidate;
|
||||||
|
return (
|
||||||
|
<div key={row.handBrakeTitleId} className="playlist-decision-item">
|
||||||
|
<label className="readonly-check-row">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={selectedHandBrakeTitleIdsState.includes(row.handBrakeTitleId)}
|
||||||
|
disabled={queueLocked || !isDecisionSelectable}
|
||||||
|
onChange={() => {
|
||||||
|
if (!isDecisionSelectable) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
return [...list, row.handBrakeTitleId];
|
setSelectedHandBrakeTitleIdsState((prev) => {
|
||||||
});
|
const list = Array.isArray(prev) ? prev : [];
|
||||||
}}
|
if (list.includes(row.handBrakeTitleId)) {
|
||||||
/>
|
return list.filter((value) => value !== row.handBrakeTitleId);
|
||||||
<span>
|
}
|
||||||
{`Titel -t ${row.handBrakeTitleId}`}
|
return [...list, row.handBrakeTitleId];
|
||||||
{isSeriesPlayAllDecisionMode && ambiguousLongestTitleId && row.handBrakeTitleId === ambiguousLongestTitleId
|
});
|
||||||
? ' | Längster Titel (PlayAll-/Doppelfolge-Kandidat)'
|
}}
|
||||||
: ''}
|
/>
|
||||||
{row.durationLabel ? ` | ${row.durationLabel}` : ''}
|
<span>
|
||||||
{row.audioTrackCount > 0 ? ` | Audio: ${row.audioTrackCount}` : ''}
|
{isAmbiguousDecisionTitle ? <strong>{`Titel -t ${row.handBrakeTitleId}`}</strong> : `Titel -t ${row.handBrakeTitleId}`}
|
||||||
{row.subtitleTrackCount > 0 ? ` | Untertitel: ${row.subtitleTrackCount}` : ''}
|
{isAmbiguousDecisionTitle ? ' | Grenzfalltitel (PlayAll/Doppelfolge)' : ''}
|
||||||
</span>
|
{isRecognizedLocked ? ' | automatisch erkannt (fix)' : ''}
|
||||||
</label>
|
{isOutOfDecisionScope ? ' | nicht entscheidungsrelevant' : ''}
|
||||||
</div>
|
{row.durationLabel ? ` | ${row.durationLabel}` : ''}
|
||||||
))}
|
{row.audioTrackCount > 0 ? ` | Audio: ${row.audioTrackCount}` : ''}
|
||||||
|
{row.subtitleTrackCount > 0 ? ` | Untertitel: ${row.subtitleTrackCount}` : ''}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<small>Keine Kandidaten verfügbar. Bitte Analyse erneut ausführen.</small>
|
<small>Keine Kandidaten verfügbar. Bitte Analyse erneut ausführen.</small>
|
||||||
@@ -3156,7 +3235,7 @@ export default function PipelineStatusCard({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{(state === 'READY_TO_ENCODE' || state === 'MEDIAINFO_CHECK' || mediaInfoReview) ? (
|
{(state === 'READY_TO_ENCODE' || state === 'MEDIAINFO_CHECK' || mediaInfoReview) && !(handBrakeTitleDecisionRequired && isSeriesPlayAllDecisionMode) ? (
|
||||||
<div className="mediainfo-review-block">
|
<div className="mediainfo-review-block">
|
||||||
{!isDvdTitleSelectionRequired ? <h3>Titel-/Spurprüfung</h3> : null}
|
{!isDvdTitleSelectionRequired ? <h3>Titel-/Spurprüfung</h3> : null}
|
||||||
{state === 'READY_TO_ENCODE' && !queueLocked && !isDvdTitleSelectionRequired ? (
|
{state === 'READY_TO_ENCODE' && !queueLocked && !isDvdTitleSelectionRequired ? (
|
||||||
|
|||||||
@@ -1112,8 +1112,20 @@ export default function HistoryPage({ refreshToken = 0 }) {
|
|||||||
: (makemkvInfo?.selectedMetadata && typeof makemkvInfo.selectedMetadata === 'object'
|
: (makemkvInfo?.selectedMetadata && typeof makemkvInfo.selectedMetadata === 'object'
|
||||||
? makemkvInfo.selectedMetadata
|
? makemkvInfo.selectedMetadata
|
||||||
: {});
|
: {});
|
||||||
|
const workflowKindRaw = String(
|
||||||
|
selectedMetadata?.workflowKind
|
||||||
|
|| analyzeContext?.workflowKind
|
||||||
|
|| ''
|
||||||
|
).trim().toLowerCase();
|
||||||
|
const workflowKind = ['film', 'movie', 'feature'].includes(workflowKindRaw)
|
||||||
|
? 'film'
|
||||||
|
: (['series', 'tv', 'season', 'episode'].includes(workflowKindRaw) ? 'series' : null);
|
||||||
const isSeriesDvd = resolveMediaType(row) === 'dvd' && isSeriesDvdJob(row);
|
const isSeriesDvd = resolveMediaType(row) === 'dvd' && isSeriesDvdJob(row);
|
||||||
const metadataProvider = isSeriesDvd ? 'tmdb' : 'omdb';
|
const metadataProvider = workflowKind === 'series'
|
||||||
|
? 'tmdb'
|
||||||
|
: (workflowKind === 'film'
|
||||||
|
? 'omdb'
|
||||||
|
: (String(selectedMetadata?.metadataProvider || analyzeContext?.metadataProvider || (isSeriesDvd ? 'tmdb' : 'omdb')).trim().toLowerCase() || 'omdb'));
|
||||||
const seasonNumber = selectedMetadata?.seasonNumber ?? analyzeContext?.seriesLookupHint?.seasonNumber ?? null;
|
const seasonNumber = selectedMetadata?.seasonNumber ?? analyzeContext?.seriesLookupHint?.seasonNumber ?? null;
|
||||||
const discNumber = selectedMetadata?.discNumber ?? analyzeContext?.seriesLookupHint?.discNumber ?? null;
|
const discNumber = selectedMetadata?.discNumber ?? analyzeContext?.seriesLookupHint?.discNumber ?? null;
|
||||||
const context = {
|
const context = {
|
||||||
|
|||||||
@@ -1352,7 +1352,7 @@ export default function RipperPage({
|
|||||||
jobId: normalizedJobId,
|
jobId: normalizedJobId,
|
||||||
detectedTitle: context?.detectedTitle || job?.detected_title || selectedMetadata?.title || '',
|
detectedTitle: context?.detectedTitle || job?.detected_title || selectedMetadata?.title || '',
|
||||||
selectedMetadata,
|
selectedMetadata,
|
||||||
metadataProvider: context?.metadataProvider || selectedMetadata?.metadataProvider || 'omdb',
|
metadataProvider: selectedMetadata?.metadataProvider || context?.metadataProvider || 'omdb',
|
||||||
metadataCandidates: Array.isArray(context?.metadataCandidates) ? context.metadataCandidates : [],
|
metadataCandidates: Array.isArray(context?.metadataCandidates) ? context.metadataCandidates : [],
|
||||||
seriesAnalysis: context?.seriesAnalysis || null,
|
seriesAnalysis: context?.seriesAnalysis || null,
|
||||||
seriesLookupHint: context?.seriesLookupHint || null,
|
seriesLookupHint: context?.seriesLookupHint || null,
|
||||||
@@ -1382,7 +1382,7 @@ export default function RipperPage({
|
|||||||
seasonNumber: null,
|
seasonNumber: null,
|
||||||
discNumber: null
|
discNumber: null
|
||||||
},
|
},
|
||||||
metadataProvider: currentContext?.metadataProvider || currentContext?.selectedMetadata?.metadataProvider || 'omdb',
|
metadataProvider: currentContext?.selectedMetadata?.metadataProvider || currentContext?.metadataProvider || 'omdb',
|
||||||
metadataCandidates: Array.isArray(currentContext?.metadataCandidates) ? currentContext.metadataCandidates : [],
|
metadataCandidates: Array.isArray(currentContext?.metadataCandidates) ? currentContext.metadataCandidates : [],
|
||||||
seriesAnalysis: currentContext?.seriesAnalysis || null,
|
seriesAnalysis: currentContext?.seriesAnalysis || null,
|
||||||
seriesLookupHint: currentContext?.seriesLookupHint || null,
|
seriesLookupHint: currentContext?.seriesLookupHint || null,
|
||||||
|
|||||||
@@ -217,6 +217,17 @@ export function isSeriesDvdJob(job) {
|
|||||||
: (job?.makemkvInfo?.selectedMetadata && typeof job.makemkvInfo.selectedMetadata === 'object'
|
: (job?.makemkvInfo?.selectedMetadata && typeof job.makemkvInfo.selectedMetadata === 'object'
|
||||||
? job.makemkvInfo.selectedMetadata
|
? job.makemkvInfo.selectedMetadata
|
||||||
: {});
|
: {});
|
||||||
|
const workflowKind = String(
|
||||||
|
selectedMetadata?.workflowKind
|
||||||
|
|| analyzeContext?.workflowKind
|
||||||
|
|| ''
|
||||||
|
).trim().toLowerCase();
|
||||||
|
if (['film', 'movie', 'feature'].includes(workflowKind)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (['series', 'tv', 'season', 'episode'].includes(workflowKind)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const metadataProvider = String(
|
const metadataProvider = String(
|
||||||
analyzeContext?.metadataProvider
|
analyzeContext?.metadataProvider
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^9.1.2"
|
"concurrently": "^9.1.2"
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.13.1-5",
|
"version": "0.13.1-6",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
|
"dev": "concurrently \"npm run dev --prefix backend\" \"npm run dev --prefix frontend\"",
|
||||||
"dev:backend": "npm run dev --prefix backend",
|
"dev:backend": "npm run dev --prefix backend",
|
||||||
|
|||||||
Reference in New Issue
Block a user