diff --git a/demo/config.json b/demo/config.json index e908808..f1ce35b 100644 --- a/demo/config.json +++ b/demo/config.json @@ -13,10 +13,12 @@ }, "keys": { - "context": " " + "context": " ", + "keep-stash": "k" }, "recording": { - "write-mp4": true + "write-mp4": true, + "video-frame-length": 30 } } diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 2b2b32e..4ef4112 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -13,7 +13,7 @@ Configuration::Configuration(Node *parent, fs::path path) : Node(parent) void Configuration::set_defaults() { sys_config["keys"] = { - {"record", {"CTRL", "SHIFT", "f10"}}, + {"record", {"CTRL", "SHIFT", "r"}}, {"screenshot", "f9"}, {"action", "space"}, {"up", "up"}, @@ -35,7 +35,10 @@ void Configuration::set_defaults() {"screenshot-extension", ".png"}, {"screenshot-zfill", 5}, {"gif-frame-length", 100}, - {"write-mp4", false} + {"write-mp4", false}, + {"max-stash-length", 5000}, + {"max-in-game-stashes", 3}, + {"max-video-stashes", 40} }; } diff --git a/src/Recorder.cpp b/src/Recorder.cpp index 6ff39f6..c682137 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -5,6 +5,8 @@ Recorder::Recorder(Node* parent) : Node(parent) { get_delegate().subscribe(&Recorder::respond, this); + in_game_stashes.push_back(Stash()); + animation.play(); } float Recorder::get_frame_length() @@ -21,15 +23,19 @@ void Recorder::respond(SDL_Event& event) } else if (get_delegate().compare(event, "record")) { - if (animation.playing) + if (is_recording) { end_recording(); } - else + else if (not writing_recording) { start_recording(); } } + else if (get_delegate().compare(event, "keep-stash")) + { + std::cout << get_memory_size() << "mb" << std::endl; + } } void Recorder::capture_screen() @@ -42,7 +48,7 @@ void Recorder::capture_screen() get(); std::string extension = config["recording"]["screenshot-extension"]. get(); - int zfill = config["recording"]["screenshot-zfill"].get(); + int zfill = config["recording"]["screenshot-zfill"]; fs::path path = sfw::get_next_file_name(directory, zfill, prefix, extension); IMG_SavePNG(surface, path.c_str()); SDL_FreeSurface(surface); @@ -52,58 +58,92 @@ void Recorder::capture_screen() void Recorder::start_recording() { std::cout << "Starting recording..." << std::endl; - animation.set_frame_length(get_frame_length()); - animation.play(); + is_recording = true; + video_stashes.push_back(Stash()); } -void Recorder::add_frame_to_video() +void Recorder::add_frame() { - // frames.push_back(get_display().get_screen_surface()); glm::ivec2 size = get_display().get_window_size(); - unsigned char* pixels = new unsigned char[Display::bpp / 8 * size.x * size.y]; + int bytes = Display::bpp / 8 * size.x * size.y; + unsigned char* pixels = new unsigned char[bytes]; get_display().get_screen_pixels(pixels, size.x, size.y); - pixel_buffers.push_back(pixels); + Stash& stash = in_game_stashes.back(); + int max_length = get_configuration()["recording"]["max-stash-length"]; + float length = get_frame_length() * stash.pixel_buffers.size(); + if (length > max_length) + { + delete[] stash.pixel_buffers.front(); + stash.pixel_buffers.erase(stash.pixel_buffers.begin()); + } + stash.pixel_buffers.push_back(pixels); + if (is_recording) + { + unsigned char* vid_pixels = new unsigned char[bytes]; + memcpy(vid_pixels, pixels, bytes); + video_stashes.back().pixel_buffers.push_back(vid_pixels); + if (video_stashes.back().pixel_buffers.size() * get_frame_length() > max_length) + { + std::function f = + std::bind(&Recorder::write_stash_frames, this, std::placeholders::_1); + std::thread writing(f, video_stashes.size() - 1); + writing.detach(); + video_stashes.push_back( + Stash(video_stashes.back().frame_offset + video_stashes.back().pixel_buffers.size())); + } + } } -void Recorder::end_recording() +int Recorder::get_memory_size() { - std::cout << "Ending recording..." << std::endl; - animation.reset(); - if (pixel_buffers.size() > 0) + glm::ivec2 window = get_display().get_window_size(); + int bytes = Display::bpp / 8 * window.x * window.y; + int size = 0; + for (Stash& stash : in_game_stashes) + { + size += stash.pixel_buffers.size(); + } + for (Stash& stash : video_stashes) + { + size += stash.pixel_buffers.size(); + } + return size * bytes / 1000000; +} + +void Recorder::write_stash_frames(int index) +{ + if (video_stashes[index].frame_offset == 0) { 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::function f = std::bind(&Recorder::write_video_frames, this, std::placeholders::_1); - std::thread writing(f, directory); - writing.detach(); + current_video_directory = directory; } -} - -void Recorder::write_video_frames(fs::path directory) -{ - std::cout << "Writing recording to " << directory << "..." << std::endl; + std::cout << "Writing stash index " << index << " to " << current_video_directory << "..." << + std::endl; SDL_Surface* frame; GifWriter gif_writer; int gif_frame_length = get_configuration()["recording"]["gif-frame-length"]; + fs::path gif_path = sfw::get_next_file_name( + current_video_directory, 3, "gif-", ".gif"); float elapsed = 0, last_gif_write = 0, gif_write_overflow = 0; - std::stringstream gif_name; - gif_name << directory.string() << ".gif"; - fs::path gif_path = directory / gif_name.str(); - for (int ii = 0; not pixel_buffers.empty(); ii++) + for (int ii = video_stashes[index].frame_offset; + not video_stashes[index].pixel_buffers.empty(); ii++) { - frame = get_display().get_screen_surface_from_pixels(pixel_buffers.front()); + frame = get_display().get_screen_surface_from_pixels( + video_stashes[index].pixel_buffers.front()); std::stringstream name; name << sfw::pad(ii, 5) << ".png"; - fs::path path = directory / name.str(); + fs::path path = current_video_directory / name.str(); IMG_SavePNG(frame, path.string().c_str()); - if (ii == 0 or elapsed - last_gif_write + gif_write_overflow >= gif_frame_length) + if (ii == video_stashes[index].frame_offset or + elapsed - last_gif_write + gif_write_overflow >= gif_frame_length) { - if (ii == 0) + if (ii == video_stashes[index].frame_offset) { - GifBegin(&gif_writer, gif_name.str().c_str(), frame->w, + GifBegin(&gif_writer, gif_path.string().c_str(), frame->w, frame->h, gif_frame_length / 10); } else @@ -115,25 +155,75 @@ void Recorder::write_video_frames(fs::path directory) frame->w, frame->h, gif_frame_length / 10); } elapsed += get_frame_length(); - delete[] pixel_buffers.front(); - pixel_buffers.erase(pixel_buffers.begin()); + delete[] video_stashes[index].pixel_buffers.front(); + video_stashes[index].pixel_buffers.erase(video_stashes[index].pixel_buffers.begin()); SDL_FreeSurface(frame); } GifEnd(&gif_writer); +} + +void Recorder::keep_stash() +{ + in_game_stashes.push_back(Stash()); + int max_stashes = get_configuration()["recording"]["max-in-game-stashes"]; + if (in_game_stashes.size() > max_stashes) + { + Stash& stash = in_game_stashes.front(); + while (not stash.pixel_buffers.empty()) + { + delete[] stash.pixel_buffers.back(); + stash.pixel_buffers.pop_back(); + } + in_game_stashes.erase(in_game_stashes.begin()); + } +} + +void Recorder::end_recording() +{ + std::cout << "Ending recording..." << std::endl; + is_recording = false; + writing_recording = true; + std::function f = std::bind(&Recorder::finish_writing_video, this); + std::thread finishing(f); + finishing.detach(); +} + +void Recorder::finish_writing_video() +{ + write_stash_frames(video_stashes.size() - 1); + int count; + while (true) + { + count = 0; + for (Stash& stash : video_stashes) + { + count += stash.pixel_buffers.size(); + } + if (count == 0) + { + break; + } + } + video_stashes.clear(); + writing_recording = false; + std::cout << "Wrote video frames to " << current_video_directory.string() << + std::endl; + glm::ivec2 size = get_display().get_window_size(); if (get_configuration()["recording"]["write-mp4"]) { std::stringstream mp4_command; - fs::path images_match = directory / "%05d.png"; + fs::path images_match = current_video_directory / "%05d.png"; mp4_command << "ffmpeg -f image2 -framerate " << (1000 / get_frame_length()) << - " -i " << images_match.string() << " -s " << frame->w << "x" << frame->h << - " -c:v libx264 -crf 17 -pix_fmt yuv420p " << directory.string() << ".mp4"; + " -i " << images_match.string() << " -s " << size.x << "x" << size.y << + " -c:v libx264 -crf 17 -pix_fmt yuv444p " << + current_video_directory.string() << ".mp4"; std::cout << mp4_command.str() << std::endl; std::system(mp4_command.str().c_str()); } - std::cout << "Wrote " << directory << std::endl; } void Recorder::update() { + animation.set_frame_length(get_frame_length()); animation.update(); } diff --git a/src/Recorder.hpp b/src/Recorder.hpp index 0d2023d..6d14791 100644 --- a/src/Recorder.hpp +++ b/src/Recorder.hpp @@ -20,20 +20,34 @@ #include "Display.hpp" #include "extension.hpp" +struct Stash +{ + std::vector pixel_buffers; + int frame_offset; + + Stash(int frame_offset = 0) : frame_offset(frame_offset) {} +}; + struct Recorder : Node { - std::vector frames; - std::vector pixel_buffers; - Animation animation = Animation(&Recorder::add_frame_to_video, this); + std::vector in_game_stashes; + std::vector video_stashes; + fs::path current_video_directory; + Animation animation = Animation(&Recorder::add_frame, this); + bool is_recording = false, writing_recording = false; Recorder(Node*); float get_frame_length(); void respond(SDL_Event&); void capture_screen(); void start_recording(); - void add_frame_to_video(); + void add_frame(); + int get_memory_size(); + void write_stash_frames(int); + void keep_stash(); void end_recording(); + void finish_writing_video(); void write_video_frames(fs::path); void update(); std::string get_class_name() { return "Recorder"; }