Skip to content
2 changes: 0 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
7 changes: 4 additions & 3 deletions src/Javis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
237 changes: 0 additions & 237 deletions src/javis_viewer.jl
Original file line number Diff line number Diff line change
@@ -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="")

Expand Down
55 changes: 55 additions & 0 deletions test/livestream.jl
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading