Refactor code formatting and improve readability: standardize line breaks and indentation across various endpoint files, enhancing code clarity and maintainability. Update package version formatting in Directory.Packages.props for consistency.
This commit is contained in:
@@ -60,11 +60,15 @@ public sealed class FfmpegBumperRenderer(
|
||||
|
||||
var playlist = Path.Combine(assetDir, "index.m3u8");
|
||||
if (!File.Exists(playlist))
|
||||
throw new InvalidOperationException("ffmpeg не создал плейлист заставки index.m3u8.");
|
||||
throw new InvalidOperationException(
|
||||
"ffmpeg не создал плейлист заставки index.m3u8."
|
||||
);
|
||||
|
||||
var segmentCount = Directory.GetFiles(assetDir, "seg*.ts").Length;
|
||||
if (segmentCount == 0)
|
||||
throw new InvalidOperationException("ffmpeg не создал ни одного сегмента заставки.");
|
||||
throw new InvalidOperationException(
|
||||
"ffmpeg не создал ни одного сегмента заставки."
|
||||
);
|
||||
|
||||
return new BumperRenderResult(
|
||||
TimeSpan.FromSeconds(target),
|
||||
@@ -168,17 +172,33 @@ public sealed class FfmpegBumperRenderer(
|
||||
var line2Size = FitSize(spec.FreeLine2, titleSize, textWidth);
|
||||
var line1Y = (int)(h * 0.40);
|
||||
var line2Y = line1Y + (int)(line2Size * 1.2);
|
||||
vchain.Append(',').Append(DrawTitle(font, nowFile, spec.AccentColor, line1Size, line1Y, 0.2));
|
||||
vchain.Append(',').Append(DrawTitle(font, nextFile, spec.TextColor, line2Size, line2Y, 0.5));
|
||||
vchain
|
||||
.Append(',')
|
||||
.Append(DrawTitle(font, nowFile, spec.AccentColor, line1Size, line1Y, 0.2));
|
||||
vchain
|
||||
.Append(',')
|
||||
.Append(DrawTitle(font, nextFile, 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, spec.NowLabel, spec.AccentColor, labelSize, nowLabelY, 0.2));
|
||||
vchain.Append(',').Append(DrawTitle(font, nowFile, spec.TextColor, nowSize, nowTitleY, 0.3));
|
||||
vchain.Append(',').Append(DrawLabel(font, spec.NextLabel, spec.AccentColor, labelSize, nextLabelY, 1.0));
|
||||
vchain.Append(',').Append(DrawTitle(font, nextFile, spec.TextColor, nextSize, nextTitleY, 1.1));
|
||||
vchain
|
||||
.Append(',')
|
||||
.Append(
|
||||
DrawLabel(font, spec.NowLabel, spec.AccentColor, labelSize, nowLabelY, 0.2)
|
||||
);
|
||||
vchain
|
||||
.Append(',')
|
||||
.Append(DrawTitle(font, nowFile, spec.TextColor, nowSize, nowTitleY, 0.3));
|
||||
vchain
|
||||
.Append(',')
|
||||
.Append(
|
||||
DrawLabel(font, spec.NextLabel, spec.AccentColor, labelSize, nextLabelY, 1.0)
|
||||
);
|
||||
vchain
|
||||
.Append(',')
|
||||
.Append(DrawTitle(font, nextFile, spec.TextColor, nextSize, nextTitleY, 1.1));
|
||||
}
|
||||
vchain.Append("[v]");
|
||||
|
||||
@@ -186,28 +206,47 @@ public sealed class FfmpegBumperRenderer(
|
||||
|
||||
var args = new List<string> { "-hide_banner", "-nostdin", "-y" };
|
||||
args.AddRange(inputs);
|
||||
args.AddRange(
|
||||
[
|
||||
"-filter_complex", filterComplex,
|
||||
"-map", "[v]",
|
||||
"-map", "[a]",
|
||||
"-threads", _media.TranscodeThreads.ToString(CultureInfo.InvariantCulture),
|
||||
"-c:v", "libx264",
|
||||
"-preset", "veryfast",
|
||||
"-crf", "21",
|
||||
"-pix_fmt", "yuv420p",
|
||||
"-force_key_frames", $"expr:gte(t,n_forced*{seg.ToString(CultureInfo.InvariantCulture)})",
|
||||
"-sc_threshold", "0",
|
||||
"-c:a", "aac",
|
||||
"-b:a", "128k",
|
||||
"-ac", "2",
|
||||
"-ar", "48000",
|
||||
"-t", Fmt(target),
|
||||
"-f", "hls",
|
||||
"-hls_time", seg.ToString(CultureInfo.InvariantCulture),
|
||||
"-hls_playlist_type", "vod",
|
||||
"-hls_list_size", "0",
|
||||
"-hls_segment_filename", Path.Combine(assetDir, "seg%05d.ts"),
|
||||
args.AddRange([
|
||||
"-filter_complex",
|
||||
filterComplex,
|
||||
"-map",
|
||||
"[v]",
|
||||
"-map",
|
||||
"[a]",
|
||||
"-threads",
|
||||
_media.TranscodeThreads.ToString(CultureInfo.InvariantCulture),
|
||||
"-c:v",
|
||||
"libx264",
|
||||
"-preset",
|
||||
"veryfast",
|
||||
"-crf",
|
||||
"21",
|
||||
"-pix_fmt",
|
||||
"yuv420p",
|
||||
"-force_key_frames",
|
||||
$"expr:gte(t,n_forced*{seg.ToString(CultureInfo.InvariantCulture)})",
|
||||
"-sc_threshold",
|
||||
"0",
|
||||
"-c:a",
|
||||
"aac",
|
||||
"-b:a",
|
||||
"128k",
|
||||
"-ac",
|
||||
"2",
|
||||
"-ar",
|
||||
"48000",
|
||||
"-t",
|
||||
Fmt(target),
|
||||
"-f",
|
||||
"hls",
|
||||
"-hls_time",
|
||||
seg.ToString(CultureInfo.InvariantCulture),
|
||||
"-hls_playlist_type",
|
||||
"vod",
|
||||
"-hls_list_size",
|
||||
"0",
|
||||
"-hls_segment_filename",
|
||||
Path.Combine(assetDir, "seg%05d.ts"),
|
||||
Path.Combine(assetDir, "index.m3u8"),
|
||||
]);
|
||||
return args;
|
||||
@@ -261,8 +300,7 @@ public sealed class FfmpegBumperRenderer(
|
||||
|
||||
/// <summary>Экранирование пути для значения опции фильтра (Windows-разделители → прямые слэши,
|
||||
/// двоеточие экранируется). На Linux (контейнере) — фактически no-op.</summary>
|
||||
private static string EscapePath(string path) =>
|
||||
path.Replace('\\', '/').Replace(":", "\\:");
|
||||
private static string EscapePath(string path) => path.Replace('\\', '/').Replace(":", "\\:");
|
||||
|
||||
/// <summary>Экранирование литерального текста подписи внутри значения опции drawtext.</summary>
|
||||
private static string EscapeText(string text) =>
|
||||
|
||||
@@ -146,21 +146,19 @@ public sealed class FfmpegMediaProcessor(
|
||||
args.Add(Fmt(target));
|
||||
}
|
||||
|
||||
args.AddRange(
|
||||
[
|
||||
"-f",
|
||||
"hls",
|
||||
"-hls_time",
|
||||
segmentSeconds.ToString(CultureInfo.InvariantCulture),
|
||||
"-hls_playlist_type",
|
||||
"vod",
|
||||
"-hls_list_size",
|
||||
"0",
|
||||
"-hls_segment_filename",
|
||||
Path.Combine(assetDir, "seg%05d.ts"),
|
||||
Path.Combine(assetDir, "index.m3u8"),
|
||||
]
|
||||
);
|
||||
args.AddRange([
|
||||
"-f",
|
||||
"hls",
|
||||
"-hls_time",
|
||||
segmentSeconds.ToString(CultureInfo.InvariantCulture),
|
||||
"-hls_playlist_type",
|
||||
"vod",
|
||||
"-hls_list_size",
|
||||
"0",
|
||||
"-hls_segment_filename",
|
||||
Path.Combine(assetDir, "seg%05d.ts"),
|
||||
Path.Combine(assetDir, "index.m3u8"),
|
||||
]);
|
||||
|
||||
return args;
|
||||
}
|
||||
@@ -169,15 +167,7 @@ public sealed class FfmpegMediaProcessor(
|
||||
{
|
||||
var result = await ProcessRunner.RunAsync(
|
||||
_media.FfprobePath,
|
||||
[
|
||||
"-v",
|
||||
"quiet",
|
||||
"-print_format",
|
||||
"json",
|
||||
"-show_format",
|
||||
"-show_streams",
|
||||
path,
|
||||
],
|
||||
["-v", "quiet", "-print_format", "json", "-show_format", "-show_streams", path],
|
||||
lowPriority: false,
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
@@ -58,9 +58,10 @@ public sealed class FileSystemMediaStorage(MediaPathResolver paths) : IMediaStor
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
var sourcePath = source == MediaSource.Inbox
|
||||
? paths.InboxPath(sourceToken)
|
||||
: paths.UploadPath(sourceToken);
|
||||
var sourcePath =
|
||||
source == MediaSource.Inbox
|
||||
? paths.InboxPath(sourceToken)
|
||||
: paths.UploadPath(sourceToken);
|
||||
|
||||
if (!File.Exists(sourcePath))
|
||||
throw new FileNotFoundException("Исходный файл не найден в хранилище.", sourcePath);
|
||||
|
||||
@@ -5,7 +5,10 @@ namespace TeleWave.Infrastructure.Media;
|
||||
/// <summary>Скачивает изображение по URL через HTTP-клиент «metadata» и определяет расширение.</summary>
|
||||
public sealed class ImageDownloader(IHttpClientFactory httpFactory) : IImageDownloader
|
||||
{
|
||||
public async Task<DownloadedImage?> DownloadAsync(string url, CancellationToken cancellationToken)
|
||||
public async Task<DownloadedImage?> DownloadAsync(
|
||||
string url,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -18,7 +21,8 @@ public sealed class ImageDownloader(IHttpClientFactory httpFactory) : IImageDown
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync(cancellationToken);
|
||||
return bytes.Length == 0 ? null : new DownloadedImage(bytes, ext);
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException or IOException)
|
||||
catch (Exception ex)
|
||||
when (ex is HttpRequestException or TaskCanceledException or IOException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,9 @@ public sealed class InboxScannerBackgroundService(
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
paths.EnsureDirectories();
|
||||
using var timer = new PeriodicTimer(TimeSpan.FromSeconds(Math.Max(1, _media.InboxScanSeconds)));
|
||||
using var timer = new PeriodicTimer(
|
||||
TimeSpan.FromSeconds(Math.Max(1, _media.InboxScanSeconds))
|
||||
);
|
||||
|
||||
while (await timer.WaitForNextTickAsync(stoppingToken))
|
||||
{
|
||||
@@ -53,7 +55,8 @@ public sealed class InboxScannerBackgroundService(
|
||||
if (!Directory.Exists(paths.InboxDir))
|
||||
return;
|
||||
|
||||
var files = Directory.EnumerateFiles(paths.InboxDir)
|
||||
var files = Directory
|
||||
.EnumerateFiles(paths.InboxDir)
|
||||
.Where(f => MediaFormats.IsAllowed(f))
|
||||
.ToList();
|
||||
|
||||
@@ -108,11 +111,19 @@ public sealed class InboxScannerBackgroundService(
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
queue.Enqueue(result.Value);
|
||||
logger.LogInformation("Из inbox зарегистрирован ассет {AssetId} ({File})", result.Value, fileName);
|
||||
logger.LogInformation(
|
||||
"Из inbox зарегистрирован ассет {AssetId} ({File})",
|
||||
result.Value,
|
||||
fileName
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("Не удалось зарегистрировать {File} из inbox: {Error}", fileName, result.Error.Code);
|
||||
logger.LogWarning(
|
||||
"Не удалось зарегистрировать {File} из inbox: {Error}",
|
||||
fileName,
|
||||
result.Error.Code
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,10 +72,7 @@ public sealed class MediaProcessingBackgroundService(
|
||||
CancellationToken.None
|
||||
);
|
||||
inFlight[task] = 0;
|
||||
_ = task.ContinueWith(
|
||||
t => inFlight.TryRemove(t, out _),
|
||||
TaskScheduler.Default
|
||||
);
|
||||
_ = task.ContinueWith(t => inFlight.TryRemove(t, out _), TaskScheduler.Default);
|
||||
}
|
||||
|
||||
// Работы нет — ждём сигнала о новой либо периодического опроса.
|
||||
@@ -118,8 +115,8 @@ public sealed class MediaProcessingBackgroundService(
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
|
||||
|
||||
var interrupted = await db.MediaAssets
|
||||
.Where(x => x.Status == MediaAssetStatus.Processing)
|
||||
var interrupted = await db
|
||||
.MediaAssets.Where(x => x.Status == MediaAssetStatus.Processing)
|
||||
.ToListAsync(cancellationToken);
|
||||
if (interrupted.Count == 0)
|
||||
return;
|
||||
@@ -134,13 +131,15 @@ public sealed class MediaProcessingBackgroundService(
|
||||
/// либо null если работы нет. Вызывается только диспетчером последовательно, поэтому два транскода
|
||||
/// не возьмут один ассет.
|
||||
/// </summary>
|
||||
private async Task<(Guid Id, string Extension)?> ClaimNextAsync(CancellationToken cancellationToken)
|
||||
private async Task<(Guid Id, string Extension)?> ClaimNextAsync(
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
|
||||
|
||||
var asset = await db.MediaAssets
|
||||
.Where(x => x.Status == MediaAssetStatus.Pending)
|
||||
var asset = await db
|
||||
.MediaAssets.Where(x => x.Status == MediaAssetStatus.Pending)
|
||||
.OrderBy(x => x.CreatedAt)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
if (asset is null)
|
||||
@@ -152,7 +151,11 @@ public sealed class MediaProcessingBackgroundService(
|
||||
}
|
||||
|
||||
/// <summary>Обрабатывает уже захваченный (Processing) ассет: транскод → Ready/Failed.</summary>
|
||||
private async Task ProcessClaimedAsync(Guid assetId, string extension, CancellationToken cancellationToken)
|
||||
private async Task ProcessClaimedAsync(
|
||||
Guid assetId,
|
||||
string extension,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -189,7 +192,10 @@ public sealed class MediaProcessingBackgroundService(
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
|
||||
|
||||
var asset = await db.MediaAssets.FirstOrDefaultAsync(x => x.Id == assetId, cancellationToken);
|
||||
var asset = await db.MediaAssets.FirstOrDefaultAsync(
|
||||
x => x.Id == assetId,
|
||||
cancellationToken
|
||||
);
|
||||
if (asset is null)
|
||||
return;
|
||||
|
||||
@@ -211,7 +217,10 @@ public sealed class MediaProcessingBackgroundService(
|
||||
await using var scope = scopeFactory.CreateAsyncScope();
|
||||
var db = scope.ServiceProvider.GetRequiredService<IAppDbContext>();
|
||||
|
||||
var asset = await db.MediaAssets.FirstOrDefaultAsync(x => x.Id == assetId, cancellationToken);
|
||||
var asset = await db.MediaAssets.FirstOrDefaultAsync(
|
||||
x => x.Id == assetId,
|
||||
cancellationToken
|
||||
);
|
||||
if (asset is null)
|
||||
return;
|
||||
|
||||
|
||||
@@ -51,7 +51,8 @@ internal static class ProcessRunner
|
||||
{
|
||||
process.PriorityClass = ProcessPriorityClass.BelowNormal;
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidOperationException or PlatformNotSupportedException)
|
||||
catch (Exception ex)
|
||||
when (ex is InvalidOperationException or PlatformNotSupportedException)
|
||||
{
|
||||
// Процесс мог завершиться мгновенно или платформа не поддерживает — не критично.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user