2016-09-24 13:25:49 -04:00
|
|
|
/*
|
2019-08-03 08:34:29 -04:00
|
|
|
* Copyright (C) 2016-2019 Robin Gareus <robin@gareus.org>
|
|
|
|
* Copyright (C) 2016 Nick Mainsbridge <mainsbridge@gmail.com>
|
|
|
|
* Copyright (C) 2017 Paul Davis <paul@linuxaudiosystems.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
|
|
*/
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
#include <iostream>
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <getopt.h>
|
|
|
|
|
|
|
|
#include <glibmm.h>
|
|
|
|
|
|
|
|
#include "pbd/file_utils.h"
|
|
|
|
#include "pbd/i18n.h"
|
|
|
|
#include "pbd/stateful.h"
|
|
|
|
|
|
|
|
#include "ardour/region_factory.h"
|
|
|
|
#include "ardour/midi_model.h"
|
|
|
|
#include "ardour/midi_region.h"
|
|
|
|
#include "ardour/midi_source.h"
|
|
|
|
#include "ardour/playlist.h"
|
|
|
|
#include "ardour/region.h"
|
|
|
|
#include "ardour/session_directory.h"
|
|
|
|
#include "ardour/source.h"
|
|
|
|
#include "ardour/source_factory.h"
|
|
|
|
#include "ardour/tempo.h"
|
|
|
|
|
2019-10-25 15:13:51 -04:00
|
|
|
#include "evoral/Note.h"
|
|
|
|
#include "evoral/Sequence.h"
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace ARDOUR;
|
|
|
|
using namespace SessionUtils;
|
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
void
|
|
|
|
session_fail (Session* session)
|
|
|
|
{
|
|
|
|
SessionUtils::unload_session(session);
|
|
|
|
SessionUtils::cleanup();
|
|
|
|
exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
bool
|
2016-09-26 09:31:05 -04:00
|
|
|
write_bbt_source_to_source (boost::shared_ptr<MidiSource> bbt_source, boost::shared_ptr<MidiSource> source,
|
2016-09-24 13:25:49 -04:00
|
|
|
const Glib::Threads::Mutex::Lock& source_lock, const double session_offset)
|
|
|
|
{
|
2016-09-26 10:59:28 -04:00
|
|
|
assert (source->empty());
|
2016-09-24 13:25:49 -04:00
|
|
|
const bool old_percussive = bbt_source->model()->percussive();
|
|
|
|
|
|
|
|
bbt_source->model()->set_percussive (false);
|
|
|
|
|
|
|
|
source->mark_streaming_midi_write_started (source_lock, bbt_source->model()->note_mode());
|
|
|
|
|
|
|
|
TempoMap& map (source->session().tempo_map());
|
|
|
|
|
|
|
|
for (Evoral::Sequence<MidiModel::TimeType>::const_iterator i = bbt_source->model()->begin(MidiModel::TimeType(), true); i != bbt_source->model()->end(); ++i) {
|
2016-11-01 06:54:11 -04:00
|
|
|
const double new_time = map.quarter_note_at_beat ((*i).time().to_double() + map.beat_at_quarter_note (session_offset * 4.0)) - (session_offset * 4.0);
|
2017-09-24 12:03:54 -04:00
|
|
|
Evoral::Event<Temporal::Beats> new_ev (*i, true);
|
|
|
|
new_ev.set_time (Temporal::Beats (new_time));
|
2016-09-24 13:25:49 -04:00
|
|
|
source->append_event_beats (source_lock, new_ev);
|
|
|
|
}
|
|
|
|
|
|
|
|
bbt_source->model()->set_percussive (old_percussive);
|
|
|
|
source->mark_streaming_write_completed (source_lock);
|
2018-11-15 10:33:54 -05:00
|
|
|
source->set_natural_position (bbt_source->natural_position());
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<MidiSource>
|
2016-09-26 11:56:27 -04:00
|
|
|
ensure_per_region_source (Session* session, boost::shared_ptr<MidiRegion> region, string newsrc_path)
|
2016-09-24 13:25:49 -04:00
|
|
|
{
|
|
|
|
boost::shared_ptr<MidiSource> newsrc;
|
|
|
|
|
|
|
|
/* create a new source if none exists and write corrected events to it.
|
|
|
|
if file exists, assume that it is correct.
|
|
|
|
*/
|
|
|
|
if (Glib::file_test (newsrc_path, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
Source::Flag flags = Source::Flag (Source::Writable | Source::CanRename);
|
|
|
|
newsrc = boost::dynamic_pointer_cast<MidiSource>(
|
|
|
|
SourceFactory::createExternal(DataType::MIDI, *session,
|
|
|
|
newsrc_path, 1, flags));
|
2016-09-26 09:31:05 -04:00
|
|
|
if (!newsrc) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " An error occurred creating external source from " << newsrc_path << " exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
session_fail (session);
|
|
|
|
}
|
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
/* hack flags */
|
|
|
|
XMLNode* node = new XMLNode (newsrc->get_state());
|
|
|
|
|
|
|
|
if (node->property ("flags") != 0) {
|
|
|
|
node->property ("flags")->set_value (enum_2_string (flags));
|
|
|
|
}
|
|
|
|
|
|
|
|
newsrc->set_state (*node, PBD::Stateful::loading_state_version);
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
delete node;
|
|
|
|
|
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Using existing midi source file" << endl
|
|
|
|
<< " " << newsrc_path << endl
|
|
|
|
<< " for region " << region->name() << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
} else {
|
|
|
|
newsrc = boost::dynamic_pointer_cast<MidiSource>(
|
|
|
|
SourceFactory::createWritable(DataType::MIDI, *session,
|
2017-09-18 12:39:17 -04:00
|
|
|
newsrc_path, false, session->sample_rate()));
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
if (!newsrc) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " An error occurred creating writeable source " << newsrc_path << " exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
session_fail (session);
|
|
|
|
}
|
|
|
|
|
2016-09-26 10:59:28 -04:00
|
|
|
if (!newsrc->empty()) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " An error occurred/ " << newsrc->name() << " is not empty. exiting." << endl;
|
2016-09-26 10:59:28 -04:00
|
|
|
session_fail (session);
|
|
|
|
}
|
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
Source::Lock newsrc_lock (newsrc->mutex());
|
|
|
|
|
2016-11-08 12:51:19 -05:00
|
|
|
write_bbt_source_to_source (region->midi_source(0), newsrc, newsrc_lock, (region->quarter_note() - region->start_beats()) / 4.0);
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Created new midi source file" << endl
|
|
|
|
<< " " << newsrc_path << endl
|
|
|
|
<< " for region " << region->name() << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return newsrc;
|
|
|
|
}
|
|
|
|
|
|
|
|
boost::shared_ptr<MidiSource>
|
2016-09-26 11:56:27 -04:00
|
|
|
ensure_per_source_source (Session* session, boost::shared_ptr<MidiRegion> region, string newsrc_path)
|
2016-09-26 09:31:05 -04:00
|
|
|
{
|
|
|
|
boost::shared_ptr<MidiSource> newsrc;
|
|
|
|
|
|
|
|
/* create a new source if none exists and write corrected events to it. */
|
|
|
|
if (Glib::file_test (newsrc_path, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
/* flags are ignored for external MIDI source */
|
|
|
|
Source::Flag flags = Source::Flag (Source::Writable | Source::CanRename);
|
|
|
|
|
|
|
|
newsrc = boost::dynamic_pointer_cast<MidiSource>(
|
|
|
|
SourceFactory::createExternal(DataType::MIDI, *session,
|
|
|
|
newsrc_path, 1, flags));
|
|
|
|
|
|
|
|
if (!newsrc) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " An error occurred creating external source from " << newsrc_path << " exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
session_fail (session);
|
|
|
|
}
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Using existing midi source file" << endl
|
|
|
|
<< " " << newsrc_path << endl
|
|
|
|
<< " for source " << region->midi_source(0)->name() << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
} else {
|
|
|
|
|
|
|
|
newsrc = boost::dynamic_pointer_cast<MidiSource>(
|
|
|
|
SourceFactory::createWritable(DataType::MIDI, *session,
|
2017-09-18 12:39:17 -04:00
|
|
|
newsrc_path, false, session->sample_rate()));
|
2016-09-26 09:31:05 -04:00
|
|
|
if (!newsrc) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<<" An error occurred creating writeable source " << newsrc_path << " exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
session_fail (session);
|
|
|
|
}
|
|
|
|
|
2016-09-26 10:59:28 -04:00
|
|
|
if (!newsrc->empty()) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " An error occurred/ " << newsrc->name() << " is not empty. exiting." << endl;
|
2016-09-26 10:59:28 -04:00
|
|
|
session_fail (session);
|
|
|
|
}
|
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
Source::Lock newsrc_lock (newsrc->mutex());
|
|
|
|
|
2016-11-08 12:51:19 -05:00
|
|
|
write_bbt_source_to_source (region->midi_source(0), newsrc, newsrc_lock, (region->quarter_note() - region->start_beats()) / 4.0);
|
2016-09-26 09:31:05 -04:00
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Created new midi source file" << endl
|
|
|
|
<< " " << newsrc_path << endl
|
|
|
|
<< " for source " << region->midi_source(0)->name() << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return newsrc;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
2016-09-28 09:33:18 -04:00
|
|
|
reset_start (Session* session, boost::shared_ptr<MidiRegion> region)
|
2016-09-24 13:25:49 -04:00
|
|
|
{
|
2016-09-28 09:33:18 -04:00
|
|
|
/* set start_beats to quarter note value from incorrect bbt*/
|
|
|
|
TempoMap& tmap (session->tempo_map());
|
2016-11-01 06:54:11 -04:00
|
|
|
double new_start_qn = tmap.quarter_note_at_beat (region->beat()) - tmap.quarter_note_at_beat (region->beat() - region->start_beats());
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-28 09:33:18 -04:00
|
|
|
/* force a change to start and start_beats */
|
|
|
|
PositionLockStyle old_pls = region->position_lock_style();
|
|
|
|
region->set_position_lock_style (AudioTime);
|
2017-09-18 12:39:17 -04:00
|
|
|
region->set_start (tmap.sample_at_quarter_note (region->quarter_note()) - tmap.sample_at_quarter_note (region->quarter_note() - new_start_qn) + 1);
|
|
|
|
region->set_start (tmap.sample_at_quarter_note (region->quarter_note()) - tmap.sample_at_quarter_note (region->quarter_note() - new_start_qn));
|
2016-09-28 09:33:18 -04:00
|
|
|
region->set_position_lock_style (old_pls);
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-28 09:33:18 -04:00
|
|
|
}
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-28 09:33:18 -04:00
|
|
|
void
|
|
|
|
reset_length (Session* session, boost::shared_ptr<MidiRegion> region)
|
|
|
|
{
|
2016-09-29 08:34:32 -04:00
|
|
|
/* set length_beats to quarter note value */
|
2016-09-28 09:33:18 -04:00
|
|
|
TempoMap& tmap (session->tempo_map());
|
2016-11-01 06:54:11 -04:00
|
|
|
double new_length_qn = tmap.quarter_note_at_beat (region->beat() + region->length_beats())
|
|
|
|
- tmap.quarter_note_at_beat (region->beat());
|
2016-09-28 09:33:18 -04:00
|
|
|
|
|
|
|
/* force a change to length and length_beats */
|
|
|
|
PositionLockStyle old_pls = region->position_lock_style();
|
|
|
|
region->set_position_lock_style (AudioTime);
|
2017-09-18 12:39:17 -04:00
|
|
|
region->set_length (tmap.sample_at_quarter_note (region->quarter_note() + new_length_qn) + 1 - region->position(), 0);
|
|
|
|
region->set_length (tmap.sample_at_quarter_note (region->quarter_note() + new_length_qn)- region->position(), 0);
|
2016-09-28 09:33:18 -04:00
|
|
|
region->set_position_lock_style (old_pls);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2016-09-26 09:31:05 -04:00
|
|
|
apply_one_source_per_region_fix (Session* session)
|
2016-09-24 13:25:49 -04:00
|
|
|
{
|
|
|
|
const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
|
|
|
|
|
|
|
|
if (!region_map.size()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
list<boost::shared_ptr<MidiSource> > old_source_list;
|
|
|
|
|
|
|
|
/* set start and length for every midi region. ensure a new converted source exists and switch to it. */
|
2016-09-24 13:25:49 -04:00
|
|
|
for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
|
2016-09-29 07:41:17 -04:00
|
|
|
boost::shared_ptr<MidiRegion> mr;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
if ((mr = boost::dynamic_pointer_cast<MidiRegion>((*i).second)) != 0) {
|
2016-09-26 11:56:27 -04:00
|
|
|
|
|
|
|
if (!mr->midi_source()->writable()) {
|
|
|
|
/* we know the midi dir is writable, so this region is external. leave it alone*/
|
|
|
|
cout << mr->source()->name() << "is not writable. skipping." << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
old_source_list.push_back (mr->midi_source());
|
|
|
|
|
2016-09-28 09:33:18 -04:00
|
|
|
reset_start (session, mr);
|
|
|
|
reset_length (session, mr);
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
string newsrc_filename = mr->name() + "-a54-compat.mid";
|
2016-09-26 09:31:05 -04:00
|
|
|
string newsrc_path = Glib::build_filename (session->session_directory().midi_path(), newsrc_filename);
|
2016-09-29 08:34:32 -04:00
|
|
|
|
2016-09-26 11:56:27 -04:00
|
|
|
boost::shared_ptr<MidiSource> newsrc = ensure_per_region_source (session, mr, newsrc_path);
|
2016-09-28 09:33:18 -04:00
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
mr->clobber_sources (newsrc);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
old_source_list.unique();
|
|
|
|
|
|
|
|
/* remove old sources from the session. current snapshot is saved.*/
|
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " clearing old sources." << endl;
|
|
|
|
|
|
|
|
for (list<boost::shared_ptr<MidiSource> >::iterator i = old_source_list.begin(); i != old_source_list.end(); ++i) {
|
|
|
|
session->remove_source (boost::weak_ptr<MidiSource> (*i));
|
|
|
|
}
|
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool
|
2016-09-26 09:31:05 -04:00
|
|
|
apply_one_source_per_source_fix (Session* session)
|
2016-09-24 13:25:49 -04:00
|
|
|
{
|
|
|
|
const RegionFactory::RegionMap& region_map (RegionFactory::all_regions());
|
|
|
|
|
|
|
|
if (!region_map.size()) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2016-09-26 10:59:28 -04:00
|
|
|
map<PBD::ID, boost::shared_ptr<MidiSource> > old_source_to_new;
|
2016-09-29 08:34:32 -04:00
|
|
|
/* reset every midi region's start and length. ensure its corrected source exists. */
|
2016-09-24 13:25:49 -04:00
|
|
|
for (RegionFactory::RegionMap::const_iterator i = region_map.begin(); i != region_map.end(); ++i) {
|
2016-09-29 07:41:17 -04:00
|
|
|
boost::shared_ptr<MidiRegion> mr;
|
2016-09-24 13:25:49 -04:00
|
|
|
map<PBD::ID, boost::shared_ptr<MidiSource> >::iterator src_it;
|
|
|
|
|
|
|
|
if ((mr = boost::dynamic_pointer_cast<MidiRegion>((*i).second)) != 0) {
|
2016-09-26 11:56:27 -04:00
|
|
|
|
|
|
|
if (!mr->midi_source()->writable()) {
|
|
|
|
cout << mr->source()->name() << "is not writable. skipping." << endl;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-09-28 09:33:18 -04:00
|
|
|
reset_start (session, mr);
|
|
|
|
reset_length (session, mr);
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-26 10:59:28 -04:00
|
|
|
if ((src_it = old_source_to_new.find (mr->midi_source()->id())) == old_source_to_new.end()) {
|
2016-09-26 09:31:05 -04:00
|
|
|
string newsrc_filename = mr->source()->name() + "-a54-compat.mid";
|
|
|
|
string newsrc_path = Glib::build_filename (session->session_directory().midi_path(), newsrc_filename);
|
|
|
|
|
2016-09-26 11:56:27 -04:00
|
|
|
boost::shared_ptr<MidiSource> newsrc = ensure_per_source_source (session, mr, newsrc_path);
|
2016-09-26 09:31:05 -04:00
|
|
|
|
2016-09-26 10:59:28 -04:00
|
|
|
old_source_to_new.insert (make_pair (mr->midi_source()->id(), newsrc));
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
mr->midi_source(0)->set_name (newsrc->name());
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
/* remove new sources from the session. current snapshot is saved.*/
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " clearing new sources." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
|
2016-09-26 10:59:28 -04:00
|
|
|
for (map<PBD::ID, boost::shared_ptr<MidiSource> >::iterator i = old_source_to_new.begin(); i != old_source_to_new.end(); ++i) {
|
2016-09-26 09:31:05 -04:00
|
|
|
session->remove_source (boost::weak_ptr<MidiSource> ((*i).second));
|
|
|
|
}
|
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-08-14 19:07:41 -04:00
|
|
|
static void usage () {
|
2016-09-24 13:25:49 -04:00
|
|
|
// help2man compatible format (standard GNU help-text)
|
|
|
|
printf (UTILNAME " - convert an ardour session with 5.0 - 5.3 midi sources to be compatible with 5.4.\n\n");
|
2016-09-26 09:31:05 -04:00
|
|
|
printf ("Usage: " UTILNAME " [ OPTIONS ] <session-dir> <snapshot-name>\n\n");
|
2016-09-24 13:25:49 -04:00
|
|
|
printf ("Options:\n\
|
2016-09-26 09:31:05 -04:00
|
|
|
-h, --help display this help and exit\n\
|
|
|
|
-f, --force override detection of affected sessions\n\
|
|
|
|
-o, --output <snapshot-name> output session snapshot name (without file suffix)\n\
|
|
|
|
-V, --version print version information and exit\n\
|
2016-09-24 13:25:49 -04:00
|
|
|
\n");
|
|
|
|
printf ("\n\
|
2016-09-29 08:34:32 -04:00
|
|
|
This Ardour-specific utility provides an upgrade path for sessions created or\n\
|
|
|
|
modified with Ardour versions 5.0 - 5.3.\n\
|
2016-09-24 13:25:49 -04:00
|
|
|
It creates a 5.4-compatible snapshot from affected Ardour session files.\n\
|
2016-09-29 08:34:32 -04:00
|
|
|
Affected versions (5.0 - 5.3 inclusive) contain a bug which caused some\n\
|
|
|
|
MIDI region properties and contents to be stored incorrectly\n\
|
|
|
|
(see more below).\n\n\
|
|
|
|
The utility will first determine whether or not a session requires any\n\
|
|
|
|
changes for 5.4 compatibility.\n\
|
|
|
|
If a session is determined to be affected by the bug, the program will take\n\
|
|
|
|
one of two approaches to correcting the problem.\n\n\
|
|
|
|
The first is to write a new MIDI source file for every existing MIDI source\n\
|
|
|
|
in the supplied snapshot.\n\
|
|
|
|
In the second approach, each MIDI region have its source converted and placed\n\
|
|
|
|
in the session midifiles directory as a new source\n\
|
|
|
|
(one source file per region).\n\
|
|
|
|
The second method is only offered if the first approach cannot logically ensure\n\
|
|
|
|
that the results would match the input snapshot.\n\
|
|
|
|
Using the first method even if the second method is offered\n\
|
|
|
|
will usually match the input exactly\n\
|
|
|
|
(partly due to a characteristic of the bug).\n\n\
|
|
|
|
Both methods update MIDI region properties and save a new snapshot in the\n\
|
|
|
|
supplied session-dir, optionally using a supplied snapshot name (-o).\n\
|
2016-09-24 13:25:49 -04:00
|
|
|
The new snapshot may be used on Ardour-5.4.\n\n\
|
2016-09-29 08:34:32 -04:00
|
|
|
Running this utility should not alter any existing files,\n\
|
|
|
|
but it is recommended that you run it on a backup of the session directory.\n\n\
|
2016-09-24 13:25:49 -04:00
|
|
|
EXAMPLE:\n\
|
2016-09-29 08:34:32 -04:00
|
|
|
ardour5-fix_bbtppq -o bantam ~/studio/leghorn leghorn\n\
|
|
|
|
will create a new snapshot file ~/studio/leghorn/bantam.ardour from\n\
|
|
|
|
~/studio/leghorn/leghorn.ardour\n\
|
|
|
|
Converted midi sources will be created in\n\
|
|
|
|
~/studio/leghorn/interchange/leghorn/midifiles/\n\
|
|
|
|
If the output option (-o) is omitted, the string \"-a54-compat\"\n\
|
|
|
|
will be appended to the supplied snapshot name.\n\n\
|
2016-09-24 13:25:49 -04:00
|
|
|
About the Bug\n\
|
2016-09-29 08:34:32 -04:00
|
|
|
If a session from affected versions used MIDI regions and a meter note divisor\n\
|
|
|
|
was set to anything but quarter notes, the source smf files would contain events\n\
|
|
|
|
at a PPQN value derived from BBT beats (using meter note divisor)\n\
|
2016-09-29 09:45:15 -04:00
|
|
|
rather than quarter-note beats.\n\
|
2016-09-24 13:25:49 -04:00
|
|
|
The region start and length offsets would also be stored incorrectly.\n\
|
|
|
|
If a MIDI session only contains quarter note meter divisors, it will be unaffected.\n\
|
|
|
|
\n");
|
|
|
|
|
|
|
|
printf ("Report bugs to <http://tracker.ardour.org/>\n"
|
|
|
|
"Website: <http://ardour.org/>\n");
|
2019-08-14 19:07:41 -04:00
|
|
|
::exit (EXIT_SUCCESS);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
int main (int argc, char* argv[])
|
|
|
|
{
|
2016-09-26 10:34:34 -04:00
|
|
|
string outfile;
|
2016-09-24 13:25:49 -04:00
|
|
|
bool force = false;
|
|
|
|
|
|
|
|
const char *optstring = "hfo:r:V";
|
|
|
|
|
|
|
|
const struct option longopts[] = {
|
|
|
|
{ "help", 0, 0, 'h' },
|
|
|
|
{ "force", 0, 0, 'f' },
|
|
|
|
{ "output", 1, 0, 'o' },
|
|
|
|
{ "version", 0, 0, 'V' },
|
|
|
|
};
|
|
|
|
|
|
|
|
int c = 0;
|
|
|
|
while (EOF != (c = getopt_long (argc, argv,
|
|
|
|
optstring, longopts, (int *) 0))) {
|
|
|
|
switch (c) {
|
|
|
|
|
|
|
|
case 'f':
|
|
|
|
force = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'o':
|
|
|
|
outfile = optarg;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 'V':
|
|
|
|
printf ("ardour-utils version %s\n\n", VERSIONSTRING);
|
|
|
|
printf ("Copyright (C) GPL 2015 Robin Gareus <robin@gareus.org>\n");
|
2019-07-04 16:21:14 -04:00
|
|
|
exit (EXIT_SUCCESS);
|
2016-09-24 13:25:49 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 'h':
|
2019-08-14 19:07:41 -04:00
|
|
|
usage ();
|
2016-09-24 13:25:49 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
2019-08-14 19:07:41 -04:00
|
|
|
cerr << "Error: unrecognized option. See --help for usage information.\n";
|
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (optind + 2 > argc) {
|
2019-08-14 19:07:41 -04:00
|
|
|
cerr << "Error: Missing parameter. See --help for usage information.\n";
|
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
SessionDirectory* session_dir = new SessionDirectory (argv[optind]);
|
2016-09-26 09:31:05 -04:00
|
|
|
string snapshot_name (argv[optind+1]);
|
|
|
|
string statefile_suffix (X_(".ardour"));
|
|
|
|
string pending_suffix (X_(".pending"));
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
XMLTree* state_tree;
|
|
|
|
|
2016-09-26 10:34:34 -04:00
|
|
|
string xmlpath(argv[optind]);
|
2016-09-26 09:31:05 -04:00
|
|
|
string out_snapshot_name;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
if (!outfile.empty()) {
|
2016-09-24 13:25:49 -04:00
|
|
|
string file_test_path = Glib::build_filename (argv[optind], outfile + statefile_suffix);
|
|
|
|
if (Glib::file_test (file_test_path, Glib::FILE_TEST_EXISTS)) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " session file " << file_test_path << " already exists!" << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
2016-09-26 09:31:05 -04:00
|
|
|
out_snapshot_name = outfile;
|
2016-09-24 13:25:49 -04:00
|
|
|
} else {
|
|
|
|
string file_test_path = Glib::build_filename (argv[optind], snapshot_name + "-a54-compat" + statefile_suffix);
|
|
|
|
if (Glib::file_test (file_test_path, Glib::FILE_TEST_EXISTS)) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " session file " << file_test_path << " already exists!" << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
2016-09-26 09:31:05 -04:00
|
|
|
out_snapshot_name = snapshot_name + "-a54-compat";
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
xmlpath = Glib::build_filename (xmlpath, legalize_for_path (snapshot_name) + pending_suffix);
|
|
|
|
|
|
|
|
if (Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
|
|
|
|
/* there is pending state from a crashed capture attempt */
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " There seems to be pending state for snapshot : " << snapshot_name << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
xmlpath = Glib::build_filename (argv[optind], argv[optind+1]);
|
|
|
|
|
|
|
|
if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
|
|
|
|
xmlpath = Glib::build_filename (argv[optind], legalize_for_path (argv[optind+1]) + ".ardour");
|
|
|
|
if (!Glib::file_test (xmlpath, Glib::FILE_TEST_EXISTS)) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " session file " << xmlpath << " doesn't exist!" << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
state_tree = new XMLTree;
|
|
|
|
|
|
|
|
bool writable = PBD::exists_and_writable (xmlpath) && PBD::exists_and_writable(Glib::path_get_dirname(xmlpath));
|
|
|
|
|
|
|
|
if (!writable) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Error : The session directory must exist and be writable." << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2016-09-28 09:33:18 -04:00
|
|
|
if (!PBD::exists_and_writable (Glib::path_get_dirname (session_dir->midi_path()))) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Error : The session midi directory " << session_dir->midi_path() << " must be writable. exiting." << endl;
|
2016-09-28 09:33:18 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
if (!state_tree->read (xmlpath)) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Could not understand session file " << xmlpath << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
delete state_tree;
|
|
|
|
state_tree = 0;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode const & root (*state_tree->root());
|
|
|
|
|
|
|
|
if (root.name() != X_("Session")) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Session file " << xmlpath<< " is not a session" << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
delete state_tree;
|
|
|
|
state_tree = 0;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
XMLProperty const * prop;
|
|
|
|
|
|
|
|
if ((prop = root.property ("version")) == 0) {
|
|
|
|
/* no version implies very old version of Ardour */
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " The session " << snapshot_name << " has no version or is too old to be affected. exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
} else {
|
|
|
|
if (prop->value().find ('.') != string::npos) {
|
|
|
|
/* old school version format */
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " The session " << snapshot_name << " is too old to be affected. exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
} else {
|
|
|
|
PBD::Stateful::loading_state_version = atoi (prop->value().c_str());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Checking snapshot : " << snapshot_name << " in directory : " << session_dir->root_path() << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
bool midi_regions_use_bbt_beats = false;
|
|
|
|
|
|
|
|
if (PBD::Stateful::loading_state_version == 3002 && writable) {
|
|
|
|
XMLNode* child;
|
|
|
|
if ((child = find_named_node (root, "ProgramVersion")) != 0) {
|
|
|
|
if ((prop = child->property ("modified-with")) != 0) {
|
2016-09-26 10:34:34 -04:00
|
|
|
string modified_with = prop->value ();
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
const double modified_with_version = atof (modified_with.substr ( modified_with.find(" ", 0) + 1, string::npos).c_str());
|
|
|
|
const int modified_with_revision = atoi (modified_with.substr (modified_with.find("-", 0) + 1, string::npos).c_str());
|
|
|
|
|
|
|
|
if (modified_with_version <= 5.3 && !(modified_with_version == 5.3 && modified_with_revision >= 42)) {
|
|
|
|
midi_regions_use_bbt_beats = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
XMLNode* tm_node;
|
|
|
|
bool all_metrum_divisors_are_quarters = true;
|
|
|
|
list<double> divisor_list;
|
|
|
|
|
|
|
|
if ((tm_node = find_named_node (root, "TempoMap")) != 0) {
|
|
|
|
XMLNodeList metrum;
|
|
|
|
XMLNodeConstIterator niter;
|
|
|
|
metrum = tm_node->children();
|
|
|
|
for (niter = metrum.begin(); niter != metrum.end(); ++niter) {
|
|
|
|
XMLNode* child = *niter;
|
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
if (child->name() == MeterSection::xml_state_node_name && (prop = child->property ("note-type")) != 0) {
|
|
|
|
double note_type;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
if (sscanf (prop->value().c_str(), "%lf", ¬e_type) ==1) {
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
if (note_type != 4.0) {
|
2016-09-24 13:25:49 -04:00
|
|
|
all_metrum_divisors_are_quarters = false;
|
|
|
|
}
|
2016-09-26 10:59:28 -04:00
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
divisor_list.push_back (note_type);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Session file " << xmlpath << " has no TempoMap node. exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (all_metrum_divisors_are_quarters && !force) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " The session " << snapshot_name << " is clear for use in 5.4 (all divisors are quarters). Use -f to override." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* check for multiple note divisors. if there is only one, we can create one file per source. */
|
2016-09-26 09:31:05 -04:00
|
|
|
bool one_source_file_per_source = false;
|
2016-09-24 13:25:49 -04:00
|
|
|
divisor_list.unique();
|
|
|
|
|
|
|
|
if (divisor_list.size() == 1) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << endl << UTILNAME << ":" << endl
|
|
|
|
<< " Snapshot " << snapshot_name << " will be converted using one new file per source." << endl
|
|
|
|
<< " To continue with per-source conversion enter s. q to quit." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
while (1) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << " [s/q]" << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
string input;
|
2016-09-26 10:34:34 -04:00
|
|
|
getline (cin, input);
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
if (input == "s") {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input == "q") {
|
|
|
|
exit (EXIT_SUCCESS);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
one_source_file_per_source = true;
|
|
|
|
} else {
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << endl << UTILNAME << ":" << endl
|
|
|
|
<< " Snapshot " << snapshot_name << " contains multiple meter note divisors." << endl
|
|
|
|
<< " Per-region source conversion ensures that the output snapshot will be identical to the original," << endl
|
|
|
|
<< " however regions in the new snapshot will no longer share sources." << endl << endl
|
|
|
|
<< " In many (but not all) cases per-source conversion will work equally well." << endl
|
|
|
|
<< " It is recommended that you test a snapshot created with the per-source method before using per-region conversion." << endl << endl
|
|
|
|
<< " To continue with per-region conversion enter r. For per-source conversion, enter s. q to quit." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
while (1) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << " [r/s/q]" << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
string input;
|
2016-09-26 10:34:34 -04:00
|
|
|
getline (cin, input);
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
if (input == "s") {
|
|
|
|
one_source_file_per_source = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input == "r") {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (input == "q") {
|
|
|
|
exit (EXIT_SUCCESS);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (midi_regions_use_bbt_beats || force) {
|
2016-09-26 09:31:05 -04:00
|
|
|
|
2016-09-24 13:25:49 -04:00
|
|
|
if (force) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Forced update of snapshot : " << snapshot_name << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
SessionUtils::init();
|
|
|
|
Session* s = 0;
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Loading snapshot " << snapshot_name << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
|
|
|
s = SessionUtils::load_session (argv[optind], argv[optind+1]);
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
/* save new snapshot and prevent alteration of the original by switching to it.
|
|
|
|
we know these files don't yet exist.
|
|
|
|
*/
|
|
|
|
if (s->save_state (out_snapshot_name, false, true)) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Could not save new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
session_fail (s);
|
|
|
|
}
|
|
|
|
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Saved new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
|
|
|
|
if (one_source_file_per_source) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Will create one MIDI file per source." << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
if (!apply_one_source_per_source_fix (s)) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " The snapshot " << snapshot_name << " is clear for use in 5.4 (no midi regions). exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
session_fail (s);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
} else {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Will create one MIDI file per midi region." << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
if (!apply_one_source_per_region_fix (s)) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " The snapshot " << snapshot_name << " is clear for use in 5.4 (no midi regions). exiting." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
session_fail (s);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
2016-09-26 09:31:05 -04:00
|
|
|
if (s->save_state (out_snapshot_name, false, true)) {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Could not save snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
session_fail (s);
|
|
|
|
}
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Saved new snapshot: " << out_snapshot_name << " in " << session_dir->root_path() << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
SessionUtils::unload_session(s);
|
|
|
|
SessionUtils::cleanup();
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " Snapshot " << out_snapshot_name << " is ready for use in 5.4" << endl;
|
2016-09-24 13:25:49 -04:00
|
|
|
} else {
|
2016-09-29 08:34:32 -04:00
|
|
|
cout << UTILNAME << ":" << endl
|
|
|
|
<< " The snapshot " << snapshot_name << " doesn't require any change for use in 5.4. Use -f to override." << endl;
|
2016-09-26 09:31:05 -04:00
|
|
|
::exit (EXIT_FAILURE);
|
2016-09-24 13:25:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|