# A tool for testing lossless video encoding

using VideoIO, ColorTypes, FixedPointNumbers, DataFrames

function collectexecoutput(exec::Cmd)
    out = Pipe()
    err = Pipe()
    p = Base.open(pipeline(ignorestatus(exec), stdout = out, stderr = err))
    close(out.in)
    close(err.in)
    err_s = readlines(err)
    out_s = readlines(out)
    return (length(out_s) > length(err_s)) ? out_s : err_s
end

function createtestvideo(;
    filename::String = "$(tempname()).mp4",
    duration::Real = 5,
    width::Int64 = 1280,
    height::Int64 = 720,
    framerate::Real = 30,
    testtype::String = "testsrc2",
    encoder::String = "libx264rgb",
)
    withenv(VideoIO.execenv) do
        return collectexecoutput(`$(VideoIO.ffmpeg) -y -f lavfi -i
                   $testtype=duration=$duration:size=$(width)x$(height):rate=$framerate
                   -c:v $encoder -preset slow -crf 0 -c:a copy $filename`)
    end
    return filename
end

function testvideocomp!(df, preset, imgstack_gray)
    t = @elapsed VideoIO.save(
        "video.mp4",
        imgstack_gray,
        framerate = 30,
        codec_name = "libx264",
        encoder_options = (color_range = 2, crf = 0, "preset" = preset),
    )
    fs = filesize("video.mp4")
    f = openvideo("video.mp4", target_format = VideoIO.AV_PIX_FMT_GRAY8)
    imgstack_gray_copy = []
    while !eof(f)
        push!(imgstack_gray_copy, read(f))
    end
    identical = !any(.!(imgstack_gray .== imgstack_gray_copy))
    return push!(df, [preset, fs, t, identical])
end

imgstack_gray_noise = map(x -> rand(Gray{N0f8}, 1280, 720), 1:1000)

f = openvideo(createtestvideo())
imgstack = []
while !eof(f)
    push!(imgstack, read(f))
end
imgstack_gray_testvid = map(x -> convert.(Gray{N0f8}, x), imgstack)

f = openvideo("videos/ladybird.mp4")
imgstack = []
while !eof(f)
    push!(imgstack, read(f))
end
imgstack_gray_ladybird = map(x -> convert.(Gray{N0f8}, x), imgstack)

df_noise = DataFrame(preset = [], filesize = [], time = [], identical = [])
df_testvid = DataFrame(preset = [], filesize = [], time = [], identical = [])
df_ladybird = DataFrame(preset = [], filesize = [], time = [], identical = [])
for preset in ["ultrafast", "superfast", "veryfast", "faster", "fast", "medium", "slow", "slower", "veryslow"]
    @show preset
    for rep in 1:3
        @show rep
        testvideocomp!(df_noise, preset, imgstack_gray_noise)
        testvideocomp!(df_testvid, preset, imgstack_gray_testvid)
        testvideocomp!(df_ladybird, preset, imgstack_gray_ladybird)
    end
end

noise_raw_size = size(imgstack_gray_noise[1], 1) * size(imgstack_gray_noise[1], 2) * length(imgstack_gray_noise)
testvid_raw_size = size(imgstack_gray_testvid[1], 1) * size(imgstack_gray_testvid[1], 2) * length(imgstack_gray_testvid)
ladybird_raw_size =
    size(imgstack_gray_ladybird[1], 1) * size(imgstack_gray_ladybird[1], 2) * length(imgstack_gray_ladybird)

df_noise[:filesize_perc] = 100 * (df_noise[:filesize] ./ noise_raw_size)
df_testvid[:filesize_perc] = 100 * (df_testvid[:filesize] ./ testvid_raw_size)
df_ladybird[:filesize_perc] = 100 * (df_ladybird[:filesize] ./ ladybird_raw_size)
df_noise[:fps] = length(imgstack_gray_noise) ./ df_noise[:time]
df_testvid[:fps] = length(imgstack_gray_testvid) ./ df_testvid[:time]
df_ladybird[:fps] = length(imgstack_gray_ladybird) ./ df_ladybird[:time]

using Statistics
df_noise_summary = by(
    df_noise,
    :preset,
    identical = :identical => minimum,
    fps_mean = :fps => mean,
    fps_std = :fps => std,
    filesize_perc_mean = :filesize_perc => mean,
    filesize_perc_std = :filesize_perc => std,
)
df_testvid_summary = by(
    df_testvid,
    :preset,
    identical = :identical => minimum,
    fps_mean = :fps => mean,
    fps_std = :fps => std,
    filesize_perc_mean = :filesize_perc => mean,
    filesize_perc_std = :filesize_perc => std,
)
df_ladybird_summary = by(
    df_ladybird,
    :preset,
    identical = :identical => minimum,
    fps_mean = :fps => mean,
    fps_std = :fps => std,
    filesize_perc_mean = :filesize_perc => mean,
    filesize_perc_std = :filesize_perc => std,
)

