mirror of
https://github.com/jeffvli/feishin.git
synced 2026-06-10 14:22:46 +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.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.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_PERCENTAGE = "${FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE}";
|
||||
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_CLIENT_ID = "${FS_DISCORD_CLIENT_ID}";
|
||||
|
||||
Vendored
+3
@@ -65,13 +65,16 @@ declare global {
|
||||
FS_LYRICS_TRANSLATION_API_KEY?: string;
|
||||
FS_LYRICS_TRANSLATION_TARGET_LANGUAGE?: string;
|
||||
FS_PLAYBACK_AUDIO_FADE_ON_STATUS_CHANGE?: string;
|
||||
FS_PLAYBACK_FILTERS?: string;
|
||||
FS_PLAYBACK_MEDIA_SESSION?: string;
|
||||
FS_PLAYBACK_PRESERVE_PITCH?: string;
|
||||
FS_PLAYBACK_SCROBBLE_AT_DURATION?: string;
|
||||
FS_PLAYBACK_SCROBBLE_AT_PERCENTAGE?: string;
|
||||
FS_PLAYBACK_SCROBBLE_ENABLED?: string;
|
||||
FS_PLAYBACK_SCROBBLE_NOTIFY?: string;
|
||||
FS_PLAYBACK_TRANSCODE_BITRATE?: string;
|
||||
FS_PLAYBACK_TRANSCODE_ENABLED?: string;
|
||||
FS_PLAYBACK_TRANSCODE_FORMAT?: string;
|
||||
FS_PLAYBACK_WEB_AUDIO?: string;
|
||||
LEGACY_AUTHENTICATION?: boolean | 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';
|
||||
|
||||
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([
|
||||
'ayuDark',
|
||||
'ayuLight',
|
||||
@@ -55,28 +121,29 @@ type DeepPartial<T> = {
|
||||
interface EnvSettingSpec {
|
||||
enumSet?: Set<string>;
|
||||
key: string;
|
||||
path: [string, string, string] | [string, string];
|
||||
path: readonly string[];
|
||||
skipIfEmpty?: boolean;
|
||||
transform?: (raw: string) => unknown;
|
||||
type: 'bool' | 'enum' | 'num' | 'string';
|
||||
}
|
||||
|
||||
function setAtPath(
|
||||
obj: EnvSettingsOverrides,
|
||||
path: [string, string, string] | [string, string],
|
||||
value: unknown,
|
||||
): void {
|
||||
const [a, b, c] = path;
|
||||
const root = (obj as Record<string, unknown>)[a] ?? {};
|
||||
(obj as Record<string, unknown>)[a] = root;
|
||||
const branch = root as Record<string, unknown>;
|
||||
if (c === undefined) {
|
||||
branch[b] = value;
|
||||
} else {
|
||||
const nested = branch[b] ?? {};
|
||||
branch[b] = nested;
|
||||
(nested as Record<string, unknown>)[c] = value;
|
||||
function setAtPath(obj: EnvSettingsOverrides, path: readonly string[], value: unknown): void {
|
||||
if (path.length < 2) return;
|
||||
let cur: Record<string, unknown> = obj as Record<string, unknown>;
|
||||
for (let i = 0; i < path.length - 1; i++) {
|
||||
const key = path[i]!;
|
||||
const existing = cur[key];
|
||||
const next: Record<string, unknown> =
|
||||
existing !== null &&
|
||||
existing !== undefined &&
|
||||
typeof existing === 'object' &&
|
||||
!Array.isArray(existing)
|
||||
? { ...(existing as Record<string, unknown>) }
|
||||
: {};
|
||||
cur[key] = next;
|
||||
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*\)$/;
|
||||
@@ -252,6 +319,23 @@ const ENV_SETTING_SPECS: EnvSettingSpec[] = [
|
||||
path: ['playback', 'transcode', 'enabled'],
|
||||
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_CLIENT_ID',
|
||||
|
||||
Reference in New Issue
Block a user