diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f4cd583
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+
+# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
+# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
+Cargo.lock
+
+# These are backup files generated by rustfmt
+**/*.rs.bk
+
+# Jetbrains stuff
+/.idea/
+*.iml
+
+# might eventually remove this but, keeping raw files out of git
+*.y4m
+*.mp4
+
+*.exe
+*.pdb
+*.log
+/results.txt
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..4d117d3
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "encoder_benchmark_tool"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+clap = { version = "4.0.32", features = ["derive"] }
+compound_duration = "1.2.0"
+crossbeam-channel = "0.5.6"
+ctrlc = "3.2.4"
+filetime = "0.2.19"
+indicatif = "0.17.2"
+itertools = "0.10.5"
+num_cpus = "1.15.0"
+regex = "1.7.0"
+rev_buf_reader = "0.3.0"
+stoppable_thread = "0.2.1"
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8423068
--- /dev/null
+++ b/README.md
@@ -0,0 +1,507 @@
+## Realtime Video Encoding Benchmark Tool
+
+A command-line tool wrapper around ffmpeg that provides:
+
+- reliable way to benchmark real-time video encoding capabilities of your hardware
+- determination of encoder-specific settings that your hardware can handle
+- optional accurate video encode quality scoring via vmaf
+- no storage overhead during benchmarking due to some clever use of data streaming via TCP
+- bitrate increasing permutations to find minimum bitrate needed for visually lossless game streaming
+- a clean report at the end of the permutation results to help guide hardware decisions & streaming guides
+
+```shell
+# sample output for encoding a 4K@60 input file
+[Permutation 2/21]
+[ETR: 9m20s]
+[Bitrate: 10Mb/s]
+[-preset p1 -tune ll -profile:v high -rc cbr -cbr true]
+Running benchmark to test whether encoder can keep up with input...
+ [00:00:25] [####################################] 1923/1923 frames (00:00:00)
+ Average FPS: 78
+ 1%'ile: 68
+ 90%'ile: 94
+```
+
+For a quick-start guide for common use-cases, see Quick-Run Guide.
+
+For a quick reference of what to expect for your hardware, or what bitrate to configure in Moonlight when doing at home
+game streaming, see the Expected Performance section at the bottom of the readme.
+
+---
+
+## Minimum system specs suggested
+
+In addition to the mentioned specs below, you should _not_ have drive compression enabled for Windows on the drive you
+plan to store the files on. This can heavily limit sequential read speeds and affect the results of the tool.
+
+```text
+OS: Windows, Mac or Linux
+Processor: CPU with at least 6 cores
+GPU: GPU w/ hardware encoder, in the main x16 PCI slot of your PC (for max PCI bandwidth)
+Memory: >= 8GB RAM (higher is always better)
+Storage Space: 120GB minimum (all benchmark files take up about 120GB)
+Storage Type/Speed:
+ -if benchmarking <= 1080@120, any SATA ssd will work
+ -if benchmarking >= 1080@120, you MUST use an nvme drive with > 1GB/s sequential read speeds
+```
+
+The more cores you have, the faster the quality scoring via vmaf will happen, and the faster the benchmarks will
+run overall. Plus, your encoder of choice might be limited based on your CPU itself.
+
+
+---
+
+## Installation & Setup requirements
+
+Note: tool has been tested with ffmpeg version `5.1.2`, so it's highly suggested to use the same version, or at least
+version `5.*` of ffmpeg/ffprobe.
+
+1) Installation of ffmpeg
+
+ - For Windows, recommend downloading the binaries for Windows from
+ gyan.dev, specifically the `ffmpeg-release-full` one, which should include all needed features and tools
+ - For Mac/Linux, install both `ffmpeg` and `ffprobe` at mentioned versions above
+2) 7-Zip to unzip any downloaded ffmpeg binaries
+3) ffmpeg/ffprobe must be available on your path (tool will error out if it can't find
+ either); quick path setup guide for
+ Windows
+ 10+
+4) Download the built executable for your platform from the release section of this repo onto the SSD that you wish to
+ run the benchmark on
+5) Download all the raw video source files onto the same SSD where the executable is (and in the same folder)
+ from here. If you only wish
+ to
+ benchmark one specific resolution, only download that file and make sure to specify it as noted in the **Run using
+ the executable** section below
+
+(Note: due to the tool sending encoded video over your local network to test quality, you will not need more storage
+space than just what is needed to hold the original source video files).
+
+---
+
+## Running the tool
+
+Keep in mind, this tool is going to stress the encoder of your choice. It's highly recommended when running this tool
+to not use your computer for any other tasks as that can affect the results.
+
+i.e. if you're using your GPU for encoding, it's best to not have any programs open that could use your GPU.
+
+#### Run using executable
+
+For a complete list of all command line arguments, run:
+
+`./ebt -h`
+
+Typical usage would be:
+
+```shell
+# general example, note that executable name might be different
+./ebt -e -b -s
+
+# specific example, wanting to encode H264 using your Nvidia GPU at a 10Mb/s bitrate target
+./ebt -e h264_nvenc -b 10 -s 1080-60.y4m
+```
+
+#### Run using cargo
+
+(See **Contributing** section for version of Rust to install)
+
+`cargo run --release -- -e h264_nvenc -b 10 -s 1080-60.y4m`
+
+#### Stopping the benchmark
+
+Kill the benchmark by hitting `ctrl-c` in the terminal/console where the benchmark is running.
+
+---
+
+## Quick-Run Guide
+
+This tool can do a lot of things, however it's likely that your use case is very specific. Here are the typical use
+cases & commands that you'd use.
+
+**Note:** the input file that you provide is what determines the resolution you'll be benchmarking.
+
+### The Benchmark: Run Pre-Configured Encodes to Compare to Others
+
+This feature of the tool is likely going to be the most popular, as it's intended to compare different systems
+encoding performances in a standardized way.
+
+Note: you must download all the video source files to be able to run the standard benchmark. See the
+Installation & Setup Requirements section.
+
+It does the following:
+
+- runs pre-configured encoder settings & bitrate values chosen by the author
+- runs benchmark on all supported resolutions, outputting fps results to the console & a `benchmark.log` file
+
+Simple run the following (and choose your encoder):
+
+`./ebc -e h264_nvenc`
+
+If you so chose, you can also calculate the quality of the encode as well, although at the target configurations &
+bitrates you should expect a vmaf score of 95:
+
+`./ebc -e h264_nvenc -c`
+
+You may want the benchmark to stop early if it can't encode at the file's target fps; if so, use the `-d` parameter to
+detect encoder overload and stop the benchmark:
+
+`./ebc -e h264_nvenc -d`
+
+**Location of Encoder Setting Used**: to see the chosen encoder preset for this benchmark, see the respective encoder's
+implementation of `run_standard_only()` method, found in `src/permutations`. These tend to lean on the side of a lower
+preset to help keep fps statistics a bit higher (since you can always use more bitrate to improve quality).
+
+**Location of Bitrate Used:** you can also find the pre-configured bitrate value for that encoder within the encoder's
+implementation of `get_resolution_to_bitrate_map()`, found in `src/permutations`. These values are typically the
+bitrates for the 120fps findings.
+
+### Running benchmark over encoder settings
+
+This will run through all permutations of encoder settings, for your given resolution input file, and produce fps
+statistics, at the default bitrate of 10Mb/s. This gives you a good idea of the performance you can expect for your
+encoder.
+
+`./ebt -e h264_nvenc -s 4k-60.y4m`
+
+You may want to gather fps statistics at higher bitrates that you're more likely to stream at, since output bitrate can
+affect encoder performance. Do this by using the following:
+
+`./ebt -e h264_nvenc -s 4k-60.y4m -b 50`
+
+If you are curious on how _the minimum bitrate required_ can be identified, this tool can help you find that as well in
+the next section.
+
+### Bitrate & encoder settings combinations to achieve visually lossless game streaming
+
+When using Moonlight as your game streaming client, it auto-recommends a bitrate for you to stream at. Most of the time
+this is pretty accurate for lower resolutions, however depending on your hardware's capabilities you might be able to
+get away with less bitrate than it suggests.
+
+For example: Moonlight auto-selects `80Mb/s` for streaming 4K@60 game content. However from our testing, you really only
+need `45-50Mb/s` when encoding using H264_NVENC.
+
+To simply test what quality scores you can get at a given bitrate, use the following:
+
+`./ebt -e h264_nvenc -s 4k-60.y4m -c -b 10`
+
+If you're not sure on what bitrate would achieve visually lossless quality, provide a starting bitrate & max bitrate to
+permute over (in 5Mb/s intervals). In the below example, every encoder setting will be tested at
+**[10, 15, 20, ..., 100 Mb/s]** (until vmaf score of 95 is achieved):
+
+`./ebt -e h264_nvenc -s 4k-60.y4m -c -b 10 -m 100`
+
+When the tool detects that you've hit a `95` vmaf score, it will stop permuting. In the above example, the tool would
+stop permuting once it gets to `50Mb/s` since we know that's the point where you get visually lossless 4K@60 with
+H264_NVENC, and any higher amount of bitrate does not significantly improve quality and can actually reduce encoder
+performance.
+
+---
+
+## Discussion
+
+### What this benchmark can provide for you
+
+1) Determine what resolution/fps your specific system can encode in real-time with less guess work
+2) Permute over configured encoder settings to identify what settings maximize encoder real-time average fps
+3) Make use of quality scores to determine encoder setting & bitrate combinations that provides visually lossless
+ quality
+4) Provide encoder settings in a copy-paste format, making it easy to apply them into OBS Studio/Sunshine
+
+### How to Interpret FPS Statistics
+
+The tool will provide you with the following FPS stats when running through permutations:
+
+```text
+ Average FPS: 78
+ 1%'ile: 68
+ 90%'ile: 94
+```
+
+Each stat has specific things that it can tell you about what your system can do in real-time.
+
+- _Average:_ gives an idea of the overall experience you can expect during encoding
+- _1%'ile:_ gives an indication of detected dips/low points in framerate
+- _90%'ile:_ gives an indication of the upper-limits of the encoding capabilities of your hardware
+
+When choosing encoder permutation settings to use, you should look at all 3 datapoints before deciding what your system
+can handle.
+
+#### A Good Encoding Experience Example
+
+Let's say you get the following fps stats:
+
+```text
+ Average FPS: 78
+ 1%'ile: 68
+ 90%'ile: 94
+```
+
+You have an average of `78fps` and a 1%'ile of `68fps`, I can confidently say that
+my system will produce a smooth and consistent `60fps` encode experience.
+
+However if I really wanted to, I could look at the 90%'ile and know that my system _could_ periodically do `90fps` but
+will drop down to as low as `68fps`. If that fps variance is fine with you, you can feel free to set your target fps
+to `90fps`. Just know that at heavier encode times or game content where there's more movement/variance, your encode fps
+will drop.
+
+#### A Bad Encoding Experience Example
+
+Let's say instead, you see the following stats:
+
+```text
+ Average FPS: 78
+ 1%'ile: 30
+ 90%'ile: 85
+```
+
+Notice the 1%'ile `30fps` is much lower than the average and the 90%'ile. In this case, you may end up seeing fps drops
+during encoding that are drastic and would most likely provide a bad encoding experience.
+
+For results like these, it's recommended to try using different hardware/encoding settings to have the 1%'ile be much
+closer to the average. Or, just know that you might see some hard dips in FPS and consider not game streaming at all for
+a better experience.
+
+### Real-time encoding terminology
+
+The benchmark is always checking whether your encoder, at a given resolution/bitrate/fps, can keep up with at least
+the given fps. By default however, the actual encoding speed may be much higher than the input file. This is to help
+cut down on runtime during benchmarking.
+
+**Disclaimers**
+
+_It is possible that when you go to apply these settings in OBS Studio, or Sunshine's game stream hosting software,
+that the encoder/ffmpeg version being used there may perform different, i.e. most likely worse than this benchmarking
+tool._
+
+_This tool only tells you whether your host system can encode the video at the given parameters; it does not tell you
+whether the client you intend to stream TO can decode at the same speed. You may find that your client device
+cannot decode the incoming video as fast as it's sent._
+
+_You may find that what works on your machine, does not work on another with similar hardware. This is expected, and is
+why you should run the tool on your machine to get specific results to your setup._
+
+### Visually Lossless Terminology
+
+Any encoded video that scores >= 95 vmaf score is considered visually lossless, and is what you are shooting for in
+terms of encoded video quality. Anything lower and you end up seeing minor blockiness or artifacting.
+
+By default, the tool does not calculate vmaf score to initially focus on producing fps statistics. However, you can have
+the tool calculate vmaf score on each permutation by using the `-c` flag.
+
+Using `-c` in combination with `--
+
+### Skipping Duplicate Scoring Permutations by Default
+
+By default, the benchmark will detect if encoder settings produce the same vmaf score as previously calculated ones.
+This is done during the initial pass of all encoder setting permutations for a given bitrate.
+
+Each subsequence bitrate & encoder settings permutation will effectively ignore duplicated encoder settings that produce
+duplicated vmaf results in an effort to cut down on pointless calculation time.
+
+If you so desire, you can still have those permutations run and end up in the normal produced report by specifying
+the `-a` option.
+
+Note: a footnote to the results file will be added of what encoder settings produced similar results for your reference
+later.
+
+### Detecting video encoder overload & skipping by default
+
+If you've streamed using OBS Studio before, you might have seen:
+
+![img.png](docs/obs-encoder-overload.png)
+
+This tool has logic in it to detect encoder overload as well, by keeping an eye on the current fps during
+encoding. For example, if the target fps is `60`, this tool will attempt to encode the source video file at
+exactly `60fps`.
+
+If at any point the tool detects that the current fps is less than the target, the tool will stop and indicate that an
+encoder overload has occurred. If you still wish to let these encodes keep going (to see full fps statistics), you can
+do this by providing the `-i` option to ignore when the encoder gets overloaded.
+
+Note: in the stats output file, any encoder/bitrate/fps permutation that results in an overloaded encoder will have
+a `[O]` at the beginning of it's result stats.
+
+### Source input files
+
+All source files for this tool are captured with OBS Studio as `yuv4` raw video with `4:2:0` chroma subsampling, at
+specific resolutions/frame rates.
+Raw video at this chroma subsample is the closest you can get to simulating encoding a game that you're playing in
+real-time, much like you would
+when streaming to Twitch/Youtube. Any higher chroma subsample will produce lower scoring/performing results.
+
+Input source files are real gameplay captures. The two recorded FPS rates is 60 and 120, however the benchmark tool can
+encode at much higher rates if your hardware can support that.
+
+You can make your own conclusions about > 120 fps or < 60 fps bitrates that you'll need, since it's typically a linear
+relationship. For example:
+
+```text
+// these have been calculated as what you'd need for visually lossless quality
+// notice how the 120fps requires twice the bitrate
+720@60 H264 -> 10Mb/s
+720@120 H264 -> 20Mb/s
+
+// you can guess that lower fps, and higher fps, will have a bitrate that scales accordingly:
+720@30 H264 -> 5Mb/s
+720@240 H264 -> 40Mb/s
+```
+
+_Note: if you play a different genre of game, or you have overlays in your OBS studio setup, your encoding performance
+may vary. It is difficult/impossible to cover all possible inputs when benchmarking video encoding._
+
+### Why results from one machine might not apply to another
+
+It can be difficult to determine what specific encoder settings would work best on your system without a lot of
+trial and error. A few of the factors that can affect this:
+
+- GPU's encoder generation/hardware
+- overall system's performance (combination of motherboard, CPU, and other factors such as storage/RAM)
+- driver versions being used to encode the video files
+
+With this in mind, you may find that what works really well on your system, may not work the same on another's (even
+with the same GPU hardware). This is why it's important to run the benchmark on your own system for specific
+settings/capabilities of your entire setup.
+
+### Higher bitrate always increases vmaf score
+
+This is expected. By convention, using a low encoder preset/tune combination but allowing more data to be sent per
+second, will mean a higher quality stream.
+
+This tool will help you find at what bitrate you reach your max achievable quality at given encoder settings, but if you
+can afford it you can always increase the bitrate above that (with diminishing quality returns). This is almost always
+the case with at-home game streaming where you're less bandwidth limited.
+
+Game streaming outside your network or over cellular is where you'll truly become bandwidth limited and where
+this tool can be useful.
+
+### FPS Statistics Do Not Change Much with Higher Bitrates
+
+This is expected. The bitrate value is simply how much data to transmit _after the encode has happened_, and typically
+does not affect how _fast_
+your encoder can encode an input.
+
+The tool defaults to not permuting over bitrate values due to this fact. The only reason you'd want to also permute over
+bitrates or try higher values, is to get a higher vmaf score or to achieve visually lossless video quality.
+
+### Number of threads used by VMAF in tool
+
+The tool automatically chooses the maximum available threads on your machine (including hyper-threads). This ensures
+maximum performance when doing vmaf calculations.
+
+There are diminishing returns the more threads you throw at VMAF, but these small gains will make a huge difference when
+running through encoder permutations/benchmarks.
+
+Note: threads higher than your physical core count don't add any extra performance and are effectively
+unused. The tool might be more robust in the future to calculate 1 thread per physical core but, there's no issue with
+just specifying all threads.
+
+### Use of 'n_subsample' for calculation speedup in VMAF
+
+The tool uses a value of the parameter `n_subsample=5` passed to VMAF, to cut down the vmaf calculation to almost half.
+This effectively tells VMAF to look every 5th frame when doing the score calculation.
+
+A negligible score difference of < 1 score point was observed when running through both low fps and high-fps
+video content, with and without the use of `n_subsample`.
+
+Higher values than 5, at least for the 30 second video inputs began to show >= 2 score point differences and provided no
+performance benefit.
+
+---
+
+## Encoder Specific Notes
+
+### H264/HEVC NVENC
+
+#### Presets & Tunes
+
+Only the latest presets, i.e. `[p1-p7]` are being used, along with the following tunes: `[ll, ull, hq]` when testing
+encoder permutations. All other legacy presets end up mapping to a combination of the mentioned presets/tunes and just
+adds extra computation time.
+
+The use of the `lossless` tune or `lossless` preset effectively ignores the bitrate that you set, so these are not
+included in permutations, since we're focused on specific/replicable bitrate targets with vmaf scores.
+
+#### Profiles
+
+It was determined that use of any other profile than `high` did not improve results; i.e. either lowered the vmaf score,
+or did not increase the average fps. Thus, the tool is sticking to using 'high' for H264, and 'main' for HEVC.
+
+Documentation used for decisions made when using this codec:
+
+-
+ Nvenc Preset Migration Guide
+-
+ Nvidia's Video Codec SDK Documentation
+
+---
+
+## Expected Performance
+
+While building this tool, the author used specific GPU's for validation. Along the way, he was able to find the maximum
+limit of the hardware he has available to him, as well as minimum bitrates needed to achieve visually lossless results.
+
+Hopefully performance details listed below will help you identify where your specific hardware lies relative to the
+author's own system.
+
+### Minimum Spec'd PC
+
+- CPU: Intel i5-8400 (6 cores/6 threads)
+- RAM: 16GB of G.Skill Ripjaws V DDR4 3200Mhz
+- GPU: Asus GTX 1660 Super
+- NVME SSD: PNY250GB NVMe PCI Gen3 x4
+- NVENC Arch & Gen: Turing, 7th Gen
+- Nvidia Driver: 527.56
+
+The following are the minimum bitrates you'd need to achieve visually lossless results on the above Turing GPU:
+
+Note: HEVC is about 30% more efficient but that doesn't appear to make that huge of a difference in your ending bitrate.
+
+```text
+NVENC H264: 720@60 -> 10Mb/s
+NVENC HEVC: 720@60 -> 5-10Mb/s
+
+NVENC H264: 720@120 -> 25Mb/s
+NVENC HEVC: 720@120 -> 20-25Mb/s
+
+NVENC H264: 1080@60 -> 20Mb/s
+NVENC HEVC: 1080@60 -> 15-20Mb/s
+
+NVENC H264: 1080@120 -> 40Mb/s
+NVENC HEVC: 1080@120 -> 35-40Mb/s
+
+NVENC H264: 2K@60 -> 20-25Mb/s
+NVENC HEVC: 2K@60 -> 20-25Mb/s
+
+NVENC H264: 2K@120 -> 50-55Mb/s
+NVENC HEVC: 2K@120 -> 50Mb/s
+
+NVENC H264: 4K@60 -> 45-50Mb/s
+NVENC HEVC: 4K@60 -> 40-45Mb/s
+
+// Note: Turing GPU used could not get any higher than 4K@75-90 average fps
+// however, most likely the bitrate required for visually lossless 4K@120 is ~100Mb/s
+
+NVENC H264: 4K@120 -> ???
+NVENC HEVC: 4K@120 -> ???
+```
+
+Notice that with HEVC you can achieve the same level of visually lossless quality with slightly less bitrate, at least
+with NVENC. This is going to be the case with an AV1 encoder on newer GPU's. When at all possible, it's suggested to use
+the encoder that provides higher quality at a lower bitrate.
+---
+
+## Issues, Bugs & Feature Requests
+
+Feel free to open issues in the repository if you find issues, and we'll try to get around to fixing them or
+implementing the feature requests.
+
+Screenshots or log file uploads are much appreciated!
+
+---
+
+## Contributing
+
+Project is written in Rust, with version `1.66.0` at time of writing.
+
+Setup your dev environment for Rust and you'll be able to contribute.
\ No newline at end of file
diff --git a/docs/obs-encoder-overload.png b/docs/obs-encoder-overload.png
new file mode 100644
index 0000000..867743c
Binary files /dev/null and b/docs/obs-encoder-overload.png differ
diff --git a/references/nvidia-turing/1080-120-h264.txt b/references/nvidia-turing/1080-120-h264.txt
new file mode 100644
index 0000000..785e72c
--- /dev/null
+++ b/references/nvidia-turing/1080-120-h264.txt
@@ -0,0 +1,178 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+1920x1080 120 5 32s 52s 49.99189 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 50.78878 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 49.87901 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 51.33805 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 47.46562 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 51.39954 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 47.82333 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 51.34720 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 5 32s 52s 50.41552 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 51.36192 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 5 32s 52s 50.62050 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 5 31s 51s 51.46517 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 50s 70.29657 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 50s 71.19142 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 51s 69.25217 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 51s 71.62771 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 51s 66.86445 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 50s 71.78528 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 51s 66.76498 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 52s 71.73936 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 10 32s 52s 69.62772 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 50s 71.78707 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 10 32s 52s 69.94501 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 10 31s 50s 72.00939 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 49s 79.79058 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 50s 80.37565 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 50s 79.43720 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 15 32s 49s 80.78581 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 50s 77.39177 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 49s 80.88729 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 51s 77.23794 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 50s 80.84431 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 15 32s 50s 79.28085 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 49s 80.85456 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 15 33s 50s 79.57948 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 15 31s 49s 81.00658 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 85.08440 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 85.47128 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 84.99984 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 85.76170 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 83.66228 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 85.82275 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 83.43204 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 85.83939 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 20 32s 50s 84.54772 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 85.84438 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 20 33s 50s 84.80801 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 20 31s 49s 86.00859 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 49s 88.25314 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 48s 88.52682 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 49s 88.38203 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 50s 88.85011 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 49s 87.33513 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 49s 88.88587 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 49s 87.18569 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 49s 88.85275 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 25 32s 49s 87.82487 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 49s 88.89854 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 25 33s 49s 88.00738 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 25 31s 49s 89.01835 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 48s 90.36114 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 48s 90.60395 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 49s 90.70490 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 49s 90.82680 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 49s 89.70013 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 49s 90.90486 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 50s 89.63236 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 49s 90.88116 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 30 32s 50s 89.98054 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 48s 90.86143 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 30 33s 49s 90.14933 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 30 31s 48s 90.99081 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 49s 91.86444 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 48s 92.03297 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 48s 92.23409 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 48s 92.23190 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 49s 91.42215 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 49s 92.27628 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 49s 91.33764 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 49s 92.25399 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 35 32s 49s 91.46214 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 48s 92.26575 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 35 33s 49s 91.57964 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 35 31s 48s 92.37749 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 48s 92.97674 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 49s 93.11378 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 48s 93.38796 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 49s 93.27600 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 49s 92.60187 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 48s 93.31536 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 49s 92.58385 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 48s 93.33156 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 40 32s 49s 92.55917 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 48s 93.31236 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 40 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 40 31s 49s 93.38749 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 48s 93.81015 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 49s 93.93367 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 49s 94.24088 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 48s 94.08202 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 49s 93.49768 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 49s 94.09909 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 49s 93.49147 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 48s 94.09524 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 45 32s 49s 93.40893 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 48s 94.10186 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 45 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 45 31s 49s 94.17725 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 48s 94.43179 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 48s 94.55545 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 49s 94.84044 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 48s 94.65708 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 49s 94.21484 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 48s 94.69174 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 49s 94.17100 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 48s 94.69593 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 50 32s 49s 94.04288 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 48s 94.69225 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 50 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 50 31s 49s 94.74464 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 48s 94.90863 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 48s 95.00902 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 48s 95.29723 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 49s 95.10140 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 55 !!! Overloaded !!! -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 48s 95.12882 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 49s 94.71027 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 49s 95.13551 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 55 32s 49s 94.54959 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 48s 95.13809 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 120 55 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 120 55 31s 49s 95.18288 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 3h7m53s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 49.991886
+ Encoded: [-preset p1 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 50.788776
+ Encoded: [-preset p2 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 51.338055
+ Encoded: [-preset p3 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 51.399544
+ Encoded: [-preset p4 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 51.347202
+ Encoded: [-preset p5 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 51.36192
+ Encoded: [-preset p6 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 51.465168
+ Encoded: [-preset p7 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/1080-120-hevc.txt b/references/nvidia-turing/1080-120-hevc.txt
new file mode 100644
index 0000000..5beada4
--- /dev/null
+++ b/references/nvidia-turing/1080-120-hevc.txt
@@ -0,0 +1,138 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+1920x1080 120 5 34s 53s 75.70850 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 52s 75.75812 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 52s 75.60134 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 52s 75.51202 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 52s 75.87301 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 53s 74.97031 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 51s 75.99516 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 52s 75.06538 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 51s 75.96230 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 52s 75.27930 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 52s 75.35414 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 5 31s 51s 76.03626 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 51s 84.90907 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 85.13658 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 51s 85.09588 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 84.74118 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 85.29262 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 10 !!! Overloaded !!! -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 85.41980 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 84.47678 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 85.43459 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 84.59978 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 84.62061 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 10 31s 52s 85.46388 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 89.01357 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 89.22966 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 89.23589 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 88.81435 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 89.39216 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 88.50737 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 89.47044 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 88.58233 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 89.48505 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 53s 88.63686 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 88.64904 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 15 31s 52s 89.52385 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 52s 91.29855 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 52s 91.54755 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 52s 91.48146 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 52s 91.10006 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 52s 91.63572 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 53s 90.88873 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 53s 91.72107 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 53s 90.90729 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 52s 91.72432 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 53s 90.99638 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 53s 91.01141 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 20 31s 53s 91.73863 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 52s 92.73186 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 92.96626 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 52s 92.90308 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 92.56429 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 52s 92.99535 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 92.36080 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 93.09842 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 92.42175 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 93.11068 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 92.47523 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 92.49652 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 25 31s 53s 93.10513 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 93.72655 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 93.96207 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 93.85986 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 93.53531 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 54s 93.94278 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 93.37828 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 94.02118 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 93.44228 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 94.02806 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 93.49054 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 53s 93.49233 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 30 31s 54s 94.03491 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.42563 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.65247 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.54723 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.24657 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.62567 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.12370 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 54s 94.69470 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.16495 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.70782 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 54s 94.21577 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 54s 94.21889 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 35 31s 53s 94.71122 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 54s 94.94276 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 53s 95.15784 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 53s 95.06442 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 53s 94.79282 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 53s 95.13419 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 40 !!! Overloaded !!! -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 54s 95.19332 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 54s 94.73313 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 54s 95.19762 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 55s 94.77101 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 54s 94.76874 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 120 40 31s 54s 95.20499 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 2h26m35s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 75.7085
+ Encoded: [-preset p1 -tune hq -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 75.60134
+ Encoded: [-preset p2 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 75.87301
+ Encoded: [-preset p3 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 75.995155
+ Encoded: [-preset p4 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 75.962296
+ Encoded: [-preset p5 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 76.03626
+ Encoded: [-preset p7 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/1080-60-h264.txt b/references/nvidia-turing/1080-60-h264.txt
new file mode 100644
index 0000000..fda782f
--- /dev/null
+++ b/references/nvidia-turing/1080-60-h264.txt
@@ -0,0 +1,106 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+1920x1080 60 5 16s 27s 74.50902 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 27s 75.34191 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 26s 72.95963 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 26s 75.88480 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 26s 69.81652 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 26s 76.10236 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 26s 69.58448 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 26s 75.95850 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 27s 73.08858 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 5 15s 27s 75.94342 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 27s 73.41910 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 5 16s 26s 76.35578 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 10 16s 26s 87.40918 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 10 15s 26s 87.76508 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 10 16s 26s 86.76790 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 10 16s 26s 88.10063 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 10 16s 26s 84.52326 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 10 16s 26s 88.20581 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 10 16s 26s 84.22622 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 10 15s 26s 88.14150 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 10 16s 27s 86.50469 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 10 15s 26s 88.17024 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 10 17s 27s 86.74608 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 10 16s 25s 88.36306 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 25s 91.88810 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 25s 92.09664 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 26s 91.67593 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 25s 92.31938 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 25s 90.19402 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 26s 92.39477 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 26s 90.05708 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 25s 92.38586 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 26s 91.29316 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 26s 92.37621 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 15 17s 26s 91.50388 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 15 16s 25s 92.51978 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 20 15s 25s 94.03944 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 20 15s 25s 94.21046 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 20 16s 25s 93.98454 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 20 16s 25s 94.35272 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 20 16s 25s 92.95072 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 20 15s 25s 94.41286 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 20 16s 25s 92.90145 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 20 16s 25s 94.42652 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 20 16s 25s 93.64183 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 20 15s 25s 94.46563 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 20 17s 25s 93.73206 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 20 16s 25s 94.52225 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 25 15s 25s 95.30466 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 25 16s 25s 95.42963 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 25 16s 25s 95.32159 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 25 16s 25s 95.57478 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 25 16s 25s 94.45645 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 25 15s 25s 95.58061 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 25 16s 25s 94.48894 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 25 15s 25s 95.59511 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 25 16s 25s 94.98222 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 25 15s 25s 95.62213 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1920x1080 60 25 18s 26s 95.07385 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1920x1080 60 25 16s 25s 95.67976 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 48m43s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 74.50902
+ Encoded: [-preset p1 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 75.34191
+ Encoded: [-preset p2 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 75.8848
+ Encoded: [-preset p3 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 76.102356
+ Encoded: [-preset p4 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 75.9585
+ Encoded: [-preset p5 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 75.94342
+ Encoded: [-preset p6 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 76.35578
+ Encoded: [-preset p7 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/1080-60-hevc.txt b/references/nvidia-turing/1080-60-hevc.txt
new file mode 100644
index 0000000..309ca28
--- /dev/null
+++ b/references/nvidia-turing/1080-60-hevc.txt
@@ -0,0 +1,90 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+1920x1080 60 5 16s 27s 85.60248 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 27s 85.82336 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 27s 85.94729 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 27s 85.24741 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 28s 86.19556 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 28s 84.77348 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 27s 86.34673 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 27s 84.80721 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 26s 86.33817 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 26s 84.88064 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 5 16s 26s 84.92223 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 5 15s 26s 86.38570 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 26s 91.63816 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 28s 91.76040 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 10 15s 26s 91.92679 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 27s 91.33148 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 27s 92.11054 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 27s 90.96395 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 10 15s 26s 92.18690 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 27s 90.99364 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 27s 92.19357 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 27s 91.01091 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 10 16s 27s 91.04804 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 10 15s 26s 92.23741 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 94.10514 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 94.06136 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 94.29094 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 28s 93.70378 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 15 15s 27s 94.40025 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 93.46300 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 94.48074 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 93.49157 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 94.47823 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 93.53397 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 93.55904 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 15 16s 27s 94.50688 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 20 15s 27s 95.33635 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 27s 95.34302 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 27s 95.46912 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 28s 94.97389 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 28s 95.54969 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 28s 94.80326 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 20 15s 28s 95.62080 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 27s 94.80145 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 27s 95.60349 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 27s 94.86880 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 20 16s 28s 94.88090 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1920x1080 60 20 15s 27s 95.64832 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 41m31s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 85.60248
+ Encoded: [-preset p1 -tune hq -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 85.94729
+ Encoded: [-preset p2 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 86.19556
+ Encoded: [-preset p3 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 86.346725
+ Encoded: [-preset p4 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 86.33817
+ Encoded: [-preset p5 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 86.385704
+ Encoded: [-preset p7 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/4k-60-h264.txt b/references/nvidia-turing/4k-60-h264.txt
new file mode 100644
index 0000000..35246fc
--- /dev/null
+++ b/references/nvidia-turing/4k-60-h264.txt
@@ -0,0 +1,158 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+3840x2160 60 10 18s 1m28s 76.68040 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 10 22s 1m28s 77.12575 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 10 23s 1m28s 75.60857 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 10 23s 1m28s 77.59946 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 10 24s 1m28s 72.22140 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 10 24s 1m28s 77.67113 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 10 30s 1m29s 72.37341 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 10 30s 1m29s 77.74998 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 10 32s 1m30s 75.76579 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 10 30s 1m29s 77.74480 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 10 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 10 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 10 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+3840x2160 60 15 23s 1m27s 83.70285 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 15 26s 1m27s 84.04021 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 15 26s 1m27s 82.89784 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 15 26s 1m27s 84.46037 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 15 27s 1m27s 79.77464 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 15 27s 1m27s 84.53457 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 15 30s 1m28s 79.90364 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 15 30s 1m28s 84.54087 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 15 32s 1m29s 82.87410 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 15 30s 1m28s 84.49250 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 15 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 15 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 15 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+3840x2160 60 20 23s 1m26s 87.77959 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 20 26s 1m29s 88.03502 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 20 26s 1m27s 87.16351 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 20 26s 1m26s 88.34522 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 20 27s 1m27s 84.60921 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 20 26s 1m26s 88.39782 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 20 30s 1m27s 84.66978 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 20 30s 1m27s 88.43704 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 20 32s 1m27s 87.11238 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 20 30s 1m29s 88.46435 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 20 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 20 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 20 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+3840x2160 60 25 23s 1m26s 90.09315 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 25 26s 1m26s 90.30676 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 25 26s 1m29s 89.69839 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 25 26s 1m26s 90.60007 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 25 26s 1m26s 87.49775 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 25 26s 1m26s 90.59245 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 25 30s 1m27s 87.54654 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 25 30s 1m26s 90.67023 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 25 32s 1m27s 89.56701 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 25 30s 1m26s 90.65359 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 25 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 25 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 25 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+3840x2160 60 30 23s 1m26s 91.81829 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 30 26s 1m26s 92.04691 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 30 26s 1m26s 91.43076 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 30 27s 1m26s 92.27461 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 30 27s 1m26s 89.51134 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 30 26s 1m26s 92.28146 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 30 30s 1m28s 89.55003 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 30 30s 1m26s 92.29030 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 30 32s 1m26s 91.26761 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 30 30s 1m26s 92.32927 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 30 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 30 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 30 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+3840x2160 60 35 23s 1m26s 93.13573 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 35 27s 1m26s 93.29479 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 35 26s 1m26s 92.68586 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 35 27s 1m26s 93.47095 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 35 27s 1m26s 91.12012 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 35 27s 1m26s 93.52312 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 35 30s 1m26s 91.13619 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 35 30s 1m26s 93.55276 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 35 32s 1m27s 92.51475 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 35 30s 1m27s 93.53512 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 35 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 35 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 35 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+3840x2160 60 40 23s 1m26s 93.97160 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 40 27s 1m26s 94.12282 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 40 27s 1m26s 93.52248 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 40 27s 1m26s 94.30790 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 40 27s 1m27s 92.10780 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 40 27s 1m26s 94.29990 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 40 30s 1m26s 92.16416 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 40 30s 1m26s 94.35481 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 40 32s 1m27s 93.40947 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 40 30s 1m26s 94.35061 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 40 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 40 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 40 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+3840x2160 60 45 23s 1m26s 94.68674 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 45 27s 1m26s 94.80742 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 45 27s 1m26s 94.28408 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 45 27s 1m26s 94.96565 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 45 27s 1m26s 92.99207 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 45 27s 1m26s 94.97440 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 45 30s 1m27s 93.06584 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 45 30s 1m27s 94.98957 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 45 32s 1m30s 94.08474 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 45 30s 1m27s 94.98637 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 45 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 45 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 45 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+3840x2160 60 50 23s 1m26s 95.23787 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 50 27s 1m26s 95.30165 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 50 27s 1m26s 94.80300 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 50 27s 1m26s 95.41363 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 50 27s 1m29s 93.70727 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 50 27s 1m26s 95.45330 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 50 30s 1m27s 93.78199 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 50 30s 1m27s 95.47023 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 50 32s 1m27s 94.61900 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 50 30s 1m26s 95.45727 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 50 !!! Overloaded !!! -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+3840x2160 60 50 !!! Overloaded !!! -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+3840x2160 60 50 !!! Overloaded !!! -preset p7 -tune ull -profile:v high -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 3h18m7s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 76.680405
+ Encoded: [-preset p1 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 77.12575
+ Encoded: [-preset p2 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 77.599464
+ Encoded: [-preset p3 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 77.671135
+ Encoded: [-preset p4 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 77.749985
+ Encoded: [-preset p5 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 77.744804
+ Encoded: [-preset p6 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/4k-60-hevc.txt b/references/nvidia-turing/4k-60-hevc.txt
new file mode 100644
index 0000000..9528995
--- /dev/null
+++ b/references/nvidia-turing/4k-60-hevc.txt
@@ -0,0 +1,150 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+3840x2160 60 10 26s 1m32s 82.95334 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 10 23s 1m29s 83.13516 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 10 23s 1m29s 83.18951 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 10 23s 1m29s 82.32758 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 10 23s 1m29s 83.54450 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 10 23s 1m29s 81.85211 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 10 24s 1m29s 83.57703 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 10 23s 1m29s 82.00096 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 10 26s 1m29s 83.56860 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 10 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 10 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 10 26s 1m29s 83.65735 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 15 23s 1m29s 87.24523 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 15 23s 1m29s 87.25827 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 15 23s 1m29s 87.53083 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 15 23s 1m30s 86.49188 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 15 23s 1m29s 87.77304 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 15 23s 1m30s 86.14337 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 15 24s 1m32s 87.88035 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 15 24s 1m29s 86.20297 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 15 27s 1m29s 87.86755 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 15 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 15 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 15 27s 1m29s 87.90140 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 20 23s 1m29s 89.75600 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 20 23s 1m29s 89.71399 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 20 23s 1m29s 90.07269 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 20 23s 1m29s 89.01703 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 20 23s 1m29s 90.28008 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 20 23s 1m29s 88.67458 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 20 24s 1m29s 90.35348 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 20 24s 1m29s 88.73602 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 20 27s 1m29s 90.37288 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 20 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 20 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 20 27s 1m29s 90.42415 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 25 22s 1m29s 91.34556 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 25 23s 1m29s 91.26055 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 25 22s 1m29s 91.59132 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 25 23s 1m29s 90.58596 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 25 23s 1m29s 91.76597 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 25 23s 1m29s 90.27117 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 25 25s 1m29s 91.85982 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 25 24s 1m29s 90.33717 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 25 27s 1m29s 91.85974 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 25 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 25 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 25 27s 1m29s 91.91479 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 30 23s 1m29s 92.51829 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 30 22s 1m29s 92.36660 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 30 23s 1m29s 92.81517 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 30 23s 1m29s 91.78934 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 30 23s 1m29s 92.92507 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 30 23s 1m29s 91.51199 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 30 25s 1m29s 93.00259 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 30 24s 1m29s 91.56116 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 30 27s 1m30s 93.00529 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 30 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 30 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 30 27s 1m29s 93.02785 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 35 23s 1m27s 93.42834 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 35 23s 1m27s 93.21355 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 35 23s 1m29s 93.59796 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 35 23s 1m27s 92.66579 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 35 23s 1m29s 93.72364 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 35 23s 1m27s 92.42847 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 35 25s 1m29s 93.79159 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 35 25s 1m29s 92.47926 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 35 27s 1m30s 93.78342 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 35 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 35 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 35 27s 1m30s 93.82080 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 40 23s 1m29s 94.10795 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 40 24s 1m29s 93.88819 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 40 23s 1m28s 94.27808 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 40 23s 1m28s 93.41139 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 40 23s 1m28s 94.34898 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 40 23s 1m28s 93.20591 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 40 25s 1m28s 94.45242 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 40 25s 1m28s 93.26141 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 40 27s 1m28s 94.43142 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 40 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 40 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 40 27s 1m28s 94.48958 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 45 23s 1m27s 94.61506 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 45 23s 1m27s 94.40565 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 45 23s 1m27s 94.78809 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 45 23s 1m27s 93.99464 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 45 23s 1m27s 94.83579 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 45 23s 1m27s 93.78939 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 45 25s 1m27s 94.87885 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 45 25s 1m27s 93.83456 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 45 27s 1m28s 94.89469 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 45 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 45 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 45 27s 1m28s 94.92305 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 50 23s 1m27s 95.03426 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 50 23s 1m27s 94.84890 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 50 23s 1m27s 95.16911 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 50 23s 1m27s 94.45891 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 50 23s 1m27s 95.24308 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 50 23s 1m27s 94.30659 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 50 25s 1m27s 95.29591 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 50 25s 1m27s 94.35288 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 50 27s 1m28s 95.28676 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+3840x2160 60 50 !!! Overloaded !!! -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 50 !!! Overloaded !!! -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+3840x2160 60 50 27s 1m27s 95.34104 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 3h10m33s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 82.95334
+ Encoded: [-preset p1 -tune hq -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 83.18951
+ Encoded: [-preset p2 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 83.5445
+ Encoded: [-preset p3 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 83.57703
+ Encoded: [-preset p4 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 83.5686
+ Encoded: [-preset p5 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 83.65735
+ Encoded: [-preset p7 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/720-120-h264.txt b/references/nvidia-turing/720-120-h264.txt
new file mode 100644
index 0000000..701f9e3
--- /dev/null
+++ b/references/nvidia-turing/720-120-h264.txt
@@ -0,0 +1,130 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+1280x720 120 5 13s 21s 64.90092 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 21s 65.52699 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 22s 64.28435 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 21s 66.20261 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 22s 62.64022 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 22s 66.50801 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 22s 62.48967 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 21s 66.51543 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 5 14s 22s 64.30239 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 22s 66.50776 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 5 14s 23s 64.78190 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 5 13s 21s 66.71972 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 21s 82.38084 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 21s 82.72018 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 21s 82.60147 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 21s 83.15195 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 22s 81.58021 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 21s 83.26570 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 22s 81.42077 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 21s 83.26820 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 10 14s 22s 81.93056 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 21s 83.32438 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 10 14s 22s 82.17594 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 10 13s 21s 83.42992 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 88.64359 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 88.86626 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 88.99073 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 89.15865 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 88.18552 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 89.26521 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 88.10307 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 89.23954 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 15 14s 21s 88.10242 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 89.25559 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 15 14s 22s 88.28796 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 15 13s 21s 89.32179 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 91.76273 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 91.92176 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 92.09645 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 92.14625 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 91.37338 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 92.18753 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 91.32249 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 92.21945 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 20 14s 22s 91.23981 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 92.21704 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 20 14s 21s 91.31812 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 20 13s 21s 92.26194 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.54258 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.64291 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.87661 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.83606 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.26871 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.90572 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.24361 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.90061 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 25 14s 21s 93.12077 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.90311 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 25 14s 21s 93.20306 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 25 13s 21s 93.92645 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.72880 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.79089 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.97000 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.91792 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.43682 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.96603 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.45422 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.96150 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 30 14s 21s 94.33981 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.95192 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 30 14s 21s 94.38073 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 30 13s 21s 94.99083 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 20s 95.48203 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 21s 95.53185 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 21s 95.73418 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 20s 95.66804 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 21s 95.25681 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 21s 95.69057 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 21s 95.24973 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 20s 95.71408 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 35 14s 22s 95.13821 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 21s 95.70564 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 120 35 14s 21s 95.16508 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 120 35 13s 21s 95.71584 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 54m39s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 64.90092
+ Encoded: [-preset p1 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 65.52699
+ Encoded: [-preset p2 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 66.202614
+ Encoded: [-preset p3 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 66.50801
+ Encoded: [-preset p4 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 66.515434
+ Encoded: [-preset p5 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 66.50776
+ Encoded: [-preset p6 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 66.71972
+ Encoded: [-preset p7 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/720-120-hevc.txt b/references/nvidia-turing/720-120-hevc.txt
new file mode 100644
index 0000000..b2d4336
--- /dev/null
+++ b/references/nvidia-turing/720-120-hevc.txt
@@ -0,0 +1,114 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+1280x720 120 5 13s 24s 79.96729 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 21s 80.34641 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 21s 80.04669 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 21s 79.97925 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 21s 80.37294 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 22s 79.36954 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 21s 80.61272 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 22s 79.51860 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 21s 80.61639 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 22s 79.73672 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 22s 79.71426 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 5 13s 21s 80.70410 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.31471 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.68100 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.46787 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.34640 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.64894 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 87.82439 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.79509 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 87.94421 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.81303 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.09540 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.11457 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 10 13s 21s 88.84178 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.73531 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.96697 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.84264 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.68526 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.96220 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.24697 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 92.06578 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.32030 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 92.06978 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.41628 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 91.47504 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 15 13s 21s 92.11024 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.54683 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.75029 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.62348 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.49738 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.72205 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.09705 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.79974 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.17129 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.80541 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.26453 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.28241 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 20 13s 21s 93.82545 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.66199 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.86075 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.74052 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.57156 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.80500 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.30806 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.84731 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.34428 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.88469 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.44082 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.45293 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 25 13s 21s 94.87225 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.40022 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.58849 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.46679 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.33219 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.52958 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.08572 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.58263 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 22s 95.10243 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.58719 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 22s 95.18350 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 22s 95.19385 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 120 30 13s 21s 95.57690 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 47m31s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 79.967285
+ Encoded: [-preset p1 -tune hq -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 80.04669
+ Encoded: [-preset p2 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 80.37294
+ Encoded: [-preset p3 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 80.612724
+ Encoded: [-preset p4 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 80.61639
+ Encoded: [-preset p5 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 80.7041
+ Encoded: [-preset p7 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/720-60-h264.txt b/references/nvidia-turing/720-60-h264.txt
new file mode 100644
index 0000000..2ba0f6a
--- /dev/null
+++ b/references/nvidia-turing/720-60-h264.txt
@@ -0,0 +1,82 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+1280x720 60 5 7s 12s 76.79397 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 5 6s 11s 77.43787 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 5 6s 11s 76.78555 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 5 7s 11s 78.04528 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 5 7s 11s 74.96706 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 5 7s 11s 78.24430 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 5 7s 11s 74.68017 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 5 7s 11s 78.27507 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 5 7s 11s 76.26791 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 5 6s 11s 78.36836 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 5 7s 11s 76.65379 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 5 7s 11s 78.58556 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 10 6s 11s 91.65628 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 10 6s 11s 91.92998 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 10 6s 11s 91.33750 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 10 7s 11s 92.28779 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 10 7s 11s 90.38431 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 10 6s 11s 92.36864 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 10 7s 11s 90.33647 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 10 6s 11s 92.39465 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 10 7s 11s 90.97400 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 10 7s 11s 92.41976 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 10 8s 11s 91.17747 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 10 7s 11s 92.61568 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 15 7s 10s 96.41919 -preset p1 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 15 7s 10s 96.50824 -preset p2 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 15 7s 10s 96.05845 -preset p3 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 15 6s 10s 96.73878 -preset p3 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 15 7s 11s 95.51065 -preset p4 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 15 6s 10s 96.76241 -preset p4 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 15 7s 11s 95.48296 -preset p5 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 15 6s 10s 96.75915 -preset p5 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 15 7s 11s 95.69723 -preset p6 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 15 7s 10s 96.79721 -preset p6 -tune ll -profile:v high -rc cbr -cbr true
+1280x720 60 15 8s 11s 95.77204 -preset p7 -tune hq -profile:v high -rc cbr -cbr true
+1280x720 60 15 7s 10s 96.91981 -preset p7 -tune ll -profile:v high -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 13m47s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 76.79397
+ Encoded: [-preset p1 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 77.437874
+ Encoded: [-preset p2 -tune hq -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 78.04528
+ Encoded: [-preset p3 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 78.2443
+ Encoded: [-preset p4 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 78.27507
+ Encoded: [-preset p5 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 78.36836
+ Encoded: [-preset p6 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+Identical score: 78.585556
+ Encoded: [-preset p7 -tune ll -profile:v high -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v high -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/references/nvidia-turing/720-60-hevc.txt b/references/nvidia-turing/720-60-hevc.txt
new file mode 100644
index 0000000..8624e0d
--- /dev/null
+++ b/references/nvidia-turing/720-60-hevc.txt
@@ -0,0 +1,66 @@
+Results from entire permutation:
+==================================================================================================================================================================
+[Resolution] [FPS] [Bitrate] [Encode Time] [VMAF Time] [VMAF Score] [Encoder Settings]
+1280x720 60 5 7s 12s 87.81937 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 87.46878 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 88.16201 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 86.93813 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 88.47124 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 86.83495 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 88.70048 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 86.93332 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 88.76997 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 87.09824 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 87.19379 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 5 7s 11s 88.81657 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 95.54867 -preset p1 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 95.08237 -preset p2 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 95.81278 -preset p2 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 94.76284 -preset p3 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 95.99773 -preset p3 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 94.63972 -preset p4 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 96.08380 -preset p4 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 94.70326 -preset p5 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 96.10679 -preset p5 -tune ll -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 94.79017 -preset p6 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 94.81934 -preset p7 -tune hq -profile:v main -rc cbr -cbr true
+1280x720 60 10 7s 11s 96.15588 -preset p7 -tune ll -profile:v main -rc cbr -cbr true
+==================================================================================================================================================================
+Benchmark runtime: 10m15s
+
+Encoder settings that produced identical scores:
+==================================================================================================================================================================
+Identical score: 87.819374
+ Encoded: [-preset p1 -tune hq -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p1 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 88.16201
+ Encoded: [-preset p2 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p2 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 88.47124
+ Encoded: [-preset p3 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p3 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 88.70048
+ Encoded: [-preset p4 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p4 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 88.76997
+ Encoded: [-preset p5 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p5 -tune ull -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p6 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+Identical score: 88.81657
+ Encoded: [-preset p7 -tune ll -profile:v main -rc cbr -cbr true]
+ Ignored: [-preset p7 -tune ull -profile:v main -rc cbr -cbr true]
+
+
+==================================================================================================================================================================
diff --git a/src/cli.rs b/src/cli.rs
new file mode 100644
index 0000000..a12dadf
--- /dev/null
+++ b/src/cli.rs
@@ -0,0 +1,91 @@
+use std::path::Path;
+
+use clap::{CommandFactory, Parser};
+use clap::error::ErrorKind;
+
+use crate::DOWNLOAD_URL;
+
+// list will grow as more permutations are supported
+const SUPPORTED_ENCODERS: [&'static str; 2] = ["h264_nvenc", "hevc_nvenc"];
+
+#[derive(Parser)]
+pub(crate) struct Cli {
+ /// the encoder you wish to benchmark: [h264_nvenc, hevc_nvenc, etc]
+ #[arg(short, long, value_name = "encoder_name", default_value = "encoder")]
+ pub(crate) encoder: String,
+ /// target bitrate (in Mb/s) to output; in combination with --bitrate-max-permutation, this is the starting permutation
+ #[arg(short, long, value_name = "bitrate", default_value = "10")]
+ pub(crate) bitrate: u32,
+ /// whether to run vmaf score on each permutation or not
+ #[arg(short, long)]
+ pub(crate) check_quality: bool,
+ // stop an encoding session if the encoder can't keep up with the input file's FPS
+ #[arg(short, long)]
+ pub(crate) detect_overload: bool,
+ /// the source file you wish to benchmark; if not provided, will run standard benchmark on all supported resolutions
+ #[arg(short, long, value_name = "source.y4m", default_value = "")]
+ pub(crate) source_file: String,
+ /// runs just the first permutation for given encoder; useful for testing the tool & output
+ #[arg(short, long)]
+ pub(crate) test_run: bool,
+ /// maximum value to increase the bitrate to (in 5Mb/s intervals); if not specified, tool will not permute over bitrate values
+ #[arg(short, long, value_name = "bitrate")]
+ pub(crate) max_bitrate_permutation: Option,
+ /// runs through permutations that have expected duplicate scores; produces more thorough results but will add substantial runtime
+ #[arg(short, long)]
+ pub(crate) allow_duplicate_scores: bool,
+ /// logs useful information to help troubleshooting
+ #[arg(short, long)]
+ pub(crate) verbose: bool,
+ /// lists the supported/implemented encoders that this tool supports
+ #[arg(short, long)]
+ pub(crate) list_supported_encoders: bool,
+}
+
+impl Cli {
+ pub(crate) fn validate(&mut self) {
+ if self.list_supported_encoders {
+ println!("Supported encoders: {:?}", SUPPORTED_ENCODERS);
+ std::process::exit(0);
+ }
+
+ // this means no encoder was specified
+ if self.encoder == "encoder" {
+ let mut cmd = Cli::command();
+ cmd.error(
+ ErrorKind::ArgumentConflict,
+ format!("Please provide one of the supported encoders via '-e encoder_name'; for a list of supported encoders use the '-l' argument"),
+ ).exit();
+ }
+
+ // check if specified encoder is supported by the tool
+ if !is_encoder_supported(&self.encoder) {
+ let mut cmd = Cli::command();
+ cmd.error(
+ ErrorKind::ArgumentConflict,
+ format!("[{}] is not a supported encoder at the moment", self.encoder),
+ ).exit();
+ }
+
+ // check if source file exists or not
+ if !self.source_file.is_empty() && !Path::new(self.source_file.as_str()).exists() {
+ let mut cmd = Cli::command();
+ cmd.error(
+ ErrorKind::ArgumentConflict,
+ format!("[{}] source file does not exist; if you want to use one of the provided source files, download them from here:\n{}", self.source_file, DOWNLOAD_URL),
+ ).exit();
+ }
+
+ if self.max_bitrate_permutation.is_none() {
+ self.max_bitrate_permutation = Option::from(self.bitrate);
+ }
+ }
+
+ pub(crate) fn has_special_options(&self) -> bool {
+ return self.check_quality || self.detect_overload || self.allow_duplicate_scores;
+ }
+}
+
+fn is_encoder_supported(potential_encoder: &String) -> bool {
+ return SUPPORTED_ENCODERS.contains(&potential_encoder.as_str());
+}
\ No newline at end of file
diff --git a/src/encode_file_downloader.rs b/src/encode_file_downloader.rs
new file mode 100644
index 0000000..b2ed9d7
--- /dev/null
+++ b/src/encode_file_downloader.rs
@@ -0,0 +1,23 @@
+use std::fs;
+
+use crate::ENCODE_FILES;
+
+pub(crate) fn are_all_source_files_present() -> bool {
+ let existing_video_files = get_video_files();
+
+ for file in ENCODE_FILES {
+ if !existing_video_files.contains(&String::from(file)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+fn get_video_files() -> Vec {
+ let paths = fs::read_dir(".").unwrap();
+ return paths.filter_map(|e| e.ok())
+ .filter(|p| p.file_type().unwrap().is_file())
+ .map(|p| p.file_name().to_str().unwrap().to_string())
+ .collect::>();
+}
\ No newline at end of file
diff --git a/src/env.rs b/src/env.rs
new file mode 100644
index 0000000..03c40f7
--- /dev/null
+++ b/src/env.rs
@@ -0,0 +1,29 @@
+use std::process::{Command, Stdio};
+
+pub(crate) fn fail_if_environment_not_setup() {
+ if !is_ffmpeg_installed() {
+ println!("ffmpeg is either not installed or not setup on your path correctly");
+ std::process::exit(1);
+ }
+
+ if !is_ffprobe_installed() {
+ println!("ffprobe is either not installed or not setup on your path correctly");
+ std::process::exit(1);
+ }
+}
+
+fn is_ffmpeg_installed() -> bool {
+ return is_installed("ffmpeg");
+}
+
+fn is_ffprobe_installed() -> bool {
+ return is_installed("ffprobe");
+}
+
+fn is_installed(program: &str) -> bool {
+ return Command::new(program)
+ // important cause we don't want the help message to output here
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .spawn().is_ok();
+}
\ No newline at end of file
diff --git a/src/ffmpeg.rs b/src/ffmpeg.rs
new file mode 100644
index 0000000..dbaaf56
--- /dev/null
+++ b/src/ffmpeg.rs
@@ -0,0 +1,4 @@
+pub mod args;
+pub mod report_files;
+pub mod ffprobe;
+pub mod metadata;
diff --git a/src/ffmpeg/args.rs b/src/ffmpeg/args.rs
new file mode 100644
index 0000000..1492a19
--- /dev/null
+++ b/src/ffmpeg/args.rs
@@ -0,0 +1,240 @@
+use std::ffi::c_float;
+
+use crate::cli::Cli;
+
+pub static TCP_LISTEN: &str = "tcp://127.0.0.1:2000?listen";
+pub static NO_OUTPUT: &str = "-f null -";
+
+#[derive(Clone)]
+pub(crate) struct FfmpegArgs {
+ fps_limit: u32,
+ report: bool,
+ send_progress: bool,
+ first_input: String,
+ second_input: String,
+ pub(crate) bitrate: u32,
+ pub(crate) encoder: String,
+ pub(crate) encoder_args: String,
+ pub(crate) output_args: String,
+ pub(crate) is_vmaf: bool,
+ pub(crate) stats_period: c_float,
+}
+
+impl Default for FfmpegArgs {
+ fn default() -> Self {
+ FfmpegArgs {
+ fps_limit: 0,
+ report: false,
+ send_progress: true,
+ first_input: String::new(),
+ second_input: String::new(),
+ bitrate: u32::default(),
+ encoder: String::new(),
+ encoder_args: String::new(),
+ output_args: NO_OUTPUT.to_string(),
+ is_vmaf: false,
+ // the lower the value, the more often the progress bar will update
+ // but your fps calculations might be a little over-inflated
+ stats_period: 0.5,
+ }
+ }
+}
+
+impl FfmpegArgs {
+ pub(crate) fn build_ffmpeg_args(benchmark_args: &Cli, encoder_args: &String, current_bitrate: u32) -> FfmpegArgs {
+ let ffmpeg_args = FfmpegArgs {
+ first_input: benchmark_args.source_file.to_string(),
+ bitrate: current_bitrate,
+ encoder: benchmark_args.encoder.to_string(),
+ encoder_args: encoder_args.to_string(),
+ ..Default::default()
+ };
+
+ return ffmpeg_args;
+ }
+
+ pub(crate) fn map_to_vmaf(&self, fps: u32) -> FfmpegArgs {
+ let mut vmaf_args = self.clone();
+
+ // required for having high fps inputs score correctly
+ vmaf_args.fps_limit = fps;
+ vmaf_args.second_input = self.first_input.clone();
+ vmaf_args.first_input = String::from(TCP_LISTEN);
+ vmaf_args.output_args = String::from(NO_OUTPUT);
+ vmaf_args.is_vmaf = true;
+ vmaf_args.send_progress = false;
+ // vmaf needs to report so we can get the vmaf score
+ vmaf_args.report = true;
+
+ return vmaf_args;
+ }
+
+ pub(crate) fn to_string(&self) -> String {
+ let mut output = String::new();
+
+ // not all will want to send progress
+ if self.send_progress {
+ output.push_str(format!("-progress tcp://127.0.0.1:1234 -stats_period {} ", self.stats_period).as_str());
+ }
+
+ if self.report {
+ output.push_str("-report ");
+ }
+
+ if self.fps_limit != 0 {
+ output.push_str(format!("-r {} ", self.fps_limit).as_str());
+ }
+
+ output.push_str(["-i", self.first_input.as_str()].join(" ").as_str());
+
+ if !self.second_input.is_empty() {
+ if self.fps_limit != 0 {
+ output.push_str(format!(" -r {}", self.fps_limit).as_str());
+ }
+
+ output.push_str([" -i", self.second_input.as_str()].join(" ").as_str());
+ }
+
+ if self.is_vmaf {
+ append_vmaf_only_args(&mut output);
+ } else {
+ append_encode_only_args(&mut output, self.bitrate, &self.encoder, &self.encoder_args);
+ }
+
+ output.push(' ');
+ output.push_str(self.output_args.as_str());
+
+ return output;
+ }
+
+ pub(crate) fn to_vec(&self) -> Vec {
+ return self.to_string().split(" ").map(|s| s.to_string()).collect();
+ }
+}
+
+fn append_encode_only_args(arg_str: &mut String, bitrate: u32, encoder: &String, encoder_args: &String) {
+ arg_str.push_str([" -b:v", bitrate.to_string().as_str()].join(" ").as_str());
+ // adding the rate amount to the end of the bitrate
+ arg_str.push('M');
+ arg_str.push_str([" -c:v", encoder.as_str()].join(" ").as_str());
+ arg_str.push(' ');
+ arg_str.push_str(encoder_args.as_str());
+}
+
+fn append_vmaf_only_args(arg_str: &mut String) {
+ arg_str.push_str(format!(" -filter_complex libvmaf='n_threads={}:n_subsample=5'", num_cpus::get()).as_str());
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::cli::Cli;
+ use crate::ffmpeg::args::{FfmpegArgs, NO_OUTPUT, TCP_LISTEN};
+
+ static INPUT_ONE: &str = "1080-60.y4m";
+ static INPUT_TWO: &str = "1080-60-2.y4m";
+ static BITRATE: u32 = 6;
+ static FPS_LIMIT: u32 = 60;
+ static ENCODER: &str = "h264_nvenc";
+ static ENCODER_ARGS: &str = "-preset hq -tune hq -profile:v high -rc cbr -multipass qres -rc-lookahead 8";
+
+ #[test]
+ fn default_args_test() {
+ let args = FfmpegArgs::default();
+
+ // check fields that have defaults
+ assert_eq!(args.fps_limit, 0);
+ assert_eq!(args.send_progress, true);
+ assert_eq!(args.report, false);
+ assert_eq!(args.bitrate, u32::default());
+ assert_eq!(args.output_args, "-f null -");
+ assert_eq!(args.is_vmaf, false);
+ assert_eq!(args.stats_period, 0.5);
+
+ // check fields that do not
+ assert!(args.first_input.is_empty());
+ assert!(args.second_input.is_empty());
+ assert!(args.encoder.is_empty());
+ assert!(args.encoder_args.is_empty());
+ }
+
+ #[test]
+ fn build_all_args_test() {
+ let ffmpeg_args = get_two_input_args();
+
+ assert_eq!(ffmpeg_args.first_input, INPUT_ONE);
+ assert_eq!(ffmpeg_args.second_input, INPUT_TWO);
+ assert_eq!(ffmpeg_args.bitrate, BITRATE);
+ assert_eq!(ffmpeg_args.encoder, ENCODER);
+ assert_eq!(ffmpeg_args.encoder_args, ENCODER_ARGS);
+ }
+
+ #[test]
+ fn build_only_one_input_test() {
+ let ffmpeg_args = get_one_input_args();
+
+ assert_eq!(ffmpeg_args.first_input, INPUT_ONE);
+ assert_eq!(ffmpeg_args.second_input, "");
+ assert_eq!(ffmpeg_args.bitrate, BITRATE);
+ assert_eq!(ffmpeg_args.encoder, ENCODER);
+ assert_eq!(ffmpeg_args.encoder_args, ENCODER_ARGS);
+ }
+
+ #[test]
+ fn to_string_one_input_test() {
+ assert_eq!(get_one_input_args().to_string(),
+ "-progress tcp://127.0.0.1:1234 -stats_period 0.5 -i 1080-60.y4m -b:v 6M -c:v h264_nvenc -preset hq -tune hq -profile:v high -rc cbr -multipass qres -rc-lookahead 8 -f null -"
+ );
+ }
+
+ #[test]
+ fn to_string_two_input_test() {
+ assert_eq!(get_two_input_args().to_string(),
+ "-progress tcp://127.0.0.1:1234 -stats_period 0.5 -i 1080-60.y4m -i 1080-60-2.y4m -b:v 6M -c:v h264_nvenc -preset hq -tune hq -profile:v high -rc cbr -multipass qres -rc-lookahead 8 -f null -"
+ );
+ }
+
+ #[test]
+ fn map_to_vmaf_test() {
+ let args = get_two_input_args();
+ let vmaf_args = args.map_to_vmaf(FPS_LIMIT);
+
+ assert_eq!(vmaf_args.fps_limit, FPS_LIMIT);
+ assert_eq!(vmaf_args.first_input, String::from(TCP_LISTEN));
+ assert_eq!(vmaf_args.second_input, args.first_input);
+ assert_eq!(vmaf_args.output_args, String::from(NO_OUTPUT));
+ assert_eq!(vmaf_args.is_vmaf, true);
+ assert_eq!(vmaf_args.send_progress, false);
+ assert_eq!(vmaf_args.report, true);
+ }
+
+ #[test]
+ fn map_to_vmaf_to_string_test() {
+ let vmaf_args = get_two_input_args().map_to_vmaf(FPS_LIMIT);
+ assert_eq!(vmaf_args.to_string(),
+ format!("-report -r {} -i tcp://127.0.0.1:2000?listen -r {} -i 1080-60.y4m -filter_complex libvmaf='n_threads={}:n_subsample=5' -f null -", FPS_LIMIT, FPS_LIMIT, num_cpus::get().to_string())
+ );
+ }
+
+ fn get_one_input_args() -> FfmpegArgs {
+ let args = Cli {
+ encoder: ENCODER.to_string(),
+ bitrate: BITRATE,
+ check_quality: false,
+ detect_overload: false,
+ source_file: INPUT_ONE.to_string(),
+ test_run: false,
+ max_bitrate_permutation: None,
+ allow_duplicate_scores: false,
+ verbose: false,
+ list_supported_encoders: false,
+ };
+
+ return FfmpegArgs::build_ffmpeg_args(&args, &ENCODER_ARGS.to_string(), args.bitrate);
+ }
+
+ fn get_two_input_args() -> FfmpegArgs {
+ let mut args = get_one_input_args();
+ args.second_input = INPUT_TWO.to_string();
+ return args;
+ }
+}
\ No newline at end of file
diff --git a/src/ffmpeg/ffprobe.rs b/src/ffmpeg/ffprobe.rs
new file mode 100644
index 0000000..a085b37
--- /dev/null
+++ b/src/ffmpeg/ffprobe.rs
@@ -0,0 +1,58 @@
+use std::process::{Command, Stdio};
+
+use crate::ffmpeg::metadata::MetaData;
+
+pub(crate) fn probe_for_video_metadata(input_file: &String) -> MetaData {
+ let args = format!("-v error -select_streams v:0 -show_entries stream=duration_ts,r_frame_rate,coded_width,coded_height -of csv=p=0 {}", input_file);
+ let ffprobe = Command::new("ffprobe")
+ .args(args.split(" "))
+ .stdout(Stdio::piped())
+ .output().expect("Unable to run ffprobe to collect metadata on the input video file");
+
+ let output = String::from_utf8_lossy(&ffprobe.stdout);
+ return extract_metadata(output.to_string());
+}
+
+fn extract_metadata(line: String) -> MetaData {
+ let splits = line.split(",").collect::>();
+
+ let fps_splits = splits.get(2).unwrap().split("/").collect::>();
+
+ let metadata = MetaData {
+ fps: fps_splits.get(0).unwrap().trim().parse::().unwrap(),
+ frames: splits.get(3).unwrap().to_string().trim().parse::().unwrap(),
+ width: splits.get(0).unwrap().to_string().trim().parse::().unwrap(),
+ height: splits.get(1).unwrap().to_string().trim().parse::().unwrap(),
+ };
+
+ return metadata;
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::ffmpeg::ffprobe::extract_metadata;
+ use crate::ffmpeg::metadata::MetaData;
+
+ static PROBE_LINE: &str = "1920,1080,60/1,1923";
+
+ #[test]
+ fn extract_metadata_test() {
+ let extracted = extract_metadata(String::from(PROBE_LINE));
+
+ let expected = MetaData {
+ fps: 60,
+ frames: 1923,
+ width: 1920,
+ height: 1080,
+ };
+
+ assert_eq!(equals(&expected, &extracted), true);
+ }
+
+ fn equals(original: &MetaData, other: &MetaData) -> bool {
+ return original.fps == other.fps
+ && original.frames == other.frames
+ && original.height == other.height
+ && original.width == other.width;
+ }
+}
\ No newline at end of file
diff --git a/src/ffmpeg/metadata.rs b/src/ffmpeg/metadata.rs
new file mode 100644
index 0000000..98ee5da
--- /dev/null
+++ b/src/ffmpeg/metadata.rs
@@ -0,0 +1,17 @@
+#[derive(Copy, Clone)]
+pub(crate) struct MetaData {
+ pub(crate) fps: u32,
+ pub(crate) frames: u64,
+ pub(crate) width: u32,
+ pub(crate) height: u32,
+}
+
+impl MetaData {
+ pub(crate) fn to_string(&self) -> String {
+ return format!("Video metadata: fps: {}, total_frames: {}, resolution: {}x{}", self.fps, self.frames, self.width, self.height);
+ }
+
+ pub(crate) fn get_res(&self) -> String {
+ return format!("{}x{}", self.width, self.height);
+ }
+}
\ No newline at end of file
diff --git a/src/ffmpeg/report_files.rs b/src/ffmpeg/report_files.rs
new file mode 100644
index 0000000..d424411
--- /dev/null
+++ b/src/ffmpeg/report_files.rs
@@ -0,0 +1,94 @@
+use std::ffi::c_float;
+use std::fs;
+use std::fs::{DirEntry, File};
+use std::io::BufRead;
+use std::num::ParseFloatError;
+use std::path::PathBuf;
+
+use filetime::FileTime;
+use regex::Regex;
+use rev_buf_reader::RevBufReader;
+
+pub(crate) fn get_latest_ffmpeg_report_file() -> PathBuf {
+ let mut log_file = None;
+ let mut latest_time = FileTime::zero();
+
+ let log_entries = get_logs_in_directory(".");
+
+ // defining entry here so we can extend it's scope
+ let mut entry: Option<&DirEntry>;
+ let mut index = 0;
+
+ while index != log_entries.len() {
+ entry = log_entries.get(index);
+ let file_time = FileTime::from_creation_time(&entry.unwrap().metadata().unwrap()).unwrap();
+ if file_time > latest_time {
+ latest_time = FileTime::from_creation_time(&entry.unwrap().metadata().unwrap()).unwrap();
+ log_file = entry;
+ }
+
+ index = index + 1;
+ }
+
+ return log_file.unwrap().path();
+}
+
+pub(crate) fn extract_vmaf_score(line: &str) -> Result {
+ return capture_group(line, r"VMAF score: ([0-9]+\.[0-9]+)")
+ .parse::();
+}
+
+pub(crate) fn read_last_line_at(line_number: i32) -> String {
+ let log_file = File::open(get_latest_ffmpeg_report_file()).unwrap();
+ let reader = RevBufReader::new(log_file);
+ let mut lines = reader.lines();
+
+ // read from bottom to just before the line we need
+ for _ in 0..line_number - 1 {
+ lines.next().unwrap().unwrap();
+ }
+
+ return lines.next().unwrap().unwrap();
+}
+
+pub(crate) fn capture_group(str: &str, regex: &str) -> String {
+ let re = Regex::new(regex).unwrap();
+ let caps = re.captures(str);
+ return if caps.is_some() {
+ caps.unwrap().get(1).unwrap().as_str().to_string()
+ } else {
+ String::new()
+ };
+}
+
+fn get_logs_in_directory(dir: &str) -> Vec {
+ let paths = fs::read_dir(dir).unwrap();
+ return paths.filter_map(|e| e.ok())
+ .filter(|p| p.file_type().unwrap().is_file())
+ .collect::>();
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fs::File;
+
+ use crate::ffmpeg::report_files::{extract_vmaf_score, get_logs_in_directory};
+
+ static VMAF_LINE: &str = "[Parsed_libvmaf_0 @ 00000169cf14fc00] VMAF score: 98.644730";
+
+ #[test]
+ fn extract_vmaf_score_test() {
+ let score = extract_vmaf_score(VMAF_LINE);
+ assert!(score.is_ok());
+ assert_eq!(score.unwrap(), 98.64473);
+ }
+
+ #[test]
+ fn log_files_only_test() {
+ File::create("tmp.log").expect("Unable to create temporary log file for testing");
+ let log_files = get_logs_in_directory("../");
+ for file in log_files {
+ assert!(file.path().extension().unwrap().to_str().unwrap().contains("log"));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..f40afdc
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,464 @@
+use std::collections::HashSet;
+use std::ffi::c_float;
+use std::fs;
+use std::process::{Child, Command, Stdio};
+use std::time::{Duration, SystemTime};
+
+use clap::Parser;
+use compound_duration::format_dhms;
+use crossbeam_channel::{bounded, Receiver, select};
+use ctrlc::Error;
+
+use ffmpeg::metadata::MetaData;
+
+use crate::cli::Cli;
+use crate::encode_file_downloader::are_all_source_files_present;
+use crate::env::fail_if_environment_not_setup;
+use crate::ffmpeg::args::FfmpegArgs;
+use crate::ffmpeg::ffprobe::probe_for_video_metadata;
+use crate::ffmpeg::report_files::{extract_vmaf_score, get_latest_ffmpeg_report_file, read_last_line_at};
+use crate::permutations::h264_hevc_nvenc::Nvenc;
+use crate::permutations::permute::Permute;
+use crate::permutations::result::{log_results_to_file, PermutationResult};
+use crate::progressbar::{draw_yellow_bar, TrialResult};
+
+mod progressbar;
+mod permutations;
+mod ffmpeg;
+mod cli;
+mod stat_tcp_listener;
+mod env;
+mod encode_file_downloader;
+
+// the hard-coded vmaf quality we want to shoot for when doing bitrate permutations
+const TARGET_QUALITY: c_float = 95.0;
+
+pub static TCP_OUTPUT: &str = "-f {} tcp://127.0.0.1:2000";
+
+// list of all encode files
+const ENCODE_FILES: [&'static str; 8] = ["720-60.y4m", "720-120.y4m", "1080-60.y4m", "1080-120.y4m", "2k-60.y4m", "2k-120.y4m", "4k-60.y4m", "4k-120.y4m"];
+
+const DOWNLOAD_URL: &str = "https://www.dropbox.com/sh/x08pkk47lc1v5ex/AADGaoOjOcA0-uPo7I0NaxL-a?dl=0";
+
+fn main() {
+ let runtime = SystemTime::now();
+
+ fail_if_environment_not_setup();
+ let mut cli = Cli::parse();
+ cli.validate();
+
+ let ctrl_channel = setup_ctrl_channel();
+
+ let is_standard_benchmark = cli.source_file.is_empty();
+
+ if is_standard_benchmark && !are_all_source_files_present() {
+ println!("You're missing some video source files to run the standard benchmark; you should have the following: \n{:?}", ENCODE_FILES);
+ println!("Please download the ones you are missing from: {}", DOWNLOAD_URL);
+ println!("If you want to run the tool against a specific resolution/fps, download just that source file and specify it with '-s'");
+ std::process::exit(1);
+ }
+
+ // eventually we'll want to support more than just these two
+ let mut permutation = Nvenc::new(cli.encoder == "hevc_nvenc");
+ let encoder_settings = if is_standard_benchmark { permutation.run_standard_only() } else { permutation.init() };
+
+ // determining if we'll be iterating over X number of input files, or just the provided one
+ let mut source_files = >::new();
+ let specified_source_file = (&cli.source_file).clone();
+ if is_standard_benchmark {
+ for source in ENCODE_FILES {
+ source_files.push(source);
+ }
+
+ // set initial source file
+ cli.source_file = source_files.get(0).unwrap().to_string();
+ } else {
+ source_files.push(specified_source_file.as_str());
+ };
+
+ let total_encoder_permutations = encoder_settings.len();
+ let bitrate_permutations = get_bitrate_permutations(cli.bitrate, cli.max_bitrate_permutation.unwrap());
+ let total_permutations = if cli.test_run { 1 } else { encoder_settings.len() * bitrate_permutations.len() * source_files.len() };
+
+ let mut all_results: Vec = Vec::new();
+ let mut dup_results: Vec = Vec::new();
+ let mut vmaf_scores: HashSet = HashSet::new();
+
+ let mut permutation_calculation_time: Option = None;
+
+ let mut target_quality_found = false;
+
+ // loop over all permutations of bitrate we want to try
+ let mut bitrate_index = 0;
+
+ // factor to apply to the ETA for whether we're ignoring any permutations
+ let mut ignore_factor: c_float = 1f32;
+
+ for input in source_files {
+ // apply the input to the cli args
+ cli.source_file = input.to_string();
+
+ let metadata = probe_for_video_metadata(&cli.source_file);
+
+ // we'll calculate this as if this is the standard benchmark
+ let res_to_bitrate_map = permutation.get_resolution_to_bitrate_map(metadata.fps);
+
+ // do not check for encoder overload
+ let ignore_overload = !cli.detect_overload;
+
+ if cli.verbose {
+ println!("{}", metadata.to_string());
+ }
+
+ // this is the actual bitrate we'll use
+ let mut effective_bitrate = if is_standard_benchmark { *(res_to_bitrate_map.get(metadata.get_res().as_str()).unwrap()) } else { cli.bitrate };
+
+ log_total_permutations(&cli, &metadata, total_permutations, bitrate_permutations.len(), is_standard_benchmark, effective_bitrate);
+
+ for bitrate in bitrate_permutations.clone() {
+ if !is_standard_benchmark {
+ effective_bitrate = bitrate;
+ }
+
+ if target_quality_found && !is_standard_benchmark {
+ println!("Found vmaf score >= {}, stopping benchmark...", TARGET_QUALITY);
+ break;
+ }
+
+ // for a given bitrate, permute over all possible encoder settings
+ while let Some((encoder_index, settings)) = permutation.next() {
+
+ // add in loop over all supported input files
+ let mut result = PermutationResult::new(&metadata, effective_bitrate, &settings, cli.encoder.as_str());
+
+ let ffmpeg_args = FfmpegArgs::build_ffmpeg_args(&cli, &settings, effective_bitrate);
+ if cli.verbose {
+ println!("ffmpeg args: {}", ffmpeg_args.to_string());
+ }
+
+ let permutation_start_time = SystemTime::now();
+
+ let current_index = (bitrate_index * total_encoder_permutations) + (encoder_index + 1);
+ log_permutation_header(total_permutations, permutation_calculation_time, effective_bitrate, current_index, settings, ignore_factor, is_standard_benchmark);
+
+ // if this permutation was added to the list of duplicates, skip to save calculation time
+ if !cli.allow_duplicate_scores && will_be_duplicate(&dup_results, &result) {
+ draw_yellow_bar(metadata.frames);
+ println!("\n!!! Above encoder settings will produce identical vmaf score as other permutations, skipping... \n");
+ continue;
+ }
+
+ let encode_start_time = SystemTime::now();
+ let mut trial_result = run_overload_benchmark(&metadata, &ffmpeg_args, cli.verbose, ignore_overload, &ctrl_channel);
+ result.was_overloaded = trial_result.was_overloaded;
+ result.encode_time = encode_start_time.elapsed().unwrap().as_secs();
+
+ // calculate the fps statistics and store this in the result
+ calculate_fps_statistics(&mut result, &mut trial_result);
+
+ // log the calculated fps statistics; two spaces match the progress bar
+ println!(" Average FPS:\t{:.0}", result.fps_stats.avg);
+ println!(" 1%'ile:\t{}", result.fps_stats.one_perc_low);
+ println!(" 90%'ile:\t{}", result.fps_stats.ninety_perc);
+
+ // retry once if it was overloaded, but only if we're not ignoring overloads
+ if !ignore_overload && result.was_overloaded {
+ println!("Retrying encode just in case this overload was a one-off...");
+ let encode_start_time = SystemTime::now();
+ let mut trial_result = run_overload_benchmark(&metadata, &ffmpeg_args, cli.verbose, ignore_overload, &ctrl_channel);
+ result.was_overloaded = trial_result.was_overloaded;
+ result.encode_time = encode_start_time.elapsed().unwrap().as_secs();
+
+ // calculate the fps statistics and store this in the result
+ calculate_fps_statistics(&mut result, &mut trial_result);
+
+ // log the calculated fps statistics; two spaces match the progress bar
+ println!(" Average FPS:\t{:.0}", result.fps_stats.avg);
+ println!(" 1%'ile:\t{}", result.fps_stats.one_perc_low);
+ println!(" 90%'ile:\t{}", result.fps_stats.ninety_perc);
+ }
+
+ // set the permutation calculation time; if we're doing vmaf score, will update a second time
+ permutation_calculation_time = Option::from(permutation_start_time.elapsed().unwrap());
+
+ // if we're not calculating vmaf score, continue to the next permutation
+ if cli.check_quality {
+ // if it's still overloaded, we won't be able to check this
+ if !result.was_overloaded {
+ let vmaf_start_time = SystemTime::now();
+ result.vmaf_score = check_encode_quality(&metadata, &ffmpeg_args, cli.verbose, &ctrl_channel);
+ result.vmaf_calculation_time = vmaf_start_time.elapsed().unwrap().as_secs();
+
+ // if this is higher than the target quality, stop at this bitrate during benchmark
+ if result.vmaf_score >= TARGET_QUALITY {
+ target_quality_found = true;
+ }
+
+ // update each permutation count
+ permutation_calculation_time = Option::from(permutation_start_time.elapsed().unwrap());
+ } else {
+ println!("Will not check vmaf score as encoder cannot handle realtime encoding of given parameters...\n");
+ }
+
+ // only do this duplicate mapping during the first bitrate permutation
+ // notice how we do not add this for overloaded results
+ if !result.was_overloaded && bitrate_index == 0 {
+ let score_str = result.vmaf_score.to_string();
+ if !vmaf_scores.contains(&score_str) {
+ all_results.push(result);
+ vmaf_scores.insert((*score_str).parse().unwrap());
+ } else {
+ dup_results.push(result);
+ }
+ } else {
+ // always keep results; any duplicates will be ignored already
+ all_results.push(result);
+ }
+ } else {
+ all_results.push(result);
+ }
+
+ if cli.test_run {
+ break;
+ }
+ }
+
+ if cli.test_run {
+ break;
+ }
+
+ // if all permutations resulted in an overload, skip additional bitrate permutations
+ if did_all_permutions_fail(&all_results) {
+ println!("None of the permutations on the starting bitrate were successful; stopping benchmark...");
+ break;
+ }
+
+ // only log this after the first bitrate permutation
+ if cli.check_quality && !cli.allow_duplicate_scores && bitrate_index == 0 {
+ println!("!!! Permutations past this point will only run if they are expected to have unique vmaf scores !!!");
+
+ // number of actually calculated permutations out of the original total
+ ignore_factor = all_results.len() as c_float / total_encoder_permutations as c_float;
+ }
+
+ // re-init the encoder settings to run with the next bitrate
+ if is_standard_benchmark {
+ permutation.run_standard_only();
+ } else {
+ permutation.init();
+ }
+ bitrate_index += 1;
+ }
+ }
+
+ let runtime_str = format_dhms(runtime.elapsed().unwrap().as_secs());
+ log_results_to_file(all_results, &runtime_str, dup_results, cli.bitrate, is_standard_benchmark);
+
+ println!("Benchmark runtime: {}", runtime_str);
+}
+
+fn did_all_permutions_fail(results: &Vec) -> bool {
+ return results.into_iter().all(|x| x.was_overloaded == true);
+}
+
+fn calculate_fps_statistics(permutation_result: &mut PermutationResult, trial_result: &mut TrialResult) {
+ // must use a much larger data type for calculating the average
+ let mut sum: u64 = 0;
+ for fps in &trial_result.all_fps {
+ sum += *fps as u64;
+ }
+
+ permutation_result.fps_stats.avg = (sum as usize / trial_result.all_fps.len()) as u16;
+
+ // create a sorted list of the fps measurements
+ trial_result.all_fps.sort();
+
+ // find the index & calculate 1%ile
+ let mut index = (0.01 as c_float * trial_result.all_fps.len() as c_float).ceil();
+ permutation_result.fps_stats.one_perc_low = *(trial_result.all_fps.get(index as usize).unwrap());
+
+ // find the index & calculate 90%ile
+ index = (0.90 as c_float * trial_result.all_fps.len() as c_float).ceil();
+ permutation_result.fps_stats.ninety_perc = *(trial_result.all_fps.get(index as usize).unwrap());
+}
+
+fn will_be_duplicate(duplicates: &Vec, result: &PermutationResult) -> bool {
+ for dup in duplicates {
+ if dup.encoder_settings == result.encoder_settings {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+fn log_permutation_header(permutation_count: usize, permutation_calculation_time: Option, bitrate: u32, index: usize, settings: String, ignored_factor: c_float, is_standard: bool) {
+ if index != 1 && !is_standard {
+ println!("====================================================================================");
+ }
+
+ if !is_standard {
+ println!("[Permutation {}/{}]", index, permutation_count);
+ }
+
+ if !is_standard {
+ if permutation_calculation_time.is_some() {
+ println!("[ETR: {}]", format_dhms(calculate_eta(permutation_calculation_time.unwrap(), index, permutation_count, ignored_factor)));
+ } else {
+ println!("[ETR: Unknown until first permutation is done]");
+ }
+ }
+
+ println!("[Bitrate: {}Mb/s]", bitrate);
+ println!("[{}]", settings);
+}
+
+fn calculate_eta(elapsed: Duration, current_perm: usize, total_perms: usize, ignored_factor: c_float) -> usize {
+ let seconds = elapsed.as_secs() as usize;
+ let remaining_permutations = total_perms - (current_perm - 1);
+ return (((seconds * remaining_permutations) as c_float) * ignored_factor) as usize;
+}
+
+fn log_total_permutations(cli: &Cli, metadata: &MetaData, permutation_count: usize, bitrate_permutations: usize, is_standard: bool, effective_bitrate: u32) {
+ println!("====================================================================================");
+ if !is_standard {
+ println!("Permutations:\t{}", permutation_count);
+ }
+ println!("Resolution:\t{}x{}", metadata.width, metadata.height);
+ println!("Encoder:\t{}", cli.encoder);
+ if bitrate_permutations > 1 {
+ println!("Min bitrate:\t{}Mb/s", cli.bitrate);
+ println!("Max bitrate:\t{}Mb/s", cli.max_bitrate_permutation.unwrap());
+ } else {
+ println!("Bitrate:\t{}Mb/s", effective_bitrate);
+ }
+
+ println!("FPS:\t\t{}", metadata.fps);
+
+ // might move this to print somewhere earlier and not here
+ if cli.has_special_options() && !is_standard {
+ println!("\nOptions:");
+ if cli.detect_overload {
+ println!(" -encoder will stop if overload is detected");
+ }
+
+ if cli.check_quality {
+ println!(" -calculating vmaf score");
+ }
+
+ if cli.allow_duplicate_scores {
+ println!(" -allowing duplicate vmaf scores");
+ }
+ }
+
+ println!();
+}
+
+fn get_bitrate_permutations(starting_bitrate: u32, max_bitrate: u32) -> Vec {
+ let interval = 5;
+ let mut bitrates = Vec::new();
+ for i in 0..(((max_bitrate - starting_bitrate) / interval) + 1) {
+ bitrates.push(starting_bitrate + (interval * i));
+ }
+
+ return bitrates;
+}
+
+fn check_encode_quality(metadata: &MetaData, ffmpeg_args: &FfmpegArgs, verbose: bool, ctrl_channel: &Result, Error>) -> c_float {
+ println!("Calculating vmaf score; might take longer than original encode depending on your CPU...");
+
+ // first spawn the ffmpeg instance to listen for incoming encode
+ let vmaf_args = ffmpeg_args.map_to_vmaf(metadata.fps);
+ if verbose {
+ println!("Vmaf args calculating quality: {}", vmaf_args.to_string());
+ }
+
+ let mut vmaf_child = spawn_ffmpeg_child(&vmaf_args);
+
+ // then spawn the ffmpeg instance to perform the encoding
+ let mut encoder_args = ffmpeg_args.clone();
+
+ encoder_args.output_args = String::from(insert_format_from(TCP_OUTPUT, &ffmpeg_args.encoder));
+
+ if verbose {
+ println!("Encoder fmmpeg args sending to vmaf: {}", encoder_args.to_string());
+ }
+
+ spawn_ffmpeg_child(&encoder_args);
+
+ // not the cleanest way to do this but oh well
+ progressbar::watch_encode_progress(metadata.frames, false, metadata.fps, verbose, ffmpeg_args.stats_period, ctrl_channel);
+
+ // need to wait for the vmaf calculating thread to finish
+ println!("VMAF calculation finishing up...");
+ vmaf_child.wait().expect("Not able to wait on the child thread to finish up");
+
+ let vmaf_log_file = get_latest_ffmpeg_report_file();
+ let vmaf_score_extract = extract_vmaf_score(read_last_line_at(3).as_str());
+ let vmaf_score = vmaf_score_extract.unwrap();
+ println!("VMAF score: {}\n", vmaf_score);
+
+ // cleanup the log file being used
+ fs::remove_file(vmaf_log_file.as_path()).unwrap();
+
+ return vmaf_score;
+}
+
+fn insert_format_from(input: &str, encoder: &String) -> String {
+ let format = if encoder == "h264_nvenc" { "h264" } else { "hevc" };
+ // this should be cleaner when we support more than 1 type
+ return input.replace("{}", format);
+}
+
+fn run_overload_benchmark(metadata: &MetaData, ffmpeg_args: &FfmpegArgs, verbose: bool, ignore_overload: bool, ctrl_channel: &Result, Error>) -> TrialResult {
+ let mut child = spawn_ffmpeg_child(ffmpeg_args);
+ if verbose {
+ println!("Successfully spawned encoding child")
+ }
+
+ let trial_result = progressbar::watch_encode_progress(metadata.frames, !ignore_overload, metadata.fps, verbose, ffmpeg_args.stats_period, ctrl_channel);
+
+ if trial_result.was_overloaded && !was_ctrl_c_received(&ctrl_channel) {
+ let _ = child.kill();
+ println!("Encoder was overloaded and could not encode the video file in realtime, stopping...");
+ }
+
+ return trial_result;
+}
+
+fn spawn_ffmpeg_child(ffmpeg_args: &FfmpegArgs) -> Child {
+ return Command::new("ffmpeg")
+ .args(ffmpeg_args.to_vec())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null())
+ .spawn().expect("Failed to start instance of ffmpeg");
+}
+
+// probably want to make this more robust where it kills all child threads instead of waiting
+fn setup_ctrl_channel() -> Result, Error> {
+ let (sender, receiver) = bounded(100);
+ ctrlc::set_handler(move || {
+ println!("Received ctrl-c, exiting gracefully...");
+ let _ = sender.send(());
+ })?;
+
+ Ok(receiver)
+}
+
+fn was_ctrl_c_received(ctrl_c_events: &Result, Error>) -> bool {
+ select! {
+ recv(ctrl_c_events.as_ref().unwrap()) -> _ => {
+ return true;
+ },
+ default() => {
+ return false;
+ }
+ }
+}
+
+fn exit_on_ctrl_c(ctrl_channel: &Result, Error>) {
+ if was_ctrl_c_received(&ctrl_channel) {
+ println!("Ctrl-C acknowledged, program exiting...");
+ std::process::exit(0);
+ }
+}
diff --git a/src/permutations.rs b/src/permutations.rs
new file mode 100644
index 0000000..24bfea3
--- /dev/null
+++ b/src/permutations.rs
@@ -0,0 +1,6 @@
+pub mod permute;
+pub mod nvenc;
+pub mod result;
+pub mod fps_stats;
+pub mod h264_hevc_nvenc;
+mod resolutions;
diff --git a/src/permutations/fps_stats.rs b/src/permutations/fps_stats.rs
new file mode 100644
index 0000000..d0916e3
--- /dev/null
+++ b/src/permutations/fps_stats.rs
@@ -0,0 +1,16 @@
+#[derive(Clone)]
+pub(crate) struct FpsStats {
+ pub(crate) avg: u16,
+ pub(crate) one_perc_low: u16,
+ pub(crate) ninety_perc: u16,
+}
+
+impl Default for FpsStats {
+ fn default() -> Self {
+ FpsStats {
+ avg: 0,
+ one_perc_low: 0,
+ ninety_perc: 0,
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/permutations/h264_hevc_nvenc.rs b/src/permutations/h264_hevc_nvenc.rs
new file mode 100644
index 0000000..3180206
--- /dev/null
+++ b/src/permutations/h264_hevc_nvenc.rs
@@ -0,0 +1,188 @@
+use std::collections::HashMap;
+
+use itertools::Itertools;
+
+use crate::permutations::nvenc::{get_nvenc_presets, get_nvenc_tunes};
+use crate::permutations::permute::Permute;
+use crate::permutations::resolutions::map_res_to_bitrate;
+
+pub(crate) struct Nvenc {
+ presets: Vec<&'static str>,
+ tunes: Vec<&'static str>,
+ profiles: Vec<&'static str>,
+ rate_controls: Vec<&'static str>,
+ // might be able to make this the size we're expecting
+ permutations: Vec,
+ index: i32,
+}
+
+impl Nvenc {
+ pub(crate) fn new(is_hevc: bool) -> Self {
+ Self {
+ presets: get_nvenc_presets(),
+ tunes: get_nvenc_tunes(),
+ // this is the only difference between hevc & h264
+ profiles: if is_hevc { vec!["main"] } else { vec!["high"] },
+ // leaving out vbr rate controls as these are not ideal for game streaming
+ rate_controls: vec!["cbr"],
+ permutations: Vec::new(),
+ // starts at -1, so that first next() will return the first element
+ index: -1,
+ }
+ }
+
+ fn has_next(&self) -> bool {
+ return self.index != (self.permutations.len() - 1) as i32;
+ }
+}
+
+#[derive(Copy, Clone)]
+struct NvencSettings {
+ preset: &'static str,
+ tune: &'static str,
+ profile: &'static str,
+ rate_control: &'static str,
+}
+
+impl NvencSettings {
+ fn to_string(&self) -> String {
+ let mut args = String::new();
+ args.push_str("-preset ");
+ args.push_str(self.preset);
+ args.push_str(" -tune ");
+ args.push_str(self.tune);
+ args.push_str(" -profile:v ");
+ args.push_str(self.profile);
+ args.push_str(" -rc ");
+ args.push_str(self.rate_control);
+ // always set this to constant bit rate to ensure reliable stream
+ args.push_str(" -cbr true");
+
+ return args;
+ }
+}
+
+impl Iterator for Nvenc {
+ type Item = (usize, String);
+
+ fn next(&mut self) -> Option {
+ if !self.has_next() {
+ return None;
+ }
+
+ self.index += 1;
+
+ let usize_index = self.index as usize;
+ return Option::from((usize_index as usize, self.permutations.get(usize_index).unwrap().to_string()));
+ }
+}
+
+impl Permute for Nvenc {
+ fn init(&mut self) -> &Vec {
+ // reset index, otherwise we won't be able to iterate at all
+ self.index = -1;
+
+ // clear the vectors if there were entries before
+ self.permutations.clear();
+
+ let mut permutations = vec![&self.presets, &self.tunes, &self.profiles, &self.rate_controls]
+ .into_iter().multi_cartesian_product();
+
+ loop {
+ let perm = permutations.next();
+ if perm.is_none() {
+ break;
+ }
+
+ let unwrapped_perm = perm.unwrap();
+ let settings = NvencSettings {
+ preset: unwrapped_perm.get(0).unwrap(),
+ tune: unwrapped_perm.get(1).unwrap(),
+ profile: unwrapped_perm.get(2).unwrap(),
+ rate_control: unwrapped_perm.get(3).unwrap(),
+ };
+
+ self.permutations.push(settings.to_string());
+ }
+
+ return &self.permutations;
+ }
+
+ fn run_standard_only(&mut self) -> &Vec {
+ // reset index, otherwise we won't be able to iterate at all
+ self.index = -1;
+
+ // clear the vectors if there were entries before
+ self.permutations.clear();
+
+ // note: this only works when hevc/h264 both use just 1 profile, if we add more this will break
+ self.permutations.push(String::from(format!("-preset p1 -tune ll -profile:v {} -rc cbr -cbr true", self.profiles.get(0).unwrap())));
+ return &self.permutations;
+ }
+
+ fn get_resolution_to_bitrate_map(&self, fps: u32) -> HashMap {
+ let mut map: HashMap = HashMap::new();
+
+ // bitrates are within 5Mb/s of each other, using higher one
+ // note: these are the 60fps bitrate values
+ let mut bitrates: [u32; 4] = [10, 20, 25, 55];
+
+ // 120 fps is effectively double the bitrate
+ if fps == 120 {
+ bitrates.iter_mut().for_each(|b| *b = *b * 2);
+ }
+
+ map_res_to_bitrate(&mut map, bitrates);
+
+ return map;
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::permutations::h264_hevc_nvenc::Nvenc;
+ use crate::permutations::permute::Permute;
+
+ #[test]
+ fn create_h264_test() {
+ let nvenc = Nvenc::new(false);
+ assert!(nvenc.profiles.contains(&"high"));
+ }
+
+ #[test]
+ fn create_hevc_test() {
+ let nvenc = Nvenc::new(true);
+ assert!(nvenc.profiles.contains(&"main"));
+ }
+
+ #[test]
+ fn iterate_to_end_test() {
+ let mut nvenc = Nvenc::new(false);
+ let perm_count = nvenc.init().len();
+
+ let mut total = 0;
+ while let Some((_usize, _string)) = nvenc.next() {
+ total += 1
+ }
+
+ // determine if we iterated over all the permutations correctly
+ assert_eq!(total, perm_count);
+ }
+
+ #[test]
+ fn total_permutations_test() {
+ let mut nvenc = Nvenc::new(false);
+ assert_eq!(nvenc.init().len(), get_expected_len(&nvenc));
+ }
+
+ #[test]
+ fn init_twice_not_double_test() {
+ let mut nvenc = Nvenc::new(false);
+ nvenc.init();
+ assert_eq!(nvenc.init().len(), get_expected_len(&nvenc));
+ }
+
+ fn get_expected_len(nvenc: &Nvenc) -> usize {
+ return nvenc.presets.len() * nvenc.tunes.len() * nvenc.profiles.len() * nvenc.rate_controls.len();
+ }
+}
\ No newline at end of file
diff --git a/src/permutations/nvenc.rs b/src/permutations/nvenc.rs
new file mode 100644
index 0000000..da158cc
--- /dev/null
+++ b/src/permutations/nvenc.rs
@@ -0,0 +1,7 @@
+pub(crate) fn get_nvenc_presets() -> Vec<&'static str> {
+ return vec!["p1", "p2", "p3", "p4", "p5", "p6", "p7"];
+}
+
+pub(crate) fn get_nvenc_tunes() -> Vec<&'static str> {
+ return vec!["hq", "ll", "ull"];
+}
\ No newline at end of file
diff --git a/src/permutations/permute.rs b/src/permutations/permute.rs
new file mode 100644
index 0000000..460ff11
--- /dev/null
+++ b/src/permutations/permute.rs
@@ -0,0 +1,12 @@
+use std::collections::HashMap;
+
+pub(crate) trait Permute: Iterator {
+ // calculates permutations and returns a reference of said permutations
+ fn init(&mut self) -> &Vec;
+
+ // overwrites the init values to just include permutations of standard runs
+ fn run_standard_only(&mut self) -> &Vec;
+
+ // takes in the fps being used; scales the necessary bitrate accordingly
+ fn get_resolution_to_bitrate_map(&self, fps: u32) -> HashMap;
+}
\ No newline at end of file
diff --git a/src/permutations/resolutions.rs b/src/permutations/resolutions.rs
new file mode 100644
index 0000000..0ee8beb
--- /dev/null
+++ b/src/permutations/resolutions.rs
@@ -0,0 +1,10 @@
+use std::collections::HashMap;
+
+pub(crate) const SUPPORTED_RESOLUTIONS: [&'static str; 4] = ["1280x720", "1920x1080", "2560x1440", "3840x2160"];
+
+pub(crate) fn map_res_to_bitrate(map: &mut HashMap, bitrates: [u32; 4]) {
+ map.insert(SUPPORTED_RESOLUTIONS.get(0).unwrap().to_string(), *bitrates.get(0).unwrap());
+ map.insert(SUPPORTED_RESOLUTIONS.get(1).unwrap().to_string(), *bitrates.get(1).unwrap());
+ map.insert(SUPPORTED_RESOLUTIONS.get(2).unwrap().to_string(), *bitrates.get(2).unwrap());
+ map.insert(SUPPORTED_RESOLUTIONS.get(3).unwrap().to_string(), *bitrates.get(3).unwrap());
+}
\ No newline at end of file
diff --git a/src/permutations/result.rs b/src/permutations/result.rs
new file mode 100644
index 0000000..02cedb4
--- /dev/null
+++ b/src/permutations/result.rs
@@ -0,0 +1,113 @@
+use std::ffi::c_float;
+use std::fs::File;
+use std::io::Write;
+
+use compound_duration::format_dhms;
+
+use crate::ffmpeg::metadata::MetaData;
+use crate::permutations::fps_stats::FpsStats;
+
+#[derive(Clone)]
+pub(crate) struct PermutationResult {
+ pub(crate) encoder: String,
+ pub(crate) was_overloaded: bool,
+ bitrate: u32,
+ metadata: MetaData,
+ pub(crate) encoder_settings: String,
+ // only if the encodes were successful
+ pub(crate) encode_time: u64,
+ pub(crate) vmaf_calculation_time: u64,
+ pub(crate) vmaf_score: c_float,
+ pub(crate) fps_stats: FpsStats,
+}
+
+impl PermutationResult {
+ pub(crate) fn new(metadata: &MetaData, bitrate: u32, encoder_settings: &String, encoder: &str) -> Self {
+ Self {
+ encoder: String::from(encoder),
+ was_overloaded: false,
+ bitrate,
+ metadata: metadata.clone(),
+ encoder_settings: encoder_settings.to_string(),
+ encode_time: 0,
+ vmaf_calculation_time: 0,
+ vmaf_score: 0.0,
+ fps_stats: FpsStats::default(),
+ }
+ }
+
+ fn to_string(&self) -> String {
+ let mut default = String::new();
+
+ let overloaded_indicator = if self.was_overloaded { "[O]" } else { " " };
+ default.push_str(format!("{}{}x{}\t{}\t{}Mb/s", overloaded_indicator, self.metadata.width,
+ self.metadata.height, self.metadata.fps, self.bitrate).as_str());
+
+ // adjust tabs based on expected vmaf score, or lack of one
+ let vmaf_score_str = if self.was_overloaded { format!("{:.5}\t\t", self.vmaf_score) } else if self.vmaf_score != 0.0 { format!("{:.5}\t", self.vmaf_score) } else { format!("0.00000\t\t") };
+
+ default.push_str(format!("\t\t{}\t\t{}\t\t{}{:.0}\t\t{}\t\t{}\t\t{}",
+ format_dhms(self.encode_time), format_dhms(self.vmaf_calculation_time), vmaf_score_str,
+ self.fps_stats.avg, self.fps_stats.one_perc_low, self.fps_stats.ninety_perc, self.encoder_settings).as_str());
+
+ return default;
+ }
+}
+
+pub(crate) fn log_results_to_file(results: Vec, runtime_str: &String, dup_results: Vec, bitrate: u32, is_standard: bool) {
+ // might make this naming here more robust eventually
+ let first_metadata = results.get(0).unwrap().metadata;
+ let encoder = results.get(0).unwrap().encoder.as_str();
+ let permute_file_name = format!("{}-{}-{}.log", encoder, first_metadata.get_res(), first_metadata.fps).to_string();
+ let benchmark_file_name = format!("{}-benchmark.log", encoder).to_string();
+ let file_name = if is_standard { benchmark_file_name } else { permute_file_name };
+
+ let mut w = File::create(file_name).unwrap();
+
+ writeln!(&mut w, "Results from entire permutation:").unwrap();
+ writeln!(&mut w, "==================================================================================================================================================================").unwrap();
+ writeln!(&mut w, " [Resolution]\t[FPS]\t[Bitrate]\t[Encode Time]\t[VMAF Time]\t[VMAF Score]\t[Average FPS]\t[1%'ile]\t[90%'ile]\t[Encoder Settings]").unwrap();
+ for result in &results {
+ writeln!(&mut w, "{}", result.to_string()).unwrap();
+ }
+ writeln!(&mut w, "==================================================================================================================================================================").unwrap();
+ writeln!(&mut w, "Benchmark runtime: {}\n", runtime_str).unwrap();
+
+ let has_logged_dup_header = false;
+
+ // log out the duplicated results so we can keep track of them
+ let initial_perms: Vec = results
+ .into_iter()
+ .filter(|res| res.bitrate == bitrate)
+ .collect();
+
+ // for each of these, collect the duplicates with the same score
+ for perm in initial_perms {
+ let moved = &dup_results;
+ let dups: Vec<&PermutationResult> = moved
+ .into_iter()
+ .filter(|res| res.vmaf_score == perm.vmaf_score)
+ .collect();
+
+ // only log entries of duplicates
+ if dups.is_empty() {
+ continue;
+ }
+
+ if !has_logged_dup_header {
+ writeln!(&mut w, "Encoder settings that produced identical scores:").unwrap();
+ writeln!(&mut w, "==================================================================================================================================================================").unwrap();
+ }
+
+ writeln!(&mut w, "Identical score: {}", perm.vmaf_score).unwrap();
+ writeln!(&mut w, "\tEncoded: [{}]", perm.encoder_settings).unwrap();
+
+ for dup in dups {
+ writeln!(&mut w, "\tIgnored: [{}]", dup.encoder_settings).unwrap();
+ }
+
+ writeln!(&mut w, "\n").unwrap();
+ }
+
+ writeln!(&mut w, "==================================================================================================================================================================").unwrap();
+}
\ No newline at end of file
diff --git a/src/progressbar.rs b/src/progressbar.rs
new file mode 100644
index 0000000..6ce16b6
--- /dev/null
+++ b/src/progressbar.rs
@@ -0,0 +1,116 @@
+use std::ffi::c_float;
+use std::fmt::Write;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::time;
+use std::time::SystemTime;
+
+use crossbeam_channel::Receiver;
+use ctrlc::Error;
+use indicatif::{ProgressBar, ProgressState, ProgressStyle};
+
+use crate::exit_on_ctrl_c;
+use crate::stat_tcp_listener::start_listening_to_ffmpeg_stats;
+
+pub(crate) struct TrialResult {
+ pub(crate) all_fps: Vec,
+ pub(crate) was_overloaded: bool,
+}
+
+impl Default for TrialResult {
+ fn default() -> Self {
+ TrialResult {
+ all_fps: vec![],
+ was_overloaded: false,
+ }
+ }
+}
+
+pub(crate) fn watch_encode_progress(total_frames: u64, check_for_overload: bool, target_fps: u32, verbose: bool, stats_period: c_float, ctrl_channel: &Result, Error>) -> TrialResult {
+ static FRAME: AtomicUsize = AtomicUsize::new(0);
+ static PREVIOUS_FRAME: AtomicUsize = AtomicUsize::new(0);
+
+ // keep track of all fps metrics to calculate on later on
+ let mut trial_result = TrialResult::default();
+ let bar = ProgressBar::new(total_frames);
+ set_bar_style(&bar, "green");
+ bar.tick();
+
+ // time it takes for the encoder to need to process the target # of frames
+ let overload_time = time::Duration::from_secs(5);
+ let mut checking_overload = false;
+ let mut first_overload_detected = SystemTime::now();
+
+ // how many milliseconds has passed since the last frame stat
+ let interval_adjustment = (1.0 / stats_period) as usize;
+
+ let stat_listener = start_listening_to_ffmpeg_stats(verbose, &FRAME, &PREVIOUS_FRAME);
+ loop {
+ // important to not get stuck in this thread
+ exit_on_ctrl_c(&ctrl_channel);
+
+ // takes into account the stat update period to properly adjust the calculated FPS
+ let calculated_fps = ((FRAME.load(Ordering::Relaxed) - PREVIOUS_FRAME.load(Ordering::Relaxed)) * interval_adjustment) as u16;
+
+ // only record fps counts that are close to 1/4 of the target; any lower is noise
+ if calculated_fps >= (target_fps / 4) as u16 {
+ trial_result.all_fps.push(calculated_fps);
+ }
+
+ // calculate the number of frames processed since the last second (more accurate than using fps from ffmpeg)
+ if check_for_overload && calculated_fps < target_fps as u16 {
+ if !checking_overload {
+ first_overload_detected = SystemTime::now();
+ checking_overload = true;
+ }
+
+ // check elapsed time since the last encoder overload detection
+ if checking_overload && first_overload_detected.elapsed().unwrap() > overload_time {
+ break;
+ }
+ } else {
+ checking_overload = false;
+ }
+
+ if FRAME.load(Ordering::Relaxed) >= total_frames as usize {
+ bar.set_position(total_frames);
+ break;
+ }
+
+ bar.set_position(FRAME.load(Ordering::Relaxed) as u64);
+ }
+
+ // change bar style as read
+ if (FRAME.load(Ordering::Relaxed) as u64) < total_frames {
+ set_bar_style(&bar, "red");
+ bar.abandon()
+ } else {
+ bar.finish();
+ }
+
+ println!();
+
+ // kill the tcp reading thread
+ stat_listener.stop().join().expect("Child thread reading TCP did not finish");
+
+ trial_result.was_overloaded = (FRAME.load(Ordering::Relaxed) as u64) != total_frames;
+ // reset the static values
+ FRAME.store(0, Ordering::Relaxed);
+ PREVIOUS_FRAME.store(0, Ordering::Relaxed);
+
+ return trial_result;
+}
+
+pub(crate) fn set_bar_style(bar: &ProgressBar, color: &str) {
+ let template = "{spinner:.%} [{elapsed_precise}] [{wide_bar:.%}] {pos}/{len} frames ({eta_precise})";
+ bar.set_style(ProgressStyle::with_template(&str::replace(template, "%", color).as_str())
+ .unwrap()
+ .with_key("eta", |state: &ProgressState, w: &mut dyn Write| write!(w, "{:.1}s", state.eta().as_secs_f64()).unwrap())
+ .progress_chars("#>-"));
+}
+
+pub(crate) fn draw_yellow_bar(total_frames: u64) {
+ let bar = ProgressBar::new(total_frames);
+ set_bar_style(&bar, "yellow");
+ bar.tick();
+ bar.abandon();
+}
\ No newline at end of file
diff --git a/src/stat_tcp_listener.rs b/src/stat_tcp_listener.rs
new file mode 100644
index 0000000..d5810a8
--- /dev/null
+++ b/src/stat_tcp_listener.rs
@@ -0,0 +1,66 @@
+use std::io::{BufRead, BufReader};
+use std::net::{TcpListener, TcpStream};
+use std::num::ParseIntError;
+use std::process;
+use std::sync::atomic::{AtomicUsize, Ordering};
+
+use stoppable_thread::StoppableHandle;
+
+use crate::ffmpeg::report_files::capture_group;
+
+static LOCALHOST: &str = "127.0.0.1";
+static PORT: &str = "1234";
+
+pub fn start_listening_to_ffmpeg_stats(verbose: bool, frame: &'static AtomicUsize, previous_frame: &'static AtomicUsize) -> StoppableHandle<()> {
+ let stat_listener = TcpListener::bind(format!("{}:{}", LOCALHOST, PORT)).unwrap();
+
+ let tcp_reading_thread;
+ match stat_listener.accept() {
+ Ok(client) => {
+ if verbose {
+ println!("Connected to ffmpeg's -progress output via TCP...");
+ }
+
+ tcp_reading_thread = spawn_tcp_reading_thread(client.0, frame, previous_frame);
+ }
+ // probably log this error eventually
+ Err(_e) => {
+ println!("Not able to connect to client for reading stats, cannot proceed");
+ process::exit(1);
+ }
+ }
+
+ // eventually we'll want to add code where we kill the listener here
+
+ return tcp_reading_thread;
+}
+
+fn spawn_tcp_reading_thread(stream: TcpStream, frame: &'static AtomicUsize, previous_frame: &'static AtomicUsize) -> StoppableHandle<()> {
+ return stoppable_thread::spawn(move |stopped| {
+ let mut reader = BufReader::new(stream.try_clone().unwrap());
+
+ let mut peek = [0u8];
+ while stream.peek(&mut peek).is_ok() {
+ if stopped.get() {
+ break;
+ }
+
+ let mut line = String::new();
+ reader.read_line(&mut line).unwrap();
+
+ if is_frame_line(line.as_str()) {
+ previous_frame.store(frame.load(Ordering::Relaxed), Ordering::Relaxed);
+ frame.store(extract_frame(line.as_str()).unwrap() as usize, Ordering::Relaxed);
+ }
+ }
+ });
+}
+
+fn is_frame_line(input: &str) -> bool {
+ return input.contains("frame=");
+}
+
+pub fn extract_frame(line: &str) -> Result {
+ return capture_group(line, r"^frame=([0-9]+)")
+ .parse::();
+}
\ No newline at end of file