public IEnumerable <BitmapStream> GetImagesFromMedia(string inputPath, int frameCount, CancellationToken cancellationToken, Action <string> log = null, bool autoToneMapHDR = true) { var mediaInfo = GetMediaInfo(inputPath, cancellationToken, log); log?.Invoke("Reading images from FFmpeg..."); var fps = frameCount / mediaInfo.Duration.TotalSeconds; var fpsFilter = $"fps={fps.ToInvariantString()}"; // Note: tone mapping algorithms have been tested (*cough* *cough* on the Interstellar movie only =°) // and compared to the SDR reference barcode, hable seems to give the closest result. // Warning: this is very slow! // https://web.archive.org/web/20190722004804/https://stevens.li/guides/video/converting-hdr-to-sdr-with-ffmpeg/ var hdrToSdrFilter = "zscale=transfer=linear:npl=100,format=gbrpf32le,zscale=primaries=bt709,tonemap=tonemap=hable,zscale=transfer=bt709:matrix=bt709:range=tv,format=yuv420p"; var vfilters = new List <string> { fpsFilter }; if (mediaInfo.IsHDR && autoToneMapHDR) { log?.Invoke("HDR video detected. Adding tone mapping filter."); vfilters.Add(hdrToSdrFilter); } // Output a raw stream of bitmap images taken at the specified frequency var args = $"-i \"{inputPath}\" -vf \"{string.Join(",", vfilters)}\" -c:v bmp -f rawvideo -an -"; log?.Invoke($"FFmpeg arguments: {args}"); var process = StartFfmpegInstance(args, redirectError: log != null); if (log != null) { process.ErrorDataReceived += (s, e) => log(e.Data); process.BeginErrorReadLine(); } IEnumerable <BitmapStream> GetLazyStream() { using (cancellationToken.Register(() => TryKill(process))) using (var reader = new BinaryReader(process.StandardOutput.BaseStream)) { while (BitmapStream.TryCreate(reader, out var bitmapStream, cancellationToken)) { yield return(bitmapStream); } } } return(GetLazyStream()); }
public static bool TryCreate(BinaryReader reader, out BitmapStream bitmapStream, CancellationToken cancellationToken) { try { // https://en.wikipedia.org/wiki/BMP_file_format var magicNumber = reader.ReadBytes(2); if (magicNumber.Length != 2) { bitmapStream = null; return(false); } if (magicNumber[0] != 0x42 || magicNumber[1] != 0x4D) { throw new InvalidDataException(); } var bmpSizeBytes = reader.ReadBytes(4); var bmpSize = BitConverter.ToInt32(bmpSizeBytes, 0); var remainingDataLength = bmpSize - bmpSizeBytes.Length - magicNumber.Length; var remainingData = reader.ReadBytes(remainingDataLength); var ms = new MemoryStream(); ms.Write(magicNumber, 0, magicNumber.Length); ms.Write(bmpSizeBytes, 0, bmpSizeBytes.Length); ms.Write(remainingData, 0, remainingData.Length); ms.Position = 0; bitmapStream = new BitmapStream(ms); return(true); } catch (Exception) { if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); throw new OperationCanceledException(); } else { throw; } } }
public override void Flush() { BitmapStream?.Flush(); DataA?.Flush(); DataB?.Flush(); }