Skip to content

Commit 80ea76e

Browse files
committed
Add menu option for encoding timelapse with ffmpeg
1 parent 56e916d commit 80ea76e

File tree

3 files changed

+105
-1
lines changed

3 files changed

+105
-1
lines changed

data/settings.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ new-image-palette = %appdata/palettes/default.png
1919
mouse-wheel-invert-x = false
2020
mouse-wheel-invert-y = false
2121

22+
#ffmpeg-path = C:/ffmpeg.exe
23+
2224
#OpenGLMajor = 3
2325
#OpenGLMinor = 0
2426
#OpenGLProfile = es

data/skins/default/gui/MainWindow.xml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
</clickout>
8181

8282
<clickout id="recmenu" class="menu">
83-
<image class="menucontainer" width="170px" x="100">
83+
<image class="menucontainer" width="200px" x="100">
8484
<menubutton
8585
controller="recorderstatus"
8686
click="togglerec"
@@ -91,6 +91,11 @@
9191
click="resetrecsequence"
9292
label="Reset Frame (#${value})"
9393
forward="labelspan.text=text"/>
94+
<menubutton
95+
controller="recorderencode"
96+
click="recorderencode"
97+
label="Encode MP4"
98+
forward="labelspan.text=text"/>
9499
</image>
95100
</clickout>
96101

src/cmd/Recording.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Read LICENSE.txt for more information.
44

55
#include <cmd/Command.hpp>
6+
#include <common/Config.hpp>
67
#include <common/Messages.hpp>
78
#include <common/PubSub.hpp>
89
#include <doc/Cell.hpp>
@@ -12,11 +13,16 @@
1213
#include <gui/Controller.hpp>
1314
#include <gui/Node.hpp>
1415
#include <log/Log.hpp>
16+
#include <optional>
17+
#include <task/TaskManager.hpp>
1518

1619
namespace msg {
1720
struct RecorderStart {};
1821
struct RecorderStop {};
1922
struct RecorderUpdateSequence {U32 number;};
23+
struct RecorderEncodingStart {};
24+
struct RecorderEncodingDone {};
25+
struct RecorderEncodingFail {};
2026
}
2127

2228
U32 initSequence() {
@@ -41,6 +47,47 @@ U32& sequence() {
4147
return num;
4248
}
4349

50+
class Encoder {
51+
public:
52+
TaskHandle handle;
53+
54+
bool done() {
55+
return handle.task->isDone();
56+
}
57+
58+
void run() {
59+
String cmd;
60+
PubSub<>::pub(msg::RecorderEncodingStart{});
61+
62+
auto userdata = std::filesystem::path{inject<FileSystem>{}->find("%userdata", "dir")->getUID()};
63+
cmd += "cd " + join(split(userdata.string(), "\\"), "\\\\") + " && ";
64+
65+
cmd += "\"";
66+
if (auto ffmpeg = inject<Config>{}->properties->get<String>("ffmpeg-path"); ffmpeg.empty()) {
67+
cmd += "ffmpeg";
68+
} else {
69+
cmd += join(split(ffmpeg, "\\"), "\\\\");
70+
}
71+
cmd += "\" -y ";
72+
cmd += "-start_number 1 -i \"recording_%d.png\" -c:v libx264 -r 30 -pix_fmt yuv420p out.mp4";
73+
74+
logI(cmd);
75+
76+
handle = inject<TaskManager>{}->add(
77+
[=]() -> int {
78+
return system(cmd.c_str());
79+
},
80+
[=](int code){
81+
if (code == 0) {
82+
PubSub<>::pub(msg::RecorderEncodingDone{});
83+
} else {
84+
PubSub<>::pub(msg::RecorderEncodingFail{});
85+
}
86+
});
87+
}
88+
};
89+
static std::optional<Encoder> encoder;
90+
4491
class Recorder {
4592
PubSub<msg::ModifyDocument,
4693
msg::Tick> pub{this};
@@ -118,6 +165,43 @@ class RecorderSequence : public ui::Controller {
118165
};
119166
static ui::Controller::Shared<RecorderSequence> recseq{"recordersequence"};
120167

168+
class RecorderEncode : public ui::Controller {
169+
public:
170+
PubSub<msg::RecorderEncodingStart,
171+
msg::RecorderEncodingDone,
172+
msg::RecorderEncodingFail,
173+
msg::RecorderUpdateSequence> pub{this};
174+
Property<String> label{this, "label", "${value}"};
175+
176+
void attach() override {
177+
if (encoder) {
178+
if (encoder->done())
179+
on(msg::RecorderEncodingDone{});
180+
else
181+
on(msg::RecorderEncodingStart{});
182+
} else {
183+
on(msg::RecorderUpdateSequence{});
184+
}
185+
}
186+
187+
void on(const msg::RecorderUpdateSequence&) {
188+
node()->set("text", "Encode MP4");
189+
}
190+
191+
void on(const msg::RecorderEncodingDone&) {
192+
node()->set("text", "Encoding Done");
193+
}
194+
195+
void on(const msg::RecorderEncodingStart&) {
196+
node()->set("text", "Encoding MP4");
197+
}
198+
199+
void on(const msg::RecorderEncodingFail&) {
200+
node()->set("text", "Encoding Error");
201+
}
202+
};
203+
static ui::Controller::Shared<RecorderEncode> recenc{"recorderencode"};
204+
121205
class ToggleRecording : public Command {
122206
public:
123207
void run() override {
@@ -138,3 +222,16 @@ class ResetRecordingSequence : public Command {
138222
}
139223
};
140224
static Command::Shared<ResetRecordingSequence> cmd2{"resetrecsequence"};
225+
226+
class RecorderEncodeCmd : public Command {
227+
public:
228+
void run() override {
229+
if (encoder && !encoder->done()) {
230+
return;
231+
}
232+
encoder = std::nullopt;
233+
encoder.emplace();
234+
encoder->run();
235+
}
236+
};
237+
static Command::Shared<RecorderEncodeCmd> cmd3{"recorderencode"};

0 commit comments

Comments
 (0)