diff --git a/Project.toml b/Project.toml index cfc9b2df6..407e3a17c 100644 --- a/Project.toml +++ b/Project.toml @@ -26,8 +26,6 @@ VideoIO = "d6d074c3-1acf-5d4c-9a43-ef38773959a2" Animations = "0.4" Cairo = "1" FFMPEG = "0.3, 0.4" -Gtk = "1.1" -GtkReactive = "1.0.3" Hungarian = "0.6" ImageIO = "0.4, 0.5, 0.6" ImageMagick = "1.1" diff --git a/src/Javis.jl b/src/Javis.jl index ffe095a60..86e6352dc 100644 --- a/src/Javis.jl +++ b/src/Javis.jl @@ -106,6 +106,9 @@ include("javis_viewer.jl") include("latex.jl") include("object_values.jl") + +export setup_stream, cancel_stream + """ projection(p::Point, l::Line) @@ -267,8 +270,7 @@ function render( return video, length(frames), objects else - _javis_viewer(video, length(frames), objects) - return "Live Preview Started" + return video, length(frames), objects end end @@ -739,7 +741,6 @@ export JBox, JCircle, JEllipse, JLine, JPoly, JRect, JStar, @JShape # custom override of luxor extensions export setline, setopacity, fontsize, get_fontsize, scale, text -export setup_stream, cancel_stream # scales export scale_linear, @scale_layer diff --git a/src/javis_viewer.jl b/src/javis_viewer.jl index 55b0c276c..0d380c896 100644 --- a/src/javis_viewer.jl +++ b/src/javis_viewer.jl @@ -1,242 +1,5 @@ include("structs/Livestream.jl") -""" - _draw_image(video::Video, objects::Vector, frame::Int, canvas::Gtk.Canvas, - img_dims::Vector) - -Internal function to create an image that is drawn on a Gtk Canvas. -""" -function _draw_image( - video::Video, - objects::Vector, - frame::Int, - canvas::Gtk.Canvas, - img_dims::Vector, -) - @guarded draw(canvas) do widget - # Gets a specific frame from graphic; transposed due to returned matrix - frame_mat = transpose(get_javis_frame(video, objects, frame; layers = video.layers)) - - # Gets the correct Canvas context to draw on - context = getgc(canvas) - - # Uses Cairo to draw on Gtk canvas context - image(context, CairoImageSurface(frame_mat), 0, 0, img_dims[1], img_dims[2]) - end -end - -""" - _increment(video::Video, widgets::Vector, objects::Vector, dims::Vector, - canvas::Gtk.Canvas, frames::Int, layers=Vector) - -Increments a given value and returns the associated frame. -""" -function _increment( - video::Video, - widgets::Vector, - objects::Vector, - dims::Vector, - canvas::Gtk.Canvas, - frames::Int, -) - # Get current frame from textbox as an Int value - curr_frame = parse(Int, get_gtk_property(widgets[2], :text, String)) - if frames > curr_frame - # `widgets[1]` represents the GtkReactive slider widget - push!(widgets[1], curr_frame + 1) - _draw_image(video, objects, curr_frame + 1, canvas, dims) - else - # `widgets[2]` represents the GtkReactive textboxwidget - push!(widgets[2], 1) # Sets the first frame shown to one - _draw_image(video, objects, 1, canvas, dims) - end -end - -""" - _decrement(video::Video, widgets::Vector, objects::Vector, dims::Vector, - canvas::Gtk.Canvas, frames::Int, layers::Vector) - -Decrements a given value and returns the associated frame. -""" -function _decrement( - video::Video, - widgets::Vector, - objects::Vector, - dims::Vector, - canvas::Gtk.Canvas, - frames::Int, -) - # Get current frame from textbox as an Int value - curr_frame = parse(Int, get_gtk_property(widgets[2], :text, String)) - if curr_frame > 1 - # `widgets[1]` represents the GtkReactive slider widget - push!(widgets[1], curr_frame - 1) - _draw_image(video, objects, curr_frame - 1, canvas, dims) - else - # `widgets[2]` represents the GtkReactive textboxwidget - push!(widgets[2], frames) # Sets the first frame shown to one - _draw_image(video, objects, frames, canvas, dims) - end -end - -""" - _javis_viewer(video::Video, frames::Int, object_list::Vector, show::Bool) - -Internal Javis Viewer built on Gtk that is called for live previewing. -""" -function _javis_viewer( - video::Video, - total_frames::Int, - object_list::Vector, - show::Bool = true, -) - ##################################################################### - # VIEWER WINDOW AND CONFIGURATION - ##################################################################### - - # Determine frame size of animation - frame_dims = [video.width, video.height] - - # Creates a GTK window for drawing; sized based on frame size - win = GtkWindow("Javis Viewer", frame_dims[1], frame_dims[2]) - - # Sets border size of window - set_gtk_property!(win, :border_width, 20) - - ##################################################################### - # DISPLAY WIDGETS - ##################################################################### - - # Create GtkScale internal widget - _slide = GtkScale(false, 1:total_frames) - - # Create GtkReactive slider widget - slide = slider(1:total_frames, value = 1, widget = _slide) - - #= - # - # NOTE: We must provide a named GtkScale widget named `_slide` to the - # GtkReactive `slider` widget so as to perform asynchronous calls - # via signal_connect. Otherwise, we will be unable to update the - # widget that is automatically created by the slider object. - # - # It should be stated that a `slider` object is essentially a - # GtkScale widget coupled with a Reactive object. - # - =# - - # Create a textbox - tbox = GtkReactive.textbox(Int; signal = signal(slide)) - - # Button for going forward through animation - forward = GtkButton("==>") - - # Button for going backward through animation - backward = GtkButton("<==") - - #= - TODO: Enable widgets of window to dynamically resize based on user changing the size of a window. - I think I can use the `configure-event` signal in GTK3 documentation - (link: https://developer.gnome.org/gtk3/stable/GtkWidget.html#GtkWidget-configure-event). - From there, I can then make a `signal_connect` set-up where I update `set_gtk_property!()` - of the windows accordingly using `:width_request` and `height_request`. - =# - - ##################################################################### - # VIEWER CANVAS AND GRID CONFIGURATION - ##################################################################### - - # Gtk Canvas object upon which to draw image; sized via frame size - canvas = Gtk.Canvas(frame_dims[1], frame_dims[2]) - - # Grid to allocate widgets - grid = Gtk.Grid() - - # Allocate the widgets in a 3x3 grid - grid[1:3, 1] = canvas - grid[1:3, 2] = slide - grid[1, 3] = backward - grid[2, 3] = tbox - grid[3, 3] = forward - - # Center all widgets vertically in grid - set_gtk_property!(grid, :valign, 3) - - # Center all widgets horizontally in grid - set_gtk_property!(grid, :halign, 3) - - # Adds grid to previously defined window - push!(win, grid) - - ##################################################################### - # DISPLAY FIRST FRAME - ##################################################################### - - _draw_image(video, object_list, 1, canvas, frame_dims) - - ##################################################################### - # SIGNAL CONNECTION FUNCTIONS - ##################################################################### - - # When the slider is changed, update currently viewed frame - signal_connect(_slide, "value-changed") do widget - # Collects GtkScale as an adjustable bounded value object - bound_slide = Gtk.GAccessor.adjustment(_slide) - - # Get frame number from bounded value object as Int - slide_val = Gtk.get_gtk_property(bound_slide, "value", Int) - - _draw_image(video, object_list, slide_val, canvas, frame_dims) - end - - # When the `Enter` key is pressed, update the frame - signal_connect(win, "key-press-event") do widget, event - if event.keyval == 65293 - # Get current frame from textbox as an Int value - curr_frame = parse(Int, get_gtk_property(tbox, :text, String)) - curr_frame = clamp(curr_frame, 1, total_frames) - _draw_image(video, object_list, curr_frame, canvas, frame_dims) - end - end - - # When the `forward` button is clicked, increment current frame number - # If at final frame, wrap viewer to first frame - signal_connect(forward, "clicked") do widget - _increment(video, [slide, tbox], object_list, frame_dims, canvas, total_frames) - end - - # When the `Right Arrow` key is pressed, increment current frame number - # If at final frame, wrap viewer to first frame - signal_connect(win, "key-press-event") do widget, event - if event.keyval == 65363 - _increment(video, [slide, tbox], object_list, frame_dims, canvas, total_frames) - end - end - - # When the `backward` button is clicked, decrement the current frame number - # If at first frame, wrap viewer to last frame - signal_connect(backward, "clicked") do widget - _decrement(video, [slide, tbox], object_list, frame_dims, canvas, total_frames) - end - - # When the `Left Arrow` key is pressed, decrement current frame number - # If at first frame, wrap viewer to last frame - signal_connect(win, "key-press-event") do widget, event - if event.keyval == 65361 - _decrement(video, [slide, tbox], object_list, frame_dims, canvas, total_frames) - end - end - - ##################################################################### - - if show - # Display image viewer - Gtk.showall(win) - else - return win, frame_dims, slide, tbox, canvas, object_list, total_frames, video - end -end - """ setup_stream(livestreamto=:local; protocol="udp", address="0.0.0.0", port=14015, twitch_key="") diff --git a/test/livestream.jl b/test/livestream.jl new file mode 100644 index 000000000..97c36c699 --- /dev/null +++ b/test/livestream.jl @@ -0,0 +1,55 @@ +function ground(args...) + background("white") + sethue("black") +end + +@testset "Livestreaming" begin + astar(args...; do_action = :stroke) = star(O, 50, 5, 0.5, 0, do_action) + acirc(args...; do_action = :stroke) = circle(Point(100, 100), 50, do_action) + + vid = Video(500, 500) + back = Background(1:100, ground) + star_obj = Object(1:100, astar) + act!(star_obj, Action(morph_to(acirc; do_action = :fill))) + + conf_local = setup_stream(:local, address = "0.0.0.0", port = 8081) + @test conf_local isa Javis.StreamConfig + @test conf_local.livestreamto == :local + @test conf_local.protocol == "udp" + @test conf_local.address == "0.0.0.0" + @test conf_local.port == 8081 + + conf_twitch_err = setup_stream(:twitch) + conf_twitch = setup_stream(:twitch, twitch_key = "foo") + @test conf_twitch_err isa Javis.StreamConfig + @test conf_twitch_err.livestreamto == :twitch + @test isempty(conf_twitch_err.twitch_key) + @test conf_twitch.twitch_key == "foo" + + render(vid, pathname = "stream_local.gif", streamconfig = conf_local) + + # errors with macos; a good test to have + # test_local = run(pipeline(`lsof -i -P -n`, `grep ffmpeg`)) + # @test test_local isa Base.ProcessChain + # @test test_local.processes isa Vector{Base.Process} + + cancel_stream() + @test_throws ProcessFailedException run( + pipeline( + `ps aux`, + pipeline(`grep ffmpeg`, pipeline(`grep stream_loop`, `awk '{print $2}'`)), + ), + ) + + vid = Video(500, 500) + back = Background(1:100, ground) + star_obj = Object(1:100, astar) + act!(star_obj, Action(morph_to(acirc; do_action = :fill))) + + @test_throws ErrorException render( + vid, + pathname = "stream_twitch.gif", + streamconfig = conf_twitch_err, + ) + rm("stream_twitch.gif") +end diff --git a/test/runtests.jl b/test/runtests.jl index d9e2eaf52..db3a75372 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -56,7 +56,7 @@ end @testset "Postprocessing" begin include("postprocessing.jl") end - @testset "Javis Viewer" begin - include("viewer.jl") + @testset "Javis LiveStream" begin + include("livestream.jl") end end diff --git a/test/viewer.jl b/test/viewer.jl deleted file mode 100644 index d8f3f8b81..000000000 --- a/test/viewer.jl +++ /dev/null @@ -1,109 +0,0 @@ -function ground(args...) - background("white") - sethue("black") -end - -@testset "Javis Viewer" begin - astar(args...; do_action = :stroke) = star(O, 50, 5, 0.5, 0, do_action) - acirc(args...; do_action = :stroke) = circle(Point(100, 100), 50, do_action) - - vid = Video(500, 500) - back = Background(1:100, ground) - star_obj = Object(1:100, astar) - act!(star_obj, Action(morph_to(acirc; do_action = :fill))) - - l1 = @JLayer 20:60 100 100 Point(0, 0) begin - obj = Object((args...) -> circle(O, 25, :fill)) - act!(obj, Action(1:20, appear(:fade))) - end - - render(vid; pathname = "") - - action_list = [back, star_obj] - - viewer_win, frame_dims, r_slide, tbox, canvas, actions, total_frames, video = - Javis._javis_viewer(vid, 100, action_list, false) - visible(viewer_win, false) - - @test get_gtk_property(viewer_win, :title, String) == "Javis Viewer" - - Javis._increment(video, [r_slide, tbox], actions, frame_dims, canvas, total_frames) - sleep(0.1) - curr_frame = Reactive.value(r_slide) - second_frame = Javis.get_javis_frame(video, actions, curr_frame, layers = [l1]) - @test Reactive.value(r_slide) == 2 - - Javis._decrement(video, [r_slide, tbox], actions, frame_dims, canvas, total_frames) - sleep(0.1) - curr_frame = Reactive.value(r_slide) - first_frame = Javis.get_javis_frame(video, actions, curr_frame, layers = [l1]) - @test Reactive.value(r_slide) == 1 - - @test first_frame != second_frame - - Javis._decrement(video, [r_slide, tbox], actions, frame_dims, canvas, total_frames) - sleep(0.1) - curr_frame = Reactive.value(r_slide) - last_frame = Javis.get_javis_frame(video, actions, curr_frame, layers = [l1]) - @test curr_frame == total_frames - - Javis._increment(video, [r_slide, tbox], actions, frame_dims, canvas, total_frames) - sleep(0.1) - curr_frame = Reactive.value(r_slide) - first_frame = Javis.get_javis_frame(video, actions, curr_frame, layers = [l1]) - @test curr_frame == 1 - - @test last_frame != first_frame -end - - -@testset "Livestreaming" begin - astar(args...; do_action = :stroke) = star(O, 50, 5, 0.5, 0, do_action) - acirc(args...; do_action = :stroke) = circle(Point(100, 100), 50, do_action) - - vid = Video(500, 500) - back = Background(1:100, ground) - star_obj = Object(1:100, astar) - act!(star_obj, Action(morph_to(acirc; do_action = :fill))) - - conf_local = setup_stream(:local, address = "0.0.0.0", port = 8081) - @test conf_local isa Javis.StreamConfig - @test conf_local.livestreamto == :local - @test conf_local.protocol == "udp" - @test conf_local.address == "0.0.0.0" - @test conf_local.port == 8081 - - conf_twitch_err = setup_stream(:twitch) - conf_twitch = setup_stream(:twitch, twitch_key = "foo") - @test conf_twitch_err isa Javis.StreamConfig - @test conf_twitch_err.livestreamto == :twitch - @test isempty(conf_twitch_err.twitch_key) - @test conf_twitch.twitch_key == "foo" - - render(vid, pathname = "stream_local.gif", streamconfig = conf_local) - - # errors with macos; a good test to have - # test_local = run(pipeline(`lsof -i -P -n`, `grep ffmpeg`)) - # @test test_local isa Base.ProcessChain - # @test test_local.processes isa Vector{Base.Process} - - cancel_stream() - @test_throws ProcessFailedException run( - pipeline( - `ps aux`, - pipeline(`grep ffmpeg`, pipeline(`grep stream_loop`, `awk '{print $2}'`)), - ), - ) - - vid = Video(500, 500) - back = Background(1:100, ground) - star_obj = Object(1:100, astar) - act!(star_obj, Action(morph_to(acirc; do_action = :fill))) - - @test_throws ErrorException render( - vid, - pathname = "stream_twitch.gif", - streamconfig = conf_twitch_err, - ) - rm("stream_twitch.gif") -end