0.16.1 Registry Fix MakeMKV
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.16.0",
|
"version": "0.16.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-backend",
|
"name": "ripster-backend",
|
||||||
"version": "0.16.0",
|
"version": "0.16.1",
|
||||||
"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.16.0",
|
"version": "0.16.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -831,6 +831,12 @@ async function removeDeprecatedSettings(db) {
|
|||||||
// Aktualisiert settings_schema-Metadaten (required, description, validation_json)
|
// Aktualisiert settings_schema-Metadaten (required, description, validation_json)
|
||||||
// für bestehende Einträge, ohne user-konfigurierte Werte in settings_values anzutasten.
|
// für bestehende Einträge, ohne user-konfigurierte Werte in settings_values anzutasten.
|
||||||
const SETTINGS_SCHEMA_METADATA_UPDATES = [
|
const SETTINGS_SCHEMA_METADATA_UPDATES = [
|
||||||
|
{
|
||||||
|
key: 'makemkv_registration_key',
|
||||||
|
required: 0,
|
||||||
|
description: 'Optionaler Registrierungsschlüssel. Wird beim Speichern nach ~/.MakeMKV/settings.conf synchronisiert. Darunter kann der aktuelle Betakey per API übernommen werden.',
|
||||||
|
validation_json: '{}'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'handbrake_preset_bluray',
|
key: 'handbrake_preset_bluray',
|
||||||
required: 0,
|
required: 0,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { SourcePlugin } = require('./PluginBase');
|
const { SourcePlugin } = require('./PluginBase');
|
||||||
const { spawnTrackedProcess } = require('../services/processRunner');
|
const { spawnTrackedProcess } = require('../services/processRunner');
|
||||||
|
const { syncRegistrationKeyToConfig } = require('../services/makemkvKeyService');
|
||||||
const { parseHandBrakeProgress, parseMakeMkvProgress } = require('../utils/progressParsers');
|
const { parseHandBrakeProgress, parseMakeMkvProgress } = require('../utils/progressParsers');
|
||||||
const { ensureDir, sanitizeFileName, renderTemplate } = require('../utils/files');
|
const { ensureDir, sanitizeFileName, renderTemplate } = require('../utils/files');
|
||||||
|
|
||||||
@@ -236,15 +237,7 @@ class ConverterPlugin extends SourcePlugin {
|
|||||||
|
|
||||||
const settings = await ctx.settings.getSettingsMap();
|
const settings = await ctx.settings.getSettingsMap();
|
||||||
const makemkvCmd = String(settings?.makemkv_command || 'makemkvcon').trim() || 'makemkvcon';
|
const makemkvCmd = String(settings?.makemkv_command || 'makemkvcon').trim() || 'makemkvcon';
|
||||||
const registrationKey = String(settings?.makemkv_registration_key || '').trim() || null;
|
await syncRegistrationKeyToConfig(settings?.makemkv_registration_key || '');
|
||||||
|
|
||||||
if (registrationKey) {
|
|
||||||
try {
|
|
||||||
await this._runCaptured(makemkvCmd, ['reg', registrationKey], { jobId: job?.id });
|
|
||||||
} catch (_err) {
|
|
||||||
// Registrierung ist optional
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const args = ['--noscan', '--decrypt', '-r', 'backup', `iso:${inputPath}`, rawJobDir];
|
const args = ['--noscan', '--decrypt', '-r', 'backup', `iso:${inputPath}`, rawJobDir];
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const userPresetService = require('../services/userPresetService');
|
|||||||
const activationBytesService = require('../services/activationBytesService');
|
const activationBytesService = require('../services/activationBytesService');
|
||||||
const diskDetectionService = require('../services/diskDetectionService');
|
const diskDetectionService = require('../services/diskDetectionService');
|
||||||
const coverArtRecoveryService = require('../services/coverArtRecoveryService');
|
const coverArtRecoveryService = require('../services/coverArtRecoveryService');
|
||||||
|
const { fetchCurrentBetaKey } = require('../services/makemkvKeyService');
|
||||||
const logger = require('../services/logger').child('SETTINGS_ROUTE');
|
const logger = require('../services/logger').child('SETTINGS_ROUTE');
|
||||||
|
|
||||||
const { getDb } = require('../db/database');
|
const { getDb } = require('../db/database');
|
||||||
@@ -52,6 +53,55 @@ router.get(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
'/makemkv/beta-key',
|
||||||
|
asyncHandler(async (req, res) => {
|
||||||
|
logger.debug('get:settings:makemkv:beta-key', { reqId: req.reqId });
|
||||||
|
const result = await fetchCurrentBetaKey();
|
||||||
|
res.json({
|
||||||
|
betaKey: result.key,
|
||||||
|
sourceUrl: result.sourceUrl
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
router.post(
|
||||||
|
'/makemkv/beta-key/apply',
|
||||||
|
asyncHandler(async (req, res) => {
|
||||||
|
logger.info('post:settings:makemkv:beta-key:apply', { reqId: req.reqId });
|
||||||
|
const force = req.body?.force === true;
|
||||||
|
const currentSettings = await settingsService.getSettingsMap({ forceRefresh: true });
|
||||||
|
const existingKey = String(currentSettings?.makemkv_registration_key || '').trim();
|
||||||
|
const betaKeyResult = await fetchCurrentBetaKey();
|
||||||
|
|
||||||
|
if (!force && existingKey && existingKey !== betaKeyResult.key) {
|
||||||
|
const syncResult = await settingsService.syncMakeMKVRegistrationKeyFromSettings({
|
||||||
|
settingsMap: currentSettings
|
||||||
|
});
|
||||||
|
res.json({
|
||||||
|
applied: false,
|
||||||
|
preservedExistingKey: true,
|
||||||
|
reason: 'existing_key',
|
||||||
|
betaKey: betaKeyResult.key,
|
||||||
|
sourceUrl: betaKeyResult.sourceUrl,
|
||||||
|
settingsFilePath: syncResult?.path || null
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = await settingsService.setSettingValue('makemkv_registration_key', betaKeyResult.key);
|
||||||
|
wsService.broadcast('SETTINGS_UPDATED', updated);
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
applied: true,
|
||||||
|
preservedExistingKey: false,
|
||||||
|
setting: updated,
|
||||||
|
betaKey: betaKeyResult.key,
|
||||||
|
sourceUrl: betaKeyResult.sourceUrl
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/scripts',
|
'/scripts',
|
||||||
asyncHandler(async (req, res) => {
|
asyncHandler(async (req, res) => {
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const os = require('os');
|
||||||
|
const path = require('path');
|
||||||
|
const logger = require('./logger').child('MAKEMKV_KEY');
|
||||||
|
|
||||||
|
const MAKEMKV_BETA_KEY_API_URL = 'https://cable.ayra.ch/makemkv/api.php?raw';
|
||||||
|
const MAKEMKV_BETA_KEY_API_USER_AGENT = process.env.MAKEMKV_BETA_KEY_API_USER_AGENT
|
||||||
|
|| 'Ripster/1.0 (MakeMKV beta key sync)';
|
||||||
|
const MAKEMKV_BETA_KEY_API_FROM = process.env.MAKEMKV_BETA_KEY_API_FROM
|
||||||
|
|| 'ripster@localhost';
|
||||||
|
|
||||||
|
function normalizeRegistrationKey(rawValue) {
|
||||||
|
return String(rawValue || '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMakeMKVConfigDir(homeDir = os.homedir()) {
|
||||||
|
return path.join(String(homeDir || '').trim() || os.homedir(), '.MakeMKV');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getMakeMKVSettingsFilePath(homeDir = os.homedir()) {
|
||||||
|
return path.join(getMakeMKVConfigDir(homeDir), 'settings.conf');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeKeyForSettingsFile(value) {
|
||||||
|
return String(value || '')
|
||||||
|
.replace(/\\/g, '\\\\')
|
||||||
|
.replace(/"/g, '\\"');
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUpdatedSettingsContent(currentContent, registrationKey) {
|
||||||
|
const existingLines = String(currentContent || '')
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.filter((line) => !/^\s*app_Key\s*=/.test(line));
|
||||||
|
const normalizedKey = normalizeRegistrationKey(registrationKey);
|
||||||
|
|
||||||
|
while (existingLines.length > 0 && existingLines[existingLines.length - 1] === '') {
|
||||||
|
existingLines.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedKey) {
|
||||||
|
existingLines.push(`app_Key = "${escapeKeyForSettingsFile(normalizedKey)}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingLines.length > 0 ? `${existingLines.join('\n')}\n` : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function syncRegistrationKeyToConfig(rawValue, options = {}) {
|
||||||
|
const normalizedKey = normalizeRegistrationKey(rawValue);
|
||||||
|
const homeDir = String(options?.homeDir || '').trim() || os.homedir();
|
||||||
|
const configDir = getMakeMKVConfigDir(homeDir);
|
||||||
|
const settingsFilePath = getMakeMKVSettingsFilePath(homeDir);
|
||||||
|
const fileExists = fs.existsSync(settingsFilePath);
|
||||||
|
const currentContent = fileExists
|
||||||
|
? await fs.promises.readFile(settingsFilePath, 'utf8')
|
||||||
|
: '';
|
||||||
|
const nextContent = buildUpdatedSettingsContent(currentContent, normalizedKey);
|
||||||
|
|
||||||
|
if (!normalizedKey && !fileExists) {
|
||||||
|
return {
|
||||||
|
changed: false,
|
||||||
|
path: settingsFilePath,
|
||||||
|
hasKey: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.promises.mkdir(configDir, { recursive: true });
|
||||||
|
|
||||||
|
if (nextContent) {
|
||||||
|
await fs.promises.writeFile(settingsFilePath, nextContent, 'utf8');
|
||||||
|
await fs.promises.chmod(settingsFilePath, 0o600).catch(() => {});
|
||||||
|
logger.info('settings-conf:key-synced', {
|
||||||
|
path: settingsFilePath,
|
||||||
|
hasKey: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await fs.promises.writeFile(settingsFilePath, '', 'utf8');
|
||||||
|
await fs.promises.chmod(settingsFilePath, 0o600).catch(() => {});
|
||||||
|
logger.info('settings-conf:key-cleared', {
|
||||||
|
path: settingsFilePath,
|
||||||
|
hasKey: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
changed: currentContent !== nextContent,
|
||||||
|
path: settingsFilePath,
|
||||||
|
hasKey: Boolean(normalizedKey)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCurrentBetaKey() {
|
||||||
|
const response = await fetch(MAKEMKV_BETA_KEY_API_URL, {
|
||||||
|
headers: {
|
||||||
|
'User-Agent': MAKEMKV_BETA_KEY_API_USER_AGENT,
|
||||||
|
'From': MAKEMKV_BETA_KEY_API_FROM,
|
||||||
|
'Accept': 'text/plain, text/html;q=0.9, */*;q=0.8',
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Pragma': 'no-cache'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = new Error(`Betakey konnte nicht geladen werden (HTTP ${response.status}).`);
|
||||||
|
error.statusCode = 502;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawBody = await response.text();
|
||||||
|
const betaKey = String(rawBody || '')
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.find(Boolean) || '';
|
||||||
|
|
||||||
|
if (!/^[A-Za-z0-9][A-Za-z0-9-]{15,}$/.test(betaKey)) {
|
||||||
|
const error = new Error('Betakey-Antwort ist ungültig.');
|
||||||
|
error.statusCode = 502;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: betaKey,
|
||||||
|
sourceUrl: MAKEMKV_BETA_KEY_API_URL
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MAKEMKV_BETA_KEY_API_URL,
|
||||||
|
fetchCurrentBetaKey,
|
||||||
|
getMakeMKVConfigDir,
|
||||||
|
getMakeMKVSettingsFilePath,
|
||||||
|
normalizeRegistrationKey,
|
||||||
|
syncRegistrationKeyToConfig
|
||||||
|
};
|
||||||
@@ -16207,27 +16207,23 @@ class PipelineService extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ensureMakeMKVRegistration(jobId, stage) {
|
async ensureMakeMKVRegistration(jobId, stage) {
|
||||||
const registrationConfig = await settingsService.buildMakeMKVRegisterConfig();
|
const syncResult = await settingsService.syncMakeMKVRegistrationKeyFromSettings();
|
||||||
if (!registrationConfig) {
|
if (!syncResult?.applied) {
|
||||||
return { applied: false, reason: 'not_configured' };
|
return { applied: false, reason: 'not_configured', path: syncResult?.path || null };
|
||||||
}
|
}
|
||||||
|
|
||||||
await historyService.appendLog(
|
await historyService.appendLog(
|
||||||
jobId,
|
jobId,
|
||||||
'SYSTEM',
|
'SYSTEM',
|
||||||
'Setze MakeMKV-Registrierungsschlüssel aus den Settings (makemkvcon reg).'
|
'Synchronisiere MakeMKV-Key aus den Settings nach ~/.MakeMKV/settings.conf.'
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.runCommand({
|
return {
|
||||||
jobId,
|
applied: true,
|
||||||
stage,
|
path: syncResult?.path || null,
|
||||||
source: 'MAKEMKV_REG',
|
changed: Boolean(syncResult?.changed),
|
||||||
cmd: registrationConfig.cmd,
|
stage
|
||||||
args: registrationConfig.args,
|
};
|
||||||
argsForLog: registrationConfig.argsForLog
|
|
||||||
});
|
|
||||||
|
|
||||||
return { applied: true };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isReviewRefreshSettingKey(key) {
|
isReviewRefreshSettingKey(key) {
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ const {
|
|||||||
} = require('../utils/validators');
|
} = require('../utils/validators');
|
||||||
const { splitArgs } = require('../utils/commandLine');
|
const { splitArgs } = require('../utils/commandLine');
|
||||||
const { setLogRootDir } = require('./logPathService');
|
const { setLogRootDir } = require('./logPathService');
|
||||||
|
const {
|
||||||
|
syncRegistrationKeyToConfig,
|
||||||
|
normalizeRegistrationKey
|
||||||
|
} = require('./makemkvKeyService');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
defaultRawDir: DEFAULT_RAW_DIR,
|
defaultRawDir: DEFAULT_RAW_DIR,
|
||||||
@@ -75,6 +79,7 @@ const SERVER_CONSOLE_LOG_SETTING_KEYS = new Set([
|
|||||||
SERVER_CONSOLE_LOG_WARN_ENABLED_KEY,
|
SERVER_CONSOLE_LOG_WARN_ENABLED_KEY,
|
||||||
SERVER_CONSOLE_LOG_ERROR_ENABLED_KEY
|
SERVER_CONSOLE_LOG_ERROR_ENABLED_KEY
|
||||||
]);
|
]);
|
||||||
|
const MAKEMKV_REGISTRATION_KEY_SETTING_KEY = 'makemkv_registration_key';
|
||||||
const MEDIA_PROFILES = ['bluray', 'dvd', 'cd', 'audiobook'];
|
const MEDIA_PROFILES = ['bluray', 'dvd', 'cd', 'audiobook'];
|
||||||
const PROFILED_SETTINGS = {
|
const PROFILED_SETTINGS = {
|
||||||
raw_dir: {
|
raw_dir: {
|
||||||
@@ -1056,7 +1061,10 @@ class SettingsService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const serializedValue = serializeValueByType(schema.type, result.normalized);
|
const serializedValue = serializeValueByType(schema.type, result.normalized);
|
||||||
|
const normalizedKey = String(key || '').trim().toLowerCase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await db.exec('BEGIN');
|
||||||
await db.run(
|
await db.run(
|
||||||
`
|
`
|
||||||
INSERT INTO settings_values (key, value, updated_at)
|
INSERT INTO settings_values (key, value, updated_at)
|
||||||
@@ -1067,14 +1075,22 @@ class SettingsService {
|
|||||||
`,
|
`,
|
||||||
[key, serializedValue]
|
[key, serializedValue]
|
||||||
);
|
);
|
||||||
|
if (normalizedKey === MAKEMKV_REGISTRATION_KEY_SETTING_KEY) {
|
||||||
|
await syncRegistrationKeyToConfig(result.normalized);
|
||||||
|
}
|
||||||
|
await db.exec('COMMIT');
|
||||||
|
} catch (error) {
|
||||||
|
await db.exec('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
logger.info('setting:updated', {
|
logger.info('setting:updated', {
|
||||||
key,
|
key,
|
||||||
value: SENSITIVE_SETTING_KEYS.has(String(key || '').trim().toLowerCase()) ? '[redacted]' : result.normalized
|
value: SENSITIVE_SETTING_KEYS.has(String(key || '').trim().toLowerCase()) ? '[redacted]' : result.normalized
|
||||||
});
|
});
|
||||||
if (String(key || '').trim().toLowerCase() === LOG_DIR_SETTING_KEY) {
|
if (normalizedKey === LOG_DIR_SETTING_KEY) {
|
||||||
applyRuntimeLogDirSetting(result.normalized);
|
applyRuntimeLogDirSetting(result.normalized);
|
||||||
}
|
}
|
||||||
if (SERVER_CONSOLE_LOG_SETTING_KEYS.has(String(key || '').trim().toLowerCase())) {
|
if (SERVER_CONSOLE_LOG_SETTING_KEYS.has(normalizedKey)) {
|
||||||
const map = await this.getSettingsMap({ forceRefresh: true });
|
const map = await this.getSettingsMap({ forceRefresh: true });
|
||||||
applyRuntimeServerConsoleLoggingSettingsFromMap(map);
|
applyRuntimeServerConsoleLoggingSettingsFromMap(map);
|
||||||
}
|
}
|
||||||
@@ -1159,6 +1175,12 @@ class SettingsService {
|
|||||||
[item.key, item.serializedValue]
|
[item.key, item.serializedValue]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const makemkvKeyChange = normalizedEntries.find(
|
||||||
|
(item) => String(item?.key || '').trim().toLowerCase() === MAKEMKV_REGISTRATION_KEY_SETTING_KEY
|
||||||
|
);
|
||||||
|
if (makemkvKeyChange) {
|
||||||
|
await syncRegistrationKeyToConfig(makemkvKeyChange.value);
|
||||||
|
}
|
||||||
await db.exec('COMMIT');
|
await db.exec('COMMIT');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await db.exec('ROLLBACK');
|
await db.exec('ROLLBACK');
|
||||||
@@ -1317,20 +1339,15 @@ class SettingsService {
|
|||||||
return { cmd, args: [...baseArgs, ...extra] };
|
return { cmd, args: [...baseArgs, ...extra] };
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildMakeMKVRegisterConfig() {
|
async syncMakeMKVRegistrationKeyFromSettings(options = {}) {
|
||||||
const map = await this.getSettingsMap();
|
const map = options?.settingsMap || await this.getSettingsMap();
|
||||||
const registrationKey = String(map.makemkv_registration_key || '').trim();
|
const registrationKey = normalizeRegistrationKey(map?.makemkv_registration_key);
|
||||||
if (!registrationKey) {
|
const fileInfo = await syncRegistrationKeyToConfig(registrationKey, options);
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cmd = map.makemkv_command || 'makemkvcon';
|
|
||||||
const args = ['reg', registrationKey];
|
|
||||||
logger.debug('cli:makemkv:register', { cmd, args: ['reg', '<redacted>'] });
|
|
||||||
return {
|
return {
|
||||||
cmd,
|
applied: Boolean(registrationKey),
|
||||||
args,
|
key: registrationKey || null,
|
||||||
argsForLog: ['reg', '<redacted>']
|
path: fileInfo?.path || null,
|
||||||
|
changed: Boolean(fileInfo?.changed)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -397,7 +397,7 @@ VALUES ('makemkv_command', 'Tools', 'MakeMKV Kommando', 'string', 1, 'Pfad oder
|
|||||||
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_command', 'makemkvcon');
|
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_command', 'makemkvcon');
|
||||||
|
|
||||||
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
VALUES ('makemkv_registration_key', 'Tools', 'MakeMKV Key', 'string', 0, 'Optionaler Registrierungsschlüssel. Wird vor Analyze/Rip automatisch per "makemkvcon reg" gesetzt.', NULL, '[]', '{}', 202);
|
VALUES ('makemkv_registration_key', 'Tools', 'MakeMKV Key', 'string', 0, 'Optionaler Registrierungsschlüssel. Wird beim Speichern nach ~/.MakeMKV/settings.conf synchronisiert. Darunter kann der aktuelle Betakey per API übernommen werden.', NULL, '[]', '{}', 202);
|
||||||
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_registration_key', NULL);
|
INSERT OR IGNORE INTO settings_values (key, value) VALUES ('makemkv_registration_key', NULL);
|
||||||
|
|
||||||
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
INSERT OR IGNORE INTO settings_schema (key, category, label, type, required, description, default_value, options_json, validation_json, order_index)
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ Features:
|
|||||||
- profilspezifische Auflösung (`resolveEffectiveToolSettings`)
|
- profilspezifische Auflösung (`resolveEffectiveToolSettings`)
|
||||||
- CLI-Config-Building für MakeMKV/HandBrake/MediaInfo/FFmpeg
|
- CLI-Config-Building für MakeMKV/HandBrake/MediaInfo/FFmpeg
|
||||||
- HandBrake-Preset-Liste via `HandBrakeCLI -z`
|
- HandBrake-Preset-Liste via `HandBrakeCLI -z`
|
||||||
- MakeMKV-Registration-Command aus `makemkv_registration_key`
|
- MakeMKV-Key-Synchronisation aus `makemkv_registration_key` nach `~/.MakeMKV/settings.conf`
|
||||||
- Pfadauflösung für alle Medienprofile inkl. Audiobook und Converter
|
- Pfadauflösung für alle Medienprofile inkl. Audiobook und Converter
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ Typische Auswirkungen:
|
|||||||
| GUI-Feld | Key | Typ | Default | Wirkung im System |
|
| GUI-Feld | Key | Typ | Default | Wirkung im System |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| `MakeMKV Kommando` | `makemkv_command` | `string` | `makemkvcon` | Pfad oder Befehl für makemkvcon. |
|
| `MakeMKV Kommando` | `makemkv_command` | `string` | `makemkvcon` | Pfad oder Befehl für makemkvcon. |
|
||||||
| `MakeMKV Key` | `makemkv_registration_key` | `string` | `leer` | Optionaler Registrierungsschlüssel. Wird vor Analyze/Rip automatisch per "makemkvcon reg" gesetzt. |
|
| `MakeMKV Key` | `makemkv_registration_key` | `string` | `leer` | Optionaler Registrierungsschlüssel. Wird beim Speichern nach `~/.MakeMKV/settings.conf` synchronisiert. In der UI kann darunter der aktuelle Betakey geladen und übernommen werden. |
|
||||||
| `Mediainfo Kommando` | `mediainfo_command` | `string` | `mediainfo` | Pfad oder Befehl für mediainfo. |
|
| `Mediainfo Kommando` | `mediainfo_command` | `string` | `mediainfo` | Pfad oder Befehl für mediainfo. |
|
||||||
| `Minimale Titellänge (Minuten)` | `makemkv_min_length_minutes` | `number` | `60` | Filtert kurze Titel beim Rip. |
|
| `Minimale Titellänge (Minuten)` | `makemkv_min_length_minutes` | `number` | `60` | Filtert kurze Titel beim Rip. |
|
||||||
| `HandBrake Kommando` | `handbrake_command` | `string` | `HandBrakeCLI` | Pfad oder Befehl für HandBrakeCLI. |
|
| `HandBrake Kommando` | `handbrake_command` | `string` | `HandBrakeCLI` | Pfad oder Befehl für HandBrakeCLI. |
|
||||||
|
|||||||
+10
-2
@@ -34,12 +34,20 @@ makemkvcon backup <source> <rawDir> --decrypt
|
|||||||
|
|
||||||
## Registrierungsschlüssel (optional)
|
## Registrierungsschlüssel (optional)
|
||||||
|
|
||||||
Wenn in `Settings` ein `MakeMKV Key` gesetzt ist, führt Ripster vor Analyse/Rip aus:
|
Wenn in `Settings` ein `MakeMKV Key` gesetzt ist, synchronisiert Ripster ihn nach:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
makemkvcon reg <key>
|
~/.MakeMKV/settings.conf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Format:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
app_Key = "product-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
Zusätzlich zeigt die UI unterhalb des Feldes den aktuellen Betakey an, der per Button in das Eingabefeld übernommen werden kann.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Relevante Felder in `Settings`
|
## Relevante Felder in `Settings`
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.16.0",
|
"version": "0.16.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster-frontend",
|
"name": "ripster-frontend",
|
||||||
"version": "0.16.0",
|
"version": "0.16.1",
|
||||||
"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.16.0",
|
"version": "0.16.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -376,6 +376,20 @@ export const api = {
|
|||||||
});
|
});
|
||||||
return normalizeHandBrakePresetPayload(result);
|
return normalizeHandBrakePresetPayload(result);
|
||||||
},
|
},
|
||||||
|
getMakeMKVBetaKey(options = {}) {
|
||||||
|
return requestCachedGet('/settings/makemkv/beta-key', {
|
||||||
|
ttlMs: 10 * 60 * 1000,
|
||||||
|
forceRefresh: options.forceRefresh
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async applyMakeMKVBetaKey(payload = {}) {
|
||||||
|
const result = await request('/settings/makemkv/beta-key/apply', {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify(payload || {})
|
||||||
|
});
|
||||||
|
afterMutationInvalidate(['/settings/makemkv/beta-key', '/settings']);
|
||||||
|
return result;
|
||||||
|
},
|
||||||
getScripts(options = {}) {
|
getScripts(options = {}) {
|
||||||
return requestCachedGet('/settings/scripts', {
|
return requestCachedGet('/settings/scripts', {
|
||||||
ttlMs: 2 * 60 * 1000,
|
ttlMs: 2 * 60 * 1000,
|
||||||
|
|||||||
@@ -655,6 +655,67 @@ function isReadonlySetting(setting) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function MakeMKVBetaKeyHint({ onApply, disabled = false }) {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [betaKey, setBetaKey] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let active = true;
|
||||||
|
setLoading(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
api.getMakeMKVBetaKey({ forceRefresh: true })
|
||||||
|
.then((response) => {
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setBetaKey(String(response?.betaKey || '').trim());
|
||||||
|
})
|
||||||
|
.catch((loadError) => {
|
||||||
|
if (!active) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setBetaKey('');
|
||||||
|
setError(loadError?.message || 'Betakey konnte nicht geladen werden.');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
if (active) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
active = false;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="makemkv-beta-key-box">
|
||||||
|
<div className="makemkv-beta-key-head">
|
||||||
|
<span className="makemkv-beta-key-title">Betakey</span>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="small"
|
||||||
|
text
|
||||||
|
label="übernehmen"
|
||||||
|
disabled={disabled || loading || !betaKey}
|
||||||
|
onClick={() => onApply?.(betaKey)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{loading ? (
|
||||||
|
<small className="setting-description">Aktueller Betakey wird geladen…</small>
|
||||||
|
) : null}
|
||||||
|
{!loading && error ? (
|
||||||
|
<small className="error-text">{error}</small>
|
||||||
|
) : null}
|
||||||
|
{!loading && !error && betaKey ? (
|
||||||
|
<code className="makemkv-beta-key-value">{betaKey}</code>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SettingField({
|
function SettingField({
|
||||||
setting,
|
setting,
|
||||||
value,
|
value,
|
||||||
@@ -782,6 +843,12 @@ function SettingField({
|
|||||||
</a>
|
</a>
|
||||||
</small>
|
</small>
|
||||||
) : null}
|
) : null}
|
||||||
|
{normalizeSettingKey(setting?.key) === 'makemkv_registration_key' ? (
|
||||||
|
<MakeMKVBetaKeyHint
|
||||||
|
disabled={isDisabled}
|
||||||
|
onApply={(nextKey) => onChange?.(setting.key, nextKey)}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
{error ? (
|
{error ? (
|
||||||
<small className="error-text">{error}</small>
|
<small className="error-text">{error}</small>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -2046,6 +2046,43 @@ body {
|
|||||||
line-height: 1.35;
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.makemkv-beta-key-box {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.35rem;
|
||||||
|
margin-top: 0.15rem;
|
||||||
|
padding: 0.55rem 0.65rem;
|
||||||
|
border: 1px dashed rgba(111, 57, 34, 0.32);
|
||||||
|
border-radius: 0.45rem;
|
||||||
|
background: rgba(255, 250, 240, 0.72);
|
||||||
|
}
|
||||||
|
|
||||||
|
.makemkv-beta-key-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.makemkv-beta-key-title {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--rip-brown-800);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.makemkv-beta-key-value {
|
||||||
|
display: block;
|
||||||
|
padding: 0.45rem 0.5rem;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
background: rgba(58, 29, 18, 0.06);
|
||||||
|
color: var(--rip-brown-900);
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
.notification-toggle-grid {
|
.notification-toggle-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
|||||||
+37
-2
@@ -440,8 +440,41 @@ install_makemkv() {
|
|||||||
cd /
|
cd /
|
||||||
rm -rf "$tmp_dir"
|
rm -rf "$tmp_dir"
|
||||||
ok "MakeMKV $makemkv_version installiert"
|
ok "MakeMKV $makemkv_version installiert"
|
||||||
warn "Hinweis: MakeMKV benötigt eine Lizenz oder den Beta-Key."
|
info "Der MakeMKV-Betakey wird nach dem Backend-Start automatisch per API übernommen, sofern noch kein eigener Key gespeichert ist."
|
||||||
warn "Beta-Key: https://www.makemkv.com/forum/viewtopic.php?t=1053"
|
}
|
||||||
|
|
||||||
|
sync_makemkv_beta_key() {
|
||||||
|
header "MakeMKV Betakey synchronisieren"
|
||||||
|
|
||||||
|
local api_url="http://127.0.0.1:${BACKEND_PORT}/settings/makemkv/beta-key/apply"
|
||||||
|
local response=""
|
||||||
|
|
||||||
|
if ! response=$(curl -fsS \
|
||||||
|
-X POST \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
--data '{}' \
|
||||||
|
"$api_url" 2>/dev/null); then
|
||||||
|
warn "MakeMKV-Betakey konnte nicht automatisch übernommen werden."
|
||||||
|
warn "Backend-Endpunkt fehlgeschlagen: $api_url"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local applied preserved settings_file
|
||||||
|
applied=$(printf '%s' "$response" | jq -r '.applied // false' 2>/dev/null || echo "false")
|
||||||
|
preserved=$(printf '%s' "$response" | jq -r '.preservedExistingKey // false' 2>/dev/null || echo "false")
|
||||||
|
settings_file=$(printf '%s' "$response" | jq -r '.settingsFilePath // empty' 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ "$applied" == "true" ]]; then
|
||||||
|
ok "MakeMKV-Betakey automatisch gespeichert${settings_file:+ ($settings_file)}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$preserved" == "true" ]]; then
|
||||||
|
ok "Vorhandener MakeMKV-Key bleibt erhalten${settings_file:+ ($settings_file)}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
warn "MakeMKV-Betakey wurde nicht übernommen."
|
||||||
}
|
}
|
||||||
|
|
||||||
select_handbrake_mode() {
|
select_handbrake_mode() {
|
||||||
@@ -987,6 +1020,8 @@ else
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
sync_makemkv_beta_key
|
||||||
|
|
||||||
if [[ "$SKIP_NGINX" == false ]]; then
|
if [[ "$SKIP_NGINX" == false ]]; then
|
||||||
systemctl enable nginx
|
systemctl enable nginx
|
||||||
systemctl restart nginx
|
systemctl restart nginx
|
||||||
|
|||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.16.0",
|
"version": "0.16.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ripster",
|
"name": "ripster",
|
||||||
"version": "0.16.0",
|
"version": "0.16.1",
|
||||||
"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.16.0",
|
"version": "0.16.1",
|
||||||
"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