annotate align/ExternalProgramAligner.cpp @ 786:1089d65c585d tip

Divert some debug output away from stderr
author Chris Cannam
date Fri, 14 Aug 2020 10:46:44 +0100
parents ee430e9ffccc
children
rev   line source
Chris@752 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@752 2
Chris@752 3 /*
Chris@752 4 Sonic Visualiser
Chris@752 5 An audio file viewer and annotation editor.
Chris@752 6 Centre for Digital Music, Queen Mary, University of London.
Chris@752 7
Chris@752 8 This program is free software; you can redistribute it and/or
Chris@752 9 modify it under the terms of the GNU General Public License as
Chris@752 10 published by the Free Software Foundation; either version 2 of the
Chris@752 11 License, or (at your option) any later version. See the file
Chris@752 12 COPYING included with this distribution for more information.
Chris@752 13 */
Chris@752 14
Chris@752 15 #include "ExternalProgramAligner.h"
Chris@752 16
Chris@752 17 #include <QFileInfo>
Chris@752 18 #include <QApplication>
Chris@752 19
Chris@752 20 #include "data/model/ReadOnlyWaveFileModel.h"
Chris@752 21 #include "data/model/SparseTimeValueModel.h"
Chris@752 22 #include "data/model/AlignmentModel.h"
Chris@752 23
Chris@752 24 #include "data/fileio/CSVFileReader.h"
Chris@752 25
Chris@752 26 #include "framework/Document.h"
Chris@752 27
Chris@752 28 ExternalProgramAligner::ExternalProgramAligner(Document *doc,
Chris@752 29 ModelId reference,
Chris@752 30 ModelId toAlign,
Chris@752 31 QString program) :
Chris@752 32 m_document(doc),
Chris@752 33 m_reference(reference),
Chris@752 34 m_toAlign(toAlign),
Chris@752 35 m_program(program),
Chris@752 36 m_process(nullptr)
Chris@752 37 {
Chris@752 38 }
Chris@752 39
Chris@752 40 ExternalProgramAligner::~ExternalProgramAligner()
Chris@752 41 {
Chris@773 42 if (m_process) {
Chris@773 43 disconnect(m_process, nullptr, this, nullptr);
Chris@773 44 }
Chris@773 45
Chris@752 46 delete m_process;
Chris@752 47 }
Chris@752 48
Chris@752 49 bool
Chris@752 50 ExternalProgramAligner::isAvailable(QString program)
Chris@752 51 {
Chris@752 52 QFileInfo file(program);
Chris@752 53 return file.exists() && file.isExecutable();
Chris@752 54 }
Chris@752 55
Chris@761 56 void
Chris@761 57 ExternalProgramAligner::begin()
Chris@752 58 {
Chris@752 59 // Run an external program, passing to it paths to the main
Chris@752 60 // model's audio file and the new model's audio file. It returns
Chris@752 61 // the path in CSV form through stdout.
Chris@752 62
Chris@752 63 auto reference = ModelById::getAs<ReadOnlyWaveFileModel>(m_reference);
Chris@752 64 auto other = ModelById::getAs<ReadOnlyWaveFileModel>(m_toAlign);
Chris@752 65 if (!reference || !other) {
Chris@752 66 SVCERR << "ERROR: ExternalProgramAligner: Can't align non-read-only models via program (no local filename available)" << endl;
Chris@761 67 return;
Chris@752 68 }
Chris@752 69
Chris@769 70 if (m_program == "") {
Chris@769 71 emit failed(m_toAlign, tr("No external program specified"));
Chris@769 72 return;
Chris@769 73 }
Chris@769 74
Chris@752 75 while (!reference->isReady(nullptr) || !other->isReady(nullptr)) {
Chris@752 76 qApp->processEvents();
Chris@752 77 }
Chris@752 78
Chris@752 79 QString refPath = reference->getLocalFilename();
Chris@752 80 if (refPath == "") {
Chris@752 81 refPath = FileSource(reference->getLocation()).getLocalFilename();
Chris@752 82 }
Chris@752 83
Chris@752 84 QString otherPath = other->getLocalFilename();
Chris@752 85 if (otherPath == "") {
Chris@752 86 otherPath = FileSource(other->getLocation()).getLocalFilename();
Chris@752 87 }
Chris@752 88
Chris@752 89 if (refPath == "" || otherPath == "") {
Chris@761 90 emit failed(m_toAlign,
Chris@761 91 tr("Failed to find local filepath for wave-file model"));
Chris@761 92 return;
Chris@752 93 }
Chris@752 94
Chris@752 95 auto alignmentModel =
Chris@752 96 std::make_shared<AlignmentModel>(m_reference, m_toAlign, ModelId());
Chris@752 97
Chris@752 98 m_alignmentModel = ModelById::add(alignmentModel);
Chris@752 99 other->setAlignment(m_alignmentModel);
Chris@752 100
Chris@752 101 m_process = new QProcess;
Chris@779 102 m_process->setProcessChannelMode(QProcess::SeparateChannels);
Chris@752 103
Chris@752 104 connect(m_process,
Chris@752 105 SIGNAL(finished(int, QProcess::ExitStatus)),
Chris@752 106 this,
Chris@752 107 SLOT(programFinished(int, QProcess::ExitStatus)));
Chris@752 108
Chris@779 109 connect(m_process,
Chris@779 110 SIGNAL(readyReadStandardError()),
Chris@779 111 this,
Chris@779 112 SLOT(logStderrOutput()));
Chris@779 113
Chris@752 114 QStringList args;
Chris@752 115 args << refPath << otherPath;
Chris@752 116
Chris@752 117 SVCERR << "ExternalProgramAligner: Starting program \""
Chris@752 118 << m_program << "\" with args: ";
Chris@752 119 for (auto a: args) {
Chris@752 120 SVCERR << "\"" << a << "\" ";
Chris@752 121 }
Chris@752 122 SVCERR << endl;
Chris@752 123
Chris@752 124 m_process->start(m_program, args);
Chris@752 125
Chris@752 126 bool success = m_process->waitForStarted();
Chris@752 127
Chris@752 128 if (!success) {
Chris@752 129
Chris@752 130 SVCERR << "ERROR: ExternalProgramAligner: Program did not start" << endl;
Chris@769 131
Chris@769 132 other->setAlignment({});
Chris@769 133 ModelById::release(m_alignmentModel);
Chris@769 134 delete m_process;
Chris@769 135 m_process = nullptr;
Chris@769 136
Chris@761 137 emit failed(m_toAlign,
Chris@761 138 tr("Alignment program \"%1\" did not start")
Chris@761 139 .arg(m_program));
Chris@752 140
Chris@752 141 } else {
Chris@769 142 alignmentModel->setCompletion(10);
Chris@752 143 m_document->addNonDerivedModel(m_alignmentModel);
Chris@752 144 }
Chris@752 145 }
Chris@752 146
Chris@752 147 void
Chris@779 148 ExternalProgramAligner::logStderrOutput()
Chris@779 149 {
Chris@779 150 if (!m_process) return;
Chris@779 151
Chris@779 152 m_process->setReadChannel(QProcess::StandardError);
Chris@779 153
Chris@779 154 qint64 byteCount = m_process->bytesAvailable();
Chris@779 155 if (byteCount == 0) {
Chris@779 156 m_process->setReadChannel(QProcess::StandardOutput);
Chris@779 157 return;
Chris@779 158 }
Chris@779 159
Chris@779 160 QByteArray buffer = m_process->read(byteCount);
Chris@779 161 while (buffer.endsWith('\n') || buffer.endsWith('\r')) {
Chris@779 162 buffer.chop(1);
Chris@779 163 }
Chris@779 164
Chris@779 165 QString str = QString::fromUtf8(buffer);
Chris@779 166
Chris@779 167 cerr << str << endl;
Chris@779 168
Chris@784 169 #if (QT_VERSION >= 0x050300)
Chris@779 170 QString pfx = QString("[pid%1] ").arg(m_process->processId());
Chris@784 171 #else
Chris@784 172 QString pfx = QString("[subproc] ");
Chris@784 173 #endif
Chris@779 174 str.replace("\r", "\\r");
Chris@779 175 str.replace("\n", "\n" + pfx);
Chris@779 176
Chris@779 177 SVDEBUG << pfx << str << endl;
Chris@779 178
Chris@779 179 m_process->setReadChannel(QProcess::StandardOutput);
Chris@779 180 }
Chris@779 181
Chris@779 182 void
Chris@769 183 ExternalProgramAligner::programFinished(int exitCode,
Chris@769 184 QProcess::ExitStatus status)
Chris@752 185 {
Chris@752 186 SVCERR << "ExternalProgramAligner::programFinished" << endl;
Chris@752 187
Chris@752 188 QProcess *process = qobject_cast<QProcess *>(sender());
Chris@752 189
Chris@752 190 if (process != m_process) {
Chris@752 191 SVCERR << "ERROR: ExternalProgramAligner: Emitting process " << process
Chris@752 192 << " is not my process!" << endl;
Chris@752 193 return;
Chris@752 194 }
Chris@752 195
Chris@779 196 logStderrOutput();
Chris@779 197
Chris@752 198 auto alignmentModel = ModelById::getAs<AlignmentModel>(m_alignmentModel);
Chris@752 199 if (!alignmentModel) {
Chris@752 200 SVCERR << "ExternalProgramAligner: AlignmentModel no longer exists"
Chris@752 201 << endl;
Chris@752 202 return;
Chris@752 203 }
Chris@769 204
Chris@769 205 QString errorText;
Chris@752 206
Chris@752 207 if (exitCode == 0 && status == 0) {
Chris@752 208
Chris@752 209 CSVFormat format;
Chris@752 210 format.setModelType(CSVFormat::TwoDimensionalModel);
Chris@752 211 format.setTimingType(CSVFormat::ExplicitTiming);
Chris@752 212 format.setTimeUnits(CSVFormat::TimeSeconds);
Chris@752 213 format.setColumnCount(2);
Chris@752 214 // The output format has time in the reference file first, and
Chris@752 215 // time in the "other" file in the second column. This is a
Chris@752 216 // more natural approach for a command-line alignment tool,
Chris@752 217 // but it's the opposite of what we expect for native
Chris@752 218 // alignment paths, which map from "other" file to
Chris@752 219 // reference. These column purpose settings reflect that.
Chris@752 220 format.setColumnPurpose(1, CSVFormat::ColumnStartTime);
Chris@752 221 format.setColumnPurpose(0, CSVFormat::ColumnValue);
Chris@752 222 format.setAllowQuoting(false);
Chris@752 223 format.setSeparator(',');
Chris@752 224
Chris@752 225 CSVFileReader reader(process, format, alignmentModel->getSampleRate());
Chris@752 226 if (!reader.isOK()) {
Chris@752 227 SVCERR << "ERROR: ExternalProgramAligner: Failed to parse output"
Chris@752 228 << endl;
Chris@769 229 errorText = tr("Failed to parse output of program: %1")
Chris@761 230 .arg(reader.getError());
Chris@769 231 alignmentModel->setError(errorText);
Chris@752 232 goto done;
Chris@752 233 }
Chris@752 234
Chris@752 235 //!!! to use ById?
Chris@752 236
Chris@752 237 Model *csvOutput = reader.load();
Chris@752 238
Chris@752 239 SparseTimeValueModel *path =
Chris@752 240 qobject_cast<SparseTimeValueModel *>(csvOutput);
Chris@752 241 if (!path) {
Chris@752 242 SVCERR << "ERROR: ExternalProgramAligner: Output did not convert to sparse time-value model"
Chris@752 243 << endl;
Chris@769 244 errorText =
Chris@761 245 tr("Output of alignment program was not in the proper format");
Chris@769 246 alignmentModel->setError(errorText);
Chris@752 247 delete csvOutput;
Chris@752 248 goto done;
Chris@752 249 }
Chris@752 250
Chris@752 251 if (path->isEmpty()) {
Chris@752 252 SVCERR << "ERROR: ExternalProgramAligner: Output contained no mappings"
Chris@752 253 << endl;
Chris@769 254 errorText =
Chris@761 255 tr("Output of alignment program contained no mappings");
Chris@769 256 alignmentModel->setError(errorText);
Chris@752 257 delete path;
Chris@752 258 goto done;
Chris@752 259 }
Chris@752 260
Chris@752 261 SVCERR << "ExternalProgramAligner: Setting alignment path ("
Chris@752 262 << path->getEventCount() << " point(s))" << endl;
Chris@752 263
Chris@752 264 auto pathId =
Chris@752 265 ModelById::add(std::shared_ptr<SparseTimeValueModel>(path));
Chris@752 266 alignmentModel->setPathFrom(pathId);
Chris@769 267 alignmentModel->setCompletion(100);
Chris@752 268
Chris@752 269 ModelById::release(pathId);
Chris@752 270
Chris@752 271 } else {
Chris@752 272 SVCERR << "ERROR: ExternalProgramAligner: Aligner program "
Chris@752 273 << "failed: exit code " << exitCode << ", status " << status
Chris@752 274 << endl;
Chris@769 275 errorText = tr("Aligner process returned non-zero exit status");
Chris@769 276 alignmentModel->setError(errorText);
Chris@752 277 }
Chris@752 278
Chris@752 279 done:
Chris@752 280 delete m_process;
Chris@752 281 m_process = nullptr;
Chris@769 282
Chris@769 283 // "This should be emitted as the last thing the aligner does, as
Chris@769 284 // the recipient may delete the aligner during the call."
Chris@769 285 if (errorText == "") {
Chris@769 286 emit complete(m_alignmentModel);
Chris@769 287 } else {
Chris@769 288 emit failed(m_toAlign, errorText);
Chris@769 289 }
Chris@752 290 }