2 * Copyright (C) 2016-2019 Robin Gareus <robin@gareus.org>
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 2 of the License, or
7 * (at your option) any later version.
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.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 #include "pbd/basename.h"
25 #include "pbd/stateful.h"
26 #include "ardour/filename_extensions.h"
27 #include "ardour/send.h"
28 #include "ardour/track.h"
35 using namespace ARDOUR;
36 using namespace SessionUtils;
38 static bool opt_debug_dump = false;
39 static bool opt_copy_busses = false;
40 static bool opt_verbose = false;
41 static bool opt_log = false;
43 /* this is copied from Session::new_route_from_template */
45 trim_state_for_mixer_copy (Session*s, XMLNode& node)
47 /* trim bitslots from listen sends so that new ones are used */
48 XMLNodeList children = node.children ();
49 for (XMLNodeList::iterator i = children.begin (); i != children.end (); ++i) {
50 if ((*i)->name() == X_("Processor")) {
51 /* ForceIDRegeneration does not catch the following */
52 XMLProperty const * role = (*i)->property (X_("role"));
53 XMLProperty const * type = (*i)->property (X_("type"));
54 if (role && role->value () == X_("Aux")) {
55 /* check if the target bus exists,
56 * HERE: we use the bus-name (not target-id)
58 XMLProperty const * target = (*i)->property (X_("name"));
60 (*i)->set_property ("type", "dangling-aux-send");
63 boost::shared_ptr<Route> r = s->route_by_name (target->value ());
64 if (!r || boost::dynamic_pointer_cast<Track> (r)) {
65 (*i)->set_property ("type", "dangling-aux-send");
68 (*i)->set_property ("target", r->id ().to_s ());
70 if (role && role->value () == X_("Listen")) {
71 (*i)->remove_property (X_("bitslot"));
73 else if (role && (role->value () == X_("Send") || role->value () == X_("Aux"))) {
76 xrole = Delivery::Role (string_2_enum (role->value (), xrole));
77 std::string name = Send::name_and_id_new_send (*s, xrole, bitslot, false);
78 (*i)->remove_property (X_("bitslot"));
79 (*i)->remove_property (X_("name"));
80 (*i)->set_property ("bitslot", bitslot);
81 (*i)->set_property ("name", name);
83 else if (type && type->value () == X_("intreturn")) {
84 // ignore, in case bus existed in old session,
85 // tracks in old session may be connected to it.
86 // if the bus is new, new_route_from_template()
87 // will have re-created an ID.
88 (*i)->set_property ("type", "ignore-aux-return");
90 else if (type && type->value () == X_("return")) {
91 // Return::set_state() generates a new one
92 (*i)->remove_property (X_("bitslot"));
94 else if (type && type->value () == X_("port")) {
95 // PortInsert::set_state() handles the bitslot
96 (*i)->remove_property (X_("bitslot"));
97 (*i)->set_property ("ignore-name", "1");
104 copy_mixer_settings (Session*s, boost::shared_ptr<Route> dst, XMLNode& state)
106 PBD::Stateful::ForceIDRegeneration force_ids;
108 trim_state_for_mixer_copy (s, state);
109 state.remove_nodes_and_delete ("Diskstream");
110 state.remove_nodes_and_delete ("Automation");
111 if (opt_debug_dump) {
115 dst->set_state (state, PBD::Stateful::loading_state_version);
119 copy_session_routes (
120 const std::string& src_path, const std::string& src_name,
121 const std::string& dst_path, const std::string& dst_load, const std::string& dst_save)
123 SessionUtils::init (opt_log);
126 typedef std::map<std::string,XMLNode*> StateMap;
130 s = SessionUtils::load_session (src_path, src_name, false);
133 fprintf (stderr, "Cannot load source session %s/%s.\n", src_path.c_str (), src_name.c_str ());
134 SessionUtils::cleanup ();
138 /* get route state from first session */
139 boost::shared_ptr<RouteList> rl = s->get_routes ();
140 for (RouteList::iterator i = rl->begin (); i != rl->end (); ++i) {
141 boost::shared_ptr<Route> r = *i;
142 if (r->is_master () || r->is_monitor () || r->is_auditioner ()) {
145 XMLNode& state (r->get_state ());
146 routestate[r->name ()] = &state;
147 if (boost::dynamic_pointer_cast<Track> (r)) {
150 buslist[r->name ()] = &state;
153 SessionUtils::unload_session (s);
156 /* open target session */
157 s = SessionUtils::load_session (dst_path, dst_load);
159 fprintf (stderr, "Cannot load target session %s/%s.\n", dst_path.c_str (), dst_load.c_str ());
160 SessionUtils::cleanup ();
164 /* iterate over all busses in the src session, add missing ones to target */
165 if (opt_copy_busses) {
166 rl = s->get_routes ();
167 for (StateMap::const_iterator i = buslist.begin (); i != buslist.end (); ++i) {
168 if (s->route_by_name (i->first)) {
171 XMLNode& rs (*(i->second));
172 s->new_route_from_template (1, PresentationInfo::max_order, rs, rs.property (X_("name"))->value (), NewPlaylist);
176 /* iterate over all *busses* in the target session.
177 * setup internal return targets.
179 rl = s->get_routes ();
180 for (RouteList::iterator i = rl->begin (); i != rl->end (); ++i) {
181 boost::shared_ptr<Route> r = *i;
182 /* skip special busses */
183 if (r->is_master () || r->is_monitor () || r->is_auditioner ()) {
186 if (boost::dynamic_pointer_cast<Track> (r)) {
189 /* find matching route by name */
190 std::map<std::string,XMLNode*>::iterator it = routestate.find (r->name ());
191 if (it == routestate.end ()) {
193 printf (" -- no match for '%s'\n", (*i)->name ().c_str ());
198 printf ("-- found match '%s'\n", (*i)->name ().c_str ());
200 XMLNode *state = it->second;
202 copy_mixer_settings (s, r, *state);
205 /* iterate over all tracks in the target session.. */
206 rl = s->get_routes ();
207 for (RouteList::iterator i = rl->begin (); i != rl->end (); ++i) {
208 boost::shared_ptr<Route> r = *i;
209 /* skip special busses */
210 if (r->is_master () || r->is_monitor () || r->is_auditioner ()) {
213 if (!boost::dynamic_pointer_cast<Track> (r)) {
217 /* find matching route by name */
218 std::map<std::string,XMLNode*>::iterator it = routestate.find (r->name ());
219 if (it == routestate.end ()) {
221 printf (" -- no match for '%s'\n", (*i)->name ().c_str ());
226 printf ("-- found match '%s'\n", (*i)->name ().c_str ());
228 XMLNode *state = it->second;
230 copy_mixer_settings (s, r, *state);
233 s->save_state (dst_save);
236 SessionUtils::unload_session (s);
239 for (StateMap::iterator i = routestate.begin (); i != routestate.end (); ++i) {
240 XMLNode *state = i->second;
244 SessionUtils::cleanup ();
249 static void usage () {
250 // help2man compatible format (standard GNU help-text)
251 printf (UTILNAME " - copy mixer settings from one session to another.\n\n");
252 printf ("Usage: " UTILNAME " [ OPTIONS ] <src> <dst>\n\n");
255 -h, --help display this help and exit\n\
256 -b, --bus-copy add busses present in src to dst\n\
257 -d, --debug print pre-processed XML for each route\n\
258 -l, --log-messages display libardour log messages\n\
259 -s, --snapshot <name> create a new snapshot in dst\n\
260 -v, --verbose show performed copy operations\n\
261 -V, --version print version information and exit\n\
265 This utility copies mixer-settings from the src-session to the dst-session.\n\
266 Both <src> and <dst> are paths to .ardour session files.\n\
267 If --snapshot is not given, the <dst> session file is overwritten.\n\
268 When --snapshot is set, a new snaphot in the <dst> session is created.\n\
271 printf ("Report bugs to <http://tracker.ardour.org/>\n"
272 "Website: <http://ardour.org/>\n");
273 ::exit (EXIT_SUCCESS);
276 static bool ends_with (std::string const& value, std::string const& ending)
278 if (ending.size() > value.size()) return false;
279 return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
282 int main (int argc, char* argv[])
284 const char *optstring = "bhls:Vv";
286 const struct option longopts[] = {
287 { "bus-copy", required_argument, 0, 'b' },
288 { "debug", no_argument, 0, 'd' },
289 { "help", no_argument, 0, 'h' },
290 { "log-messages", no_argument, 0, 'l' },
291 { "snapshot", no_argument, 0, 's' },
292 { "version", no_argument, 0, 'V' },
293 { "vebose", no_argument, 0, 'v' },
297 std::string dst_snapshot_name = "";
299 while (EOF != (c = getopt_long (argc, argv,
300 optstring, longopts, (int *) 0))) {
303 opt_copy_busses = true;
307 opt_debug_dump = true;
319 dst_snapshot_name = optarg;
323 printf ("ardour-utils version %s\n\n", VERSIONSTRING);
324 printf ("Copyright (C) GPL 2016 Robin Gareus <robin@gareus.org>\n");
333 cerr << "Error: unrecognized option. See --help for usage information.\n";
334 ::exit (EXIT_FAILURE);
339 // TODO parse path/name from a single argument.
341 if (optind + 2 > argc) {
342 cerr << "Error: Missing parameter. See --help for usage information.\n";
343 ::exit (EXIT_FAILURE);
346 std::string src = argv[optind];
347 std::string dst = argv[optind + 1];
351 if (!ends_with (src, statefile_suffix)) {
352 fprintf (stderr, "source is not a .ardour session file.\n");
355 if (!ends_with (dst, statefile_suffix)) {
356 fprintf (stderr, "target is not a .ardour session file.\n");
359 if (!Glib::file_test (src, Glib::FILE_TEST_IS_REGULAR)) {
360 fprintf (stderr, "source is not a regular file.\n");
363 if (!Glib::file_test (dst, Glib::FILE_TEST_IS_REGULAR)) {
364 fprintf (stderr, "target is not a regular file.\n");
368 std::string src_path = Glib::path_get_dirname (src);
369 std::string src_name = PBD::basename_nosuffix (src);
370 std::string dst_path = Glib::path_get_dirname (dst);
371 std::string dst_name = PBD::basename_nosuffix (dst);
373 // TODO check if src != dst ..
374 return copy_session_routes (