• 1405/07/13

اعتبار سنجی + اکستنشن ذخیره ، کاهش حجم ، بهینه سازی ویدیو :

قابل استفاده برای ویدیوی محصول 

ویدیو 10 مگابابیتی به 1 مگابایت کاهش حجم داده میشه

برای کاهش حجم ویدیو به ابزار زیر نیاز داریم که دانلودش کنید.

ffmpeg-git-essentials.7z (نسخه برای ویندوز)

در صورت استفاده از لینوکس نسخه مورد نظر رو  از سایت ها پیدا کنید.

در پوشه bin فایل دانلود شده دو عدد از فایل ها رو اینگونه داخل پروژه قرار بدید( نام گذاری ها مهم است) در WEB

برای مسیر دهی این فایل ها به اکستنشن از helper زیر استفاده کنید.

    public static class FFmpegPathHelper
    {
        public static string GetFFprobePath()
        {
            var baseDirectory = AppContext.BaseDirectory;

            if (OperatingSystem.IsWindows())
            {
                return Path.Combine(
                    baseDirectory,
                    "Tools",
                    "ffmpeg",
                    "win-x64",
                    "ffprobe.exe");
            }

            if (OperatingSystem.IsLinux())
            {
                return Path.Combine(
                    baseDirectory,
                    "Tools",
                    "ffmpeg",
                    "linux-x64",
                    "ffprobe");
            }

            throw new PlatformNotSupportedException();
        }


        public static string GetFFmpegPath()
        {
            var baseDirectory = AppContext.BaseDirectory;

            if (OperatingSystem.IsWindows())
            {
                return Path.Combine(
                    baseDirectory,
                    "Tools",
                    "ffmpeg",
                    "win-x64",
                    "ffmpeg.exe");
            }

            if (OperatingSystem.IsLinux())
            {
                return Path.Combine(
                    baseDirectory,
                    "Tools",
                    "ffmpeg",
                    "linux-x64",
                    "ffmpeg");
            }

            throw new PlatformNotSupportedException();
        }
    }

