Writing Videos

Writing Videos

Note: Writing of audio streams is not yet implemented

Single-step Encoding

Videos can be encoded directly from image stack using encodevideo(filename::String,imgstack::Array) where imgstack is an array of image arrays with identical type and size.

For instance, say an image stack has been constructed from reading a series of image files 1.png, 2.png,3.png etc. :

using FileIO
imgnames = filter(x->occursin(".png",x),readdir()) # Populate list of all .pngs
intstrings =  map(x->split(x,".")[1],imgnames) # Extract index from filenames
p = sortperm(parse.(Int,intstrings)) #sort files numerically
imgstack = []
for imgname in imgnames[p]
    push!(imgstack,load(imgname))
end

The entire image stack can be encoded in a single step:

using VideoIO
props = [:priv_data => ("crf"=>"22","preset"=>"medium")]
encodevideo("video.mp4",imgstack,framerate=30,AVCodecContextProperties=props)

[ Info: Video file saved: /Users/username/Documents/video.mp4
[ Info: frame=  100 fps=0.0 q=-1.0 Lsize=  129867kB time=00:00:03.23 bitrate=329035.1kbits/s speed=8.17x    
[ Info: video:129865kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 0.001692%
VideoIO.encodevideoFunction.
encodevideo(filename::String,imgstack::Array;
    AVCodecContextProperties = AVCodecContextPropertiesDefault,
    codec_name = "libx264",
    framerate = 24)

Encode image stack to video file and return filepath.

Iterative Encoding

Alternatively, videos can be encoded iteratively within custom loops. The encoding steps follow:

  1. Initialize encoder with prepareencoder
  2. Iteratively add frames with appendencode
  3. End the encoding with finishencode
  4. Multiplex the stream into a video container with mux

For instance:

using VideoIO
framestack = map(x->rand(UInt8, 100, 100), 1:100) #vector of 2D arrays

props = [:priv_data => ("crf"=>"22","preset"=>"medium")]
framerate=24
encoder = prepareencoder(framestack[1], framerate=framerate, AVCodecContextProperties=props)

open("temp.stream", "w") do io
    for i in 1:length(framestack)
        appendencode!(encoder, io, framestack[i], i)
    end
    finishencode!(encoder, io)
end

mux("temp.stream", "video.mp4", framerate) #Multiplexes the stream into a video container

A working example to save a series of png files as a video:

using VideoIO, ProgressMeter

dir = "" #path to directory holding images
imgnames = filter(x->occursin(".png",x),readdir(dir)) # Populate list of all .pngs
intstrings =  map(x->split(x,".")[1],imgnames) # Extract index from filenames
p = sortperm(parse.(Int,intstrings)) #sort files numerically
imgnames = imgnames[p]

filename = "video.mp4"
framerate = 24
props = [:priv_data => ("crf"=>"22","preset"=>"medium")]

firstimg = load(joinpath(dir,imgnames[1]))
encoder = prepareencoder(firstimg, framerate=framerate, AVCodecContextProperties=props)

io = Base.open("temp.stream","w")
@showprogress "Encoding video frames.." for i in 1:length(imgnames)
    img = load(joinpath(dir,imgnames[i]))
    appendencode!(encoder, io, img, i)
end

finishencode!(encoder, io)
close(io)

mux("temp.stream",filename,framerate) #Multiplexes the stream into a video container
prepareencoder(firstimg;framerate=30,AVCodecContextProperties=[:priv_data => ("crf"=>"22","preset"=>"medium")],codec_name::String="libx264")

Prepare encoder and return AV objects.

VideoIO.appendencode!Function.
appendencode(encoder::VideoEncoder, io::IO, img, index::Integer)

Send image object to ffmpeg encoder and encode

VideoIO.finishencode!Function.
finishencode(encoder::VideoEncoder, io::IO)

End encoding by sending endencode package to ffmpeg, and close objects.

VideoIO.muxFunction.
mux(srcfilename,destfilename,framerate;silent=false,deletestream=true)

Multiplex stream file into video container. Deletes stream file by default.

Supported Colortypes

Encoding of the following image element color types currently supported:

Encoder Settings

The AVCodecContextProperties object allows control of the majority of settings required. Optional fields can be found here.

A few helpful presets for h264:

GoalAVCodecContextProperties value
Perceptual compression, h264 default. Best for most cases[:priv_data => ("crf"=>"23","preset"=>"medium")
Lossless compression. Fastest, largest file size[:priv_data => ("crf"=>"0","preset"=>"ultrafast")]
Lossless compression. Slowest, smallest file size[:priv_data => ("crf"=>"0","preset"=>"ultraslow")]
Direct control of bitrate and frequency of intra frames (every 10)[:bit_rate => 400000,:gop_size = 10,:max_b_frames=1]

Lossless Encoding

Lossless RGB

If lossless encoding of RGB{N0f8} is required, true lossless requires using codec_name = "libx264rgb", to avoid the lossy RGB->YUV420 conversion, and "crf" => "0".

Lossless Grayscale

If lossless encoding of Gray{N0f8} or UInt8 is required, "crf" => "0" should be set, as well as :color_range=>2 to ensure full 8-bit pixel color representation. i.e. [:color_range=>2, :priv_data => ("crf"=>"0","preset"=>"medium")]

Encoding Performance

See examples/lossless_video_encoding_testing.jl for testing of losslessness, speed, and compression as a function of h264 encoding preset, for 3 example videos.