对于一些MP4文件的压缩

事情是这样的

有一些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; } 
         
    }
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

月巴月巴白勺合鸟月半

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值