From cc809b97a3f0369ca0d81d3291060fbab358b1a7 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 8 Feb 2023 08:41:07 -0500 Subject: [PATCH 01/11] add test for framerate output stability --- test/reading.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/reading.jl b/test/reading.jl index 4933fccb..871ebb35 100644 --- a/test/reading.jl +++ b/test/reading.jl @@ -21,10 +21,16 @@ trimmed_comparison_frame = comparison_frame end - @test VideoIO.framerate(v) != 0 + fps1 = VideoIO.framerate(v) + @test fps1 != 0 # Find the first non-trivial image first_img = read(v) + + # fps should be the same before and after first read + fps2 = VideoIO.framerate(v) + @test fps1 == fps2 + first_time = VideoIO.gettime(v) seekstart(v) img = read(v) From e30e0d78cb0ed983b7e4997c29359444a00cb657 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 8 Feb 2023 08:43:19 -0500 Subject: [PATCH 02/11] add VideoIO.framerate to the manual --- docs/src/reading.md | 7 ++++++- src/avio.jl | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/src/reading.md b/docs/src/reading.md index 7157772c..35781c05 100644 --- a/docs/src/reading.md +++ b/docs/src/reading.md @@ -89,6 +89,11 @@ Total available frame count is available via `counttotalframes(f)` VideoIO.counttotalframes ``` +The video framerate can be read via `framerate(f)` +```@docs +VideoIO.framerate +``` + !!! note H264 videos encoded with `crf>0` have been observed to have 4-fewer frames available for reading. @@ -174,7 +179,7 @@ julia> opts["video_size"] = "640x480" julia> opencamera(VideoIO.DEFAULT_CAMERA_DEVICE[], VideoIO.DEFAULT_CAMERA_FORMAT[], opts) VideoReader(...) ``` - + Or more simply, change the default. For example: ```julia julia> VideoIO.DEFAULT_CAMERA_OPTIONS["video_size"] = "640x480" diff --git a/src/avio.jl b/src/avio.jl index 36ee4421..8936e13f 100644 --- a/src/avio.jl +++ b/src/avio.jl @@ -393,7 +393,13 @@ function aspect_ratio(f::VideoReader) return fixed_aspect end +""" + framerate(f::VideoReader) + +Read the framerate of a VideoReader object. +""" framerate(f::VideoReader) = f.codec_context.time_base.den // f.codec_context.time_base.num + height(f::VideoReader) = f.codec_context.height width(f::VideoReader) = f.codec_context.width From 21b9337e0bde2fe3e5d37949136fceadb7f4bd46 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 8 Feb 2023 09:31:28 -0500 Subject: [PATCH 03/11] try an eof to see if it populates time_base --- src/avio.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/avio.jl b/src/avio.jl index 8936e13f..7897ecda 100644 --- a/src/avio.jl +++ b/src/avio.jl @@ -398,7 +398,10 @@ end Read the framerate of a VideoReader object. """ -framerate(f::VideoReader) = f.codec_context.time_base.den // f.codec_context.time_base.num +function framerate(f::VideoReader) + eof(f) + return f.codec_context.time_base.den // f.codec_context.time_base.num +end height(f::VideoReader) = f.codec_context.height width(f::VideoReader) = f.codec_context.width From 7f2c7173addc2f2cc31594bca4512bae8e7049e0 Mon Sep 17 00:00:00 2001 From: Ian Butterworth Date: Wed, 8 Feb 2023 09:44:11 -0500 Subject: [PATCH 04/11] more proper fix --- src/avio.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/avio.jl b/src/avio.jl index 7897ecda..939c4666 100644 --- a/src/avio.jl +++ b/src/avio.jl @@ -399,8 +399,8 @@ end Read the framerate of a VideoReader object. """ function framerate(f::VideoReader) - eof(f) - return f.codec_context.time_base.den // f.codec_context.time_base.num + stream = get_stream(f) + return convert(Rational, stream.time_base) end height(f::VideoReader) = f.codec_context.height From 246e2e1d19f88686e1890e4efd37f260575a27ed Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 9 Feb 2023 12:12:46 -0500 Subject: [PATCH 05/11] add ffprobe fps test util get_fps Co-Authored-By: AbelHo <21101710+AbelHo@users.noreply.github.com> --- test/utils.jl | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/utils.jl b/test/utils.jl index 222aab5d..44b80a9f 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -173,3 +173,26 @@ macro memory_profile() end end end + +""" + get_fps(file [, streamno]) + +Query the container `file` for the frame per second(fps) of the video stream +`streamno` if applicable, instead returning `nothing`. +""" +function get_fps(file::AbstractString, streamno::Integer = 0) + streamno >= 0 || throw(ArgumentError("streamno must be non-negative")) + fps_strs = FFMPEG.exe( + `-v 0 -of compact=p=0 -select_streams 0 -show_entries stream=r_frame_rate $file`, + command = FFMPEG.ffprobe, + collect = true, + ) + fps = split(fps_strs[1], '=')[2] + if occursin("No such file or directory", fps) + error("Could not find file $file") + elseif occursin("N/A", fps) + return nothing + end + + return reduce(//, parse.(Int, split(fps,'/')) ) +end From e9375f866b15349120a4870fd966ab09596ffa7c Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 9 Feb 2023 12:13:18 -0500 Subject: [PATCH 06/11] testing WIP --- src/avio.jl | 2 +- src/testvideos.jl | 10 ++++++++-- test/reading.jl | 2 ++ test/writing.jl | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/avio.jl b/src/avio.jl index 939c4666..32b247a1 100644 --- a/src/avio.jl +++ b/src/avio.jl @@ -400,7 +400,7 @@ Read the framerate of a VideoReader object. """ function framerate(f::VideoReader) stream = get_stream(f) - return convert(Rational, stream.time_base) + return stream.time_base.den // stream.time_base.num # fps is inverse of time_base end height(f::VideoReader) = f.codec_context.height diff --git a/src/testvideos.jl b/src/testvideos.jl index 449e32e2..74cca627 100644 --- a/src/testvideos.jl +++ b/src/testvideos.jl @@ -17,6 +17,7 @@ mutable struct VideoFile{compression} source::AbstractString download_url::AbstractString numframes::Int + framerate::Rational testframe::Int summarysize::Int end @@ -32,12 +33,13 @@ VideoFile: source: $(v.source) download_url: $(v.download_url) numframes: $(v.numframes) + framerate: $(v.framerate) summarysize: $(v.summarysize) """, ) -VideoFile(name, description, license, credit, source, download_url, numframes, testframe, summarysize) = - VideoFile{:raw}(name, description, license, credit, source, download_url, numframes, testframe, summarysize) +VideoFile(name, description, license, credit, source, download_url, numframes, framerate, testframe, summarysize) = + VideoFile{:raw}(name, description, license, credit, source, download_url, numframes, framerate, testframe, summarysize) # Standard test videos const videofiles = Dict( @@ -49,6 +51,7 @@ const videofiles = Dict( "https://downloadnatureclip.blogspot.com/p/download-links.html", "https://archive.org/download/LadybirdOpeningWingsCCBYNatureClip/Ladybird%20opening%20wings%20CC-BY%20NatureClip.mp4", 397, + 30000//1, 13, 3216, ), @@ -60,6 +63,7 @@ const videofiles = Dict( "https://commons.wikimedia.org/wiki/File:Annie_Oakley_shooting_glass_balls,_1894.ogg", "https://upload.wikimedia.org/wikipedia/commons/8/87/Annie_Oakley_shooting_glass_balls%2C_1894.ogv", 726, + 30000//1001, 2, 167311096, ), @@ -71,6 +75,7 @@ const videofiles = Dict( "https://commons.wikimedia.org/wiki/File:2010-10-10-Lune.ogv", "https://upload.wikimedia.org/wikipedia/commons/e/ef/2010-10-10-Lune.ogv", 1213, + 25//1, 1, 9744, ), @@ -82,6 +87,7 @@ const videofiles = Dict( "https://www.eso.org/public/videos/eso1004a/", "https://upload.wikimedia.org/wikipedia/commons/1/13/Artist%E2%80%99s_impression_of_the_black_hole_inside_NGC_300_X-1_%28ESO_1004c%29.webm", 597, + 1000//1, 1, 4816, ), diff --git a/test/reading.jl b/test/reading.jl index 871ebb35..4792cd47 100644 --- a/test/reading.jl +++ b/test/reading.jl @@ -11,6 +11,8 @@ try time_seconds = VideoIO.gettime(v) @test time_seconds == 0 + @test VideoIO.framerate(v) == testvid.framerate + @test get_fps(testvid_path) == testvid.framerate # ffprobe sanity check width, height = VideoIO.out_frame_size(v) @test VideoIO.width(v) == width @test VideoIO.height(v) == height diff --git a/test/writing.jl b/test/writing.jl index 4a7139ef..28e0bfb4 100644 --- a/test/writing.jl +++ b/test/writing.jl @@ -25,6 +25,7 @@ end VideoIO.save( tempvidpath, img_stack; + framerate = 24, codec_name = codec_name, encoder_private_options = encoder_private_options, encoder_options = encoder_options, @@ -36,6 +37,7 @@ end try notempty = !eof(f) @test notempty + @test VideoIO.framerate(f) == 24 if notempty img = read(f) test_img = scanline_arg ? parent(img) : img @@ -179,6 +181,30 @@ end end end +@testset "Encoding video with integer frame rates" begin + n = 100 + for fr in 20:30 + target_dur = n / fr + @testset "Encoding with frame rate $(fr)" begin + imgstack = map(x -> rand(UInt8, 100, 100), 1:n) + encoder_options = (color_range = 2, crf = 0, preset = "medium") + VideoIO.save(tempvidpath, imgstack, framerate = fr, encoder_options = encoder_options) + @test stat(tempvidpath).size > 100 + measured_dur_str = VideoIO.FFMPEG.exe( + `-v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 $(tempvidpath)`, + command = VideoIO.FFMPEG.ffprobe, + collect = true, + ) + @test parse(Float64, measured_dur_str[1]) ≈ target_dur rtol = 0.01 + @testset "Reading framerate" begin + f = VideoIO.openvideo(tempvidpath) + @test VideoIO.framerate(f) == fr + @test get_fps(tempvidpath) == fr # ffprobe sanity check + end + end + end +end + @testset "Encoding video with rational frame rates" begin n = 100 fr = 59 // 2 # 29.5 @@ -194,6 +220,11 @@ end collect = true, ) @test parse(Float64, measured_dur_str[1]) == target_dur + @testset "Reading framerate" begin + f = VideoIO.openvideo(tempvidpath) + @test VideoIO.framerate(f) == fr + @test get_fps(tempvidpath) == fr # ffprobe sanity check + end end end @@ -212,5 +243,10 @@ end collect = true, ) @test parse(Float64, measured_dur_str[1]) == target_dur + @testset "Reading framerate" begin + f = VideoIO.openvideo(tempvidpath) + @test VideoIO.framerate(f) == fr + @test get_fps(tempvidpath) == fr # ffprobe sanity check + end end end From 33531a87b8e3ab074376f6f74c1023edbdadc7d0 Mon Sep 17 00:00:00 2001 From: AbelHo <21101710+AbelHo@users.noreply.github.com> Date: Fri, 12 May 2023 04:42:35 +0800 Subject: [PATCH 07/11] fixed test result for frame rate (fps) (#394) --- src/testvideos.jl | 4 ++-- test/utils.jl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/testvideos.jl b/src/testvideos.jl index 74cca627..6dfbb955 100644 --- a/src/testvideos.jl +++ b/src/testvideos.jl @@ -51,7 +51,7 @@ const videofiles = Dict( "https://downloadnatureclip.blogspot.com/p/download-links.html", "https://archive.org/download/LadybirdOpeningWingsCCBYNatureClip/Ladybird%20opening%20wings%20CC-BY%20NatureClip.mp4", 397, - 30000//1, + 30000//1001, 13, 3216, ), @@ -87,7 +87,7 @@ const videofiles = Dict( "https://www.eso.org/public/videos/eso1004a/", "https://upload.wikimedia.org/wikipedia/commons/1/13/Artist%E2%80%99s_impression_of_the_black_hole_inside_NGC_300_X-1_%28ESO_1004c%29.webm", 597, - 1000//1, + 25//1, 1, 4816, ), diff --git a/test/utils.jl b/test/utils.jl index 44b80a9f..585030f8 100644 --- a/test/utils.jl +++ b/test/utils.jl @@ -183,7 +183,7 @@ Query the container `file` for the frame per second(fps) of the video stream function get_fps(file::AbstractString, streamno::Integer = 0) streamno >= 0 || throw(ArgumentError("streamno must be non-negative")) fps_strs = FFMPEG.exe( - `-v 0 -of compact=p=0 -select_streams 0 -show_entries stream=r_frame_rate $file`, + `-v 0 -of compact=p=0 -select_streams v:$(streamno) -show_entries stream=r_frame_rate $file`, command = FFMPEG.ffprobe, collect = true, ) From 2882e39326e0a157f25fc9e547a44260a8960a0d Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 11 May 2023 16:50:59 -0400 Subject: [PATCH 08/11] fixup --- src/testvideos.jl | 20 ++------------------ test/reading.jl | 11 +---------- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/src/testvideos.jl b/src/testvideos.jl index aa5ffab1..1dace3a3 100644 --- a/src/testvideos.jl +++ b/src/testvideos.jl @@ -21,22 +21,6 @@ mutable struct VideoFile{compression} testframe::Int summarysize::Int - fps::Union{Nothing,Rational} - - VideoFile{compression}( - name::AbstractString, - description::AbstractString, - license::AbstractString, - credit::AbstractString, - source::AbstractString, - download_url::AbstractString, - numframes::Int, - testframe::Int, - summarysize::Int, - fps::Rational, - ) where {compression} = - new(name, description, license, credit, source, download_url, numframes, testframe, summarysize, fps) - VideoFile{compression}( name::AbstractString, description::AbstractString, @@ -131,10 +115,10 @@ const videofiles = Dict( "https://peach.blender.org/", "https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/360/Big_Buck_Bunny_360_10s_1MB.mp4", 300, - 2, - 207376840, # Can be also 30000/1001 30 // 1, + 2, + 207376840, ), ) diff --git a/test/reading.jl b/test/reading.jl index db40d2cd..d7b51c7c 100644 --- a/test/reading.jl +++ b/test/reading.jl @@ -31,7 +31,7 @@ # fps should be the same before and after first read fps2 = VideoIO.framerate(v) - @test fps1 == fps2 + @test_broken fps1 == fps2 first_time = VideoIO.gettime(v) seekstart(v) @@ -40,15 +40,6 @@ @test img == first_img @test size(img) == VideoIO.out_frame_size(v)[[2, 1]] - - # First read(v) then framerate(v) - # https://github.com/JuliaIO/VideoIO.jl/issues/349 - if !isnothing(testvid.fps) - @test isapprox(VideoIO.framerate(v), testvid.fps, rtol=0.01) - else - @test VideoIO.framerate(v) != 0 - end - # no scaling currently @test VideoIO.out_frame_size(v) == VideoIO.raw_frame_size(v) @test VideoIO.raw_pixel_format(v) == 0 # true for current test videos From 14550ab98c14c61ab234d9bdde9402deddd18c79 Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 11 May 2023 17:04:01 -0400 Subject: [PATCH 09/11] tidy --- src/testvideos.jl | 6 ++---- test/reading.jl | 3 +-- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/testvideos.jl b/src/testvideos.jl index 1dace3a3..cb2bd8aa 100644 --- a/src/testvideos.jl +++ b/src/testvideos.jl @@ -29,10 +29,11 @@ mutable struct VideoFile{compression} source::AbstractString, download_url::AbstractString, numframes::Int, + framerate::Rational, testframe::Int, summarysize::Int, ) where {compression} = - new(name, description, license, credit, source, download_url, numframes, testframe, summarysize, nothing) + new(name, description, license, credit, source, download_url, numframes, framerate, testframe, summarysize) end show(io::IO, v::VideoFile) = print( @@ -54,9 +55,6 @@ VideoFile: VideoFile(name, description, license, credit, source, download_url, numframes, framerate, testframe, summarysize) = VideoFile{:raw}(name, description, license, credit, source, download_url, numframes, framerate, testframe, summarysize) -VideoFile(name, description, license, credit, source, download_url, numframes, testframe, summarysize, fps) = - VideoFile{:raw}(name, description, license, credit, source, download_url, numframes, testframe, summarysize, fps) - # Standard test videos const videofiles = Dict( "ladybird.mp4" => VideoFile( diff --git a/test/reading.jl b/test/reading.jl index d7b51c7c..f7fcb32d 100644 --- a/test/reading.jl +++ b/test/reading.jl @@ -24,14 +24,13 @@ end fps1 = VideoIO.framerate(v) - @test fps1 != 0 # Find the first non-trivial image first_img = read(v) # fps should be the same before and after first read fps2 = VideoIO.framerate(v) - @test_broken fps1 == fps2 + @test fps1 == fps2 first_time = VideoIO.gettime(v) seekstart(v) From e584b51f6a7163a27e9d4d8da150dea1da62d9a7 Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 11 May 2023 17:21:40 -0400 Subject: [PATCH 10/11] fixes --- src/avio.jl | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/avio.jl b/src/avio.jl index bcdc3c3e..479762ce 100644 --- a/src/avio.jl +++ b/src/avio.jl @@ -405,17 +405,25 @@ Like containers, elementary streams also can store timestamps, 1/time_base is th For some codecs, the time base is closer to the field rate than the frame rate. Most notably, H.264 and MPEG-2 specify time_base as half of frame duration if no telecine is used ... -Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2. +Set to time_base ticks per frame. Default 1, e.g., H.264/MPEG-2 set it to 2. =# """ framerate(f::VideoReader) Read the framerate of a VideoReader object. """ -framerate(f::VideoReader) = - f.codec_context.time_base.den // f.codec_context.time_base.num // f.codec_context.ticks_per_frame -height(f::VideoReader) = f.codec_context.height -width(f::VideoReader) = f.codec_context.width +function framerate(f::VideoReader) + stream = get_stream(f) + return stream.time_base.den // stream.time_base.num // stream.ticks_per_frame +end +function height(f::VideoReader) + stream = get_stream(f) + return stream.height +end +function width(f::VideoReader) + stream = get_stream(f) + return stream.width +end # Does not check input size, meant for internal use only function stash_graph_input!(imgbuf, r::VideoReader, align = VIO_ALIGN) From 7eb87fce6b3e9c92796e09d4d98ca884bb7becf8 Mon Sep 17 00:00:00 2001 From: Ian Date: Thu, 11 May 2023 17:35:57 -0400 Subject: [PATCH 11/11] more fix --- src/avio.jl | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/avio.jl b/src/avio.jl index 479762ce..b7c6fbb9 100644 --- a/src/avio.jl +++ b/src/avio.jl @@ -414,16 +414,10 @@ Read the framerate of a VideoReader object. """ function framerate(f::VideoReader) stream = get_stream(f) - return stream.time_base.den // stream.time_base.num // stream.ticks_per_frame -end -function height(f::VideoReader) - stream = get_stream(f) - return stream.height -end -function width(f::VideoReader) - stream = get_stream(f) - return stream.width + return stream.time_base.den // stream.time_base.num // f.codec_context.ticks_per_frame end +height(f::VideoReader) = f.codec_context.height +width(f::VideoReader) = f.codec_context.width # Does not check input size, meant for internal use only function stash_graph_input!(imgbuf, r::VideoReader, align = VIO_ALIGN)