feat: localize quality labels and add quality template token
This commit is contained in:
@@ -572,6 +572,28 @@ export const recordingTemplateLabelMap: Record<number, string> = {
|
||||
2: "归档 TS"
|
||||
};
|
||||
|
||||
export const qualityLabelMap: Record<string, string> = {
|
||||
origin: "原画",
|
||||
FULL_HD: "超清",
|
||||
HD: "高清",
|
||||
SD: "标清"
|
||||
};
|
||||
|
||||
export const qualityOptionList = [
|
||||
{ value: "origin", label: qualityLabelMap.origin },
|
||||
{ value: "FULL_HD", label: qualityLabelMap.FULL_HD },
|
||||
{ value: "HD", label: qualityLabelMap.HD },
|
||||
{ value: "SD", label: qualityLabelMap.SD }
|
||||
] as const;
|
||||
|
||||
export function formatQualityLabel(value?: string | null) {
|
||||
if (!value) {
|
||||
return "-";
|
||||
}
|
||||
|
||||
return qualityLabelMap[value] ?? value;
|
||||
}
|
||||
|
||||
export const autoStartDecisionLabelMap: Record<string, string> = {
|
||||
started: "已启动",
|
||||
skipped_disabled: "已禁用",
|
||||
|
||||
@@ -6,9 +6,11 @@ import { useViewport } from "@/composables/useViewport";
|
||||
import type { BatchLiveRoomsResult, ImportLiveRoomsRequest, ImportLiveRoomsResult, LiveRoom, RecordTask } from "@/types";
|
||||
import {
|
||||
autoStartDecisionLabelMap,
|
||||
formatQualityLabel,
|
||||
availabilityLabelMap,
|
||||
currentRecordingStateLabelMap,
|
||||
outputFormatLabelMap,
|
||||
qualityOptionList,
|
||||
recordingTemplateLabelMap,
|
||||
saveModeLabelMap
|
||||
} from "@/types";
|
||||
@@ -89,7 +91,8 @@ const platformOptions = [
|
||||
{ label: "虎牙", value: 3 }
|
||||
];
|
||||
|
||||
const qualityOptions = ["origin", "FULL_HD", "HD", "SD"];
|
||||
const qualityOptions = qualityOptionList;
|
||||
const qualitySupportHint = "抖音和 Bilibili 支持按画质选流;虎牙暂未实现。若目标档位不可用,平台会自动回退到最接近的可用画质。";
|
||||
const booleanOverrideOptions = [
|
||||
{ label: "跟随全局", value: inheritValue },
|
||||
{ label: "开启", value: "true" },
|
||||
@@ -143,6 +146,35 @@ function getRoomsTableScrollWrap() {
|
||||
shell.querySelector<HTMLElement>(".el-table__body-wrapper");
|
||||
}
|
||||
|
||||
function getRoomsTableContentWidth(wrap: HTMLElement) {
|
||||
const shell = roomsTableShellRef.value;
|
||||
|
||||
if (!shell) {
|
||||
return wrap.scrollWidth;
|
||||
}
|
||||
|
||||
const widths = [
|
||||
wrap.scrollWidth,
|
||||
wrap.firstElementChild?.scrollWidth ?? 0,
|
||||
shell.querySelector<HTMLElement>(".el-table__body table")?.scrollWidth ?? 0,
|
||||
shell.querySelector<HTMLElement>(".el-table__header table")?.scrollWidth ?? 0
|
||||
];
|
||||
|
||||
const wrapRect = wrap.getBoundingClientRect();
|
||||
let maxRight = 0;
|
||||
|
||||
shell.querySelectorAll<HTMLElement>(
|
||||
".el-table__header th, .el-table__body td, .room-actions-cell, .config-summary, .auto-start-cell"
|
||||
).forEach((element) => {
|
||||
const rect = element.getBoundingClientRect();
|
||||
maxRight = Math.max(maxRight, rect.right - wrapRect.left + wrap.scrollLeft);
|
||||
});
|
||||
|
||||
widths.push(Math.ceil(maxRight) + 24);
|
||||
|
||||
return Math.max(...widths);
|
||||
}
|
||||
|
||||
function handleRoomsTableBodyScroll() {
|
||||
if (syncingRoomsTableProxy) {
|
||||
return;
|
||||
@@ -235,11 +267,11 @@ async function syncRoomsTableProxyScroll() {
|
||||
return;
|
||||
}
|
||||
|
||||
const scrollWidth = Math.max(wrap.scrollWidth, wrap.firstElementChild?.scrollWidth ?? 0);
|
||||
const scrollWidth = getRoomsTableContentWidth(wrap);
|
||||
const clientWidth = wrap.clientWidth;
|
||||
const canScrollHorizontally = scrollWidth > clientWidth + 1;
|
||||
|
||||
roomsTableProxyInnerWidth.value = scrollWidth;
|
||||
roomsTableProxyInnerWidth.value = canScrollHorizontally ? scrollWidth : 0;
|
||||
showRoomsTableProxyScroll.value = canScrollHorizontally;
|
||||
|
||||
if (canScrollHorizontally && proxy && Math.abs(proxy.scrollLeft - wrap.scrollLeft) > 1) {
|
||||
@@ -653,6 +685,10 @@ function getRoomAvatarText(room: LiveRoom) {
|
||||
return source.slice(0, 1).toUpperCase();
|
||||
}
|
||||
|
||||
function getQualityLabel(quality?: string | null) {
|
||||
return formatQualityLabel(quality);
|
||||
}
|
||||
|
||||
function autoStartDecisionLabel(code?: string) {
|
||||
if (!code) {
|
||||
return "未记录";
|
||||
@@ -862,7 +898,7 @@ onBeforeUnmount(() => {
|
||||
<div style="grid-column: 1 / -1;">
|
||||
<dt>房间配置</dt>
|
||||
<dd>
|
||||
{{ row.effectiveSettings.preferredQuality }} ·
|
||||
{{ getQualityLabel(row.effectiveSettings.preferredQuality) }} ·
|
||||
{{ outputFormatLabelMap[row.effectiveSettings.outputFormat] }} ·
|
||||
{{ saveModeLabelMap[row.effectiveSettings.saveMode] }} ·
|
||||
{{ row.effectiveSettings.enableDanmakuRecording ? "弹幕开" : "弹幕关" }}
|
||||
@@ -914,7 +950,7 @@ onBeforeUnmount(() => {
|
||||
v-loading="loading"
|
||||
:height="tableHeight"
|
||||
class="premium-table rooms-table"
|
||||
table-layout="auto"
|
||||
table-layout="fixed"
|
||||
row-key="id"
|
||||
@selection-change="handleRoomSelectionChange"
|
||||
>
|
||||
@@ -982,10 +1018,10 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="房间配置" min-width="240">
|
||||
<el-table-column label="房间配置" width="280">
|
||||
<template #default="{ row }">
|
||||
<div class="config-summary">
|
||||
<span>画质 {{ row.effectiveSettings.preferredQuality }}</span>
|
||||
<span>画质 {{ getQualityLabel(row.effectiveSettings.preferredQuality) }}</span>
|
||||
<span>{{ outputFormatLabelMap[row.effectiveSettings.outputFormat] }}</span>
|
||||
<span>{{ saveModeLabelMap[row.effectiveSettings.saveMode] }}</span>
|
||||
<span>{{ row.effectiveSettings.enableDanmakuRecording ? "弹幕开" : "弹幕关" }}</span>
|
||||
@@ -1012,7 +1048,7 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column label="操作" width="320">
|
||||
<el-table-column label="操作" width="380">
|
||||
<template #default="{ row }">
|
||||
<div class="room-actions-cell">
|
||||
<el-button size="small" @click="refreshRoom(row)">刷新</el-button>
|
||||
@@ -1172,10 +1208,17 @@ onBeforeUnmount(() => {
|
||||
<el-form label-position="top">
|
||||
<el-form-item label="清晰度">
|
||||
<el-select v-model="recordForm.preferredQuality">
|
||||
<el-option v-for="option in qualityOptions" :key="option" :label="option" :value="option" />
|
||||
<el-option
|
||||
v-for="option in qualityOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<div class="field-help">{{ qualitySupportHint }}</div>
|
||||
|
||||
<el-form-item label="输出格式">
|
||||
<el-select v-model="recordForm.outputFormat">
|
||||
<el-option
|
||||
@@ -1241,10 +1284,17 @@ onBeforeUnmount(() => {
|
||||
<el-form-item label="默认画质">
|
||||
<el-select v-model="settingsForm.preferredQualityOverride">
|
||||
<el-option label="跟随全局" :value="inheritValue" />
|
||||
<el-option v-for="option in qualityOptions" :key="option" :label="option" :value="option" />
|
||||
<el-option
|
||||
v-for="option in qualityOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<div class="field-help field-help--full">{{ qualitySupportHint }}</div>
|
||||
|
||||
<el-form-item label="默认输出格式">
|
||||
<el-select v-model="settingsForm.outputFormatOverride">
|
||||
<el-option label="跟随全局" :value="inheritValue" />
|
||||
@@ -1454,7 +1504,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.rooms-table {
|
||||
min-width: 1380px;
|
||||
min-width: 1920px;
|
||||
}
|
||||
|
||||
.rooms-table :deep(.el-table__cell) {
|
||||
@@ -1462,7 +1512,7 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.rooms-table :deep(.cell) {
|
||||
overflow: visible;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.room-avatar {
|
||||
@@ -1557,16 +1607,29 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
|
||||
.room-actions-cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, max-content));
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.room-actions-cell :deep(.el-button) {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.field-help {
|
||||
margin-top: -8px;
|
||||
margin-bottom: 14px;
|
||||
color: var(--text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.field-help--full {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
.settings-note {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
RecordSessionTimelineSegment
|
||||
} from "@/types";
|
||||
import {
|
||||
formatQualityLabel,
|
||||
logLevelLabelMap,
|
||||
outputFormatLabelMap,
|
||||
platformLabelMap,
|
||||
@@ -277,7 +278,7 @@ onMounted(loadDetail);
|
||||
{{ outputFormatLabelMap[detail.session.outputFormat] }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="清晰度">
|
||||
<span class="monospace">{{ detail.session.preferredQuality }}</span>
|
||||
<span>{{ formatQualityLabel(detail.session.preferredQuality) }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="分片数量">
|
||||
{{ detail.session.segmentCount }}
|
||||
|
||||
@@ -6,6 +6,7 @@ import apiClient, { getApiErrorMessage } from "@/api/client";
|
||||
import { useViewport } from "@/composables/useViewport";
|
||||
import type { RecordArtifactUploadItemResult, RecordPreviewTicket, RecordTaskDetail } from "@/types";
|
||||
import {
|
||||
formatQualityLabel,
|
||||
logLevelLabelMap,
|
||||
outputFormatLabelMap,
|
||||
sessionStatusLabelMap,
|
||||
@@ -249,7 +250,7 @@ onMounted(loadDetailAndPreview);
|
||||
<span class="monospace">#{{ detail.task.segmentIndex }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="RoomId">{{ detail.task.roomId }}</el-descriptions-item>
|
||||
<el-descriptions-item label="清晰度">{{ detail.task.preferredQuality }}</el-descriptions-item>
|
||||
<el-descriptions-item label="清晰度">{{ formatQualityLabel(detail.task.preferredQuality) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="输出格式">
|
||||
{{ outputFormatLabelMap[detail.task.outputFormat] }}
|
||||
</el-descriptions-item>
|
||||
|
||||
@@ -17,6 +17,7 @@ import type {
|
||||
RecordTask
|
||||
} from "@/types";
|
||||
import {
|
||||
formatQualityLabel,
|
||||
outputFormatLabelMap,
|
||||
saveModeLabelMap,
|
||||
sessionStatusLabelMap,
|
||||
@@ -747,7 +748,7 @@ onBeforeUnmount(() => {
|
||||
<div class="data-card__grid">
|
||||
<div>
|
||||
<dt>清晰度</dt>
|
||||
<dd class="monospace">{{ task.preferredQuality }}</dd>
|
||||
<dd>{{ formatQualityLabel(task.preferredQuality) }}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>时长</dt>
|
||||
@@ -905,7 +906,7 @@ onBeforeUnmount(() => {
|
||||
|
||||
<el-table-column label="清晰度" width="120">
|
||||
<template #default="{ row }">
|
||||
<span class="monospace">{{ row.preferredQuality }}</span>
|
||||
<span>{{ formatQualityLabel(row.preferredQuality) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
@@ -1435,4 +1436,3 @@ onBeforeUnmount(() => {
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import type {
|
||||
SystemSettings,
|
||||
WebhookTestResult
|
||||
} from "@/types";
|
||||
import { outputFormatLabelMap, recordingTemplateLabelMap, saveModeLabelMap } from "@/types";
|
||||
import { outputFormatLabelMap, qualityOptionList, recordingTemplateLabelMap, saveModeLabelMap } from "@/types";
|
||||
import { useAuthStore } from "@/stores/auth";
|
||||
import { useViewport } from "@/composables/useViewport";
|
||||
import { useUiPreferences } from "@/composables/useUiPreferences";
|
||||
@@ -42,6 +42,8 @@ const webhookTestResult = ref<WebhookTestResult | null>(null);
|
||||
const retentionCleanupResult = ref<RetentionCleanupResult | null>(null);
|
||||
const loadError = ref("");
|
||||
const activeSettingTab = ref("recording");
|
||||
const qualitySupportHint = "抖音和 Bilibili 支持按画质选流;虎牙暂未实现。若目标档位不可用,平台会自动回退到最接近的可用画质。";
|
||||
const qualityOptions = qualityOptionList;
|
||||
|
||||
const savebarStyle = computed(() => {
|
||||
if (isMobile.value) {
|
||||
@@ -63,7 +65,7 @@ const form = reactive<SystemSettings>({
|
||||
ffmpegPath: "ffmpeg",
|
||||
outputRoot: "records",
|
||||
outputDirectoryTemplate: "{platform}/{yyyy}/{MM}/{dd}/{anchor}",
|
||||
outputFileNameTemplate: "{HHmmss}_{anchor}_{title}_{roomId}{segmentSuffix}",
|
||||
outputFileNameTemplate: "{HHmmss}_{quality}_{anchor}_{title}_{roomId}{segmentSuffix}",
|
||||
defaultQuality: "origin",
|
||||
defaultOutputFormat: 0,
|
||||
saveMode: 0,
|
||||
@@ -187,6 +189,7 @@ const outputTemplateTokens = [
|
||||
"{roomId}",
|
||||
"{anchor}",
|
||||
"{title}",
|
||||
"{quality}",
|
||||
"{yyyy}",
|
||||
"{MM}",
|
||||
"{dd}",
|
||||
@@ -266,12 +269,12 @@ const canSendTestWebhook = computed(() => Boolean(form.webhookUrl.trim()));
|
||||
|
||||
const segmentedExamplePath = computed(() => {
|
||||
const extension = form.defaultOutputFormat === 1 ? "ts" : "mp4";
|
||||
return `Douyin/2026/04/15/主播名/221530_主播名_直播标题_123456789_00001.${extension}`;
|
||||
return `Douyin/2026/04/15/主播名/221530_origin_主播名_直播标题_123456789_00001.${extension}`;
|
||||
});
|
||||
|
||||
const nestedSegmentedExamplePath = computed(() => {
|
||||
const extension = form.defaultOutputFormat === 1 ? "ts" : "mp4";
|
||||
return `Douyin/2026/04/15/主播名/221530_主播名_直播标题_123456789/221530_主播名_直播标题_123456789_00001.${extension}`;
|
||||
return `Douyin/origin/2026/04/15/主播名/221530_origin_主播名_直播标题_123456789/221530_origin_主播名_直播标题_123456789_00001.${extension}`;
|
||||
});
|
||||
|
||||
async function loadSettings() {
|
||||
@@ -599,7 +602,14 @@ onMounted(loadSettings);
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="默认清晰度">
|
||||
<el-input v-model="form.defaultQuality" placeholder="origin / FULL_HD / HD / SD" />
|
||||
<el-select v-model="form.defaultQuality">
|
||||
<el-option
|
||||
v-for="option in qualityOptions"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
@@ -670,6 +680,10 @@ onMounted(loadSettings);
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
|
||||
<div class="helper-panel">
|
||||
{{ qualitySupportHint }}
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="surface-card settings-card" shadow="never">
|
||||
@@ -815,7 +829,7 @@ onMounted(loadSettings);
|
||||
v-model="form.outputFileNameTemplate"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="{HHmmss}_{anchor}_{title}_{roomId}{segmentSuffix}"
|
||||
placeholder="{HHmmss}_{quality}_{anchor}_{title}_{roomId}{segmentSuffix}"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -834,6 +848,10 @@ onMounted(loadSettings);
|
||||
目录模板和文件名模板中的时间变量统一按北京时间(UTC+8)渲染,示例路径也按北京时间展示。
|
||||
</div>
|
||||
|
||||
<div class="helper-panel">
|
||||
<code>{quality}</code> 输出稳定画质键,例如 <code>origin</code>、<code>FULL_HD</code>、<code>HD</code>、<code>SD</code>,适合放进目录名或文件名里做脚本处理。
|
||||
</div>
|
||||
|
||||
<div class="token-list">
|
||||
<span v-for="token in outputTemplateTokens" :key="token" class="token-chip">{{ token }}</span>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ public sealed class SystemSettingsDto
|
||||
|
||||
public string OutputDirectoryTemplate { get; set; } = "{platform}/{yyyy}/{MM}/{dd}/{anchor}";
|
||||
|
||||
public string OutputFileNameTemplate { get; set; } = "{HHmmss}_{anchor}_{title}_{roomId}";
|
||||
public string OutputFileNameTemplate { get; set; } = "{HHmmss}_{quality}_{anchor}_{title}_{roomId}{segmentSuffix}";
|
||||
|
||||
public string DefaultQuality { get; set; } = "origin";
|
||||
|
||||
@@ -233,7 +233,7 @@ public sealed class UpdateSystemSettingsRequest
|
||||
|
||||
public string OutputDirectoryTemplate { get; set; } = "{platform}/{yyyy}/{MM}/{dd}/{anchor}";
|
||||
|
||||
public string OutputFileNameTemplate { get; set; } = "{HHmmss}_{anchor}_{title}_{roomId}";
|
||||
public string OutputFileNameTemplate { get; set; } = "{HHmmss}_{quality}_{anchor}_{title}_{roomId}{segmentSuffix}";
|
||||
|
||||
public string DefaultQuality { get; set; } = "origin";
|
||||
|
||||
|
||||
@@ -284,6 +284,7 @@ public sealed class RecordService
|
||||
liveRoom.RoomId,
|
||||
storageAnchorName,
|
||||
liveRoom.Title,
|
||||
streamResult.SelectedQuality,
|
||||
outputFormat,
|
||||
saveMode,
|
||||
now);
|
||||
@@ -770,6 +771,7 @@ public sealed class RecordService
|
||||
string roomId,
|
||||
string? anchorName,
|
||||
string? title,
|
||||
string selectedQuality,
|
||||
RecordOutputFormat outputFormat,
|
||||
RecordSaveMode saveMode,
|
||||
DateTimeOffset now)
|
||||
@@ -783,6 +785,7 @@ public sealed class RecordService
|
||||
safeRoomId,
|
||||
anchorName,
|
||||
title,
|
||||
selectedQuality,
|
||||
localNow,
|
||||
segmentSuffix: string.Empty);
|
||||
var directoryPath = BuildDirectoryPath(
|
||||
@@ -791,6 +794,7 @@ public sealed class RecordService
|
||||
safeRoomId,
|
||||
anchorName,
|
||||
title,
|
||||
selectedQuality,
|
||||
localNow,
|
||||
baseFileStem);
|
||||
var fileNameStem = BuildFileNameStem(
|
||||
@@ -799,6 +803,7 @@ public sealed class RecordService
|
||||
safeRoomId,
|
||||
anchorName,
|
||||
title,
|
||||
selectedQuality,
|
||||
localNow,
|
||||
saveMode == RecordSaveMode.Segmented ? "_%05d" : string.Empty);
|
||||
var folder = Path.Combine(outputRoot, directoryPath);
|
||||
@@ -838,6 +843,7 @@ public sealed class RecordService
|
||||
string roomId,
|
||||
string? anchorName,
|
||||
string? title,
|
||||
string quality,
|
||||
DateTimeOffset now,
|
||||
string fileStem)
|
||||
{
|
||||
@@ -847,6 +853,7 @@ public sealed class RecordService
|
||||
roomId,
|
||||
anchorName,
|
||||
title,
|
||||
quality,
|
||||
now,
|
||||
forPathSegment: true,
|
||||
fileStem,
|
||||
@@ -866,6 +873,7 @@ public sealed class RecordService
|
||||
string roomId,
|
||||
string? anchorName,
|
||||
string? title,
|
||||
string quality,
|
||||
DateTimeOffset now,
|
||||
string segmentSuffix)
|
||||
{
|
||||
@@ -875,6 +883,7 @@ public sealed class RecordService
|
||||
roomId,
|
||||
anchorName,
|
||||
title,
|
||||
quality,
|
||||
now,
|
||||
forPathSegment: false,
|
||||
fileStem: string.Empty,
|
||||
@@ -885,7 +894,7 @@ public sealed class RecordService
|
||||
private static string EnsureSegmentSuffixTemplate(string template, RecordSaveMode saveMode)
|
||||
{
|
||||
var effectiveTemplate = string.IsNullOrWhiteSpace(template)
|
||||
? "{HHmmss}_{anchor}_{title}_{roomId}{segmentSuffix}"
|
||||
? "{HHmmss}_{quality}_{anchor}_{title}_{roomId}{segmentSuffix}"
|
||||
: template.Trim();
|
||||
|
||||
if (saveMode != RecordSaveMode.Segmented)
|
||||
@@ -908,13 +917,14 @@ public sealed class RecordService
|
||||
string roomId,
|
||||
string? anchorName,
|
||||
string? title,
|
||||
string quality,
|
||||
DateTimeOffset now,
|
||||
bool forPathSegment,
|
||||
string fileStem,
|
||||
string segmentSuffix)
|
||||
{
|
||||
var effectiveTemplate = string.IsNullOrWhiteSpace(template)
|
||||
? (forPathSegment ? "{platform}/{yyyy}/{MM}/{dd}/{anchor}" : "{HHmmss}_{anchor}_{title}_{roomId}{segmentSuffix}")
|
||||
? (forPathSegment ? "{platform}/{yyyy}/{MM}/{dd}/{anchor}" : "{HHmmss}_{quality}_{anchor}_{title}_{roomId}{segmentSuffix}")
|
||||
: template;
|
||||
|
||||
// Path tokens must stay case-sensitive so {MM} (month) and {mm} (minute) don't collide.
|
||||
@@ -924,6 +934,7 @@ public sealed class RecordService
|
||||
["roomId"] = roomId,
|
||||
["anchor"] = NormalizeTokenValue(anchorName, "unknown-anchor"),
|
||||
["title"] = NormalizeTokenValue(title, "untitled"),
|
||||
["quality"] = NormalizeTokenValue(quality, "origin"),
|
||||
["fileStem"] = fileStem,
|
||||
["segmentSuffix"] = segmentSuffix,
|
||||
["yyyy"] = now.ToString("yyyy"),
|
||||
|
||||
@@ -115,7 +115,7 @@ public sealed class SystemSettingsService : ISystemSettingsService
|
||||
FfmpegPath = GetValue(lookup, FfmpegPathKey, "ffmpeg"),
|
||||
OutputRoot = GetValue(lookup, OutputRootKey, "records"),
|
||||
OutputDirectoryTemplate = GetValue(lookup, OutputDirectoryTemplateKey, "{platform}/{yyyy}/{MM}/{dd}/{anchor}"),
|
||||
OutputFileNameTemplate = GetValue(lookup, OutputFileNameTemplateKey, "{HHmmss}_{anchor}_{title}_{roomId}{segmentSuffix}"),
|
||||
OutputFileNameTemplate = GetValue(lookup, OutputFileNameTemplateKey, "{HHmmss}_{quality}_{anchor}_{title}_{roomId}{segmentSuffix}"),
|
||||
DefaultQuality = GetValue(lookup, DefaultQualityKey, "origin"),
|
||||
DefaultOutputFormat = Enum.TryParse(GetValue(lookup, DefaultOutputFormatKey, "Mp4"), true, out RecordOutputFormat outputFormat)
|
||||
? outputFormat
|
||||
|
||||
Reference in New Issue
Block a user