Migrate remaining legacy operator workflows
This commit is contained in:
@@ -15,6 +15,7 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
int? OutputChannel,
|
||||
int LayoutIndex,
|
||||
string? SceneDirectory,
|
||||
string? OperatorBackgroundDirectory,
|
||||
Regex? TestProcessWindowTitleRegex,
|
||||
IReadOnlySet<string> TestSceneAllowlist,
|
||||
bool TrustedLiveOutputEnabled,
|
||||
@@ -59,6 +60,13 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
sceneDirectory = ValidateSceneDirectory(options.SceneDirectory);
|
||||
}
|
||||
|
||||
var operatorBackgroundDirectory =
|
||||
PlayoutSceneCompositionFactory.TryGetConfiguredBackgroundDirectory(
|
||||
options,
|
||||
out var configuredBackgroundDirectory)
|
||||
? configuredBackgroundDirectory
|
||||
: null;
|
||||
|
||||
RequireRange(options.QueueCapacity, 1, 4_096, nameof(options.QueueCapacity));
|
||||
RequireRange(
|
||||
options.ConnectTimeoutMilliseconds,
|
||||
@@ -156,6 +164,7 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
options.OutputChannel,
|
||||
options.LayoutIndex,
|
||||
sceneDirectory,
|
||||
operatorBackgroundDirectory,
|
||||
titleRegex,
|
||||
allowlist,
|
||||
options.TrustedLiveOutputEnabled,
|
||||
@@ -356,9 +365,14 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
return background;
|
||||
case PlayoutSetBackgroundTexture texture:
|
||||
ValidateMutationTiming(texture.Timing);
|
||||
return texture with { AssetPath = ResolveAssetPath(texture.AssetPath) };
|
||||
ValidateAssetRoot(texture.AssetRoot);
|
||||
return texture with
|
||||
{
|
||||
AssetPath = ResolveAssetPath(texture.AssetPath, texture.AssetRoot)
|
||||
};
|
||||
case PlayoutSetBackgroundVideo video:
|
||||
ValidateMutationTiming(video.Timing);
|
||||
ValidateAssetRoot(video.AssetRoot);
|
||||
// The legacy MainForm intentionally uses 2004 for the common studio
|
||||
// background. Keep the input bounded while retaining that vendor value.
|
||||
if (video.LoopCount is < 0 or > 10_000)
|
||||
@@ -366,16 +380,28 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
throw new PlayoutRequestException("장면 배경 영상 반복 값이 올바르지 않습니다.");
|
||||
}
|
||||
|
||||
return video with { AssetPath = ResolveAssetPath(video.AssetPath) };
|
||||
return video with
|
||||
{
|
||||
AssetPath = ResolveAssetPath(video.AssetPath, video.AssetRoot)
|
||||
};
|
||||
default:
|
||||
throw new PlayoutRequestException("지원하지 않는 장면 변경 항목입니다.");
|
||||
}
|
||||
}
|
||||
|
||||
private string ResolveAssetPath(string? relativePath)
|
||||
=> ResolveAssetPath(relativePath, PlayoutAssetRoot.SceneDirectory);
|
||||
|
||||
private string ResolveAssetPath(string? relativePath, PlayoutAssetRoot assetRoot)
|
||||
{
|
||||
relativePath = relativePath?.Trim();
|
||||
if (SceneDirectory is null || string.IsNullOrEmpty(relativePath) ||
|
||||
var root = assetRoot switch
|
||||
{
|
||||
PlayoutAssetRoot.SceneDirectory => SceneDirectory,
|
||||
PlayoutAssetRoot.OperatorBackgroundDirectory => OperatorBackgroundDirectory,
|
||||
_ => null
|
||||
};
|
||||
if (root is null || string.IsNullOrEmpty(relativePath) ||
|
||||
Path.IsPathRooted(relativePath) || relativePath.Contains("..", StringComparison.Ordinal) ||
|
||||
relativePath.Any(char.IsControl))
|
||||
{
|
||||
@@ -393,12 +419,28 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
throw new PlayoutRequestException("지원하지 않는 장면 자산 형식입니다.");
|
||||
}
|
||||
|
||||
var candidate = Path.GetFullPath(Path.Combine(SceneDirectory, relativePath));
|
||||
var rootPrefix = SceneDirectory.EndsWith(Path.DirectorySeparatorChar)
|
||||
? SceneDirectory
|
||||
: SceneDirectory + Path.DirectorySeparatorChar;
|
||||
if (assetRoot == PlayoutAssetRoot.OperatorBackgroundDirectory)
|
||||
{
|
||||
try
|
||||
{
|
||||
return TrustedPlayoutAssetPath.ResolveRelativeFile(
|
||||
root,
|
||||
relativePath,
|
||||
new HashSet<string>(allowedExtensions, StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
catch (TrustedPlayoutAssetException)
|
||||
{
|
||||
throw new PlayoutRequestException(
|
||||
"신뢰 배경 파일을 확인할 수 없습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
var candidate = Path.GetFullPath(Path.Combine(root, relativePath));
|
||||
var rootPrefix = root.EndsWith(Path.DirectorySeparatorChar)
|
||||
? root
|
||||
: root + Path.DirectorySeparatorChar;
|
||||
if (!candidate.StartsWith(rootPrefix, StringComparison.OrdinalIgnoreCase) ||
|
||||
!File.Exists(candidate) || HasReparsePointBetween(SceneDirectory, candidate))
|
||||
!File.Exists(candidate) || HasReparsePointBetween(root, candidate))
|
||||
{
|
||||
throw new PlayoutRequestException("허용된 장면 자산을 찾을 수 없습니다.");
|
||||
}
|
||||
@@ -469,6 +511,14 @@ internal sealed record ValidatedPlayoutOptions(
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateAssetRoot(PlayoutAssetRoot assetRoot)
|
||||
{
|
||||
if (!Enum.IsDefined(assetRoot))
|
||||
{
|
||||
throw new PlayoutRequestException("배경 자산 루트가 올바르지 않습니다.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsByte(int value) => value is >= byte.MinValue and <= byte.MaxValue;
|
||||
|
||||
private static bool CanMatchProgramTitle(Regex regex)
|
||||
|
||||
Reference in New Issue
Block a user