事情是这样的
有一些MP4文件文件,体积比较大,需要压缩。
处理
这些视频文件主要是一些网课。20-30分钟一个视频,文件的大小在 300到500M。经过压缩后基本变为原来的十分之一的大小(例如452M的视频压缩到38M)。画面质量有所下降,但是不妨碍观看。
根据这批视频的特点。开始选择压缩的方式。
对于每个视频:
第一步先使用 FfmpegMediaInfo 获得视频的信息。
然后根据视频的信息,生成压缩参数
默认参数为 -crf 25
string arguments = @" -y -i ""[fn1]"" -vcodec libx264 -crf 25 ".Replace("[fn1]", fn);
先判断视频宽度:
if (mediaInfo.Width >= 1280)
{
arguments = arguments + " -vf scale=1080:-1";
}
然后设置-b:v
if (mediaInfo.Bitrate > 0)
{
if (mediaInfo.Bitrate < 300)
{
}
else if (mediaInfo.Bitrate < 400)
{
arguments = arguments + " -b:v 300k";
}
else if (mediaInfo.Bitrate < 512)
{
arguments = arguments + " -b:v 400k";
}
else
{
arguments = arguments + " -b:v 512k";
}
}
然后设置帧率,因为这些视频基本都是网课,所以帧率可以设置到很低
if (!string.IsNullOrEmpty(mediaInfo.AvgFrameRate))
{
double r;
if (double.TryParse(mediaInfo.AvgFrameRate.Trim(), out r))
{
if (r > 16)
arguments = arguments + " -r 16";
}
else
{
int ri = (int)r;
ri = ri - 2;
if (ri < 5)
ri = 5;
arguments = arguments + " -r " + ri.ToString();
}
}
else
{
arguments = arguments + " -r 16";
}
然后设置音频,32k 差不多是电话质量,对于普通对话来说还可以。
arguments = arguments + " -ac 1 -b:a 32k ";
然后压缩
var compressor = new VideoCompressor(ffmpegPath);
compressor.ProgressUpdated += ProgressUpdated;
compressor.CompressionCompleted += CompressionCompleted;
CompressionResult cr = await compressor.CompressVideoAsync(arguments, mediaInfo);
相关代码
{
public class FfmpegMediaInfo
{
private string _ffmpegPath;
public FfmpegMediaInfo(string ffmpegPath)
{
if (!File.Exists(ffmpegPath))
throw new FileNotFoundException("FFmpeg executable not found", ffmpegPath);
_ffmpegPath = ffmpegPath;
}
public MediaInfo GetMediaInfo(string filePath)
{
if (!File.Exists(filePath))
throw new FileNotFoundException("Media file not found", filePath);
var mediaInfo = new MediaInfo();
try
{
// 获取详细媒体信息
string output = ExecuteFfmpegCommand($"-i \"{filePath}\"");
// 按行解析输出信息
ParseMediaInfoByLines(output, mediaInfo);
// 获取文件大小
mediaInfo.FileSize = new FileInfo(filePath).Length;
// 获取文件路径和名称
mediaInfo.FilePath = filePath;
mediaInfo.FileName = Path.GetFileName(filePath);
mediaInfo.FileExtension = Path.GetExtension(filePath);
}
catch (Exception ex)
{
Console.WriteLine($"Error getting media info: {ex.Message}");
throw;
}
return mediaInfo;
}
private void ParseMediaInfoByLines(string output, MediaInfo mediaInfo)
{
try
{
// 将输出按行分割
string[] lines = output.Split(new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries);
bool isVideoStream = false;
bool isAudioStream = false;
foreach (string line in lines)
{
string trimmedLine = line.Trim();
// 解析格式信息
if (trimmedLine.StartsWith("Duration:"))
{
ParseDurationLine(trimmedLine, mediaInfo);
}
// 解析视频流信息
else if (trimmedLine.Contains(": Video:"))
{
ParseVideoStreamLine(trimmedLine, mediaInfo);
isVideoStream = true;
isAudioStream = false;
}
// 解析音频流信息
else if (trimmedLine.Contains(": Audio:"))
{
ParseAudioStreamLine(trimmedLine, mediaInfo);
isVideoStream = false;
isAudioStream = true;
}
// 解析流的详细信息(如果有的话)
else if (trimmedLine.StartsWith("Stream #") && !trimmedLine.Contains("Video:") && !trimmedLine.Contains("Audio:"))
{
isVideoStream = false;
isAudioStream = false;
}
}
// 设置视频和音频时长(如果未单独获取)
if (mediaInfo.VideoDuration == TimeSpan.Zero)
mediaInfo.VideoDuration = mediaInfo.Duration;
if (mediaInfo.AudioDuration == TimeSpan.Zero)
mediaInfo.AudioDuration = mediaInfo.Duration;
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing media info: {ex.Message}");
throw;
}
}
private void ParseDurationLine(string line, MediaInfo mediaInfo)
{
try
{
// 格式: Duration: 00:05:30.12, start: 0.000000, bitrate: 1280 kb/s
string[] parts = line.Split(',');
foreach (string part in parts)
{
string trimmedPart = part.Trim();
if (trimmedPart.StartsWith("Duration:"))
{
string durationStr = trimmedPart.Substring("Duration:".Length).Trim();
mediaInfo.Duration = TimeSpan.Parse(durationStr);
}
else if (trimmedPart.StartsWith("start:"))
{
string startStr = trimmedPart.Substring("start:".Length).Trim();
mediaInfo.StartTime = double.Parse(startStr);
}
else if (trimmedPart.StartsWith("bitrate:"))
{
string bitrateStr = trimmedPart.Substring("bitrate:".Length).Trim().Replace("kb/s", "").Trim();
mediaInfo.Bitrate = int.Parse(bitrateStr);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing duration line: {ex.Message}");
}
}
private void ParseVideoStreamLine(string line, MediaInfo mediaInfo)
{
try
{
// 格式示例: Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 1920x1080, 1150 kb/s, 24 fps, 24 tbr, 12288 tbn, 48 tbc (default)
string[] parts = line.Split(',');
foreach (string part in parts)
{
string trimmedPart = part.Trim();
if (trimmedPart.Contains(": Video:"))
{
// 提取视频编码
string videoPart = trimmedPart.Substring(trimmedPart.IndexOf(": Video:") + ": Video:".Length).Trim();
mediaInfo.VideoCodec = videoPart.Split()[0];
}
else if (trimmedPart.Contains("x") && IsResolution(trimmedPart))
{
// 提取分辨率
mediaInfo.Resolution = trimmedPart;
string[] resolutionParts = trimmedPart.Split('x');
if (resolutionParts.Length == 2)
{
mediaInfo.Width = int.Parse(resolutionParts[0]);
mediaInfo.Height = int.Parse(resolutionParts[1].Split()[0]);
}
}
else if (trimmedPart.EndsWith("kb/s"))
{
// 提取视频码率
string bitrateStr = trimmedPart.Replace("kb/s", "").Trim();
if (int.TryParse(bitrateStr, out int bitrate))
{
mediaInfo.VideoBitrate = bitrate;
}
}
else if (trimmedPart.EndsWith("fps") || trimmedPart.Contains("tbr"))
{
// 提取帧率
if (trimmedPart.EndsWith("fps"))
{
mediaInfo.FrameRate = trimmedPart.Replace("fps", "").Trim();
mediaInfo.AvgFrameRate = mediaInfo.FrameRate;
}
else if (trimmedPart.Contains("tbr"))
{
string tbrValue = trimmedPart.Split()[0];
if (tbrValue.Contains("/"))
{
var fpsParts = tbrValue.Split('/');
if (fpsParts.Length == 2)
{
double numerator = double.Parse(fpsParts[0]);
double denominator = double.Parse(fpsParts[1]);
mediaInfo.AvgFrameRate = (numerator / denominator).ToString("F2");
}
}
else
{
mediaInfo.AvgFrameRate = tbrValue;
}
}
}
else if (!trimmedPart.Contains("kb/s") && !trimmedPart.Contains("fps") &&
!trimmedPart.Contains("tbr") && !trimmedPart.Contains("x") &&
!string.IsNullOrEmpty(trimmedPart))
{
// 尝试提取像素格式
if (string.IsNullOrEmpty(mediaInfo.PixelFormat))
{
mediaInfo.PixelFormat = trimmedPart;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing video stream line: {ex.Message}");
}
}
private void ParseAudioStreamLine(string line, MediaInfo mediaInfo)
{
try
{
// 格式示例: Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
string[] parts = line.Split(',');
foreach (string part in parts)
{
string trimmedPart = part.Trim();
if (trimmedPart.Contains(": Audio:"))
{
// 提取音频编码
string audioPart = trimmedPart.Substring(trimmedPart.IndexOf(": Audio:") + ": Audio:".Length).Trim();
mediaInfo.AudioCodec = audioPart.Split()[0];
}
else if (trimmedPart.EndsWith("Hz"))
{
// 提取采样率
mediaInfo.SampleRate = trimmedPart;
}
else if (trimmedPart.Contains("channel") || trimmedPart.Contains("stereo") ||
trimmedPart.Contains("mono") || trimmedPart.Contains("surround"))
{
// 提取声道信息
mediaInfo.Channels = trimmedPart;
}
else if (trimmedPart.EndsWith("kb/s"))
{
// 提取音频码率
string bitrateStr = trimmedPart.Replace("kb/s", "").Trim();
if (int.TryParse(bitrateStr, out int bitrate))
{
mediaInfo.AudioBitrate = bitrate;
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing audio stream line: {ex.Message}");
}
}
private bool IsResolution(string part)
{
// 简单判断是否为分辨率格式 (数字x数字)
if (string.IsNullOrEmpty(part))
return false;
string[] parts = part.Split('x');
if (parts.Length != 2)
return false;
if (!int.TryParse(parts[0], out int width))
return false;
string heightPart = parts[1].Split()[0];
if (!int.TryParse(heightPart, out int height))
return false;
return width > 0 && height > 0;
}
private string ExecuteFfmpegCommand(string arguments)
{
try
{
using (var process = new Process())
{
process.StartInfo.FileName = _ffmpegPath;
process.StartInfo.Arguments = arguments;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
process.Start();
string output = process.StandardOutput.ReadToEnd();
string error = process.StandardError.ReadToEnd();
process.WaitForExit();
// FFmpeg通常将信息输出到stderr
return error + output;
}
}
catch (Exception ex)
{
Console.WriteLine($"Error executing FFmpeg command: {ex.Message}");
throw;
}
}
}
public class MediaInfo
{
// 文件信息
public string FilePath { get; set; }
public string FileName { get; set; }
public string FileExtension { get; set; }
public long FileSize { get; set; }
// 总体信息
public TimeSpan Duration { get; set; }
public double StartTime { get; set; }
public int Bitrate { get; set; }
// 视频信息
public string VideoCodec { get; set; }
public string Resolution { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public string PixelFormat { get; set; }
public string FrameRate { get; set; }
public string AvgFrameRate { get; set; }
public TimeSpan VideoDuration { get; set; }
public int VideoBitrate { get; set; }
// 音频信息
public string AudioCodec { get; set; }
public string SampleRate { get; set; }
public string Channels { get; set; }
public int AudioBitrate { get; set; }
public TimeSpan AudioDuration { get; set; }
public override string ToString()
{
return $"MediaInfo: {FileName}\n" +
$"Duration: {Duration}\n" +
$"Resolution: {Width}x{Height}\n" +
$"Video Codec: {VideoCodec}\n" +
$"Audio Codec: {AudioCodec}\n" +
$"FrameRate: {FrameRate}\n" +
$"AvgFrameRate: {AvgFrameRate}\n" +
$"Bitrate: {Bitrate} kbps\n" +
$"File Size: {FormatFileSize(FileSize)}";
}
public string FormatFileSize(long bytes)
{
if (bytes < 1024) return $"{bytes} B";
else if (bytes < 1024 * 1024) return $"{(bytes / 1024.0):F2} KB";
else if (bytes < 1024 * 1024 * 1024) return $"{(bytes / (1024.0 * 1024.0)):F2} MB";
else return $"{(bytes / (1024.0 * 1024.0 * 1024.0)):F2} GB";
}
}
public class VideoCompressor
{
private string _ffmpegPath;
private CancellationTokenSource _cts;
// 压缩进度事件
public event EventHandler<CompressionProgressEventArgs> ProgressUpdated;
// 压缩完成事件
public event EventHandler<CompressionCompletedEventArgs> CompressionCompleted;
public VideoCompressor(string ffmpegPath)
{
if (!File.Exists(ffmpegPath))
throw new FileNotFoundException("FFmpeg executable not found", ffmpegPath);
_ffmpegPath = ffmpegPath;
}
/// <summary>
/// 压缩视频文件
/// </summary>
/// <param name="inputPath">输入文件路径</param>
/// <param name="outputPath">输出文件路径</param>
/// <param name="quality">质量参数 (0-51, 越低质量越好)</param>
/// <param name="crf">CRF参数 (0-51, 越低质量越好,默认23)</param>
/// <param name="preset">预设参数 (ultrafast, superfast, veryfast, faster, fast, medium, slow, slower, veryslow)</param>
// public async Task CompressVideoAsync(string inputPath, string outputPath, int crf = 23, string preset = "medium", CancellationToken cancellationToken = default)
public async Task<CompressionResult> CompressVideoAsync(string arguments, MediaInfo mediaInfo, CancellationToken cancellationToken = default)
{
var result = new CompressionResult
{
Success = false
};
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
try
{
// 构建FFmpeg命令
using (var process = new Process())
{
process.StartInfo.FileName = _ffmpegPath;
process.StartInfo.Arguments = arguments;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
// 进度跟踪变量
double lastProgress = 0;
DateTime lastUpdateTime = DateTime.MinValue;
// 错误输出处理
process.ErrorDataReceived += (sender, e) =>
{
if (string.IsNullOrEmpty(e.Data))
return;
try
{
// 解析进度信息
string data = e.Data.Trim();
if (data.StartsWith("frame=") && data.Contains("time="))
{
// 格式示例: frame= 100 fps= 24 q=23.0 size= 10240kB time=00:00:04.17 bitrate=20000.0kbits/s speed= 1x
string[] parts = data.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string part in parts)
{
if (part.StartsWith("time="))
{
string timeStr = part.Substring("time=".Length);
if (TimeSpan.TryParse(timeStr, out TimeSpan currentTime))
{
double progress = (currentTime.TotalSeconds / mediaInfo.Duration.TotalSeconds) * 100;
progress = Math.Min(progress, 100); // 确保不超过100%
// 限制更新频率,避免UI过于频繁刷新
if (progress > lastProgress + 0.5 ||
(progress > 0 && DateTime.Now - lastUpdateTime > TimeSpan.FromSeconds(1)))
{
lastProgress = progress;
lastUpdateTime = DateTime.Now;
// 触发进度更新事件
ProgressUpdated?.Invoke(this, new CompressionProgressEventArgs
{
ProgressPercentage = progress,
CurrentTime = currentTime,
TotalDuration = mediaInfo.Duration,
Status = "Compressing..."
});
}
}
break;
}
}
}
else if (data.Contains("Error") || data.Contains("error") || data.Contains("failed"))
{
// 触发错误事件
CompressionCompleted?.Invoke(this, new CompressionCompletedEventArgs
{
Success = false,
ErrorMessage = data
});
}
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing FFmpeg output: {ex.Message}");
}
};
// 启动进程
process.Start();
process.BeginErrorReadLine();
// 等待进程完成或取消
var processTask = Task.Run(() => process.WaitForExit(), _cts.Token);
try
{
await processTask;
}
catch (TaskCanceledException)
{
// 取消操作
process.Kill();
throw new OperationCanceledException("Compression was canceled");
}
// 检查进程退出代码
if (process.ExitCode == 0)
{
// 压缩成功
CompressionCompleted?.Invoke(this, new CompressionCompletedEventArgs
{
Success = true
});
result.Success = true;
}
else
{
// 压缩失败
CompressionCompleted?.Invoke(this, new CompressionCompletedEventArgs
{
Success = false,
ErrorMessage = $"FFmpeg exited with code {process.ExitCode}"
});
result.ErrorMessage = string.IsNullOrEmpty(result.ErrorMessage)
? $"FFmpeg exited with code {process.ExitCode}"
: result.ErrorMessage;
}
}
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
CompressionCompleted?.Invoke(this, new CompressionCompletedEventArgs
{
Success = false,
ErrorMessage = ex.Message
});
result.ErrorMessage = ex.Message;
}
finally
{
_cts.Dispose();
}
return result;
}
/// <summary>
/// 取消当前的压缩操作
/// </summary>
public void CancelCompression()
{
_cts?.Cancel();
}
}
public class CompressionResult
{
/// <summary>是否成功</summary>
public bool Success { get; set; }
/// <summary>是否被取消</summary>
public bool Canceled { get; set; }
/// <summary>错误信息</summary>
public string ErrorMessage { get; set; }
public override string ToString()
{
if (Success)
{
return $"Compression succeeded. ";
}
else if (Canceled)
{
return "Compression was canceled.";
}
else
{
return $"Compression failed: {ErrorMessage}";
}
}
}
// 进度事件参数
public class CompressionProgressEventArgs : EventArgs
{
public double ProgressPercentage { get; set; }
public TimeSpan CurrentTime { get; set; }
public TimeSpan TotalDuration { get; set; }
public string Status { get; set; }
}
// 完成事件参数
public class CompressionCompletedEventArgs : EventArgs
{
public bool Success { get; set; }
public string ErrorMessage { get; set; }
}
}
4314

被折叠的 条评论
为什么被折叠?



