Skip to content

Commit ed1619a

Browse files
committed
feat: waveform, start/end grabs, more stuff
1 parent 97a1db9 commit ed1619a

11 files changed

Lines changed: 598 additions & 67 deletions

File tree

src/common/utils.cpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,54 @@ u::VideoInfo u::get_video_info(const std::filesystem::path& path) {
359359
return info;
360360
}
361361

362+
std::vector<int16_t> u::get_video_waveform(const std::filesystem::path& path) {
363+
namespace bp = boost::process;
364+
365+
bp::ipstream pipe_stream;
366+
bp::child c(
367+
boost::filesystem::path{ blur.ffmpeg_path },
368+
"-v",
369+
"error",
370+
"-i",
371+
path.string(),
372+
"-f",
373+
"s16le",
374+
"-acodec",
375+
"pcm_s16le",
376+
"-ac",
377+
"1",
378+
"-ar",
379+
"44100",
380+
"-",
381+
bp::std_out > pipe_stream,
382+
bp::std_err.null()
383+
#ifdef _WIN32
384+
,
385+
bp::windows::create_no_window
386+
#endif
387+
);
388+
389+
std::vector<int16_t> samples;
390+
std::vector<char> buffer(4096);
391+
392+
while (pipe_stream.read(buffer.data(), buffer.size()) || pipe_stream.gcount() > 0) {
393+
auto bytes_read = static_cast<std::size_t>(pipe_stream.gcount());
394+
395+
// Ensure we read full samples
396+
if (bytes_read % 2 != 0)
397+
--bytes_read;
398+
399+
std::vector<int16_t> chunk(bytes_read / 2);
400+
std::memcpy(chunk.data(), buffer.data(), bytes_read);
401+
402+
samples.insert(samples.end(), chunk.begin(), chunk.end());
403+
}
404+
405+
c.wait();
406+
407+
return samples;
408+
}
409+
362410
bool u::test_hardware_device(const std::string& device_type) {
363411
namespace bp = boost::process;
364412

src/common/utils.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ namespace u {
370370
};
371371

372372
VideoInfo get_video_info(const std::filesystem::path& path);
373+
std::vector<int16_t> get_video_waveform(const std::filesystem::path& path);
373374

374375
struct EncodingDevice {
375376
std::string type; // "nvidia", "amd", "intel", "mac"

src/gui/components/main.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,13 +223,21 @@ void main::home_screen(ui::Container& container, float delta_time) {
223223
"blur title text", container, title_pos, "blur", gfx::Color::white(), fonts::header_font, FONT_CENTERED_X
224224
);
225225

226-
ui::add_video(
226+
auto video = ui::add_video(
227227
"test video",
228228
container,
229229
tasks::video_player_path,
230230
gfx::Size(container.get_usable_rect().w, container.get_usable_rect().h / 2)
231231
);
232232

233+
if (video) {
234+
auto video_rect = (*video)->element->rect;
235+
236+
const auto& video_data = std::get<ui::VideoElementData>((*video)->element->data);
237+
238+
ui::add_video_track("test video track", container, video_rect.w, video_data);
239+
}
240+
233241
if (!initialisation_res) {
234242
ui::add_text(
235243
"failed to initialise text",

src/gui/render/render.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include <misc/freetype/imgui_freetype.h>
77
#include <imgui_internal.h>
88

9+
#include <algorithm>
10+
911
#include "../fonts/dejavu_sans.h"
1012
#include "../fonts/eb_garamond.h"
1113
#include "../fonts/icons.h"
@@ -647,6 +649,84 @@ void render::loader(const gfx::Rect& rect, const gfx::Color& color) {
647649
text(rect.center(), color, "loading...", fonts::dejavu, FONT_CENTERED_X | FONT_CENTERED_Y);
648650
}
649651

652+
void render::waveform(
653+
const gfx::Rect& rect,
654+
const gfx::Rect& active_rect,
655+
const gfx::Color& color,
656+
const std::vector<int16_t>& samples,
657+
bool filled
658+
) {
659+
if (samples.empty())
660+
return;
661+
662+
const size_t width = rect.w;
663+
const size_t height = rect.h;
664+
665+
// Find max amplitude to normalize waveform
666+
int16_t max_sample = 1;
667+
for (auto s : samples) {
668+
max_sample = std::max<int>(std::abs(s), max_sample);
669+
}
670+
671+
for (size_t x = 0; x < width; ++x) {
672+
// Map pixel x to corresponding sample index
673+
float sample_pos = static_cast<float>(x) / width * samples.size();
674+
auto idx_start = static_cast<size_t>(sample_pos);
675+
size_t idx_end =
676+
std::min(static_cast<size_t>(sample_pos + (samples.size() / static_cast<float>(width))), samples.size());
677+
678+
// Take max amplitude in this range
679+
int16_t max_amp = 0;
680+
for (size_t i = idx_start; i < idx_end; ++i) {
681+
max_amp = std::max(max_amp, static_cast<int16_t>(std::abs(samples[i])));
682+
}
683+
684+
// Normalize to [0,1]
685+
float norm = static_cast<float>(max_amp) / max_sample;
686+
int y_center = rect.y + (height / 2);
687+
int y_half = static_cast<int>(norm * (height / 2.f));
688+
689+
auto x_color = color;
690+
if (!active_rect.contains(gfx::Point(rect.x + x, y_center)))
691+
x_color = color.adjust_alpha(0.5f);
692+
693+
if (filled) {
694+
gfx::Rect r{ static_cast<int>(rect.x + x), y_center - y_half, 1, y_half * 2 };
695+
rect_filled(r, x_color);
696+
}
697+
else {
698+
gfx::Point p1{ static_cast<int>(rect.x + x), y_center - y_half };
699+
gfx::Point p2{ static_cast<int>(rect.x + x), y_center + y_half };
700+
line(p1, p2, x_color, true);
701+
}
702+
}
703+
}
704+
705+
void render::rect_side(const gfx::Rect& rect, const gfx::Color& color, RectSide side, int thickness) {
706+
switch (side) {
707+
case RectSide::LEFT: {
708+
// Top horizontal
709+
rect_filled(gfx::Rect{ rect.x, rect.y - thickness, rect.w, thickness }, color);
710+
// Vertical
711+
rect_filled(
712+
gfx::Rect{ rect.x - thickness, rect.y - thickness, thickness, rect.h + (thickness * 2) }, color
713+
);
714+
// Bottom horizontal
715+
rect_filled(gfx::Rect{ rect.x, rect.y + rect.h, rect.w, thickness }, color);
716+
break;
717+
}
718+
case RectSide::RIGHT: {
719+
// Top horizontal
720+
rect_filled(gfx::Rect{ rect.x, rect.y - thickness, rect.w, thickness }, color);
721+
// Vertical
722+
rect_filled(gfx::Rect{ rect.x + rect.w, rect.y - thickness, thickness, rect.h + (thickness * 2) }, color);
723+
// Bottom horizontal
724+
rect_filled(gfx::Rect{ rect.x, rect.y + rect.h, rect.w, thickness }, color);
725+
break;
726+
}
727+
}
728+
}
729+
650730
void render::push_clip_rect(const gfx::Rect& rect, bool intersect_clip_rect) {
651731
imgui.drawlist->PushClipRect(rect.origin(), rect.max(), intersect_clip_rect);
652732
}

src/gui/render/render.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,21 @@ namespace render {
241241

242242
void loader(const gfx::Rect& rect, const gfx::Color& color);
243243

244+
void waveform(
245+
const gfx::Rect& rect,
246+
const gfx::Rect& active_rect,
247+
const gfx::Color& color,
248+
const std::vector<int16_t>& samples,
249+
bool filled = true
250+
);
251+
252+
enum class RectSide {
253+
LEFT,
254+
RIGHT
255+
};
256+
257+
void rect_side(const gfx::Rect& rect, const gfx::Color& color, RectSide side, int thickness = 1);
258+
244259
void push_clip_rect(const gfx::Rect& rect, bool intersect_clip_rect = false);
245260
void push_clip_rect(int x1, int y1, int x2, int y2, bool intersect_clip_rect = false);
246261
void push_fullscreen_clip_rect();

src/gui/ui/elements/video.cpp

Lines changed: 8 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <algorithm>
2+
13
#include "../ui.h"
24
#include "../../render/render.h"
35
#include "../keys.h"
@@ -17,11 +19,11 @@ namespace {
1719

1820
if (it == video_players.end()) {
1921
auto player = std::make_shared<VideoPlayer>();
20-
player->load_file(key.c_str());
22+
player->load_file(key);
2123
u::log("loaded video from {}", key);
2224

2325
auto insert_result = video_players.insert({ key, player });
24-
it = insert_result.first; // safe because insert always returns valid iterator
26+
it = insert_result.first;
2527
}
2628

2729
return it->second;
@@ -61,23 +63,6 @@ void ui::handle_videos_event(const SDL_Event& event, bool& to_render) {
6163
}
6264
}
6365

64-
void ui::render_video_track(
65-
const Container& container, const AnimatedElement& element, std::optional<FrameData> frame_data
66-
) {
67-
const auto& video_data = std::get<VideoElementData>(element.element->data);
68-
float anim = element.animations.at(hasher("main")).current;
69-
70-
auto usable_rect = element.element->rect.expand(2);
71-
72-
render::line({ usable_rect.x, usable_rect.y2() }, { usable_rect.x2(), usable_rect.y2() }, gfx::Color::green());
73-
74-
if (frame_data) {
75-
float perc = static_cast<float>(frame_data->current_frame) / static_cast<float>(frame_data->total_frames);
76-
auto progress = perc * usable_rect.w;
77-
render::circle_filled({ static_cast<int>(usable_rect.x + progress), usable_rect.y2() }, 99, gfx::Color::red());
78-
}
79-
}
80-
8166
void ui::render_video(const Container& container, const AnimatedElement& element) {
8267
const auto& video_data = std::get<VideoElementData>(element.element->data);
8368

@@ -94,7 +79,7 @@ void ui::render_video(const Container& container, const AnimatedElement& element
9479
return;
9580
}
9681

97-
auto usable_rect = element.element->rect.shrink(2); // account for border
82+
auto usable_rect = element.element->rect.shrink(3); // account for border
9883

9984
// TODO: render::image
10085
render::imgui.drawlist->AddImage(
@@ -106,18 +91,9 @@ void ui::render_video(const Container& container, const AnimatedElement& element
10691
IM_COL32(255, 255, 255, alpha) // apply alpha for fade animations
10792
);
10893

109-
auto frame_data = video_data.player->get_video_frame_data();
110-
111-
if (frame_data) {
112-
render::text(
113-
{ usable_rect.center().x + 30, usable_rect.y - fonts::dejavu.height() },
114-
gfx::Color::white(),
115-
std::format("{}/{}", frame_data->current_frame, frame_data->total_frames),
116-
fonts::dejavu
117-
);
118-
}
119-
120-
render_video_track(container, element, frame_data);
94+
render::borders(
95+
element.element->rect, gfx::Color(155, 155, 155, stroke_alpha), gfx::Color(80, 80, 80, stroke_alpha)
96+
);
12197
}
12298

12399
bool ui::update_video(const Container& container, AnimatedElement& element) {

0 commit comments

Comments
 (0)