825b4e0561399e3702599091144665de3cd241b7
[ardour.git] / session_utils / copy-mixer.cc
1 #include <iostream>
2 #include <cstdlib>
3 #include <getopt.h>
4 #include <glibmm.h>
5
6 #include "pbd/basename.h"
7 #include "pbd/stateful.h"
8 #include "ardour/filename_extensions.h"
9 #include "ardour/send.h"
10 #include "ardour/track.h"
11
12 #include "common.h"
13
14 #define X_(Text) Text
15
16 using namespace std;
17 using namespace ARDOUR;
18 using namespace SessionUtils;
19
20 static bool opt_debug_dump = false;
21 static bool opt_copy_busses = false;
22 static bool opt_verbose = false;
23 static bool opt_log = false;
24
25 /* this is copied from  Session::new_route_from_template */
26 static void
27 trim_state_for_mixer_copy (Session*s, XMLNode& node)
28 {
29         /* trim bitslots from listen sends so that new ones are used */
30         XMLNodeList children = node.children ();
31         for (XMLNodeList::iterator i = children.begin (); i != children.end (); ++i) {
32                 if ((*i)->name() == X_("Processor")) {
33                         /* ForceIDRegeneration does not catch the following */
34                         XMLProperty const * role = (*i)->property (X_("role"));
35                         XMLProperty const * type = (*i)->property (X_("type"));
36                         if (role && role->value () == X_("Aux")) {
37                                 /* check if the target bus exists,
38                                  * HERE: we use the bus-name (not target-id)
39                                  */
40                                 XMLProperty const * target = (*i)->property (X_("name"));
41                                 if (!target) {
42                                         (*i)->add_property ("type", "dangling-aux-send");
43                                         continue;
44                                 }
45                                 boost::shared_ptr<Route> r = s->route_by_name (target->value ());
46                                 if (!r || boost::dynamic_pointer_cast<Track> (r)) {
47                                         (*i)->add_property ("type", "dangling-aux-send");
48                                         continue;
49                                 }
50                                 (*i)->add_property ("target", r->id ().to_s ());
51                         }
52                         if (role && role->value () == X_("Listen")) {
53                                 (*i)->remove_property (X_("bitslot"));
54                         }
55                         else if (role && (role->value () == X_("Send") || role->value () == X_("Aux"))) {
56                                 char buf[32];
57                                 Delivery::Role xrole;
58                                 uint32_t bitslot = 0;
59                                 xrole = Delivery::Role (string_2_enum (role->value (), xrole));
60                                 std::string name = Send::name_and_id_new_send (*s, xrole, bitslot, false);
61                                 snprintf (buf, sizeof (buf), "%" PRIu32, bitslot);
62                                 (*i)->remove_property (X_("bitslot"));
63                                 (*i)->remove_property (X_("name"));
64                                 (*i)->add_property ("bitslot", buf);
65                                 (*i)->add_property ("name", name);
66                         }
67                         else if (type && type->value () == X_("intreturn")) {
68                                 // ignore, in case bus existed in old session,
69                                 // tracks in old session may be connected to it.
70                                 // if the bus is new, new_route_from_template()
71                                 // will have re-created an ID.
72                                 (*i)->add_property ("type", "ignore-aux-return");
73                         }
74                         else if (type && type->value () == X_("return")) {
75                                 // Return::set_state() generates a new one
76                                 (*i)->remove_property (X_("bitslot"));
77                         }
78                         else if (type && type->value () == X_("port")) {
79                                 // PortInsert::set_state() handles the bitslot
80                                 (*i)->remove_property (X_("bitslot"));
81                                 (*i)->add_property ("ignore-name", "1");
82                         }
83                 }
84         }
85 }
86
87 static void
88 copy_mixer_settings (Session*s, boost::shared_ptr<Route> dst, XMLNode& state)
89 {
90         PBD::Stateful::ForceIDRegeneration force_ids;
91
92         trim_state_for_mixer_copy (s, state);
93         state.remove_nodes_and_delete ("Diskstream");
94         state.remove_nodes_and_delete ("Automation");
95         if (opt_debug_dump) {
96                 state.dump (cout);
97         }
98
99         dst->set_state (state, PBD::Stateful::loading_state_version);
100 }
101
102 static int
103 copy_session_routes (
104                 const std::string& src_path, const std::string& src_name,
105                 const std::string& dst_path, const std::string& dst_load, const std::string& dst_save)
106 {
107         SessionUtils::init (opt_log);
108         Session* s = 0;
109
110         typedef std::map<std::string,XMLNode*> StateMap;
111         StateMap routestate;
112         StateMap buslist;
113
114         s = SessionUtils::load_session (src_path, src_name, false);
115
116         if (!s) {
117                 fprintf (stderr, "Cannot load source session %s/%s.\n", src_path.c_str (), src_name.c_str ());
118                 SessionUtils::cleanup ();
119                 return -1;
120         }
121
122         /* get route state from first session */
123         boost::shared_ptr<RouteList> rl = s->get_routes ();
124         for (RouteList::iterator i = rl->begin (); i != rl->end (); ++i) {
125                 boost::shared_ptr<Route> r = *i;
126                 if (r->is_master () || r->is_monitor () || r->is_auditioner ()) {
127                         continue;
128                 }
129                 XMLNode& state (r->get_state ());
130                 routestate[r->name ()] = &state;
131                 if (boost::dynamic_pointer_cast<Track> (r)) {
132                         continue;
133                 }
134                 buslist[r->name ()] = &state;
135         }
136         rl.reset ();
137         SessionUtils::unload_session (s);
138
139
140         /* open target session */
141         s = SessionUtils::load_session (dst_path, dst_load);
142         if (!s) {
143                 fprintf (stderr, "Cannot load target session %s/%s.\n", dst_path.c_str (), dst_load.c_str ());
144                 SessionUtils::cleanup ();
145                 return -1;
146         }
147
148         /* iterate over all busses in the src session, add missing ones to target */
149         if (opt_copy_busses) {
150                 rl = s->get_routes ();
151                 for (StateMap::const_iterator i = buslist.begin (); i != buslist.end (); ++i) {
152                         if (s->route_by_name (i->first)) {
153                                 continue;
154                         }
155                         XMLNode& rs (*(i->second));
156                         s->new_route_from_template (1, rs, rs.property (X_("name"))->value (), NewPlaylist);
157                 }
158         }
159
160         /* iterate over all *busses* in the target session.
161          * setup internal return targets.
162          */
163         rl = s->get_routes ();
164         for (RouteList::iterator i = rl->begin (); i != rl->end (); ++i) {
165                 boost::shared_ptr<Route> r = *i;
166                 /* skip special busses */
167                 if (r->is_master () || r->is_monitor () || r->is_auditioner ()) {
168                         continue;
169                 }
170                 if (boost::dynamic_pointer_cast<Track> (r)) {
171                         continue;
172                 }
173                 /* find matching route by name */
174                 std::map<std::string,XMLNode*>::iterator it = routestate.find (r->name ());
175                 if (it == routestate.end ()) {
176                         if (opt_verbose) {
177                                 printf (" -- no match for '%s'\n", (*i)->name ().c_str ());
178                         }
179                         continue;
180                 }
181                 if (opt_verbose) {
182                         printf ("-- found match '%s'\n", (*i)->name ().c_str ());
183                 }
184                 XMLNode *state = it->second;
185                 // copy state
186                 copy_mixer_settings (s, r, *state);
187         }
188
189         /* iterate over all tracks in the target session.. */
190         rl = s->get_routes ();
191         for (RouteList::iterator i = rl->begin (); i != rl->end (); ++i) {
192                 boost::shared_ptr<Route> r = *i;
193                 /* skip special busses */
194                 if (r->is_master () || r->is_monitor () || r->is_auditioner ()) {
195                         continue;
196                 }
197                 if (!boost::dynamic_pointer_cast<Track> (r)) {
198                         continue;
199                 }
200
201                 /* find matching route by name */
202                 std::map<std::string,XMLNode*>::iterator it = routestate.find (r->name ());
203                 if (it == routestate.end ()) {
204                         if (opt_verbose) {
205                                 printf (" -- no match for '%s'\n", (*i)->name ().c_str ());
206                         }
207                         continue;
208                 }
209                 if (opt_verbose) {
210                         printf ("-- found match '%s'\n", (*i)->name ().c_str ());
211                 }
212                 XMLNode *state = it->second;
213                 /* copy state */
214                 copy_mixer_settings (s, r, *state);
215         }
216
217         s->save_state (dst_save);
218
219         rl.reset ();
220         SessionUtils::unload_session (s);
221
222         // clean up.
223         for (StateMap::iterator i = routestate.begin (); i != routestate.end (); ++i) {
224                 XMLNode *state = i->second;
225                 delete state;
226         }
227
228         SessionUtils::cleanup ();
229         return 0;
230 }
231
232
233 static void usage (int status) {
234         // help2man compatible format (standard GNU help-text)
235         printf (UTILNAME " - copy mixer settings from one session to another.\n\n");
236         printf ("Usage: " UTILNAME " [ OPTIONS ] <src> <dst>\n\n");
237
238         printf ("Options:\n\
239   -h, --help                 display this help and exit\n\
240   -b, --bus-copy             add busses present in src to dst\n\
241   -d, --debug                print pre-processed XML for each route\n\
242   -l, --log-messages         display libardour log messages\n\
243   -s, --snapshot <name>      create a new snapshot in dst\n\
244   -v, --verbose              show perfomed copy opeations\n\
245   -V, --version              print version information and exit\n\
246 \n");
247
248         printf ("\n\
249 This utility copies mixer-settings from the src-session to the dst-session.\n\
250 Both <src> and <dst> are paths to .ardour session files.\n\
251 If --snapshot is not given, the <dst> session file is overwritten.\n\
252 When --snapshot is set, a new snaphot in the <dst> session is created.\n\
253 \n");
254
255         printf ("Report bugs to <http://tracker.ardour.org/>\n"
256                 "Website: <http://ardour.org/>\n");
257         ::exit (status);
258 }
259
260 static bool ends_with (std::string const& value, std::string const& ending)
261 {
262         if (ending.size() > value.size()) return false;
263         return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
264 }
265
266 int main (int argc, char* argv[])
267 {
268         const char *optstring = "bhls:Vv";
269
270         const struct option longopts[] = {
271                 { "bus-copy",     required_argument, 0, 'b' },
272                 { "debug",        no_argument,       0, 'd' },
273                 { "help",         no_argument,       0, 'h' },
274                 { "log-messages", no_argument,       0, 'l' },
275                 { "snapshot",     no_argument,       0, 's' },
276                 { "version",      no_argument,       0, 'V' },
277                 { "vebose",       no_argument,       0, 'v' },
278         };
279
280         int c = 0;
281         std::string dst_snapshot_name = "";
282
283         while (EOF != (c = getopt_long (argc, argv,
284                                         optstring, longopts, (int *) 0))) {
285                 switch (c) {
286                         case 'b':
287                                 opt_copy_busses = true;
288                                 break;
289
290                         case 'd':
291                                 opt_debug_dump = true;
292                                 break;
293
294                         case 'h':
295                                 usage (0);
296                                 break;
297
298                         case 'l':
299                                 opt_log = true;
300                                 break;
301
302                         case 's':
303                                 dst_snapshot_name = optarg;
304                                 break;
305
306                         case 'V':
307                                 printf ("ardour-utils version %s\n\n", VERSIONSTRING);
308                                 printf ("Copyright (C) GPL 2016 Robin Gareus <robin@gareus.org>\n");
309                                 exit (0);
310                                 break;
311
312                         case 'v':
313                                 opt_verbose = true;
314                                 break;
315
316                         default:
317                                         usage (EXIT_FAILURE);
318                                         break;
319                 }
320         }
321
322         // TODO parse path/name  from a single argument.
323
324         if (optind + 2 > argc) {
325                 usage (EXIT_FAILURE);
326         }
327
328         std::string src = argv[optind];
329         std::string dst = argv[optind + 1];
330
331         // statefile_suffix
332
333         if (!ends_with (src, statefile_suffix)) {
334                 fprintf (stderr, "source is not a .ardour session file.\n");
335                 exit (1);
336         }
337         if (!ends_with (dst, statefile_suffix)) {
338                 fprintf (stderr, "target is not a .ardour session file.\n");
339                 exit (1);
340         }
341         if (!Glib::file_test (src, Glib::FILE_TEST_IS_REGULAR)) {
342                 fprintf (stderr, "source is not a regular file.\n");
343                 exit (1);
344         }
345         if (!Glib::file_test (dst, Glib::FILE_TEST_IS_REGULAR)) {
346                 fprintf (stderr, "target is not a regular file.\n");
347                 exit (1);
348         }
349
350         std::string src_path = Glib::path_get_dirname (src);
351         std::string src_name = PBD::basename_nosuffix (src);
352         std::string dst_path = Glib::path_get_dirname (dst);
353         std::string dst_name = PBD::basename_nosuffix (dst);
354
355         // TODO check if src != dst ..
356         return copy_session_routes (
357                         src_path, src_name,
358                         dst_path, dst_name,
359                         dst_snapshot_name);
360 }