mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-16 16:34:24 +02:00
add transcode and playback filters to env settings (#2018)
This commit is contained in:
@@ -66,6 +66,9 @@ These variables override app settings **on first run** when no persisted setting
|
|||||||
| `playback.scrobble.scrobbleAtDuration` | `240` | `FS_PLAYBACK_SCROBBLE_AT_DURATION` | Seconds of playback before scrobble. |
|
| `playback.scrobble.scrobbleAtDuration` | `240` | `FS_PLAYBACK_SCROBBLE_AT_DURATION` | Seconds of playback before scrobble. |
|
||||||
| `playback.scrobble.scrobbleAtPercentage` | `75` | `FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE` | Percentage of track before scrobble. |
|
| `playback.scrobble.scrobbleAtPercentage` | `75` | `FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE` | Percentage of track before scrobble. |
|
||||||
| `playback.transcode.enabled` | `false` | `FS_PLAYBACK_TRANSCODE_ENABLED` | `true` / `false` — Enable transcoding. |
|
| `playback.transcode.enabled` | `false` | `FS_PLAYBACK_TRANSCODE_ENABLED` | `true` / `false` — Enable transcoding. |
|
||||||
|
| `playback.transcode.format` | *(unset)* | `FS_PLAYBACK_TRANSCODE_FORMAT` | Transcode format string (codec/container), e.g. server-specific value. Empty = use default. |
|
||||||
|
| `playback.transcode.bitrate` | *(unset)* | `FS_PLAYBACK_TRANSCODE_BITRATE` | Transcode bitrate (number, kbps or as defined by server). |
|
||||||
|
| `playback.filters` | `[]` | `FS_PLAYBACK_FILTERS` | JSON array of player filters: each object needs `id`, `field`, `operator`, `value`; optional `isEnabled`. Invalid JSON or shape is ignored. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,9 @@ window.FS_PLAYBACK_SCROBBLE_NOTIFY = "${FS_PLAYBACK_SCROBBLE_NOTIFY}";
|
|||||||
window.FS_PLAYBACK_SCROBBLE_AT_DURATION = "${FS_PLAYBACK_SCROBBLE_AT_DURATION}";
|
window.FS_PLAYBACK_SCROBBLE_AT_DURATION = "${FS_PLAYBACK_SCROBBLE_AT_DURATION}";
|
||||||
window.FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE = "${FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE}";
|
window.FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE = "${FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE}";
|
||||||
window.FS_PLAYBACK_TRANSCODE_ENABLED = "${FS_PLAYBACK_TRANSCODE_ENABLED}";
|
window.FS_PLAYBACK_TRANSCODE_ENABLED = "${FS_PLAYBACK_TRANSCODE_ENABLED}";
|
||||||
|
window.FS_PLAYBACK_TRANSCODE_FORMAT = "${FS_PLAYBACK_TRANSCODE_FORMAT}";
|
||||||
|
window.FS_PLAYBACK_TRANSCODE_BITRATE = "${FS_PLAYBACK_TRANSCODE_BITRATE}";
|
||||||
|
window.FS_PLAYBACK_FILTERS = "${FS_PLAYBACK_FILTERS}";
|
||||||
|
|
||||||
window.FS_DISCORD_ENABLED = "${FS_DISCORD_ENABLED}";
|
window.FS_DISCORD_ENABLED = "${FS_DISCORD_ENABLED}";
|
||||||
window.FS_DISCORD_CLIENT_ID = "${FS_DISCORD_CLIENT_ID}";
|
window.FS_DISCORD_CLIENT_ID = "${FS_DISCORD_CLIENT_ID}";
|
||||||
|
|||||||
Vendored
+3
@@ -65,13 +65,16 @@ declare global {
|
|||||||
FS_LYRICS_TRANSLATION_API_KEY?: string;
|
FS_LYRICS_TRANSLATION_API_KEY?: string;
|
||||||
FS_LYRICS_TRANSLATION_TARGET_LANGUAGE?: string;
|
FS_LYRICS_TRANSLATION_TARGET_LANGUAGE?: string;
|
||||||
FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE?: string;
|
FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE?: string;
|
||||||
|
FS_PLAYBACK_FILTERS?: string;
|
||||||
FS_PLAYBACK_MEDIA_SESSION?: string;
|
FS_PLAYBACK_MEDIA_SESSION?: string;
|
||||||
FS_PLAYBACK_PRESERVE_PITCH?: string;
|
FS_PLAYBACK_PRESERVE_PITCH?: string;
|
||||||
FS_PLAYBACK_SCROBBLE_AT_DURATION?: string;
|
FS_PLAYBACK_SCROBBLE_AT_DURATION?: string;
|
||||||
FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE?: string;
|
FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE?: string;
|
||||||
FS_PLAYBACK_SCROBBLE_ENABLED?: string;
|
FS_PLAYBACK_SCROBBLE_ENABLED?: string;
|
||||||
FS_PLAYBACK_SCROBBLE_NOTIFY?: string;
|
FS_PLAYBACK_SCROBBLE_NOTIFY?: string;
|
||||||
|
FS_PLAYBACK_TRANSCODE_BITRATE?: string;
|
||||||
FS_PLAYBACK_TRANSCODE_ENABLED?: string;
|
FS_PLAYBACK_TRANSCODE_ENABLED?: string;
|
||||||
|
FS_PLAYBACK_TRANSCODE_FORMAT?: string;
|
||||||
FS_PLAYBACK_WEB_AUDIO?: string;
|
FS_PLAYBACK_WEB_AUDIO?: string;
|
||||||
LEGACY_AUTHENTICATION?: boolean | string;
|
LEGACY_AUTHENTICATION?: boolean | string;
|
||||||
REMOTE_URL?: string;
|
REMOTE_URL?: string;
|
||||||
|
|||||||
@@ -1,7 +1,73 @@
|
|||||||
import type { SettingsState } from './settings.store';
|
import type { PlayerFilter, SettingsState } from './settings.store';
|
||||||
|
|
||||||
import { sanitizeCss } from '/@/renderer/utils/sanitize';
|
import { sanitizeCss } from '/@/renderer/utils/sanitize';
|
||||||
|
|
||||||
|
const PLAYER_FILTER_FIELDS = new Set([
|
||||||
|
'albumArtist',
|
||||||
|
'artist',
|
||||||
|
'duration',
|
||||||
|
'favorite',
|
||||||
|
'genre',
|
||||||
|
'name',
|
||||||
|
'note',
|
||||||
|
'path',
|
||||||
|
'playCount',
|
||||||
|
'rating',
|
||||||
|
'year',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const PLAYER_FILTER_OPERATORS = new Set([
|
||||||
|
'after',
|
||||||
|
'afterDate',
|
||||||
|
'before',
|
||||||
|
'beforeDate',
|
||||||
|
'contains',
|
||||||
|
'endsWith',
|
||||||
|
'gt',
|
||||||
|
'inTheLast',
|
||||||
|
'inTheRange',
|
||||||
|
'inTheRangeDate',
|
||||||
|
'is',
|
||||||
|
'isNot',
|
||||||
|
'lt',
|
||||||
|
'notContains',
|
||||||
|
'notInTheLast',
|
||||||
|
'regex',
|
||||||
|
'startsWith',
|
||||||
|
]);
|
||||||
|
|
||||||
|
function isValidPlayerFilter(item: unknown): item is PlayerFilter {
|
||||||
|
if (!item || typeof item !== 'object' || Array.isArray(item)) return false;
|
||||||
|
const o = item as Record<string, unknown>;
|
||||||
|
if (typeof o.id !== 'string') return false;
|
||||||
|
if (typeof o.field !== 'string' || !PLAYER_FILTER_FIELDS.has(o.field)) return false;
|
||||||
|
if (typeof o.operator !== 'string' || !PLAYER_FILTER_OPERATORS.has(o.operator)) return false;
|
||||||
|
if (!isValidPlayerFilterValue(o.value)) return false;
|
||||||
|
if (o.isEnabled !== undefined && typeof o.isEnabled !== 'boolean') return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidPlayerFilterValue(value: unknown): boolean {
|
||||||
|
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!Array.isArray(value)) return false;
|
||||||
|
return value.every((v) => typeof v === 'string' || typeof v === 'number');
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePlaybackFiltersJson(raw: string): unknown {
|
||||||
|
const t = raw.trim();
|
||||||
|
if (t === '') return undefined;
|
||||||
|
try {
|
||||||
|
const v = JSON.parse(t) as unknown;
|
||||||
|
if (!Array.isArray(v)) return undefined;
|
||||||
|
if (!v.every(isValidPlayerFilter)) return undefined;
|
||||||
|
return v;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const APP_THEMES = new Set([
|
const APP_THEMES = new Set([
|
||||||
'ayuDark',
|
'ayuDark',
|
||||||
'ayuLight',
|
'ayuLight',
|
||||||
@@ -55,28 +121,29 @@ type DeepPartial<T> = {
|
|||||||
interface EnvSettingSpec {
|
interface EnvSettingSpec {
|
||||||
enumSet?: Set<string>;
|
enumSet?: Set<string>;
|
||||||
key: string;
|
key: string;
|
||||||
path: [string, string, string] | [string, string];
|
path: readonly string[];
|
||||||
skipIfEmpty?: boolean;
|
skipIfEmpty?: boolean;
|
||||||
transform?: (raw: string) => unknown;
|
transform?: (raw: string) => unknown;
|
||||||
type: 'bool' | 'enum' | 'num' | 'string';
|
type: 'bool' | 'enum' | 'num' | 'string';
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAtPath(
|
function setAtPath(obj: EnvSettingsOverrides, path: readonly string[], value: unknown): void {
|
||||||
obj: EnvSettingsOverrides,
|
if (path.length < 2) return;
|
||||||
path: [string, string, string] | [string, string],
|
let cur: Record<string, unknown> = obj as Record<string, unknown>;
|
||||||
value: unknown,
|
for (let i = 0; i < path.length - 1; i++) {
|
||||||
): void {
|
const key = path[i]!;
|
||||||
const [a, b, c] = path;
|
const existing = cur[key];
|
||||||
const root = (obj as Record<string, unknown>)[a] ?? {};
|
const next: Record<string, unknown> =
|
||||||
(obj as Record<string, unknown>)[a] = root;
|
existing !== null &&
|
||||||
const branch = root as Record<string, unknown>;
|
existing !== undefined &&
|
||||||
if (c === undefined) {
|
typeof existing === 'object' &&
|
||||||
branch[b] = value;
|
!Array.isArray(existing)
|
||||||
} else {
|
? { ...(existing as Record<string, unknown>) }
|
||||||
const nested = branch[b] ?? {};
|
: {};
|
||||||
branch[b] = nested;
|
cur[key] = next;
|
||||||
(nested as Record<string, unknown>)[c] = value;
|
cur = next;
|
||||||
}
|
}
|
||||||
|
cur[path[path.length - 1]!] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RGB_ACCENT_REGEX = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/;
|
const RGB_ACCENT_REGEX = /^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/;
|
||||||
@@ -252,6 +319,23 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [
|
|||||||
path: ['playback', 'transcode', 'enabled'],
|
path: ['playback', 'transcode', 'enabled'],
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'FS_PLAYBACK_TRANSCODE_FORMAT',
|
||||||
|
path: ['playback', 'transcode', 'format'],
|
||||||
|
skipIfEmpty: true,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'FS_PLAYBACK_TRANSCODE_BITRATE',
|
||||||
|
path: ['playback', 'transcode', 'bitrate'],
|
||||||
|
type: 'num',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'FS_PLAYBACK_FILTERS',
|
||||||
|
path: ['playback', 'filters'],
|
||||||
|
transform: parsePlaybackFiltersJson,
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
{ key: 'FS_DISCORD_ENABLED', path: ['discord', 'enabled'], type: 'bool' },
|
{ key: 'FS_DISCORD_ENABLED', path: ['discord', 'enabled'], type: 'bool' },
|
||||||
{
|
{
|
||||||
key: 'FS_DISCORD_CLIENT_ID',
|
key: 'FS_DISCORD_CLIENT_ID',
|
||||||
|
|||||||
Reference in New Issue
Block a user