123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135 |
- /* waveform.cpp - waveform timeline "widget"
- * Copyright (C) 2017-2018 caryoscelus
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <thread>
- #include <iostream>
- #include <sys/wait.h>
- #include <fmt/format.h>
- #include <core/audio.h>
- #include <core/node/abstract_node.h>
- #include <core/os/fork_pipe.h>
- #include <generic/node_editor.h>
- #include <generic/timeline_editor.h>
- #include <widgets/timeline_area.h>
- #include <util/strings.h>
- using namespace fmt::literals;
- namespace rainynite::studio {
- class WaveformDisplay :
- public TimelineEditor,
- public NodeEditor
- {
- public:
- virtual ~WaveformDisplay() {
- if (read_thread_1.joinable())
- read_thread_1.join();
- if (read_thread_2.joinable())
- read_thread_2.join();
- }
- void setup_canvas() override {
- node_update();
- }
- void node_update() override {
- if (auto canvas = get_canvas()) {
- if (auto file_path = get_path(); !file_path.empty()) {
- if (file_path != cached_path) {
- generate_waveform(file_path);
- }
- } else {
- canvas->set_background_image({});
- }
- }
- }
- private:
- string get_path() {
- if (auto audio_value = get_node_as<core::Audio>()) {
- auto node = dynamic_cast<core::AbstractNode*>(audio_value.get());
- if (node == nullptr)
- return "";
- try {
- auto path_node = node->get_property_as<string>("file_path");
- if (path_node == nullptr)
- return "";
- return path_node->value(get_core_context());
- } catch (core::NodeAccessError) {
- return "";
- }
- } else {
- return "";
- }
- }
- void generate_waveform(string const& file_path) {
- FILE* pipe_input; // unused
- FILE* pipe_output;
- #warning "it isn't very safe to pass unchecked string to external program"
- auto pid = fork_pipe(pipe_input, pipe_output, {"/usr/bin/env", "ffprobe", "-i", file_path, "-show_entries", "format=duration", "-v", "quiet", "-of", "csv=p=0"});
- cached_path = file_path;
- read_thread_1 = std::thread([this, pipe_output, pid]() {
- int status;
- waitpid(pid, &status, 0);
- if (status != 0)
- return;
- double duration;
- if (fscanf(pipe_output, "%lf", &duration) < 1)
- return;
- if (duration <= 0)
- return;
- generate_waveform_with_duration(duration);
- });
- }
- void generate_waveform_with_duration(double duration) {
- FILE* pipe_input; // unused
- FILE* pipe_output; // unused
- #warning "it isn't very safe to pass unchecked string to external program"
- auto pid = fork_pipe(pipe_input, pipe_output, {"/usr/bin/env", "ffmpeg", "-i", cached_path, "-filter_complex", "showwavespic=s={}x{}:colors=black|gray"_format(int(duration*pixels_per_second), 80), "-frames:v", "1", "-y", cached_path+".png"});
- if (read_thread_2.joinable())
- read_thread_2.detach();
- read_thread_2 = std::thread([this, pid]() {
- int status;
- waitpid(pid, &status, 0);
- if (status == 0)
- load_waveform();
- });
- }
- void load_waveform() {
- QPixmap pixmap;
- pixmap.load(util::str(cached_path+".png"));
- get_canvas()->set_background_image(pixmap);
- get_canvas()->set_bg_transform(QTransform().scale(1.0/pixels_per_second, 1));
- }
- private:
- string cached_path;
- const double pixels_per_second = 48;
- std::thread read_thread_1;
- std::thread read_thread_2;
- };
- REGISTER_CANVAS_EDITOR(TimelineArea, WaveformDisplay, core::Audio);
- }
|