Refactor various components to improve code clarity and maintainability. Update ListUsersQueryHandler to utilize UserListFilter for parameter handling. Refactor BumperSpecFactory and related classes to encapsulate input parameters into dedicated records, enhancing readability. Adjust MediaAsset and Slot classes to streamline content updates with new content models. Improve BumperRenderBackgroundService and MediaProcessingBackgroundService to use MediaReadyInfo for asset readiness, ensuring consistent parameter management across the application.
ci / build-backend (push) Successful in 1m55s
ci / build-frontend (push) Successful in 45s
ci / tests (push) Successful in 2m33s
ci / sonar (push) Successful in 6m28s

This commit is contained in:
Leonid Pershin
2026-07-27 00:56:04 +03:00
parent 419aff54fa
commit 19fa23b619
47 changed files with 556 additions and 423 deletions
@@ -185,16 +185,12 @@ internal sealed class IdentityService(
}
public async Task<PagedList<UserSummaryDto>> ListUsersAsync(
int page,
int pageSize,
string? search,
Guid? roleId,
bool? isBlocked,
string? sort,
bool desc,
UserListFilter filter,
CancellationToken cancellationToken
)
{
var (page, pageSize, search, roleId, isBlocked, sort, desc) = filter;
var query =
from user in dbContext.Users
join userRole in dbContext.UserRoles on user.Id equals userRole.UserId into userRoles
@@ -60,14 +60,16 @@ internal sealed class BumperRenderBackgroundService(
job.AssetId,
asset =>
asset.MarkReady(
render.Duration,
render.SegmentSeconds,
render.SegmentCount,
render.Width,
render.Height,
"h264",
"aac",
render.RelativePath
new MediaReadyInfo(
render.Duration,
render.SegmentSeconds,
render.SegmentCount,
render.Width,
render.Height,
"h264",
"aac",
render.RelativePath
)
),
cancellationToken
);
@@ -21,6 +21,23 @@ public sealed class FfmpegBumperRenderer(
private readonly StorageOptions _storage = storageOptions.Value;
private readonly MediaOptions _media = mediaOptions.Value;
/// <summary>
/// Файлы с динамическим текстом заставки. Всё пользователь-редактируемое (названия шоу, подписи,
/// свободные строки) ffmpeg читает через <c>textfile=</c> с <c>expansion=none</c>: иначе запятая,
/// <c>;</c>, <c>[</c> или <c>]</c> в тексте ломают (или инъектируют звенья в) цепочку
/// <c>-filter_complex</c>.
/// </summary>
private sealed record TextFiles(string Now, string Next, string NowLabel, string NextLabel)
{
public static TextFiles In(string assetDir) =>
new(
Path.Combine(assetDir, "now.txt"),
Path.Combine(assetDir, "next.txt"),
Path.Combine(assetDir, "nowlabel.txt"),
Path.Combine(assetDir, "nextlabel.txt")
);
}
public async Task<BumperRenderResult> RenderAsync(
Guid assetId,
BumperRenderSpec spec,
@@ -36,31 +53,24 @@ public sealed class FfmpegBumperRenderer(
Directory.Delete(assetDir, recursive: true);
Directory.CreateDirectory(assetDir);
// Динамический текст (названия шоу / свободные строки) пишем в файлы и читаем через textfile=
// с expansion=none — так произвольные символы/кириллица не ломают синтаксис фильтра.
var nowFile = Path.Combine(assetDir, "now.txt");
var nextFile = Path.Combine(assetDir, "next.txt");
// Названия шоу / свободные строки.
var text = TextFiles.In(assetDir);
var line1 = spec.FreeText ? spec.FreeLine1 : spec.NowTitle;
var line2 = spec.FreeText ? spec.FreeLine2 : spec.NextTitle;
await File.WriteAllTextAsync(nowFile, line1, new UTF8Encoding(false), cancellationToken);
await File.WriteAllTextAsync(nextFile, line2, new UTF8Encoding(false), cancellationToken);
await File.WriteAllTextAsync(text.Now, line1, new UTF8Encoding(false), cancellationToken);
await File.WriteAllTextAsync(text.Next, line2, new UTF8Encoding(false), cancellationToken);
// Подписи «Сейчас/Далее» тоже пользователь-редактируемы (валидатор ограничивает только длину),
// поэтому их так же читаем через textfile=, а не подставляем в text= инлайн: иначе запятая/`;`/`[`/`]`
// в подписи ломают (или инъектируют звенья в) цепочку -filter_complex. Нужны лишь в режиме
// «Сейчас/Далее» (не FreeText), где рисуются подписи.
var nowLabelFile = Path.Combine(assetDir, "nowlabel.txt");
var nextLabelFile = Path.Combine(assetDir, "nextlabel.txt");
// Подписи «Сейчас/Далее» нужны лишь в одноимённом режиме — в FreeText их не рисуют.
if (!spec.FreeText)
{
await File.WriteAllTextAsync(
nowLabelFile,
text.NowLabel,
spec.NowLabel,
new UTF8Encoding(false),
cancellationToken
);
await File.WriteAllTextAsync(
nextLabelFile,
text.NextLabel,
spec.NextLabel,
new UTF8Encoding(false),
cancellationToken
@@ -69,16 +79,7 @@ public sealed class FfmpegBumperRenderer(
try
{
var args = BuildArgs(
assetDir,
seg,
target,
nowFile,
nextFile,
nowLabelFile,
nextLabelFile,
spec
);
var args = BuildArgs(assetDir, seg, target, text, spec);
var result = await ProcessRunner.RunAsync(
_media.FfmpegPath,
args,
@@ -114,10 +115,10 @@ public sealed class FfmpegBumperRenderer(
}
finally
{
TryDelete(nowFile);
TryDelete(nextFile);
TryDelete(nowLabelFile);
TryDelete(nextLabelFile);
TryDelete(text.Now);
TryDelete(text.Next);
TryDelete(text.NowLabel);
TryDelete(text.NextLabel);
}
}
@@ -125,10 +126,7 @@ public sealed class FfmpegBumperRenderer(
string assetDir,
int seg,
int target,
string nowFile,
string nextFile,
string nowLabelFile,
string nextLabelFile,
TextFiles text,
BumperRenderSpec spec
)
{
@@ -211,29 +209,31 @@ public sealed class FfmpegBumperRenderer(
var line2Y = line1Y + (int)(line2Size * 1.2);
vchain
.Append(',')
.Append(DrawTitle(font, nowFile, spec.AccentColor, line1Size, line1Y, 0.2));
.Append(DrawTitle(font, text.Now, spec.AccentColor, line1Size, line1Y, 0.2));
vchain
.Append(',')
.Append(DrawTitle(font, nextFile, spec.TextColor, line2Size, line2Y, 0.5));
.Append(DrawTitle(font, text.Next, spec.TextColor, line2Size, line2Y, 0.5));
}
else
{
var nowSize = FitSize(spec.NowTitle, titleSize, textWidth);
var nextSize = FitSize(spec.NextTitle, titleSize, textWidth);
vchain
.Append(',')
.Append(DrawLabel(font, nowLabelFile, spec.AccentColor, labelSize, nowLabelY, 0.2));
vchain
.Append(',')
.Append(DrawTitle(font, nowFile, spec.TextColor, nowSize, nowTitleY, 0.3));
vchain
.Append(',')
.Append(
DrawLabel(font, nextLabelFile, spec.AccentColor, labelSize, nextLabelY, 1.0)
DrawLabel(font, text.NowLabel, spec.AccentColor, labelSize, nowLabelY, 0.2)
);
vchain
.Append(',')
.Append(DrawTitle(font, nextFile, spec.TextColor, nextSize, nextTitleY, 1.1));
.Append(DrawTitle(font, text.Now, spec.TextColor, nowSize, nowTitleY, 0.3));
vchain
.Append(',')
.Append(
DrawLabel(font, text.NextLabel, spec.AccentColor, labelSize, nextLabelY, 1.0)
);
vchain
.Append(',')
.Append(DrawTitle(font, text.Next, spec.TextColor, nextSize, nextTitleY, 1.1));
}
vchain.Append("[v]");
@@ -53,14 +53,16 @@ internal sealed class MediaProcessingBackgroundService(
job.AssetId,
asset =>
asset.MarkReady(
result.Duration,
result.SegmentSeconds,
result.SegmentCount,
result.Width,
result.Height,
result.VideoCodec,
result.AudioCodec,
result.RelativePath
new MediaReadyInfo(
result.Duration,
result.SegmentSeconds,
result.SegmentCount,
result.Width,
result.Height,
result.VideoCodec,
result.AudioCodec,
result.RelativePath
)
),
cancellationToken
);