@show df_noise_summary
@show df_testvid_summary
@show df_ladybird_summary

### Results (generated 2019-05-29 on a 2019 Macbook Pro)
### OUTDATED. Generated before change to VideoIO.save
#=
df_noise_summary = 9×6 DataFrame
│ Row │ preset    │ identical │ fps_mean │ fps_std │ filesize_perc_mean │ filesize_perc_std │
│     │ Any       │ Bool      │ Float64  │ Float64 │ Float64            │ Float64           │
├─────┼───────────┼───────────┼──────────┼─────────┼────────────────────┼───────────────────┤
│ 1   │ ultrafast │ true      │ 92.5769  │ 8.40224 │ 156.444            │ 0.0               │
│ 2   │ superfast │ true      │ 62.3509  │ 1.19652 │ 144.019            │ 0.0               │
│ 3   │ veryfast  │ true      │ 59.9182  │ 1.77294 │ 144.019            │ 0.0               │
│ 4   │ faster    │ true      │ 60.3482  │ 2.32679 │ 144.02             │ 0.0               │
│ 5   │ fast      │ true      │ 149.169  │ 1.56068 │ 100.784            │ 0.0               │
│ 6   │ medium    │ true      │ 146.141  │ 3.41282 │ 100.784            │ 0.0               │
│ 7   │ slow      │ true      │ 147.214  │ 1.23929 │ 100.784            │ 0.0               │
│ 8   │ slower    │ true      │ 138.808  │ 2.553   │ 100.784            │ 0.0               │
│ 9   │ veryslow  │ true      │ 132.505  │ 3.28558 │ 100.784            │ 0.0               │

df_testvid_summary = 9×6 DataFrame
│ Row │ preset    │ identical │ fps_mean │ fps_std │ filesize_perc_mean │ filesize_perc_std │
│     │ Any       │ Bool      │ Float64  │ Float64 │ Float64            │ Float64           │
├─────┼───────────┼───────────┼──────────┼─────────┼────────────────────┼───────────────────┤
│ 1   │ ultrafast │ true      │ 228.166  │ 75.1439 │ 4.80392            │ 0.0               │
│ 2   │ superfast │ true      │ 239.73   │ 54.2033 │ 3.62199            │ 0.0               │
│ 3   │ veryfast  │ true      │ 197.506  │ 13.1121 │ 3.59901            │ 0.0               │
│ 4   │ faster    │ true      │ 174.174  │ 18.0316 │ 3.60282            │ 0.0               │
│ 5   │ fast      │ true      │ 235.181  │ 7.40358 │ 3.44104            │ 0.0               │
│ 6   │ medium    │ true      │ 219.654  │ 3.27445 │ 3.40832            │ 0.0               │
│ 7   │ slow      │ true      │ 171.337  │ 3.92415 │ 3.33917            │ 0.0               │
│ 8   │ slower    │ true      │ 105.24   │ 6.59151 │ 3.25774            │ 5.43896e-16       │
│ 9   │ veryslow  │ true      │ 63.1136  │ 2.47291 │ 3.2219             │ 0.0               │

df_ladybird_summary = 9×6 DataFrame
│ Row │ preset    │ identical │ fps_mean │ fps_std  │ filesize_perc_mean │ filesize_perc_std │
│     │ Any       │ Bool      │ Float64  │ Float64  │ Float64            │ Float64           │
├─────┼───────────┼───────────┼──────────┼──────────┼────────────────────┼───────────────────┤
│ 1   │ ultrafast │ true      │ 176.787  │ 36.5227  │ 12.2293            │ 0.0               │
│ 2   │ superfast │ true      │ 135.925  │ 7.04431  │ 10.3532            │ 0.0               │
│ 3   │ veryfast  │ true      │ 117.115  │ 1.28102  │ 10.1954            │ 0.0               │
│ 4   │ faster    │ true      │ 94.39    │ 3.48494  │ 9.85604            │ 0.0               │
│ 5   │ fast      │ true      │ 69.657   │ 1.61004  │ 9.62724            │ 0.0               │
│ 6   │ medium    │ true      │ 54.9621  │ 0.568074 │ 9.51032            │ 0.0               │
│ 7   │ slow      │ true      │ 37.8888  │ 1.27484  │ 9.33622            │ 0.0               │
│ 8   │ slower    │ true      │ 20.1112  │ 1.04282  │ 9.25529            │ 0.0               │
│ 9   │ veryslow  │ true      │ 10.0016  │ 0.473213 │ 9.24999            │ 0.0               │
=#

# HISTOGRAM COMPARISON - useful for diagnosing range compression
# using PyPlot, ImageCore
# figure()
# hist(rawview(channelview(imgstack_gray_copy[1]))[:],0:256,label="copy")
# hist(rawview(channelview(imgstack_gray[1]))[:],0:256,label="original")
# legend()
