feat: add fnOS packaging, storage workflows and release pipeline

This commit is contained in:
2026-08-11 18:05:49 +08:00
parent c5922f9b08
commit 95932f0199
181 changed files with 24024 additions and 1164 deletions
+65 -49
View File
@@ -236,12 +236,11 @@ namespace dy.net.utils
if (imageFilePaths == null || !imageFilePaths.Any())
throw new ArgumentException("图片路径列表不能为空。", nameof(imageFilePaths));
if (string.IsNullOrEmpty(audioFilePath) || !File.Exists(audioFilePath))
throw new FileNotFoundException("音频文件未找到。", audioFilePath);
if (string.IsNullOrEmpty(outputVideoPath))
throw new ArgumentNullException(nameof(outputVideoPath));
var hasAudio = !string.IsNullOrWhiteSpace(audioFilePath) && File.Exists(audioFilePath);
foreach (var imagePath in imageFilePaths)
{
if (!File.Exists(imagePath.Path))
@@ -278,7 +277,7 @@ namespace dy.net.utils
// 获取音频时长
double audioDurationSeconds = imageList.Count * ImageDisplayDurationSeconds;
try
if (hasAudio) try
{
var startInfo = new ProcessStartInfo
{
@@ -329,50 +328,10 @@ namespace dy.net.utils
// 组合最终滤镜:先适配尺寸,再循环
string filterComplex = $"[0:v]{fitFilter},loop=loop={loopCount - 1}:size={imageCount}[v]";
var arguments = new List<string>
{
"-y", // 覆盖输出文件
// 图片序列输入
"-f", "image2",
"-framerate", imageFps.ToString(CultureInfo.InvariantCulture),
"-start_number", "1", // 修正:临时文件是 temp_001、temp_002... 所以起始编号为1
"-i", imageSequencePattern,
// 音频输入
"-i", audioFilePath,
// 滤镜:尺寸适配 + 循环
"-filter_complex", filterComplex,
// 流映射
"-map", "[v]",
"-map", "1:a",
// 视频编码参数
"-c:v", VideoCodec,
"-preset", VideoPreset,
"-crf", $"{VideoCrf}",
"-s", $"{VideoWidth}x{VideoHeight}",
"-pix_fmt", "yuv420p",
"-profile:v", "main",
// 音频编码参数
"-c:a", AudioCodec,
"-b:a", $"{AudioBitrate}",
"-ac", "2",
"-ar", "44100",
// 封装优化
"-f", "mp4",
"-movflags", "+faststart",
// 同步参数
"-shortest",
// 输出路径
outputVideoPath
};
var arguments = BuildImageVideoArguments(
imageSequencePattern, hasAudio ? audioFilePath : null, outputVideoPath,
imageFps, filterComplex, VideoWidth, VideoHeight,
VideoCodec, VideoPreset, VideoCrf, AudioCodec, AudioBitrate);
// 执行FFmpeg命令
await ExecuteFFmpegAsync(arguments, progress, cancellationToken);
@@ -401,6 +360,63 @@ namespace dy.net.utils
}
}
internal static List<string> BuildImageVideoArguments(
string imageSequencePattern,
string audioFilePath,
string outputVideoPath,
double imageFps,
string filterComplex,
int videoWidth,
int videoHeight,
string videoCodec = "libx264",
string videoPreset = "medium",
int videoCrf = 23,
string audioCodec = "aac",
string audioBitrate = "192k")
{
if (string.IsNullOrWhiteSpace(imageSequencePattern)) throw new ArgumentNullException(nameof(imageSequencePattern));
if (string.IsNullOrWhiteSpace(outputVideoPath)) throw new ArgumentNullException(nameof(outputVideoPath));
if (string.IsNullOrWhiteSpace(filterComplex)) throw new ArgumentNullException(nameof(filterComplex));
var hasAudio = !string.IsNullOrWhiteSpace(audioFilePath) && File.Exists(audioFilePath);
var arguments = new List<string>
{
"-y",
"-f", "image2",
"-framerate", imageFps.ToString(CultureInfo.InvariantCulture),
"-start_number", "1",
"-i", imageSequencePattern
};
if (hasAudio) arguments.AddRange(new[] { "-i", audioFilePath });
arguments.AddRange(new[]
{
"-filter_complex", filterComplex,
"-map", "[v]",
"-c:v", videoCodec,
"-preset", videoPreset,
"-crf", $"{videoCrf}",
"-s", $"{videoWidth}x{videoHeight}",
"-pix_fmt", "yuv420p",
"-profile:v", "main"
});
if (hasAudio)
{
arguments.AddRange(new[]
{
"-map", "1:a",
"-c:a", audioCodec,
"-b:a", audioBitrate,
"-ac", "2",
"-ar", "44100"
});
}
else arguments.Add("-an");
arguments.AddRange(new[] { "-f", "mp4", "-movflags", "+faststart" });
if (hasAudio) arguments.Add("-shortest");
arguments.Add(outputVideoPath);
return arguments;
}
@@ -782,4 +798,4 @@ namespace dy.net.utils
_ffmpegProcess?.Dispose();
}
}
}
}