waveform.cpp 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. /* waveform.cpp - waveform timeline "widget"
  2. * Copyright (C) 2017-2018 caryoscelus
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include <thread>
  18. #include <iostream>
  19. #include <sys/wait.h>
  20. #include <fmt/format.h>
  21. #include <core/audio.h>
  22. #include <core/node/abstract_node.h>
  23. #include <core/os/fork_pipe.h>
  24. #include <generic/node_editor.h>
  25. #include <generic/timeline_editor.h>
  26. #include <widgets/timeline_area.h>
  27. #include <util/strings.h>
  28. using namespace fmt::literals;
  29. namespace rainynite::studio {
  30. class WaveformDisplay :
  31. public TimelineEditor,
  32. public NodeEditor
  33. {
  34. public:
  35. virtual ~WaveformDisplay() {
  36. if (read_thread_1.joinable())
  37. read_thread_1.join();
  38. if (read_thread_2.joinable())
  39. read_thread_2.join();
  40. }
  41. void setup_canvas() override {
  42. node_update();
  43. }
  44. void node_update() override {
  45. if (auto canvas = get_canvas()) {
  46. if (auto file_path = get_path(); !file_path.empty()) {
  47. if (file_path != cached_path) {
  48. generate_waveform(file_path);
  49. }
  50. } else {
  51. canvas->set_background_image({});
  52. }
  53. }
  54. }
  55. private:
  56. string get_path() {
  57. if (auto audio_value = get_node_as<core::Audio>()) {
  58. auto node = dynamic_cast<core::AbstractNode*>(audio_value.get());
  59. if (node == nullptr)
  60. return "";
  61. try {
  62. auto path_node = node->get_property_as<string>("file_path");
  63. if (path_node == nullptr)
  64. return "";
  65. return path_node->value(get_core_context());
  66. } catch (core::NodeAccessError) {
  67. return "";
  68. }
  69. } else {
  70. return "";
  71. }
  72. }
  73. void generate_waveform(string const& file_path) {
  74. FILE* pipe_input; // unused
  75. FILE* pipe_output;
  76. #warning "it isn't very safe to pass unchecked string to external program"
  77. 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"});
  78. cached_path = file_path;
  79. read_thread_1 = std::thread([this, pipe_output, pid]() {
  80. int status;
  81. waitpid(pid, &status, 0);
  82. if (status != 0)
  83. return;
  84. double duration;
  85. if (fscanf(pipe_output, "%lf", &duration) < 1)
  86. return;
  87. if (duration <= 0)
  88. return;
  89. generate_waveform_with_duration(duration);
  90. });
  91. }
  92. void generate_waveform_with_duration(double duration) {
  93. FILE* pipe_input; // unused
  94. FILE* pipe_output; // unused
  95. #warning "it isn't very safe to pass unchecked string to external program"
  96. 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"});
  97. if (read_thread_2.joinable())
  98. read_thread_2.detach();
  99. read_thread_2 = std::thread([this, pid]() {
  100. int status;
  101. waitpid(pid, &status, 0);
  102. if (status == 0)
  103. load_waveform();
  104. });
  105. }
  106. void load_waveform() {
  107. QPixmap pixmap;
  108. pixmap.load(util::str(cached_path+".png"));
  109. get_canvas()->set_background_image(pixmap);
  110. get_canvas()->set_bg_transform(QTransform().scale(1.0/pixels_per_second, 1));
  111. }
  112. private:
  113. string cached_path;
  114. const double pixels_per_second = 48;
  115. std::thread read_thread_1;
  116. std::thread read_thread_2;
  117. };
  118. REGISTER_CANVAS_EDITOR(TimelineArea, WaveformDisplay, core::Audio);
  119. }