diff --git a/src/Application.cppm b/src/Application.cppm index 6a7510c..9e8af0b 100644 --- a/src/Application.cppm +++ b/src/Application.cppm @@ -2,6 +2,7 @@ module; #include "macros.hpp" #include +#include #include #include #include @@ -16,6 +17,7 @@ export module Application; import Node; import Base; import Button; +using command_line_options::detail::list; namespace ffmpegraph { export class Application { public: @@ -60,11 +62,12 @@ Application::Application(std::string _title) InitWindow(800, 600, this->title.c_str()); SetTargetFPS(60); SetExitKey(KEY_NULL); - add_buttons.emplace_back(10, 10, 100, 20, "String Constant"); - add_buttons.emplace_back(10, 35, 100, 20, "Input File"); - add_buttons.emplace_back(10, 60, 100, 20, "Output File"); - add_buttons.emplace_back(10, 85 , 100, 20, "Stream Spec Constant"); - + add_buttons.emplace_back(0, 0, 200, 20, "String Constant"); + add_buttons.emplace_back(0, 0, 200, 20, "File Input"); + add_buttons.emplace_back(0, 0, 200, 20, "File Output"); + add_buttons.emplace_back(0, 0, 200, 20, "Stream Spec Constant"); + add_buttons.emplace_back(0, 0, 200, 20, "Splitter"); + AllOptions::each([&]() { add_buttons.emplace_back(0, 0, 200, 20, std::string(T::label_view)); }); run_button.width = 10 + MeasureText("Run", 10); } @@ -83,19 +86,25 @@ void Application::ProcessEvents() { void Application::OnLeftClick() { if (current_state == State::ADDING) { - if (add_buttons[0].IsHovered()) { - nodes.push_back(std::make_unique()); - } else if (add_buttons[1].IsHovered()) { - nodes.push_back(std::make_unique()); - } else if (add_buttons[2].IsHovered()) { - nodes.push_back(std::make_unique()); - } else if (add_buttons[3].IsHovered()) { - nodes.push_back(std::make_unique()); - } + usz i = 0; current_state = State::DEFAULT; + list::each([&]() { + if (add_buttons[i++].IsHovered()) { + nodes.push_back(std::make_unique()); + selected_node = nodes.back().get(); + current_state = State::EDITING; + } + }); + AllOptions::each([&]() { + if (add_buttons[i++].IsHovered()) { + nodes.push_back(std::make_unique()); + selected_node = nodes.back().get(); + current_state = State::EDITING; + } + }); + return; } - auto GetPort = [&] -> Port * { for (auto &node : nodes) { for (auto *port : node->ports) { @@ -116,15 +125,13 @@ void Application::OnLeftClick() { selected_port = nullptr; current_state = State::DEFAULT; } else if (run_button.IsHovered()) { - if (auto res = ExecuteGraph(); not res) { - message_queue.push_front(res.error()); - } + if (auto res = ExecuteGraph(); not res) { message_queue.push_front(res.error()); } } else { i32 x = GetMouseX(); i32 y = GetMouseY(); auto port = GetPort(); auto oport = dynamic_cast(port); - if (oport) { + if (oport and oport->type != PortTypeE::GENERIC) { if (oport->GetConnected()) { oport->Disconnect(); } else { @@ -154,13 +161,20 @@ Result<> Application::ExecuteGraph() { for (auto [i, node] : vws::enumerate(nodes)) { Node *raw_node = node.get(); if (auto n = dynamic_cast(raw_node)) { - if (auto c = n->output_port.GetConnected()) { adj_list[index_map[c->owner]].insert(index_map[raw_node]); } + if (auto c = n->out.GetConnected()) { adj_list[index_map[c->owner]].insert(index_map[raw_node]); } } if (auto n = dynamic_cast(raw_node)) { if (auto c = n->out.GetConnected()) { adj_list[index_map[c->owner]].insert(index_map[raw_node]); } } if (auto _ = dynamic_cast(raw_node)) {} // OutputNode doesn’t have output ports. + if (auto n = dynamic_cast(raw_node)) { + if (auto c = n->out.GetConnected()) { adj_list[index_map[c->owner]].insert(index_map[raw_node]); } + } + if (auto n = dynamic_cast(raw_node)) { + if (auto c = n->out1.GetConnected()) { adj_list[index_map[c->owner]].insert(index_map[raw_node]); } + if (auto c = n->out2.GetConnected()) { adj_list[index_map[c->owner]].insert(index_map[raw_node]); } + } } // TODO add cases for all new types of nodes @@ -177,26 +191,20 @@ Result<> Application::ExecuteGraph() { break; } } - if (not changed) { - return Error("Graph has cycle"); - } + if (not changed) { return Error("Graph has cycle"); } } for (auto node : sorted_nodes) { Try(node->Run()); } - return{}; + return {}; } void Application::OnKeyPressed(i32 key) { - if ((key == KEY_EQUAL or key == KEY_KP_ADD) and current_state == State::DEFAULT) { - current_state = State::ADDING; - } + if ((key == KEY_EQUAL or key == KEY_KP_ADD) and current_state == State::DEFAULT) { current_state = State::ADDING; } if (current_state != State::EDITING) return; if (key == KEY_DELETE) { - for(auto& node : nodes) { - for (auto raw_port: node->ports) { - if (auto port = dynamic_cast(raw_port)) { - if (auto a = port->GetConnected(); a and a->owner == selected_node) { - port->Disconnect(); - } + for (auto &node : nodes) { + for (auto raw_port : node->ports) { + if (auto port = dynamic_cast(raw_port)) { + if (auto a = port->GetConnected(); a and a->owner == selected_node) { port->Disconnect(); } } } } @@ -257,8 +265,18 @@ void Application::Render() { offset += 20; } if (current_state == State::ADDING) { + auto maxx = GetScreenWidth(); + auto currentx = 10; + auto currenty = 10; for (auto b : add_buttons) { + b.pos_x = currentx; + b.pos_y = currenty; b.Render(); + currentx += 210; + if (currentx + 200 > maxx) { + currentx = 10; + currenty += 25; + } } } else { if (selected_port) { DrawLine(selected_port->pos_x, selected_port->pos_y, GetMouseX(), GetMouseY(), BLACK); } diff --git a/src/Node.cppm b/src/Node.cppm index a105607..bf52120 100644 --- a/src/Node.cppm +++ b/src/Node.cppm @@ -1,5 +1,5 @@ module; -#include +#include #include #include #include @@ -7,6 +7,8 @@ export module Node; export import :Port; import Base; +using command_line_options::detail::list; +using command_line_options::detail::static_string; export namespace ffmpegraph { struct Node { @@ -27,14 +29,18 @@ struct Node { std::vector ports; virtual Result<> Run() = 0; virtual void OnDisconnect() {} + virtual void OnConnect() {} protected: i32 width = min_port_width; }; + struct InputNode : Node { InputNode(); Label label; - InputPort filename_port; - OutputPort output_port; + InputPort prev; + InputPort opts; + InputPort filename; + OutputPort out; Result<> Run() override; }; @@ -42,8 +48,9 @@ struct OutputNode : Node { OutputNode(); Result<> Run() override; Label label; - InputPort in_data; - InputPort in_filename; + InputPort prev; + InputPort opts; + InputPort filename; }; struct StringConstantNode : Node { @@ -63,7 +70,105 @@ struct StreamSpecConstantNode : Node { }; struct SplitterNode : Node { + SplitterNode(); + Result<> Run() override; + void OnDisconnect() override; + void OnConnect() override; + + Label label; + InputPort in; + OutputPort out1; + OutputPort out2; +}; + +template +struct OptionNode : Node { + OptionNode() + : label(std::string(name.sv()), this) + , prev("Chain", this, PortTypeE::OPTIONS) + , arg(std::string(arg_name.sv()), this, arg_type) + , out("Out", this, PortTypeE::OPTIONS) { + ports.push_back(&label); + ports.push_back(&prev); + ports.push_back(&arg); + ports.push_back(&out); + } + Result<> Run() override { + out.SetValue(std::format("{} {} {}", prev.GetValue(), ffmpegopt.sv(), arg.GetValue())); + return {}; + } + + Label label; + InputPort prev; + InputPort arg; + OutputPort out; + static constexpr std::string_view label_view = name.sv(); +}; + +template +struct SpecOptionNode : Node { + SpecOptionNode() + : label(std::string(name.sv()), this) + , prev("Chain", this, PortTypeE::OPTIONS) + , spec("spec", this, PortTypeE::STREAM) + , arg(std::string(arg_name.sv()), this, arg_type) + , out("Out", this, PortTypeE::OPTIONS) { + ports.push_back(&label); + ports.push_back(&prev); + ports.push_back(&spec); + ports.push_back(&arg); + ports.push_back(&out); + } + Result<> Run() override { + out.SetValue( + std::format( + "{} {}{}{} {}", prev.GetValue(), ffmpegopt.sv(), std::get_if(&spec.GetValue()) ? "" : ":", + spec.GetValue(), arg.GetValue() + ) + ); + return {}; + } + Label label; + InputPort prev; + InputPort spec; + InputPort arg; + OutputPort out; + static constexpr std::string_view label_view = name.sv(); }; -} +// General options +using ForceFormatOptionNode = OptionNode<"Force Format", "-f", PortTypeE::STRING>; +using StreamLoopOptionNode = OptionNode<"Loop Stream", "-stream_loop", PortTypeE::INT, "Count">; +using DurationOptionNode = OptionNode<"Duration", "-t", PortTypeE::DURATION>; +using PositionOptionNode = OptionNode<"Position", "-to", PortTypeE::DURATION>; +using SizeLimitOptionNode = OptionNode<"Size Limit", "-fs", PortTypeE::INT, "Bytes">; +using SeekStartOptionNode = OptionNode<"Seek from Start", "-ss", PortTypeE::DURATION>; +using SeekEndOptionNode = OptionNode<"Seek from End", "-sseof", PortTypeE::DURATION>; +using SyncToOptionNode = OptionNode<"Sync to Input", "-isync", PortTypeE::INT, "Input index">; +using InputTimeOffsetOptionNode = OptionNode<"Input Time Offset", "-itsoffset", PortTypeE::DURATION>; +using InputTimeScaleOptionNode = OptionNode<"Input Time Scale", "-itsscale", PortTypeE::DURATION>; +using TimestampOptionNode = OptionNode<"Output Timestamp", "-timestamp", PortTypeE::STRING>; +using TargetOptionNode = OptionNode<"Target", "-target", PortTypeE::STRING>; +using DiscardAllOptionNode = OptionNode<"Discard All", "-dn", PortTypeE::STRING, "IGNORED">; +using AttachOptionNode = OptionNode<"Attach", "-attach", PortTypeE::STRING, "Filename">; +using CodecOptionNode = SpecOptionNode<"Codec", "-c", PortTypeE::STRING>; +using DispositionOptionNode = SpecOptionNode<"Disposition", "-disposition", PortTypeE::STRING>; +using FrameLimitOptionNode = SpecOptionNode<"Frame Limit", "-frames", PortTypeE::INT, "Count">; +using QualityScaleOptionNode = SpecOptionNode<"Quality Scale", "-qscale", PortTypeE::STRING>; +using PresetOptionNode = SpecOptionNode<"Preset", "-pre", PortTypeE::STRING>; +using ExtractAttachment = SpecOptionNode<"Extract Attachment", "-dump_attachment", PortTypeE::STRING, "Filename">; +using AllOptions = list< + ForceFormatOptionNode, StreamLoopOptionNode, DurationOptionNode, PositionOptionNode, SizeLimitOptionNode, + SeekStartOptionNode, SeekEndOptionNode, SyncToOptionNode, InputTimeOffsetOptionNode, InputTimeScaleOptionNode, + TimestampOptionNode, TargetOptionNode, DiscardAllOptionNode, AttachOptionNode, CodecOptionNode, + DispositionOptionNode, FrameLimitOptionNode, QualityScaleOptionNode, PresetOptionNode, ExtractAttachment>; + +// -program [title=title:][program_num=program_num:]st=stream[:st=stream...] +// -stream_group +// [map=input_file_id=stream_group][type=type:]st=stream[:st=stream][:stg=stream_group][:id=stream_group_id...] +// -filter[:stream_specifier] filtergraph +// -reinit_filter[:stream_specifier] integer +// -metadata[:metadata_specifier] key=value + +} // namespace ffmpegraph diff --git a/src/Nodes/InputNode.cpp b/src/Nodes/InputNode.cpp index cbceccf..164526a 100644 --- a/src/Nodes/InputNode.cpp +++ b/src/Nodes/InputNode.cpp @@ -5,17 +5,21 @@ module Node; using namespace ffmpegraph; InputNode::InputNode() - : label("Input File", this) - , filename_port("Filename", this, PortTypeE::STRING) - , output_port("Out", this, PortTypeE::STRING) { + : label("File Input", this) + , prev("Chain", this, PortTypeE::INPUTS) + , opts("Options", this, PortTypeE::OPTIONS) + , filename("Filename", this, PortTypeE::STRING) + , out("Out", this, PortTypeE::INPUTS) { ports.push_back(&label); - ports.push_back(&filename_port); - ports.push_back(&output_port); + ports.push_back(&prev); + ports.push_back(&opts); + ports.push_back(&filename); + ports.push_back(&out); } Result<> InputNode::Run() { - auto data = std::get_if(&filename_port.GetValue()); + auto data = std::get_if(&filename.GetValue()); if (not data or data->empty()) return Error("File Input needs a filename "); - output_port.SetValue(std::format("-i {}", *data)); + out.SetValue(std::format("{} {} -i '{}'", prev.GetValue(), opts.GetValue(), *data)); return {}; } diff --git a/src/Nodes/OutputNode.cpp b/src/Nodes/OutputNode.cpp index 6ba22a2..71bffed 100644 --- a/src/Nodes/OutputNode.cpp +++ b/src/Nodes/OutputNode.cpp @@ -1,20 +1,24 @@ module; +#include #include module Node; using namespace ffmpegraph; OutputNode::OutputNode() : label("File Output", this) - , in_data("Data", this, PortTypeE::STRING) - , in_filename("File", this, PortTypeE::STRING) { + , prev("Chain", this, PortTypeE::INPUTS) + , opts("Opts", this, PortTypeE::OPTIONS) + , filename("File", this, PortTypeE::STRING) { ports.push_back(&label); - ports.push_back(&in_data); - ports.push_back(&in_filename); + ports.push_back(&prev); + ports.push_back(&opts); + ports.push_back(&filename); } + Result<> OutputNode::Run() { - auto data = std::get_if(&in_data.GetValue()); + auto data = std::get_if(&prev.GetValue()); if (not data) return {}; - auto fname = std::get_if(&in_filename.GetValue()); + auto fname = std::get_if(&filename.GetValue()); auto path = fname and not fname->empty() ? std::string_view{*fname} : "/tmp/ffmpegraph_out"; - return Error("ffmpeg {} {}", *data, path); -} \ No newline at end of file + return Error("ffmpeg -nostdin {} {} '{}'", *data, opts.GetValue(), path); +} diff --git a/src/Nodes/SplitterNode.cpp b/src/Nodes/SplitterNode.cpp new file mode 100644 index 0000000..37642d9 --- /dev/null +++ b/src/Nodes/SplitterNode.cpp @@ -0,0 +1,34 @@ +module Node; +import Base; +using namespace ffmpegraph; + +SplitterNode::SplitterNode() + : label("Splitter", this) + , in("Input", this, PortTypeE::GENERIC) + , out1("Output", this, PortTypeE::GENERIC) + , out2("Output", this, PortTypeE::GENERIC) { + ports.push_back(&label); + ports.push_back(&in); + ports.push_back(&out1); + ports.push_back(&out2); +} + +Result<> SplitterNode::Run() { + out1.SetValue(in.GetValue()); + out2.SetValue(in.GetValue()); + return {}; +} + +void SplitterNode::OnConnect() { + out1.type = out2.type = in.type; +} + + +void SplitterNode::OnDisconnect() { + in.type = out1.type = out2.type = PortTypeE::GENERIC; + out1.Disconnect(); + out2.Disconnect(); +} + + + diff --git a/src/Nodes/StreamSpecConstantNode.cpp b/src/Nodes/StreamSpecConstantNode.cpp index 2ee2ce5..86ad484 100644 --- a/src/Nodes/StreamSpecConstantNode.cpp +++ b/src/Nodes/StreamSpecConstantNode.cpp @@ -2,7 +2,7 @@ module Node; using namespace ffmpegraph; StreamSpecConstantNode::StreamSpecConstantNode() - : label("Stream Specifier Constant", this), in("Stream Specifier", this), out("Out", this, PortTypeE::STREAM_SPEC) { + : label("Stream Specifier Constant", this), in("Stream Specifier", this), out("Out", this, PortTypeE::STREAM) { ports.push_back(&label); ports.push_back(&in); ports.push_back(&out); diff --git a/src/Port.cppm b/src/Port.cppm index 7e2e0b0..6d5cbdc 100644 --- a/src/Port.cppm +++ b/src/Port.cppm @@ -12,7 +12,10 @@ enum struct PortTypeE { GENERIC, INT, STRING, - STREAM_SPEC + STREAM, + OPTIONS, + INPUTS, + DURATION, }; struct PortType { @@ -29,9 +32,12 @@ template <> struct std::formatter : std::formatter string_view { switch (data.t) { case ffmpegraph::PortTypeE::INT: return "integer"; - case ffmpegraph::PortTypeE::STREAM_SPEC: return "stream spec"; + case ffmpegraph::PortTypeE::STREAM: return "stream"; case ffmpegraph::PortTypeE::STRING: return "string"; case ffmpegraph::PortTypeE::GENERIC: return "any"; + case ffmpegraph::PortTypeE::OPTIONS: return "options"; + case ffmpegraph::PortTypeE::INPUTS: return "inputs"; + case ffmpegraph::PortTypeE::DURATION: return "duration"; default: return "unknown_type"; } }(); @@ -40,7 +46,7 @@ template <> struct std::formatter : std::formatter; } @@ -48,7 +54,7 @@ template <> struct std::formatter : std::formatter auto format(ffmpegraph::PortData const& data, FormatContext& ctx) const { auto repr = std::visit( utils::Overloaded{ - [](std::monostate) { return "()"s; }, + [](std::monostate) { return ""s; }, [](std::string const& x) { return x; }, [](i32 x) { return std::to_string(x); }, }, @@ -75,18 +81,17 @@ struct OutputPort; struct InputPort : Port { protected: - PortType type; PortData value = std::monostate{}; public: InputPort(std::string name, Node* owner, PortType type) : Port(std::move(name), owner), type(type) {} PortData const& GetValue() const; + PortType type; i32 Render(i32 x, i32 y, i32 width) override; friend OutputPort; }; struct OutputPort : Port { protected: - PortType type; InputPort* connected = nullptr; public: OutputPort(std::string name, Node* owner, PortType type) : Port(std::move(name), owner), type(type) {} @@ -94,6 +99,7 @@ public: bool TryConnect(InputPort& ip); InputPort* GetConnected() const { return connected; } void Disconnect(); + PortType type; i32 Render(i32 x, i32 y, i32 width) override; }; diff --git a/src/Ports/InputPort.cpp b/src/Ports/InputPort.cpp index 9aeed16..7995f8a 100644 --- a/src/Ports/InputPort.cpp +++ b/src/Ports/InputPort.cpp @@ -14,8 +14,3 @@ i32 InputPort::Render(i32 x, i32 y, i32 width) { DrawCircle(x, y + 10, 2.5f, BLACK); return 10 + MeasureText(formatted.c_str(), 10); } -i32 Label::Render(i32 x, i32 y, i32 width) { - DrawRectangleLines(x, y, width, 20, BLACK); - DrawText(name.c_str(), x + 5, y + 5, 10, BLACK); - return 10 + MeasureText(name.c_str(), 10); -} diff --git a/src/Ports/Label.cpp b/src/Ports/Label.cpp new file mode 100644 index 0000000..d1d758e --- /dev/null +++ b/src/Ports/Label.cpp @@ -0,0 +1,10 @@ +module; +#include +module Node; +using namespace ffmpegraph; + +i32 Label::Render(i32 x, i32 y, i32 width) { + DrawRectangleLines(x, y, width, 20, BLACK); + DrawText(name.c_str(), x + 5, y + 5, 10, BLACK); + return 10 + MeasureText(name.c_str(), 10); +} diff --git a/src/Ports/OutputPort.cpp b/src/Ports/OutputPort.cpp index e791d94..528cfbf 100644 --- a/src/Ports/OutputPort.cpp +++ b/src/Ports/OutputPort.cpp @@ -1,6 +1,6 @@ module; -#include #include +#include module Node; using namespace ffmpegraph; @@ -8,16 +8,19 @@ void OutputPort::SetValue(PortData value) { if (connected) { connected->value = std::move(value); } } bool OutputPort::TryConnect(InputPort& ip) { + if (ip.type == PortTypeE::GENERIC) { + ip.type = type; + ip.owner->OnConnect(); + } if (ip.type == type) { connected = &ip; + return true; } return false; } void OutputPort::Disconnect() { - if (connected) { - connected->owner->OnDisconnect(); - } + if (connected) { connected->owner->OnDisconnect(); } connected = nullptr; } i32 OutputPort::Render(i32 x, i32 y, i32 width) { @@ -29,4 +32,4 @@ i32 OutputPort::Render(i32 x, i32 y, i32 width) { if (type != PortTypeE::GENERIC) DrawCircle(x + width, y + 10, 2.5f, BLACK); if (connected) { DrawLine(pos_x, pos_y, connected->pos_x, connected->pos_y, BLUE); } return 10 + MeasureText(formatted.c_str(), 10); -} \ No newline at end of file +}