اکستنشن ذخیره + کاهش حجم ویدیو

    public static class VideoExtention
    {
        private const int ProcessTimeoutMilliseconds = 5 * 60 * 1000;


        public static async Task<string> SaveVideoAsync(
            this IFormFile file,
            string path,
            CancellationToken cancellationToken = default)
        {
            if (file == null || file.Length == 0)
                throw new ArgumentException(
                    "Video not found.",
                    nameof(file));


            if (string.IsNullOrWhiteSpace(path))
                throw new ArgumentException(
                    "Path is required.",
                    nameof(path));


            if (!path.StartsWith(
                    "wwwroot",
                    StringComparison.OrdinalIgnoreCase))
            {
                throw new ArgumentException(
                    "Path must start with 'wwwroot'.",
                    nameof(path));
            }


            // -----------------------------------------
            // FFmpeg
            // -----------------------------------------

            var ffmpegPath =
                FFmpegPathHelper.GetFFmpegPath();


            if (!File.Exists(ffmpegPath))
            {
                throw new FileNotFoundException(
                    "FFmpeg executable was not found.",
                    ffmpegPath);
            }


            // -----------------------------------------
            // Output directory
            // -----------------------------------------

            var outputDirectory = Path.Combine(
                Directory.GetCurrentDirectory(),
                path);


            Directory.CreateDirectory(
                outputDirectory);


            // -----------------------------------------
            // Generate output filename
            // -----------------------------------------

            var fileName =
                NameGenarator.GenerateUniqName() + ".mp4";


            var outputFile =
                Path.Combine(
                    outputDirectory,
                    fileName);


            // -----------------------------------------
            // Temporary input
            // -----------------------------------------

            var tempDirectory =
                Path.Combine(
                    Path.GetTempPath(),
                    "AlendaTajhiz",
                    "Videos");


            Directory.CreateDirectory(
                tempDirectory);


            var tempFile =
                Path.Combine(
                    tempDirectory,
                    $"{Guid.NewGuid():N}.tmp");


            try
            {
                // -----------------------------------------
                // Save uploaded file temporarily
                // -----------------------------------------

                await using (var stream = new FileStream(
                    tempFile,
                    FileMode.CreateNew,
                    FileAccess.Write,
                    FileShare.None,
                    64 * 1024,
                    useAsync: true))
                {
                    await file.CopyToAsync(
                        stream,
                        cancellationToken);
                }


                // -----------------------------------------
                // FFmpeg process
                // -----------------------------------------

                var startInfo = new ProcessStartInfo
                {
                    FileName = ffmpegPath,

                    UseShellExecute = false,

                    RedirectStandardOutput = true,

                    RedirectStandardError = true,

                    CreateNoWindow = true
                };


                // Input
                startInfo.ArgumentList.Add("-i");
                startInfo.ArgumentList.Add(tempFile);


                // Video
                startInfo.ArgumentList.Add("-vf");
                startInfo.ArgumentList.Add(
                    "scale='min(1920,iw)':-2");


                startInfo.ArgumentList.Add("-c:v");
                startInfo.ArgumentList.Add("libx264");


                startInfo.ArgumentList.Add("-crf");
                startInfo.ArgumentList.Add("28");


                startInfo.ArgumentList.Add("-preset");
                startInfo.ArgumentList.Add("veryfast");


                // Audio
                startInfo.ArgumentList.Add("-c:a");
                startInfo.ArgumentList.Add("aac");


                startInfo.ArgumentList.Add("-b:a");
                startInfo.ArgumentList.Add("128k");


                // Web optimized MP4
                startInfo.ArgumentList.Add("-movflags");
                startInfo.ArgumentList.Add("+faststart");


                // Overwrite
                startInfo.ArgumentList.Add("-y");


                // Output
                startInfo.ArgumentList.Add(outputFile);


                using var process = new Process
                {
                    StartInfo = startInfo
                };


                if (!process.Start())
                {
                    throw new Exception(
                        "FFmpeg process could not be started.");
                }


                var errorTask =
                    process.StandardError.ReadToEndAsync();

                var outputTask =
                    process.StandardOutput.ReadToEndAsync();


                using var timeoutCts =
                    CancellationTokenSource
                        .CreateLinkedTokenSource(
                            cancellationToken);


                timeoutCts.CancelAfter(
                    ProcessTimeoutMilliseconds);


                try
                {
                    await process.WaitForExitAsync(
                        timeoutCts.Token);
                }
                catch (OperationCanceledException)
                {
                    try
                    {
                        if (!process.HasExited)
                            process.Kill(true);
                    }
                    catch
                    {
                        // Ignore process kill errors
                    }


                    if (cancellationToken.IsCancellationRequested)
                        throw;


                    throw new TimeoutException(
                        "FFmpeg processing timed out.");
                }


                var error =
                    await errorTask;

                var output =
                    await outputTask;


                // -----------------------------------------
                // FFmpeg failed
                // -----------------------------------------

                if (process.ExitCode != 0)
                {
                    throw new Exception(
                        $"FFmpeg failed. {error}");
                }


                // -----------------------------------------
                // Verify output
                // -----------------------------------------

                if (!File.Exists(outputFile))
                {
                    throw new Exception(
                        "FFmpeg completed but output video was not created.");
                }


                var outputInfo =
                    new FileInfo(outputFile);


                if (outputInfo.Length == 0)
                {
                    throw new Exception(
                        "Generated video is empty.");
                }


                return fileName;
            }
            catch
            {
                // اگر تبدیل شکست خورد،
                // فایل خروجی ناقص را حذف کن.

                try
                {
                    if (File.Exists(outputFile))
                        File.Delete(outputFile);
                }
                catch
                {
                    // Ignore cleanup errors
                }


                throw;
            }
            finally
            {
                // -----------------------------------------
                // Delete temporary input
                // -----------------------------------------

                try
                {
                    if (File.Exists(tempFile))
                        File.Delete(tempFile);
                }
                catch
                {
                    // Ignore cleanup errors
                }
            }
        }
    }

 

