headless-chicken: skip !writable sources, ensure midi path is writable.
[ardour.git] / session_utils / headless-chicken.cc
1 /*
2     Copyright (C) 2000-2006 Paul Davis
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 2 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, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <iostream>
20 #include <cstdlib>
21 #include <getopt.h>
22
23 #include <glibmm.h>
24
25 #include "pbd/file_utils.h"
26 #include "pbd/i18n.h"
27 #include "pbd/stateful.h"
28
29 #include "ardour/region_factory.h"
30 #include "ardour/midi_model.h"
31 #include "ardour/midi_region.h"
32 #include "ardour/midi_source.h"
33 #include "ardour/playlist.h"
34 #include "ardour/region.h"
35 #include "ardour/session_directory.h"
36 #include "ardour/source.h"
37 #include "ardour/source_factory.h"
38 #include "ardour/tempo.h"
39
40 #include "evoral/Note.hpp"
41 #include "evoral/Sequence.hpp"
42
43 #include "common.h"
44
45 using namespace std;
46 using namespace ARDOUR;
47 using namespace SessionUtils;
48
49 void
50 session_fail (Session* session)
51 {
52         SessionUtils::unload_session(session);
53         SessionUtils::cleanup();
54         exit (EXIT_FAILURE);
55 }
56
57 bool
58 write_bbt_source_to_source (boost::shared_ptr<MidiSource>  bbt_source, boost::shared_ptr<MidiSource> source,
59                                  const Glib::Threads::Mutex::Lock& source_lock, const double session_offset)
60 {
61         assert (source->empty());
62
63         const bool old_percussive = bbt_source->model()->percussive();
64
65         bbt_source->model()->set_percussive (false);
66
67         source->mark_streaming_midi_write_started (source_lock, bbt_source->model()->note_mode());
68
69         TempoMap& map (source->session().tempo_map());
70
71         for (Evoral::Sequence<MidiModel::TimeType>::const_iterator i = bbt_source->model()->begin(MidiModel::TimeType(), true); i != bbt_source->model()->end(); ++i) {
72                 const double new_time = map.quarter_note_at_beat ((*i).time().to_double() + map.beat_at_pulse (session_offset)) - (session_offset * 4.0);
73                 Evoral::Event<Evoral::Beats> new_ev (*i, true);
74                 new_ev.set_time (Evoral::Beats (new_time));
75                 source->append_event_beats (source_lock, new_ev);
76         }
77
78         bbt_source->model()->set_percussive (old_percussive);
79         source->mark_streaming_write_completed (source_lock);
80
81         return true;
82 }
83
84 boost::shared_ptr<MidiSource>
85 ensure_per_region_source (Session* session, boost::shared_ptr<MidiRegion> region, string newsrc_path)
86 {
87         boost::shared_ptr<MidiSource> newsrc;
88
89         /* create a new source if none exists and write corrected events to it.
90            if file exists, assume that it is correct.
91         */
92         if (Glib::file_test (newsrc_path, Glib::FILE_TEST_EXISTS)) {
93                 Source::Flag flags =  Source::Flag (Source::Writable | Source::CanRename);
94                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
95                         SourceFactory::createExternal(DataType::MIDI, *session,
96                                                       newsrc_path, 1, flags));
97                 if (!newsrc) {
98                         cout << UTILNAME << "An error occurred creating external source from " << newsrc_path << " exiting." << endl;
99                         session_fail (session);
100                 }
101
102                 /* hack flags */
103                 XMLNode* node = new XMLNode (newsrc->get_state());
104
105                 if (node->property ("flags") != 0) {
106                         node->property ("flags")->set_value (enum_2_string (flags));
107                 }
108
109                 newsrc->set_state (*node, PBD::Stateful::loading_state_version);
110
111                 cout << UTILNAME << ": Using existing midi source file " << newsrc_path << endl;
112                 cout << "for region : " << region->name() << endl;
113
114         } else {
115                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
116                         SourceFactory::createWritable(DataType::MIDI, *session,
117                                                       newsrc_path, false, session->frame_rate()));
118
119                 if (!newsrc) {
120                         cout << UTILNAME << "An error occurred creating writeable source " << newsrc_path << " exiting." << endl;
121                         session_fail (session);
122                 }
123
124                 if (!newsrc->empty()) {
125                         cout << UTILNAME << "An error occurred/ " << newsrc->name() << " is not empty. exiting." << endl;
126                         session_fail (session);
127                 }
128
129                 Source::Lock newsrc_lock (newsrc->mutex());
130
131                 write_bbt_source_to_source (region->midi_source(0), newsrc, newsrc_lock, region->pulse() - (region->start_beats().to_double() / 4.0));
132
133                 cout << UTILNAME << ": Created new midi source file " << newsrc_path << endl;
134                 cout << "for region : " <<  region->name() << endl;
135
136         }
137
138         return newsrc;
139 }
140
141 boost::shared_ptr<MidiSource>
142 ensure_per_source_source (Session* session, boost::shared_ptr<MidiRegion> region, string newsrc_path)
143 {
144         boost::shared_ptr<MidiSource> newsrc;
145
146         /* create a new source if none exists and write corrected events to it. */
147         if (Glib::file_test (newsrc_path, Glib::FILE_TEST_EXISTS)) {
148                 /* flags are ignored for external MIDI source */
149                 Source::Flag flags =  Source::Flag (Source::Writable | Source::CanRename);
150
151                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
152                         SourceFactory::createExternal(DataType::MIDI, *session,
153                                                       newsrc_path, 1, flags));
154
155                 if (!newsrc) {
156                         cout << UTILNAME << "An error occurred creating external source from " << newsrc_path << " exiting." << endl;
157                         session_fail (session);
158                 }
159
160                 cout << UTILNAME << ": Using existing midi source file " << newsrc_path << endl;
161                 cout << "for source : " <<  region->midi_source(0)->name() << endl;
162         } else {
163
164                 newsrc = boost::dynamic_pointer_cast<MidiSource>(
165                         SourceFactory::createWritable(DataType::MIDI, *session,
166                                                       newsrc_path, false, session->frame_rate()));
167                 if (!newsrc) {
168                         cout << UTILNAME << "An error occurred creating writeable source " << newsrc_path << " exiting." << endl;
169                         session_fail (session);
170                 }
171
172                 if (!newsrc->empty()) {
173                         cout << UTILNAME << "An error occurred/ " << newsrc->name() << " is not empty. exiting." << endl;
174                         session_fail (session);
175                 }
176
177                 Source::Lock newsrc_lock (newsrc->mutex());
178
179                 write_bbt_source_to_source (region->midi_source(0), newsrc, newsrc_lock, region->pulse() - (region->start_beats().to_double() / 4.0));
180
181                 cout << UTILNAME << ": Created new midi source file " << newsrc_path << endl;
182                 cout << "for source : " <<  region->midi_source(0)->name() << endl;
183
184         }
185
186         return newsrc;
187 }
188
189 void
190 reset_start_and_length (Session* session, boost::shared_ptr<MidiRegion> region)
191 {
192         /* set start_beats & length_beats to quarter note value */
193         TempoMap& map (session->tempo_map());
194
195         region->set_start_beats (Evoral::Beats ((map.pulse_at_beat (region->beat())
196                                                  - map.pulse_at_beat (region->beat() - region->start_beats().to_double())) * 4.0));
197
198         region->set_length_beats (Evoral::Beats ((map.pulse_at_beat (region->beat() + region->length_beats().to_double())
199                                                   - map.pulse_at_beat (region->beat())) * 4.0));
200
201         cout << UTILNAME << ": Reset start and length beats for region : " << region->name() << endl;
202 }
203
204 bool
205 apply_one_source_per_region_fix (Session* session)
206 {
207         const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
208
209         if (!region_map.size()) {
210                 return false;
211         }
212
213         /* for every midi region, ensure a new source and switch to it. */
214         for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
215                 boost::shared_ptr<MidiRegion> mr = 0;
216
217                 if ((mr = boost::dynamic_pointer_cast<MidiRegion>((*i).second)) != 0) {
218
219                         if (!mr->midi_source()->writable()) {
220                                 /* we know the midi dir is writable, so this region is external. leave it alone*/
221                                 cout << mr->source()->name() << "is not writable. skipping." << endl;
222                                 continue;
223                         }
224
225                         reset_start_and_length (session, mr);
226                         string newsrc_filename = mr->name() +  "-a54-compat.mid";
227                         string newsrc_path = Glib::build_filename (session->session_directory().midi_path(), newsrc_filename);
228                         boost::shared_ptr<MidiSource> newsrc = ensure_per_region_source (session, mr, newsrc_path);
229                         mr->clobber_sources (newsrc);
230                 }
231         }
232
233         return true;
234 }
235
236 bool
237 apply_one_source_per_source_fix (Session* session)
238 {
239         const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
240
241         if (!region_map.size()) {
242                 return false;
243         }
244
245         map<PBD::ID, boost::shared_ptr<MidiSource> > old_source_to_new;
246         /* for every midi region, ensure a converted source exists. */
247         for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
248                 boost::shared_ptr<MidiRegion> mr = 0;
249                 map<PBD::ID, boost::shared_ptr<MidiSource> >::iterator src_it;
250
251                 if ((mr = boost::dynamic_pointer_cast<MidiRegion>((*i).second)) != 0) {
252
253                         if (!mr->midi_source()->writable()) {
254                                 cout << mr->source()->name() << "is not writable. skipping." << endl;
255                                 continue;
256                         }
257
258                         reset_start_and_length (session, mr);
259
260                         if ((src_it = old_source_to_new.find (mr->midi_source()->id())) == old_source_to_new.end()) {
261                                 string newsrc_filename = mr->source()->name() +  "-a54-compat.mid";
262                                 string newsrc_path = Glib::build_filename (session->session_directory().midi_path(), newsrc_filename);
263
264                                 boost::shared_ptr<MidiSource> newsrc = ensure_per_source_source (session, mr, newsrc_path);
265
266                                 old_source_to_new.insert (make_pair (mr->midi_source()->id(), newsrc));
267
268                                 mr->midi_source(0)->set_name (newsrc->name());
269                         }
270                 }
271         }
272
273         /* remove new sources from the session. current snapshot is saved.*/
274         cout << UTILNAME << ": clearing new sources." << endl;
275
276         for (map<PBD::ID, boost::shared_ptr<MidiSource> >::iterator i = old_source_to_new.begin(); i != old_source_to_new.end(); ++i) {
277                 session->remove_source (boost::weak_ptr<MidiSource> ((*i).second));
278         }
279
280         return true;
281 }
282
283 static void usage (int status) {
284         // help2man compatible format (standard GNU help-text)
285         printf (UTILNAME " - convert an ardour session with 5.0 - 5.3 midi sources to be compatible with 5.4.\n\n");
286         printf ("Usage: " UTILNAME " [ OPTIONS ] <session-dir> <snapshot-name>\n\n");
287         printf ("Options:\n\
288   -h, --help                    display this help and exit\n\
289   -f, --force                   override detection of affected sessions\n\
290   -o, --output <snapshot-name>  output session snapshot name (without file suffix)\n\
291   -V, --version                 print version information and exit\n\
292 \n");
293         printf ("\n\
294 This Ardour-specific utility provides an upgrade path for sessions created or modified with Ardour versions 5.0 - 5.3.\n\
295 It creates a 5.4-compatible snapshot from affected Ardour session files.\n\
296 Affected versions (5.0 - 5.3 inclusive) contain a bug which caused some MIDI region properties and contents\n\
297 to be stored incorrectly (see more below).\n\n\
298 The utility will first determine whether or not a session requires any changes for 5.4 compatibility.\n\
299 If a session is determined to be affected by the bug, the program will take one of two approaches to correcting the problem.\n\n\
300 The first is to write a new MIDI source file for every existing MIDI source in the supplied snapshot.\n\
301 In the second approach, each MIDI region have its source converted and placed in the session midifiles directory\n\
302 as a new source (one source file per region).\n\
303 The second method is only offered if the first approach cannot logically ensure that the results would match the input snapshot.\n\
304 Using the first method even if the second method is offered will usually match the input exactly (partly due to a characteristic of the bug).\n\n\
305 Both methods update MIDI region properties and save a new snapshot in the supplied session-dir, optionally using a supplied snapshot name (-o).\n\
306 The new snapshot may be used on Ardour-5.4.\n\n\
307 Running this utility should not alter any existing files, but it is recommended that you run it on a backup of the session directory.\n\n\
308 EXAMPLE:\n\
309 ardour5-headless-chicken -o bantam ~/studio/leghorn leghorn\n\
310 will create a new snapshot file ~/studio/leghorn/bantam.ardour from ~/studio/leghorn/leghorn.ardour\n\
311 Converted midi sources will be created in ~/studio/leghorn/interchange/leghorn/midifiles/\n\
312 If the output option (-o) is omitted, the string \"-a54-compat\" will be appended to the supplied snapshot name.\n\n\
313 About the Bug\n\
314 If a session from affected versions used MIDI regions and a meter note divisor was set to anything but quarter notes,\n\
315 the source smf files would contain events at a PPQN value derived from BBT beats (using meter note divisor) rather than quarter-note beatss.\n\
316 The region start and length offsets would also be stored incorrectly.\n\
317 If a MIDI session only contains quarter note meter divisors, it will be unaffected.\n\
318 \n");
319
320         printf ("Report bugs to <http://tracker.ardour.org/>\n"
321                 "Website: <http://ardour.org/>\n");
322         ::exit (status);
323 }
324
325 int main (int argc, char* argv[])
326 {
327         string outfile;
328         bool force = false;
329
330         const char *optstring = "hfo:r:V";
331
332         const struct option longopts[] = {
333                 { "help",       0, 0, 'h' },
334                 { "force",      0, 0, 'f' },
335                 { "output",     1, 0, 'o' },
336                 { "version",    0, 0, 'V' },
337         };
338
339         int c = 0;
340         while (EOF != (c = getopt_long (argc, argv,
341                                         optstring, longopts, (int *) 0))) {
342                 switch (c) {
343
344                 case 'f':
345                         force = true;
346                         break;
347
348                 case 'o':
349                         outfile = optarg;
350                         if (outfile.empty()) {
351                                 usage (0);
352                         }
353                         break;
354
355                 case 'V':
356                         printf ("ardour-utils version %s\n\n", VERSIONSTRING);
357                         printf ("Copyright (C) GPL 2015 Robin Gareus <robin@gareus.org>\n");
358                         exit (0);
359                         break;
360
361                 case 'h':
362                         usage (0);
363                         break;
364
365                 default:
366                         usage (EXIT_FAILURE);
367                         break;
368                 }
369         }
370
371         if (optind + 2 > argc) {
372                 usage (EXIT_FAILURE);
373         }
374
375         SessionDirectory* session_dir = new SessionDirectory (argv[optind]);
376         string snapshot_name (argv[optind+1]);
377         string statefile_suffix (X_(".ardour"));
378         string pending_suffix (X_(".pending"));
379
380         XMLTree* state_tree;
381
382         string xmlpath(argv[optind]);
383         string out_snapshot_name;
384
385         if (!outfile.empty()) {
386                 string file_test_path = Glib::build_filename (argv[optind], outfile + statefile_suffix);
387                 if (Glib::file_test (file_test_path, Glib::FILE_TEST_EXISTS)) {
388                         cout << UTILNAME << ": session file " << file_test_path << " already exists!" << endl;
389                         ::exit (EXIT_FAILURE);
390                 }
391                 out_snapshot_name = outfile;
392         } else {
393                 string file_test_path = Glib::build_filename (argv[optind], snapshot_name + "-a54-compat" + statefile_suffix);
394                 if (Glib::file_test (file_test_path, Glib::FILE_TEST_EXISTS)) {
395                         cout << UTILNAME << ": session file " << file_test_path << " already exists!" << endl;
396                         ::exit (EXIT_FAILURE);
397                 }
398                 out_snapshot_name = snapshot_name + "-a54-compat";
399         }
400
401         xmlpath = Glib::build_filename (xmlpath, legalize_for_path (snapshot_name) + pending_suffix);
402
403         if (Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
404
405                 /* there is pending state from a crashed capture attempt */
406                 cout << UTILNAME << ": There seems to be pending state for snapshot : " << snapshot_name << endl;
407
408         }
409
410         xmlpath = Glib::build_filename (argv[optind], argv[optind+1]);
411
412         if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
413                 xmlpath = Glib::build_filename (argv[optind], legalize_for_path (argv[optind+1]) + ".ardour");
414                 if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
415                         cout << UTILNAME << ": session file " << xmlpath << " doesn't exist!" << endl;
416                         ::exit (EXIT_FAILURE);
417                 }
418         }
419
420         state_tree = new XMLTree;
421
422         bool writable = PBD::exists_and_writable (xmlpath) && PBD::exists_and_writable(Glib::path_get_dirname(xmlpath));
423
424         if (!writable) {
425                 cout << UTILNAME << ": Error : The session directory must exist and be writable." << endl;
426                 return -1;
427         }
428
429         if (!state_tree->read (xmlpath)) {
430                 cout << UTILNAME << ": Could not understand session file " << xmlpath << endl;
431                 delete state_tree;
432                 state_tree = 0;
433                 ::exit (EXIT_FAILURE);
434         }
435
436         XMLNode const & root (*state_tree->root());
437
438         if (root.name() != X_("Session")) {
439                 cout << UTILNAME << ": Session file " << xmlpath<< " is not a session" << endl;
440                 delete state_tree;
441                 state_tree = 0;
442                 ::exit (EXIT_FAILURE);
443         }
444
445         XMLProperty const * prop;
446
447         if ((prop = root.property ("version")) == 0) {
448                 /* no version implies very old version of Ardour */
449                 cout << UTILNAME << ": The session " << snapshot_name << " has no version or is too old to be affected. exiting." << endl;
450                 ::exit (EXIT_FAILURE);
451         } else {
452                 if (prop->value().find ('.') != string::npos) {
453                         /* old school version format */
454                         cout << UTILNAME << ": The session " << snapshot_name << " is too old to be affected. exiting." << endl;
455                         ::exit (EXIT_FAILURE);
456                 } else {
457                         PBD::Stateful::loading_state_version = atoi (prop->value().c_str());
458                 }
459         }
460
461         cout <<  UTILNAME << ": Checking snapshot : " << snapshot_name << " in directory : " << session_dir->root_path() << endl;
462
463         bool midi_regions_use_bbt_beats = false;
464
465         if (PBD::Stateful::loading_state_version == 3002 && writable) {
466                 XMLNode* child;
467                 if ((child = find_named_node (root, "ProgramVersion")) != 0) {
468                         if ((prop = child->property ("modified-with")) != 0) {
469                                 string modified_with = prop->value ();
470
471                                 const double modified_with_version = atof (modified_with.substr ( modified_with.find(" ", 0) + 1, string::npos).c_str());
472                                 const int modified_with_revision = atoi (modified_with.substr (modified_with.find("-", 0) + 1, string::npos).c_str());
473
474                                 if (modified_with_version <= 5.3 && !(modified_with_version == 5.3 && modified_with_revision >= 42)) {
475                                         midi_regions_use_bbt_beats = true;
476                                 }
477                         }
478                 }
479         }
480
481         XMLNode* tm_node;
482         bool all_metrum_divisors_are_quarters = true;
483         list<double> divisor_list;
484
485         if ((tm_node = find_named_node (root, "TempoMap")) != 0) {
486                 XMLNodeList metrum;
487                 XMLNodeConstIterator niter;
488                 metrum = tm_node->children();
489                 for (niter = metrum.begin(); niter != metrum.end(); ++niter) {
490                         XMLNode* child = *niter;
491
492                         if (child->name() == MeterSection::xml_state_node_name && (prop = child->property ("note-type")) != 0) {
493                                 double note_type;
494
495                                 if (sscanf (prop->value().c_str(), "%lf", &note_type) ==1) {
496
497                                         if (note_type != 4.0) {
498                                                 all_metrum_divisors_are_quarters = false;
499                                         }
500
501                                         divisor_list.push_back (note_type);
502                                 }
503                         }
504                 }
505         } else {
506                 cout << UTILNAME << ": Session file " <<  xmlpath << " has no TempoMap node. exiting." << endl;
507                 ::exit (EXIT_FAILURE);
508         }
509
510         if (all_metrum_divisors_are_quarters && !force) {
511                 cout << UTILNAME << ": The session " << snapshot_name << " is clear for use in 5.4 (all divisors are quarters). Use -f to override." << endl;
512                 ::exit (EXIT_FAILURE);
513         }
514
515         /* check for multiple note divisors. if there is only one, we can create one file per source. */
516         bool one_source_file_per_source = false;
517         divisor_list.unique();
518
519         if (divisor_list.size() == 1) {
520                 cout  << UTILNAME << ": Snapshot " << snapshot_name << " will be converted using one new file per source." << endl;
521                 cout  << "To continue with per-source conversion enter s. q to quit." << endl;
522
523                 while (1) {
524                         cout  << "[s/q]" << endl;
525
526                         string input;
527                         getline (cin, input);
528
529                         if (input == "s") {
530                                 break;
531                         }
532
533                         if (input == "q") {
534                                 exit (EXIT_SUCCESS);
535                                 break;
536                         }
537                 }
538
539                 one_source_file_per_source = true;
540         } else {
541
542                 cout  << UTILNAME << ": Snapshot " << snapshot_name << " contains multiple meter note divisors." << endl;
543                 cout  << "per-region source conversion ensures that the output snapshot will be identical to the original," << endl;
544                 cout  << "however regions in the new snapshot will no longer share sources." << endl;
545                 cout  << "In many (but not all) cases per-source conversion will work equally well." << endl;
546                 cout  << "It is recommended that you test a snapshot created with the per-source method before using per-region conversion." << endl;
547                 cout  << "To continue with per-region conversion enter r. For per-source conversion, enter s. q to quit." << endl;
548
549                 while (1) {
550                         cout  << "[r/s/q]" << endl;
551
552                         string input;
553                         getline (cin, input);
554
555                         if (input == "s") {
556                                 one_source_file_per_source = true;
557                                 break;
558                         }
559
560                         if (input == "r") {
561                                 break;
562                         }
563
564                         if (input == "q") {
565                                 exit (EXIT_SUCCESS);
566                                 break;
567                         }
568                 }
569         }
570
571         if (midi_regions_use_bbt_beats || force) {
572
573                 if (force) {
574                         cout << UTILNAME << ": Forced update of snapshot : " << snapshot_name << endl;
575                 }
576
577                 SessionUtils::init();
578                 Session* s = 0;
579
580                 cout << UTILNAME << ": Loading snapshot." << endl;
581
582                 s = SessionUtils::load_session (argv[optind], argv[optind+1]);
583
584                 if (!PBD::exists_and_writable (Glib::path_get_dirname (session_dir->midi_path()))) {
585                         cout << UTILNAME << ": the directory " << session_dir->midi_path() << " must be writable. exiting." << endl;
586                         session_fail (s);
587                 }
588
589                 /* save new snapshot and prevent alteration of the original by switching to it.
590                    we know these files don't yet exist.
591                 */
592                 if (s->save_state (out_snapshot_name, false, true)) {
593                         cout << UTILNAME << ": Could not save new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
594
595                         session_fail (s);
596                 }
597
598                 cout << UTILNAME << ": Saved new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
599
600                 if (one_source_file_per_source) {
601                         cout << UTILNAME << ": Will create one MIDI file per source." << endl;
602
603                         if (!apply_one_source_per_source_fix (s)) {
604                                 cout << UTILNAME << ": The snapshot " << snapshot_name << " is clear for use in 5.4 (no midi regions). exiting." << endl;
605                                 session_fail (s);
606                         }
607                 } else {
608                         cout << UTILNAME << ": Will create one MIDI file per midi region." << endl;
609
610                         if (!apply_one_source_per_region_fix (s)) {
611                                 cout << UTILNAME << ": The snapshot " << snapshot_name << " is clear for use in 5.4 (no midi regions). exiting."  << endl;
612                                 session_fail (s);
613                         }
614
615                         if (s->save_state (out_snapshot_name, false, true)) {
616                                 cout << UTILNAME << ": Could not save snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
617                                 session_fail (s);
618                         }
619                         cout << UTILNAME << ": Saved new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
620                 }
621
622                 SessionUtils::unload_session(s);
623                 SessionUtils::cleanup();
624                 cout << UTILNAME << ": Snapshot " << out_snapshot_name << " is ready for use in 5.4" << endl;
625         } else {
626                 cout << UTILNAME << ": The snapshot " << snapshot_name << " doesn't require any change for use in 5.4. Use -f to override." << endl;
627                 ::exit (EXIT_FAILURE);
628         }
629
630         return 0;
631 }