Skip to content

Commit

Permalink
rework the recorder layer
Browse files Browse the repository at this point in the history
  • Loading branch information
matcool committed Sep 6, 2021
1 parent c2c98a2 commit f065319
Show file tree
Hide file tree
Showing 9 changed files with 166 additions and 71 deletions.
7 changes: 4 additions & 3 deletions libraries/subprocess.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ namespace subprocess {
STARTUPINFOA start_info = {0};

start_info.cb = sizeof(start_info);
start_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
start_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
start_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
start_info.dwFlags |= STARTF_USESTDHANDLES;
Expand All @@ -78,12 +77,14 @@ namespace subprocess {
return exit_code;
}

void close(bool should_wait = true) {
int close(bool should_wait = true) {
int exit_code = 0;
m_stdin.close();
m_stdout.close();
if (should_wait) wait();
if (should_wait) exit_code = wait();
CloseHandle(m_proc_info.hProcess);
CloseHandle(m_proc_info.hThread);
return exit_code;
}
};
}
76 changes: 57 additions & 19 deletions src/nodes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,28 @@
#include <functional>
#include <stdexcept>

class NumberInputNode : public CCNode, public gd::TextInputDelegate {
#define GEN_CREATE(class_name) template <typename... Args> \
static auto create(Args... args) { \
auto node = new class_name; \
if (node && node->init(args...)) \
node->autorelease(); \
else \
CC_SAFE_DELETE(node); \
return node; \
}

class TextInputNode : public CCNode, public gd::TextInputDelegate {
public:
gd::CCTextInputNode* input_node;
cocos2d::extension::CCScale9Sprite* background;
std::function<void(NumberInputNode*)> callback = [](auto){};
std::function<void(TextInputNode*)> callback = [](auto){};

static auto create(CCSize size, float bg_scale = 1.f) {
auto node = new NumberInputNode;
if (node && node->init(size, bg_scale)) {
node->autorelease();
} else {
CC_SAFE_DELETE(node);
}
return node;
}
GEN_CREATE(TextInputNode)

bool init(CCSize size, float scale) {
bool init(CCSize size, float scale = 1.f, const std::string& font = "bigFont.fnt") {
if (!CCNode::init()) return false;

input_node = gd::CCTextInputNode::create("", this, "bigFont.fnt", size.width, size.height);
input_node->setAllowedChars("0123456789");
input_node = gd::CCTextInputNode::create("", this, font.c_str(), size.width, size.height);
input_node->setDelegate(this);
addChild(input_node);

Expand All @@ -38,19 +39,56 @@ class NumberInputNode : public CCNode, public gd::TextInputDelegate {
return true;
}

virtual void textChanged(gd::CCTextInputNode*) {
callback(this);
}
virtual void textChanged(gd::CCTextInputNode*) { callback(this); }

void set_value(const std::string& value) { input_node->setString(value.c_str()); }
std::string get_value() { return input_node->getString(); }
};

class NumberInputNode : public TextInputNode {
public:
std::function<void(NumberInputNode*)> callback = [](auto){};

GEN_CREATE(NumberInputNode)

void set_value(int value) {
input_node->setString(std::to_string(value).c_str());
bool init(CCSize size, float scale = 1.f, const std::string& font = "bigFont.fnt") {
if (!TextInputNode::init(size, scale, font)) return false;
input_node->setAllowedChars("0123456789");
return true;
}
virtual void textChanged(gd::CCTextInputNode*) { callback(this); }

void set_value(int value) { input_node->setString(std::to_string(value).c_str()); }
int get_value() {
try {
return std::stoi(input_node->getString());
} catch (const std::invalid_argument&) {
return -1;
}
}
};

template <typename T>
class NodeFactory {
public:
static auto& start(T* node) { return *reinterpret_cast<NodeFactory*>(node); }

template <typename... Args>
static auto& start(Args... args) { return *reinterpret_cast<NodeFactory*>(T::create(args...)); }

operator T*() { return reinterpret_cast<T*>(this); }

#define _gen_func(name) template <typename... Args> \
inline auto& name(Args... args) { \
reinterpret_cast<T*>(this)->name(args...); \
return *this; \
}

_gen_func(setPosition)
_gen_func(setScale)
_gen_func(setContentSize)
_gen_func(setOpacity)
_gen_func(setZOrder)

#undef _gen_func
};
11 changes: 11 additions & 0 deletions src/overlay_layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -289,5 +289,16 @@ void OverlayLayer::on_toggle_showcase(CCObject* toggle_) {
}

void OverlayLayer::on_recorder(CCObject*) {
static bool has_ffmpeg = false;
if (!has_ffmpeg) {
// theres prob a way to do it by not spawning a process but im lazy and hate dealing with winapi
auto process = subprocess::Popen("where ffmpeg");
if (process.close())
gd::FLAlertLayer::create(nullptr, "Error", "Ok", nullptr, "ffmpeg was not found in your path, recorder will not work without it")->show();
else
has_ffmpeg = true;
if (!has_ffmpeg)
return;
}
RecorderLayer::create()->show();
}
17 changes: 10 additions & 7 deletions src/recorder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@

Recorder::Recorder() : m_width(1280), m_height(720), m_fps(60) {}

void Recorder::start() {
void Recorder::start(const std::string& path) {
m_recording = true;
m_last_frame_t = m_extra_t = 0;
m_renderer.m_width = m_width;
m_renderer.m_height = m_height;
m_renderer.begin();
std::cout << "i will now create da thread" << std::endl;
std::thread([&]() {
std::cout << "in da thread" << std::endl;
std::thread([&, path]() {
std::stringstream stream;
stream << "ffmpeg -y -f rawvideo -pix_fmt rgb24 -s " << m_width << "x" << m_height << " -r " << m_fps
<< " -i - -c:v h264_amf -b:v 50M -vf \"vflip\" -an \"" << m_output_path << "\" "; // i hope just putting it in "" escapes it
std::cout << "i will now create process" << std::endl;
<< " -i - ";
if (!m_codec.empty())
stream << "-c:v " << m_codec << " ";
if (!m_bitrate.empty())
stream << "-b:v " << m_bitrate << " ";
if (!m_extra_args.empty())
stream << m_extra_args << " ";
stream << "-vf \"vflip\" -an \"" << path << "\" "; // i hope just putting it in "" escapes it
auto process = subprocess::Popen(stream.str());
while (m_recording) {
m_lock.lock();
Expand All @@ -33,7 +37,6 @@ void Recorder::start() {
}

void Recorder::stop() {
std::cout << "stopping renderer" << std::endl;
m_renderer.end();
m_recording = false;
}
Expand Down
5 changes: 2 additions & 3 deletions src/recorder.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ class MyRenderTexture {
class Recorder {
public:
Recorder();
// subprocess::Popen m_process;
std::queue<std::vector<u8>> m_frames;
std::mutex m_lock;
MyRenderTexture m_renderer;
Expand All @@ -58,9 +57,9 @@ class Recorder {
bool m_recording = false;
float m_last_frame_t, m_extra_t;
bool m_until_end = true;
std::string m_output_path = "recording.mp4";
std::string m_codec = "", m_bitrate = "30M", m_extra_args = "";

void start();
void start(const std::string& path);
void stop();
void capture_frame();
};
114 changes: 78 additions & 36 deletions src/recorder_layer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ bool RecorderLayer::init() {
addChild(m_pLayer);

auto bg = cocos2d::extension::CCScale9Sprite::create("GJ_square01.png", { 0.0f, 0.0f, 80.0f, 80.0f });
bg->setContentSize({250, 200});
const CCSize window_size(400, 250);
bg->setContentSize(window_size);
bg->setPosition(win_size / 2);
m_pLayer->addChild(bg);

const CCPoint top_left = win_size / 2.f - ccp(window_size.width / 2.f, -window_size.height / 2.f);

m_pButtonMenu = CCMenu::create();
m_pButtonMenu->setPosition({0, 0});
auto menu = m_pButtonMenu; // sorry m_pButtonMenu is too much to type
Expand All @@ -29,58 +32,95 @@ bool RecorderLayer::init() {
auto& rs = ReplaySystem::get_instance();

auto toggler = gd::CCMenuItemToggler::create(check_off_sprite, check_on_sprite, this, menu_selector(RecorderLayer::on_toggle_recorder));
toggler->setPosition(center - ccp(98.0f, -70.0f));
toggler->setPosition(top_left + ccp(30.f, -30.f));
toggler->toggle(rs.recorder.m_recording);
auto label = CCLabelBMFont::create("Record", "bigFont.fnt");
label->setPosition(center - ccp(73.f, -70.f));
label->setPosition(top_left + ccp(55.f, -30.f));
label->setScale(0.7f);
label->setAnchorPoint({0, 0.5f});
menu->addChild(toggler);
layer->addChild(label);

toggler = gd::CCMenuItemToggler::create(check_off_sprite, check_on_sprite, this, menu_selector(RecorderLayer::on_toggle_until_end));
toggler->setPosition(center - ccp(98.0f, -35.0f));
toggler->setPosition(top_left + ccp(30.f, -65.f));
toggler->toggle(rs.recorder.m_until_end);
label = CCLabelBMFont::create("Record until the end", "bigFont.fnt");
label->limitLabelWidth(180.f, 1.f, 0.1f);
label->setPosition(center - ccp(73.f, -35.f));
label->setScale(0.7f);
label->setPosition(top_left + ccp(55.f, -65.f));
label->setAnchorPoint({0, 0.5f});
menu->addChild(toggler);
layer->addChild(label);

auto input = NumberInputNode::create({70.f, 30.f});
auto input = NumberInputNode::create(CCSize(70.f, 30.f));
input->set_value(rs.recorder.m_width);
input->setPosition(center - ccp(79, 0));
input->setPosition(top_left + ccp(49.f, -104.f));
input->input_node->setMaxLabelScale(0.73f);
input->callback = [](auto input) {
ReplaySystem::get_instance().recorder.m_width = input->get_value();
input->callback = [&rs](auto input) {
rs.recorder.m_width = input->get_value();
};
layer->addChild(input);

input = NumberInputNode::create({70.f, 30.f});
layer->addChild(NodeFactory<CCLabelBMFont>::start("x", "bigFont.fnt")
.setPosition(top_left + ccp(93.5f, -104.f))
.setScale(0.5f));

input = NumberInputNode::create(CCSize(70.f, 30.f));
input->set_value(rs.recorder.m_height);
input->setPosition(center - ccp(4, 0));
input->setPosition(top_left + ccp(137.f, -104.f));
input->input_node->setMaxLabelScale(0.73f);
input->callback = [&rs](auto input) {
rs.recorder.m_height = input->get_value();
};
layer->addChild(input);

input = NumberInputNode::create({50.f, 30.f});
layer->addChild(NodeFactory<CCLabelBMFont>::start("@", "bigFont.fnt")
.setPosition(top_left + ccp(185.5f, -104.f))
.setScale(0.5f));

input = NumberInputNode::create(CCSize(50.f, 30.f));
input->set_value(rs.recorder.m_fps);
input->setPosition(center + ccp(76, 0));
input->setPosition(top_left + ccp(225.f, -104.f));
input->input_node->setMaxLabelScale(0.73f);
input->callback = [&rs](auto input) {
rs.recorder.m_fps = input->get_value();
};
layer->addChild(input);

auto btn = gd::CCMenuItemSpriteExtra::create(
CCSprite::createWithSpriteFrameName("gj_folderBtn_001.png"),
this,
menu_selector(RecorderLayer::on_pick_path)
);
btn->setPosition(center - ccp(98.f, 63.f));
menu->addChild(btn);
const std::string broad_filter = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ.,;-_=+@!\":0123456789$[](){}";

auto text_input = TextInputNode::create(CCSize(60.f, 30.f), 1.f, "chatFont.fnt");
text_input->setPosition(top_left + ccp(291.5f, -177.f));
text_input->input_node->setAllowedChars(broad_filter);
text_input->set_value(rs.recorder.m_bitrate);
text_input->callback = [&rs](auto input) {
rs.recorder.m_bitrate = input->get_value();
};
layer->addChild(text_input);

text_input = TextInputNode::create(CCSize(60.f, 30.f), 1.f, "chatFont.fnt");
text_input->setPosition(top_left + ccp(359.5f, -177.f));
text_input->input_node->m_sCaption = "Default";
text_input->input_node->setAllowedChars(broad_filter);
text_input->input_node->setLabelPlaceholderColor({200, 200, 200});
text_input->set_value(rs.recorder.m_codec);
text_input->callback = [&rs](auto input) {
rs.recorder.m_codec = input->get_value();
};
layer->addChild(text_input);

text_input = TextInputNode::create(CCSize(128.f, 30.f), 1.f, "chatFont.fnt");
text_input->setPosition(top_left + ccp(324.5f, -217.f));
text_input->input_node->m_sCaption = "Extra options";
text_input->input_node->setAllowedChars(broad_filter);
text_input->set_value(rs.recorder.m_extra_args);
text_input->callback = [&rs](auto input) {
rs.recorder.m_extra_args = input->get_value();
};
text_input->input_node->setLabelPlaceholderColor({200, 200, 200});
layer->addChild(text_input);

layer->addChild(NodeFactory<CCLabelBMFont>::start("Bitrate", "bigFont.fnt").setPosition(top_left + ccp(291.5f, -152.f)).setScale(0.4f));
layer->addChild(NodeFactory<CCLabelBMFont>::start("Codec", "bigFont.fnt").setPosition(top_left + ccp(359.5f, -152.f)).setScale(0.4f));

registerWithTouchDispatcher();
CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2);
Expand All @@ -92,7 +132,8 @@ bool RecorderLayer::init() {
);

m_pButtonMenu->addChild(close_btn);
close_btn->setPosition(50.f, win_size.height - 50.f);
close_btn->setPosition(18.f, win_size.height - 18.f);
close_btn->getNormalImage()->setScale(.75f);

setKeypadEnabled(true);
setTouchEnabled(true);
Expand All @@ -111,25 +152,26 @@ void RecorderLayer::on_close(CCObject*) {

void RecorderLayer::on_toggle_recorder(CCObject* obj) {
auto& rs = ReplaySystem::get_instance();
if (static_cast<gd::CCMenuItemToggler*>(obj)->isOn()) {
auto toggler = static_cast<gd::CCMenuItemToggler*>(obj);
if (toggler->isOn()) {
rs.recorder.stop();
} else {
// TODO: warn the user? idk theyre kinda dumb
if (!rs.is_playing())
rs.toggle_playing();
rs.recorder.start();
nfdchar_t* path = nullptr;
if (NFD_SaveDialog("mp4", nullptr, &path) == NFD_OKAY) {
// TODO: warn the user? idk theyre kinda dumb
if (!rs.is_playing())
rs.toggle_playing();
std::string p = std::string(path) + ".mp4";
std::cout << "saving it to " << p << std::endl;
rs.recorder.start(p);
free(path);
} else {
// toggle it on so then gd does !on and then turns it off then boom success
toggler->toggle(true);
}
}
}

void RecorderLayer::on_toggle_until_end(CCObject* obj) {
ReplaySystem::get_instance().recorder.m_until_end = !static_cast<gd::CCMenuItemToggler*>(obj)->isOn();
}

void RecorderLayer::on_pick_path(CCObject*) {
nfdchar_t* path = nullptr;
if (NFD_SaveDialog("mp4", nullptr, &path) == NFD_OKAY) {
ReplaySystem::get_instance().recorder.m_output_path = std::string(path) + ".mp4";
std::cout << "set path to " << ReplaySystem::get_instance().recorder.m_output_path << std::endl;
free(path);
}
}
Loading

0 comments on commit f065319

Please sign in to comment.