✅ اعتبار سنجی ویدیو ( قبل از ذخیره ویدیو از این اعتبار سنجی استفاده کنید تا تضمین بشه داده ارسالی ویدیو هست و فایل مخرب نیست) 

    public static class VideoValidator
    {
        // =========================================================
        // Settings
        // =========================================================

        private const long MaxVideoSize =
            100L * 1024L * 1024L; // 100 MB

        private const double MaxDurationSeconds = 90;

        private const int ProcessTimeoutMilliseconds =
            15_000; // 15 seconds

        // =========================================================
        // Allowed Extensions
        // =========================================================

        private static readonly HashSet<string> AllowedExtensions =
            new(StringComparer.OrdinalIgnoreCase)
            {
                ".mp4",
                ".webm",
                ".mov"
            };

        // =========================================================
        // Allowed Content Types
        // =========================================================

        private static readonly HashSet<string> AllowedContentTypes =
            new(StringComparer.OrdinalIgnoreCase)
            {
                "video/mp4",
                "video/webm",
                "video/quicktime"
            };

        // =========================================================
        // Allowed Video Codecs
        // =========================================================

        private static readonly HashSet<string> AllowedVideoCodecs =
            new(StringComparer.OrdinalIgnoreCase)
            {
                "h264",
                "hevc",
                "vp8",
                "vp9",
                "av1"
            };

        // =========================================================
        // Main Validator
        // =========================================================

        public static async Task<ProductVideoResult> ValidateVideoAsync(
            this IFormFile? file,
            CancellationToken cancellationToken = default)
        {
            // -----------------------------------------------------
            // 1. Basic file validation
            // -----------------------------------------------------

            if (file == null || file.Length <= 0)
                return ProductVideoResult.NotVideoFile;

            // -----------------------------------------------------
            // 2. File size
            // -----------------------------------------------------

            if (file.Length > MaxVideoSize)
                return ProductVideoResult.FileTooLarge;

            // -----------------------------------------------------
            // 3. Extension
            // -----------------------------------------------------

            var extension =
                Path.GetExtension(file.FileName);

            if (string.IsNullOrWhiteSpace(extension))
                return ProductVideoResult.InvalidExtension;

            extension =
                extension.ToLowerInvariant();

            if (!AllowedExtensions.Contains(extension))
                return ProductVideoResult.InvalidExtension;

            // -----------------------------------------------------
            // 4. Content Type
            // -----------------------------------------------------

            if (string.IsNullOrWhiteSpace(file.ContentType) ||
                !AllowedContentTypes.Contains(file.ContentType))
            {
                return ProductVideoResult.InvalidContentType;
            }

            // -----------------------------------------------------
            // 5. Get FFprobe path
            // -----------------------------------------------------

            string ffprobePath;

            try
            {
                ffprobePath =
                    FFmpegPathHelper.GetFFprobePath();
            }
            catch
            {
                return ProductVideoResult.FFprobeNotFound;
            }

            // -----------------------------------------------------
            // 6. Check FFprobe exists
            // -----------------------------------------------------

            if (!File.Exists(ffprobePath))
                return ProductVideoResult.FFprobeNotFound;

            // -----------------------------------------------------
            // 7. Temporary directory
            // -----------------------------------------------------

            var tempDirectory =
                Path.Combine(
                    Path.GetTempPath(),
                    "AlendaTajhiz",
                    "VideoValidation");

            Directory.CreateDirectory(tempDirectory);

            // -----------------------------------------------------
            // 8. Temporary file
            // -----------------------------------------------------

            var tempFilePath =
                Path.Combine(
                    tempDirectory,
                    $"{Guid.NewGuid():N}{extension}");

            try
            {
                // -------------------------------------------------
                // 9. Save uploaded file temporarily
                // -------------------------------------------------

                await using (var stream = new FileStream(
                    tempFilePath,
                    FileMode.CreateNew,
                    FileAccess.Write,
                    FileShare.None,
                    bufferSize: 64 * 1024,
                    useAsync: true))
                {
                    await file.CopyToAsync(
                        stream,
                        cancellationToken);
                }

                // -------------------------------------------------
                // 10. Run FFprobe
                // -------------------------------------------------

                var probeResult =
                    await RunFFprobeAsync(
                        ffprobePath,
                        tempFilePath,
                        cancellationToken);

                // -------------------------------------------------
                // 11. FFprobe failed
                // -------------------------------------------------

                if (!probeResult.Success)
                    return ProductVideoResult.CorruptedVideo;

                // -------------------------------------------------
                // 12. Video stream check
                // -------------------------------------------------

                if (string.IsNullOrWhiteSpace(
                        probeResult.VideoCodec))
                {
                    return ProductVideoResult.NotVideoFile;
                }

                // -------------------------------------------------
                // 13. Codec validation
                // -------------------------------------------------

                if (!AllowedVideoCodecs.Contains(
                        probeResult.VideoCodec))
                {
                    return ProductVideoResult.InvalidVideoCodec;
                }

                // -------------------------------------------------
                // 14. Duration existence
                // -------------------------------------------------

                if (!probeResult.Duration.HasValue)
                    return ProductVideoResult.CorruptedVideo;

                var duration =
                    probeResult.Duration.Value;

                // -------------------------------------------------
                // 15. Duration validity
                // -------------------------------------------------

                if (double.IsNaN(duration) ||
                    double.IsInfinity(duration) ||
                    duration <= 0)
                {
                    return ProductVideoResult.CorruptedVideo;
                }

                // -------------------------------------------------
                // 16. Maximum duration
                // -------------------------------------------------

                if (duration > MaxDurationSeconds)
                    return ProductVideoResult.DurationExceeded;

                // -------------------------------------------------
                // 17. Everything is valid
                // -------------------------------------------------

                return ProductVideoResult.Success;
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch
            {
                return ProductVideoResult.UnknownError;
            }
            finally
            {
                // -------------------------------------------------
                // Cleanup temporary file
                // -------------------------------------------------

                try
                {
                    if (File.Exists(tempFilePath))
                        File.Delete(tempFilePath);
                }
                catch
                {
                    // Ignore cleanup errors
                }
            }
        }

        // =========================================================
        // FFprobe
        // =========================================================

        private static async Task<FFprobeResult> RunFFprobeAsync(
            string ffprobePath,
            string videoPath,
            CancellationToken cancellationToken)
        {
            var startInfo =
                new ProcessStartInfo
                {
                    FileName = ffprobePath,

                    UseShellExecute = false,

                    RedirectStandardOutput = true,

                    RedirectStandardError = true,

                    CreateNoWindow = true
                };

            // -----------------------------------------------------
            // FFprobe arguments
            // -----------------------------------------------------

            startInfo.ArgumentList.Add("-v");
            startInfo.ArgumentList.Add("error");

            startInfo.ArgumentList.Add("-select_streams");
            startInfo.ArgumentList.Add("v:0");

            startInfo.ArgumentList.Add("-show_entries");

            startInfo.ArgumentList.Add(
                "stream=codec_type,codec_name:format=duration");

            startInfo.ArgumentList.Add("-of");
            startInfo.ArgumentList.Add("json");

            startInfo.ArgumentList.Add(videoPath);

            using var process =
                new Process
                {
                    StartInfo = startInfo
                };

            try
            {
                // -------------------------------------------------
                // Start process
                // -------------------------------------------------

                if (!process.Start())
                    return FFprobeResult.Failed();

                // -------------------------------------------------
                // Read output
                // -------------------------------------------------

                var outputTask =
                    process.StandardOutput.ReadToEndAsync();

                var errorTask =
                    process.StandardError.ReadToEndAsync();

                // -------------------------------------------------
                // Timeout
                // -------------------------------------------------

                using var timeoutCts =
                    CancellationTokenSource
                        .CreateLinkedTokenSource(
                            cancellationToken);

                timeoutCts.CancelAfter(
                    ProcessTimeoutMilliseconds);

                try
                {
                    await process.WaitForExitAsync(
                        timeoutCts.Token);
                }
                catch (OperationCanceledException)
                {
                    // ---------------------------------------------
                    // Kill FFprobe
                    // ---------------------------------------------

                    try
                    {
                        if (!process.HasExited)
                            process.Kill(entireProcessTree: true);
                    }
                    catch
                    {
                        // Ignore process kill errors
                    }

                    // ---------------------------------------------
                    // Real cancellation
                    // ---------------------------------------------

                    if (cancellationToken.IsCancellationRequested)
                        throw;

                    // ---------------------------------------------
                    // Timeout
                    // ---------------------------------------------

                    return FFprobeResult.Failed();
                }

                // -------------------------------------------------
                // Read process output
                // -------------------------------------------------

                var output =
                    await outputTask;

                var error =
                    await errorTask;

                // -------------------------------------------------
                // Check exit code
                // -------------------------------------------------

                if (process.ExitCode != 0)
                    return FFprobeResult.Failed();

                // -------------------------------------------------
                // Check output
                // -------------------------------------------------

                if (string.IsNullOrWhiteSpace(output))
                    return FFprobeResult.Failed();

                // -------------------------------------------------
                // Parse result
                // -------------------------------------------------

                return ParseFFprobeOutput(output);
            }
            catch (OperationCanceledException)
            {
                throw;
            }
            catch
            {
                return FFprobeResult.Failed();
            }
        }

        // =========================================================
        // Parse FFprobe JSON
        // =========================================================

        private static FFprobeResult ParseFFprobeOutput(
            string output)
        {
            try
            {
                using var document =
                    JsonDocument.Parse(output);

                var root =
                    document.RootElement;

                // -------------------------------------------------
                // Streams
                // -------------------------------------------------

                if (!root.TryGetProperty(
                        "streams",
                        out var streams))
                {
                    return FFprobeResult.Failed();
                }

                string? codecName = null;

                // -------------------------------------------------
                // Find video stream
                // -------------------------------------------------

                foreach (var stream in streams.EnumerateArray())
                {
                    if (!stream.TryGetProperty(
                            "codec_type",
                            out var codecType))
                    {
                        continue;
                    }

                    var codecTypeValue =
                        codecType.GetString();

                    if (!string.Equals(
                            codecTypeValue,
                            "video",
                            StringComparison.OrdinalIgnoreCase))
                    {
                        continue;
                    }

                    if (stream.TryGetProperty(
                            "codec_name",
                            out var codec))
                    {
                        codecName =
                            codec.GetString();
                    }

                    break;
                }

                // -------------------------------------------------
                // No video codec
                // -------------------------------------------------

                if (string.IsNullOrWhiteSpace(codecName))
                    return FFprobeResult.Failed();

                // -------------------------------------------------
                // Duration
                // -------------------------------------------------

                double? duration = null;

                if (root.TryGetProperty(
                        "format",
                        out var format))
                {
                    if (format.TryGetProperty(
                            "duration",
                            out var durationElement))
                    {
                        // -----------------------------------------
                        // JSON Number
                        // -----------------------------------------

                        if (durationElement.ValueKind ==
                            JsonValueKind.Number)
                        {
                            if (durationElement.TryGetDouble(
                                    out var parsedDuration))
                            {
                                duration =
                                    parsedDuration;
                            }
                        }

                        // -----------------------------------------
                        // JSON String
                        // -----------------------------------------

                        else if (
                            durationElement.ValueKind ==
                            JsonValueKind.String)
                        {
                            var durationText =
                                durationElement.GetString();

                            if (double.TryParse(
                                    durationText,
                                    NumberStyles.Float,
                                    CultureInfo.InvariantCulture,
                                    out var parsedStringDuration))
                            {
                                duration =
                                    parsedStringDuration;
                            }
                        }
                    }
                }

                // -------------------------------------------------
                // Return parsed result
                // -------------------------------------------------

                return new FFprobeResult
                {
                    Success = true,

                    VideoCodec = codecName,

                    Duration = duration
                };
            }
            catch
            {
                return FFprobeResult.Failed();
            }
        }

        // =========================================================
        // FFprobe Result
        // =========================================================

        private sealed class FFprobeResult
        {
            public bool Success { get; init; }

            public string? VideoCodec { get; init; }

            public double? Duration { get; init; }

            public static FFprobeResult Failed()
            {
                return new FFprobeResult
                {
                    Success = false
                };
            }
        }
    }

پایان!

  • 1405/07/14
  • ساعت 10:12

عالی بود