recorder writes video frames to folder
This commit is contained in:
parent
1dbb2a2e1d
commit
321d9df1be
|
@ -1,8 +1,9 @@
|
||||||
/***
|
/***
|
||||||
|
|
||||||
reset, pause, auto reset, analog d-pad, gamepad config, any key, confirm exit
|
reset, pause, auto reset, analog d-pad, gamepad config, any key, confirm exit
|
||||||
game, screen wipes
|
game, screen wipes, screen offset, screen scale
|
||||||
:) SWEATY HANDS :) OILY SNACKS :) AND BAD HYGIENE :)
|
:) SWEATY HANDS :) OILY SNACKS :) AND BAD HYGIENE :)
|
||||||
|
*surf's up broccoli* <it's surfing time but it's treacherous>
|
||||||
|
|
||||||
***/
|
***/
|
||||||
|
|
||||||
|
@ -65,56 +66,6 @@ int link_shader(GLuint program)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface* get_screen_surface(SDL_Window *window)
|
|
||||||
{
|
|
||||||
int w, h;
|
|
||||||
SDL_GetWindowSize(window, &w, &h);
|
|
||||||
unsigned char* pixels = new unsigned char[24 * w * h];
|
|
||||||
GLenum format;
|
|
||||||
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
|
||||||
format = GL_RGB;
|
|
||||||
#else
|
|
||||||
format = GL_BGR;
|
|
||||||
#endif
|
|
||||||
glReadPixels(0, 0, w, h, format, GL_UNSIGNED_BYTE, pixels);
|
|
||||||
SDL_Surface *surface = zoomSurface(
|
|
||||||
SDL_CreateRGBSurfaceFrom(pixels, w, h, 24, 3 * w,
|
|
||||||
0, 0, 0, 0), 1, -1, SMOOTHING_OFF);
|
|
||||||
delete[] pixels;
|
|
||||||
return surface;
|
|
||||||
}
|
|
||||||
|
|
||||||
void capture_screen(SDL_Window *window)
|
|
||||||
{
|
|
||||||
SDL_Surface *surface = get_screen_surface(window);
|
|
||||||
IMG_SavePNG(surface, "screen.png");
|
|
||||||
printf("saved png to screen.png\n");
|
|
||||||
SDL_FreeSurface(surface);
|
|
||||||
}
|
|
||||||
|
|
||||||
void start_recording(bool *is_recording)
|
|
||||||
{
|
|
||||||
*is_recording = true;
|
|
||||||
printf("start recording\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
void end_recording(std::list<SDL_Surface*> frames, bool *is_recording)
|
|
||||||
{
|
|
||||||
*is_recording = false;
|
|
||||||
printf("end recording\n");
|
|
||||||
SDL_Surface *frame;
|
|
||||||
int ii = 0;
|
|
||||||
while (not frames.empty())
|
|
||||||
{
|
|
||||||
frame = frames.front();
|
|
||||||
char path[22];
|
|
||||||
sprintf(path, "frames/%03i.png", ii++);
|
|
||||||
IMG_SavePNG(frame, path);
|
|
||||||
frames.pop_front();
|
|
||||||
SDL_FreeSurface(frame);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint get_gl_texture_from_surface(SDL_Surface *surface, GLint mipmap_filter)
|
GLuint get_gl_texture_from_surface(SDL_Surface *surface, GLint mipmap_filter)
|
||||||
{
|
{
|
||||||
GLuint id;
|
GLuint id;
|
||||||
|
@ -162,12 +113,9 @@ struct Demo : Game
|
||||||
{
|
{
|
||||||
|
|
||||||
SDL_Texture *grass_texture;
|
SDL_Texture *grass_texture;
|
||||||
int recording_capture_framerate = 100, frame_time_overflow = 0,
|
int frame_count = 0, frame_count_timestamp;
|
||||||
capture_time_overflow = 0, frame_count = 0, frame_count_timestamp,
|
bool right_active = false, down_active = false, left_active = false,
|
||||||
last_capture_timestamp;
|
up_active = false;
|
||||||
std::list<SDL_Surface*> frames;
|
|
||||||
bool is_recording = false, right_active = false, down_active = false,
|
|
||||||
left_active = false, up_active = false;
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program,
|
GLuint vbo, space_texture_id, mvp_id, framerate_texture_id, flat_program,
|
||||||
world_program, fake_texture_id;
|
world_program, fake_texture_id;
|
||||||
|
@ -341,7 +289,7 @@ struct Demo : Game
|
||||||
glBindTexture(GL_TEXTURE_2D, space_texture_id);
|
glBindTexture(GL_TEXTURE_2D, space_texture_id);
|
||||||
glUniform1i(sampler_uniform_id, 0);
|
glUniform1i(sampler_uniform_id, 0);
|
||||||
glDepthFunc(GL_LESS);
|
glDepthFunc(GL_LESS);
|
||||||
frame_count_timestamp = last_capture_timestamp = SDL_GetTicks();
|
frame_count_timestamp = SDL_GetTicks();
|
||||||
framerate_texture_id = get_gl_texture_from_surface(
|
framerate_texture_id = get_gl_texture_from_surface(
|
||||||
get_framerate_indicator_surface(frame_count), GL_LINEAR);
|
get_framerate_indicator_surface(frame_count), GL_LINEAR);
|
||||||
}
|
}
|
||||||
|
@ -367,18 +315,7 @@ struct Demo : Game
|
||||||
// {
|
// {
|
||||||
// if (event.type == SDL_KEYDOWN)
|
// if (event.type == SDL_KEYDOWN)
|
||||||
// {
|
// {
|
||||||
// if (event.key.keysym.sym == SDLK_F10)
|
// if (event.key.keysym.sym == SDLK_F11)
|
||||||
// {
|
|
||||||
// if (not is_recording)
|
|
||||||
// {
|
|
||||||
// start_recording(&is_recording);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// end_recording(frames, &is_recording);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// else if (event.key.keysym.sym == SDLK_F11)
|
|
||||||
// {
|
// {
|
||||||
// if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN)
|
// if (SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN)
|
||||||
// {
|
// {
|
||||||
|
@ -441,20 +378,6 @@ struct Demo : Game
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
if (is_recording and ticks - last_capture_timestamp + capture_time_overflow >
|
|
||||||
recording_capture_framerate)
|
|
||||||
{
|
|
||||||
frames.push_back(get_screen_surface(window));
|
|
||||||
printf("added frame at %i\n", ticks);
|
|
||||||
capture_time_overflow = ticks - last_capture_timestamp + capture_time_overflow -
|
|
||||||
recording_capture_framerate;
|
|
||||||
last_capture_timestamp = ticks;
|
|
||||||
for (int ii = 1; capture_time_overflow > recording_capture_framerate;
|
|
||||||
ii++, capture_time_overflow -= recording_capture_framerate)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "lost %i frame(s) during capture\n", ii);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (is_gl_context)
|
if (is_gl_context)
|
||||||
{
|
{
|
||||||
// glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
// glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
|
||||||
|
|
|
@ -36,18 +36,21 @@ $(SDLGFX2_DIR)%.o: $(SDLGFX2_DIR)%.c $(SDLGFX2_DIR)%.h
|
||||||
$(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h
|
$(GLEW_DIR)%.o: $(GLEW_DIR)%.c $(GLEW_DIR)%.h
|
||||||
$(CC_LINUX) $(CFLAGS) $< -o $@
|
$(CC_LINUX) $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
$(SFW_SRC_DIR)Sprite.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Location.*pp)
|
$(SFW_SRC_DIR)Sprite.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Location.*pp Node.*pp)
|
||||||
$(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp Display.*pp Recorder.*pp)
|
$(SFW_SRC_DIR)Game.o: $(addprefix $(SFW_SRC_DIR),Sprite.*pp Configuration.*pp Delegate.*pp Display.*pp \
|
||||||
$(SFW_SRC_DIR)Node.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Configuration.*pp)
|
Recorder.*pp Node.*pp)
|
||||||
$(SFW_SRC_DIR)Recorder.o: $(addprefix $(SFW_SRC_DIR),extension.*pp)
|
$(SFW_SRC_DIR)Node.o: $(addprefix $(SFW_SRC_DIR),Game.*pp Configuration.*pp Node.*pp)
|
||||||
$(SFW_SRC_DIR)%.o: $(addprefix $(SFW_SRC_DIR),%.cpp %.hpp Node.*pp)
|
$(SFW_SRC_DIR)Animation.o: $(addprefix $(SFW_SRC_DIR),Timer.*pp)
|
||||||
|
$(SFW_SRC_DIR)Recorder.o: $(addprefix $(SFW_SRC_DIR),extension.*pp Node.*pp)
|
||||||
|
$(SFW_SRC_DIR)%.o: $(addprefix $(SFW_SRC_DIR),%.cpp %.hpp)
|
||||||
$(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@
|
$(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@
|
||||||
|
|
||||||
Demo.o: Demo.cpp Demo.hpp $(addprefix $(SFW_SRC_DIR),Sprite.*pp Node.*pp Game.*pp Location.*pp Input.*pp Recorder.*pp)
|
Demo.o: Demo.cpp Demo.hpp $(addprefix $(SFW_SRC_DIR),Sprite.*pp Node.*pp Game.*pp Location.*pp Input.*pp \
|
||||||
|
Recorder.*pp Timer.*pp Animation.*pp extension.*pp)
|
||||||
$(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@
|
$(CPPC_LINUX) $(CPP_FLAGS) $(SDL_FLAGS) $< -o $@
|
||||||
|
|
||||||
linux: Demo.o $(addprefix $(SFW_SRC_DIR),Sprite.o Node.o Game.o Location.o Configuration.o Input.o Delegate.o \
|
linux: Demo.o $(addprefix $(SFW_SRC_DIR),Sprite.o Node.o Game.o Location.o Configuration.o Input.o Delegate.o \
|
||||||
Display.o Recorder.o extension.o) \
|
Display.o Recorder.o Timer.o Animation.o extension.o) \
|
||||||
$(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o)
|
$(GLEW_DIR)glew.o $(addprefix $(SDLGFX2_DIR),SDL2_rotozoom.o SDL2_gfxPrimitives.o)
|
||||||
$(CPPC_LINUX) $(LFLAGS) -D__LINUX__ $^ -lGL -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs -o demo
|
$(CPPC_LINUX) $(LFLAGS) -D__LINUX__ $^ -lGL -lSDL2_image -lSDL2_ttf -lSDL2_mixer -lstdc++fs -o demo
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,8 @@
|
||||||
},
|
},
|
||||||
"path":
|
"path":
|
||||||
{
|
{
|
||||||
"screenshots": "local/screenshots"
|
"screenshots": "local/screenshots",
|
||||||
|
"video": "local/video"
|
||||||
},
|
},
|
||||||
"gamepad":
|
"gamepad":
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
#include "Animation.hpp"
|
||||||
|
|
||||||
|
void Animation::play(int delay, bool play_once)
|
||||||
|
{
|
||||||
|
this->delay = delay;
|
||||||
|
playing = true;
|
||||||
|
paused = false;
|
||||||
|
previous_step_time = timer.elapsed;
|
||||||
|
overflow = 0;
|
||||||
|
count = 0;
|
||||||
|
ending = play_once;
|
||||||
|
if (delay <= 0)
|
||||||
|
{
|
||||||
|
timer.toggle(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::play_once(int delay)
|
||||||
|
{
|
||||||
|
play(delay, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::pause()
|
||||||
|
{
|
||||||
|
timer.toggle(false);
|
||||||
|
paused = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::unpause()
|
||||||
|
{
|
||||||
|
timer.toggle(true);
|
||||||
|
paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::reset()
|
||||||
|
{
|
||||||
|
timer.toggle(false);
|
||||||
|
playing = false;
|
||||||
|
timer.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animation::update()
|
||||||
|
{
|
||||||
|
timer.update();
|
||||||
|
if (playing and not paused)
|
||||||
|
{
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
delay -= timer.frame_duration;
|
||||||
|
if (delay <= 0)
|
||||||
|
{
|
||||||
|
timer.toggle(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (delay <= 0)
|
||||||
|
{
|
||||||
|
if (timer.elapsed - previous_step_time + overflow > framerate)
|
||||||
|
{
|
||||||
|
overflow = timer.elapsed - previous_step_time + overflow - framerate;
|
||||||
|
previous_step_time = timer.elapsed;
|
||||||
|
step();
|
||||||
|
count++;
|
||||||
|
if (ending)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
#ifndef Animation_h_
|
||||||
|
#define Animation_h_
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "Timer.hpp"
|
||||||
|
|
||||||
|
typedef std::function<void()> callback;
|
||||||
|
|
||||||
|
struct Animation
|
||||||
|
{
|
||||||
|
|
||||||
|
bool playing = false, ending = false, paused = false;
|
||||||
|
int previous_step_time = 0, delay = 0, overflow = 0, count = 0, framerate;
|
||||||
|
callback step;
|
||||||
|
Timer timer = Timer();
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
Animation(void(T::*f)(), T* o, int framerate = 0) : framerate(framerate)
|
||||||
|
{
|
||||||
|
step = std::bind(f, o);
|
||||||
|
timer.toggle(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void play(int = 0, bool = false);
|
||||||
|
void play_once(int = 0);
|
||||||
|
void pause();
|
||||||
|
void unpause();
|
||||||
|
void reset();
|
||||||
|
void update();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -15,20 +15,24 @@ void Configuration::set_defaults()
|
||||||
sys_config["keys"] = {
|
sys_config["keys"] = {
|
||||||
{"record", {"CTRL", "SHIFT", "f10"}},
|
{"record", {"CTRL", "SHIFT", "f10"}},
|
||||||
{"screenshot", "f9"},
|
{"screenshot", "f9"},
|
||||||
{"action", " "},
|
{"action", "space"},
|
||||||
{"up", "up"},
|
{"up", "up"},
|
||||||
{"right", "right"},
|
{"right", "right"},
|
||||||
{"down", "down"},
|
{"down", "down"},
|
||||||
{"left", "left"}
|
{"left", "left"},
|
||||||
|
{"pause", "enter"},
|
||||||
|
{"fullscreen", {"ALT", "enter"}}
|
||||||
};
|
};
|
||||||
sys_config["path"] = {
|
sys_config["path"] = {
|
||||||
{"screenshots", "."}
|
{"screenshots", "."},
|
||||||
|
{"video", "."}
|
||||||
};
|
};
|
||||||
sys_config["display"] = {
|
sys_config["display"] = {
|
||||||
{"dimensions", {640, 480}},
|
{"dimensions", {640, 480}},
|
||||||
{"screenshot-prefix", "screenshot-"},
|
{"screenshot-prefix", "screenshot-"},
|
||||||
{"screenshot-extension", ".png"},
|
{"screenshot-extension", ".png"},
|
||||||
{"screenshot-zfill", 5}
|
{"screenshot-zfill", 5},
|
||||||
|
{"recording-framerate", 100}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,3 +9,27 @@ glm::ivec2 Display::get_window_size()
|
||||||
SDL_GetWindowSize(get_root()->window, &size.x, &size.y);
|
SDL_GetWindowSize(get_root()->window, &size.x, &size.y);
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Display::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y)
|
||||||
|
{
|
||||||
|
GLenum format;
|
||||||
|
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
||||||
|
format = GL_RGB;
|
||||||
|
#else
|
||||||
|
format = GL_BGR;
|
||||||
|
#endif
|
||||||
|
glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Surface* Display::get_screen_surface()
|
||||||
|
{
|
||||||
|
glm::ivec2 size = get_window_size();
|
||||||
|
unsigned char* pixels = new unsigned char[bpp / 8 * size.x * size.y];
|
||||||
|
get_screen_pixels(pixels, size.x, size.y);
|
||||||
|
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(
|
||||||
|
pixels, size.x, size.y, bpp, bpp / 8 * size.x, 0, 0, 0, 0);
|
||||||
|
SDL_Surface* zoomed_surface = zoomSurface(surface, 1, -1, SMOOTHING_OFF);
|
||||||
|
SDL_FreeSurface(surface);
|
||||||
|
delete[] pixels;
|
||||||
|
return zoomed_surface;
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,14 @@
|
||||||
#define GLM_ENABLE_EXPERIMENTAL
|
#define GLM_ENABLE_EXPERIMENTAL
|
||||||
#include "glm/vec2.hpp"
|
#include "glm/vec2.hpp"
|
||||||
|
|
||||||
|
#define GL_GLEXT_PROTOTYPES
|
||||||
|
#define GLEW_STATIC
|
||||||
|
#include "glew/glew.h"
|
||||||
|
|
||||||
|
#include <SDL_image.h>
|
||||||
|
#include "sdl2-gfx/SDL2_gfxPrimitives.h"
|
||||||
|
#include "sdl2-gfx/SDL2_rotozoom.h"
|
||||||
|
|
||||||
#include "SDL.h"
|
#include "SDL.h"
|
||||||
|
|
||||||
#include "Node.hpp"
|
#include "Node.hpp"
|
||||||
|
@ -11,8 +19,12 @@
|
||||||
struct Display : Node
|
struct Display : Node
|
||||||
{
|
{
|
||||||
|
|
||||||
|
const static int bpp = 24;
|
||||||
|
|
||||||
Display(Node*);
|
Display(Node*);
|
||||||
glm::ivec2 get_window_size();
|
glm::ivec2 get_window_size();
|
||||||
|
SDL_Surface* get_screen_surface();
|
||||||
|
void get_screen_pixels(unsigned char*, int, int, int = 0, int = 0);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ void Game::run()
|
||||||
last_frame_length = ticks - last_frame_timestamp;
|
last_frame_length = ticks - last_frame_timestamp;
|
||||||
frame_time_overflow = last_frame_length + frame_time_overflow - frame_length;
|
frame_time_overflow = last_frame_length + frame_time_overflow - frame_length;
|
||||||
last_frame_timestamp = ticks;
|
last_frame_timestamp = ticks;
|
||||||
|
recorder->update();
|
||||||
delegate->dispatch();
|
delegate->dispatch();
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,7 +40,9 @@ struct Input : Node
|
||||||
{"f9", SDLK_F9},
|
{"f9", SDLK_F9},
|
||||||
{"f10", SDLK_F10},
|
{"f10", SDLK_F10},
|
||||||
{"f11", SDLK_F11},
|
{"f11", SDLK_F11},
|
||||||
{"f12", SDLK_F11}
|
{"f12", SDLK_F11},
|
||||||
|
{"enter", SDLK_RETURN},
|
||||||
|
{"space", SDLK_SPACE}
|
||||||
};
|
};
|
||||||
std::vector<KeyCombination> key_map;
|
std::vector<KeyCombination> key_map;
|
||||||
|
|
||||||
|
|
20
src/Node.cpp
20
src/Node.cpp
|
@ -15,16 +15,6 @@ nlohmann::json& Node::get_configuration()
|
||||||
return get_root()->configuration->config;
|
return get_root()->configuration->config;
|
||||||
}
|
}
|
||||||
|
|
||||||
Delegate* Node::get_delegate()
|
|
||||||
{
|
|
||||||
return get_root()->delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
Display* Node::get_display()
|
|
||||||
{
|
|
||||||
return get_root()->display;
|
|
||||||
}
|
|
||||||
|
|
||||||
Game* Node::get_root()
|
Game* Node::get_root()
|
||||||
{
|
{
|
||||||
Node *current = this;
|
Node *current = this;
|
||||||
|
@ -35,6 +25,16 @@ Game* Node::get_root()
|
||||||
return static_cast<Game*>(current);
|
return static_cast<Game*>(current);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Delegate* Node::get_delegate()
|
||||||
|
{
|
||||||
|
return get_root()->delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
Display* Node::get_display()
|
||||||
|
{
|
||||||
|
return get_root()->display;
|
||||||
|
}
|
||||||
|
|
||||||
void Node::print_branch()
|
void Node::print_branch()
|
||||||
{
|
{
|
||||||
Node *current = this;
|
Node *current = this;
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
struct Game;
|
struct Game;
|
||||||
struct Delegate;
|
struct Delegate;
|
||||||
struct Display;
|
struct Display;
|
||||||
|
struct TimeFilter;
|
||||||
|
|
||||||
struct Node
|
struct Node
|
||||||
{
|
{
|
||||||
|
|
|
@ -11,65 +11,71 @@ void Recorder::respond(SDL_Event& event)
|
||||||
{
|
{
|
||||||
capture_screen();
|
capture_screen();
|
||||||
}
|
}
|
||||||
|
else if (get_delegate()->compare(event, "record"))
|
||||||
|
{
|
||||||
|
if (animation.playing)
|
||||||
|
{
|
||||||
|
end_recording();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
start_recording();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Recorder::capture_screen()
|
void Recorder::capture_screen()
|
||||||
{
|
{
|
||||||
nlohmann::json config = get_configuration();
|
nlohmann::json config = get_configuration();
|
||||||
SDL_Surface* surface = get_screen_surface();
|
SDL_Surface* surface = get_display()->get_screen_surface();
|
||||||
fs::path directory = config["path"]["screenshots"];
|
fs::path directory = config["path"]["screenshots"];
|
||||||
fs::create_directories(directory);
|
fs::create_directories(directory);
|
||||||
std::string prefix = config["display"]["screenshot-prefix"].
|
std::string prefix = config["display"]["screenshot-prefix"].
|
||||||
get<std::string>();
|
get<std::string>();
|
||||||
std::string extension = config["display"]["screenshot-extension"].
|
std::string extension = config["display"]["screenshot-extension"].
|
||||||
get<std::string>();
|
get<std::string>();
|
||||||
std::stringstream file_pattern;
|
|
||||||
file_pattern << prefix << "(.*)" << extension;
|
|
||||||
fs::path query = directory / file_pattern.str();
|
|
||||||
std::vector<fs::path> files = sfw::glob(query);
|
|
||||||
int zfill = config["display"]["screenshot-zfill"].get<int>();
|
int zfill = config["display"]["screenshot-zfill"].get<int>();
|
||||||
int index = 1;
|
fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension);
|
||||||
if (files.size())
|
|
||||||
{
|
|
||||||
const std::string last = files.back().string();
|
|
||||||
std::smatch matches;
|
|
||||||
std::regex_match(last, matches, std::regex(query.string()));
|
|
||||||
index = std::stoi(matches[1]) + 1;
|
|
||||||
}
|
|
||||||
std::stringstream filename;
|
|
||||||
fs::path path;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
filename << prefix << sfw::pad(index++, zfill) << extension;
|
|
||||||
path = directory / filename.str();
|
|
||||||
filename.str("");
|
|
||||||
filename.clear();
|
|
||||||
}
|
|
||||||
while (fs::exists(path));
|
|
||||||
IMG_SavePNG(surface, path.c_str());
|
IMG_SavePNG(surface, path.c_str());
|
||||||
std::cout << "screenshot saved to " << path.string() << std::endl;
|
SDL_FreeSurface(surface);
|
||||||
|
std::cout << "Saved screenshot to " << path.string() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Surface* Recorder::get_screen_surface()
|
void Recorder::start_recording()
|
||||||
{
|
{
|
||||||
glm::ivec2 size = get_display()->get_window_size();
|
std::cout << "Starting recording..." << std::endl;
|
||||||
unsigned char* pixels = new unsigned char[24 * size.x * size.y];
|
animation.play();
|
||||||
get_screen_pixels(pixels, size.x, size.y);
|
|
||||||
SDL_Surface* surface = zoomSurface(
|
|
||||||
SDL_CreateRGBSurfaceFrom(
|
|
||||||
pixels, size.x, size.y, 24, 3 * size.x, 0, 0, 0, 0),
|
|
||||||
1, -1, SMOOTHING_OFF);
|
|
||||||
delete[] pixels;
|
|
||||||
return surface;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Recorder::get_screen_pixels(unsigned char* pixels, int w, int h, int x, int y)
|
void Recorder::add_frame_to_video()
|
||||||
{
|
{
|
||||||
GLenum format;
|
frames.push_back(get_display()->get_screen_surface());
|
||||||
#if SDL_BYTEORDER == SDL_BIG_ENDIAN
|
}
|
||||||
format = GL_RGB;
|
|
||||||
#else
|
void Recorder::end_recording()
|
||||||
format = GL_BGR;
|
{
|
||||||
#endif
|
std::cout << "Ending recording..." << std::endl;
|
||||||
glReadPixels(x, y, w, h, format, GL_UNSIGNED_BYTE, pixels);
|
animation.reset();
|
||||||
|
SDL_Surface* frame;
|
||||||
|
nlohmann::json config = get_configuration();
|
||||||
|
fs::path root = config["path"]["video"];
|
||||||
|
fs::create_directories(root);
|
||||||
|
fs::path directory = sfw::get_next_file_name(root, 5, "video-");
|
||||||
|
fs::create_directories(directory);
|
||||||
|
std::cout << "Writing recording to " << directory << "..." << std::endl;
|
||||||
|
for (int ii = 0; not frames.empty(); ii++)
|
||||||
|
{
|
||||||
|
frame = frames.front();
|
||||||
|
std::stringstream name;
|
||||||
|
name << sfw::pad(ii, 5) << ".png";
|
||||||
|
fs::path path = directory / name.str();
|
||||||
|
IMG_SavePNG(frame, path.string().c_str());
|
||||||
|
frames.erase(frames.begin());
|
||||||
|
SDL_FreeSurface(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Recorder::update()
|
||||||
|
{
|
||||||
|
animation.update();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,18 +9,10 @@
|
||||||
#define GLM_ENABLE_EXPERIMENTAL
|
#define GLM_ENABLE_EXPERIMENTAL
|
||||||
#include "glm/ext.hpp"
|
#include "glm/ext.hpp"
|
||||||
|
|
||||||
#define GL_GLEXT_PROTOTYPES
|
|
||||||
#define GLEW_STATIC
|
|
||||||
#include "glew/glew.h"
|
|
||||||
|
|
||||||
#include <SDL_image.h>
|
|
||||||
#include "sdl2-gfx/SDL2_gfxPrimitives.h"
|
|
||||||
#include "sdl2-gfx/SDL2_rotozoom.h"
|
|
||||||
|
|
||||||
#include "json/json.hpp"
|
#include "json/json.hpp"
|
||||||
|
|
||||||
#include "filesystem.hpp"
|
#include "filesystem.hpp"
|
||||||
#include "Node.hpp"
|
#include "Animation.hpp"
|
||||||
#include "Delegate.hpp"
|
#include "Delegate.hpp"
|
||||||
#include "Display.hpp"
|
#include "Display.hpp"
|
||||||
#include "extension.hpp"
|
#include "extension.hpp"
|
||||||
|
@ -28,11 +20,17 @@
|
||||||
struct Recorder : Node
|
struct Recorder : Node
|
||||||
{
|
{
|
||||||
|
|
||||||
|
std::vector<SDL_Surface*> frames;
|
||||||
|
Animation animation = Animation(&Recorder::add_frame_to_video, this, 100);
|
||||||
|
|
||||||
Recorder(Node*);
|
Recorder(Node*);
|
||||||
void respond(SDL_Event&);
|
void respond(SDL_Event&);
|
||||||
void capture_screen();
|
void capture_screen();
|
||||||
SDL_Surface* get_screen_surface();
|
void start_recording();
|
||||||
void get_screen_pixels(unsigned char*, int, int, int = 0, int = 0);
|
void add_frame_to_video();
|
||||||
|
void end_recording();
|
||||||
|
void update();
|
||||||
|
std::string get_class_name() { return "Recorder"; }
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
#include "Timer.hpp"
|
||||||
|
|
||||||
|
Timer::Timer()
|
||||||
|
{
|
||||||
|
ticks = SDL_GetTicks();
|
||||||
|
ticks_previous = ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timer::toggle()
|
||||||
|
{
|
||||||
|
is_timing = not is_timing;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timer::toggle(bool state)
|
||||||
|
{
|
||||||
|
is_timing = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timer::reset()
|
||||||
|
{
|
||||||
|
elapsed = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Timer::update()
|
||||||
|
{
|
||||||
|
ticks = SDL_GetTicks();
|
||||||
|
frame_duration = ticks - ticks_previous;
|
||||||
|
if (is_timing)
|
||||||
|
{
|
||||||
|
elapsed += frame_duration;
|
||||||
|
}
|
||||||
|
ticks_previous = ticks;
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#ifndef Timer_h_
|
||||||
|
#define Timer_h_
|
||||||
|
|
||||||
|
#include "SDL.h"
|
||||||
|
|
||||||
|
struct Timer
|
||||||
|
{
|
||||||
|
|
||||||
|
int ticks, ticks_previous, frame_duration = 0, elapsed = 0;
|
||||||
|
bool is_timing = true;
|
||||||
|
|
||||||
|
Timer();
|
||||||
|
|
||||||
|
void toggle();
|
||||||
|
void toggle(bool);
|
||||||
|
void reset();
|
||||||
|
void update();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -9,7 +9,6 @@ std::vector<fs::path> sfw::glob(fs::path query)
|
||||||
}
|
}
|
||||||
std::regex expression(query.string());
|
std::regex expression(query.string());
|
||||||
std::vector<fs::path> files;
|
std::vector<fs::path> files;
|
||||||
std::cout << basename << " " << query << std::endl;
|
|
||||||
for (auto& entry: fs::directory_iterator(basename))
|
for (auto& entry: fs::directory_iterator(basename))
|
||||||
{
|
{
|
||||||
if (std::regex_match(entry.path().string(), expression))
|
if (std::regex_match(entry.path().string(), expression))
|
||||||
|
@ -20,3 +19,31 @@ std::vector<fs::path> sfw::glob(fs::path query)
|
||||||
std::sort(files.begin(), files.end());
|
std::sort(files.begin(), files.end());
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs::path sfw::get_next_file_name(
|
||||||
|
fs::path directory, int zfill, std::string prefix, std::string extension)
|
||||||
|
{
|
||||||
|
std::stringstream file_pattern;
|
||||||
|
file_pattern << prefix << "([0-9]+)" << extension;
|
||||||
|
fs::path query = directory / file_pattern.str();
|
||||||
|
std::vector<fs::path> files = sfw::glob(query);
|
||||||
|
int index = 1;
|
||||||
|
if (files.size())
|
||||||
|
{
|
||||||
|
const std::string last = files.back().string();
|
||||||
|
std::smatch matches;
|
||||||
|
std::regex_match(last, matches, std::regex(query.string()));
|
||||||
|
index = std::stoi(matches[1]) + 1;
|
||||||
|
}
|
||||||
|
std::stringstream filename;
|
||||||
|
fs::path path;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
filename << prefix << sfw::pad(index++, zfill) << extension;
|
||||||
|
path = directory / filename.str();
|
||||||
|
filename.str("");
|
||||||
|
filename.clear();
|
||||||
|
}
|
||||||
|
while (fs::exists(path));
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
namespace sfw
|
namespace sfw
|
||||||
{
|
{
|
||||||
std::vector<fs::path> glob(fs::path);
|
std::vector<fs::path> glob(fs::path);
|
||||||
|
fs::path get_next_file_name(
|
||||||
|
fs::path, int = 0, std::string = "", std::string = "");
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
std::string pad(T end, int width, char fill = '0')
|
std::string pad(T end, int width, char fill = '0')
|
||||||
|
|
Loading…
Reference in New Issue