gyorgyf@60: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ gyorgyf@60: gyorgyf@60: /* gyorgyf@60: Sonic Annotator gyorgyf@60: A utility for batch feature extraction from audio files. gyorgyf@60: Mark Levy, Chris Sutton and Chris Cannam, Queen Mary, University of London. gyorgyf@60: Copyright 2007-2008 QMUL. gyorgyf@60: gyorgyf@60: This program is free software; you can redistribute it and/or gyorgyf@60: modify it under the terms of the GNU General Public License as gyorgyf@60: published by the Free Software Foundation; either version 2 of the gyorgyf@60: License, or (at your option) any later version. See the file gyorgyf@60: COPYING included with this distribution for more information. gyorgyf@60: */ gyorgyf@60: gyorgyf@60: #include gyorgyf@60: gyorgyf@60: #include gyorgyf@60: gyorgyf@60: #include gyorgyf@60: gyorgyf@60: #include gyorgyf@60: gyorgyf@60: #include "BinaryFeatureWriter.h" gyorgyf@60: gyorgyf@60: #include "base/RealTime.h" gyorgyf@60: gyorgyf@60: #include "../version.h" gyorgyf@60: gyorgyf@60: #ifdef _WIN32 gyorgyf@60: #define platform "Windows" gyorgyf@60: #elif __APPLE__ gyorgyf@60: #define platform "MacOS" gyorgyf@60: #elif __linux__ gyorgyf@60: #define platform "Linux" gyorgyf@60: #else gyorgyf@60: #define platform "Unix" gyorgyf@60: #endif gyorgyf@60: #ifdef __LP64__ //__x86_64__ gyorgyf@60: #define arch "64" gyorgyf@60: #elif _WIN64 gyorgyf@60: #define arch "64" gyorgyf@60: #else gyorgyf@60: #define arch "32" gyorgyf@60: #endif gyorgyf@60: gyorgyf@60: # define MAJOR_VERSION_PY 0 gyorgyf@60: gyorgyf@60: using namespace std; gyorgyf@60: using namespace Vamp; gyorgyf@60: gyorgyf@60: // Parameter names gyorgyf@60: string gyorgyf@60: BinaryFeatureWriter::outputFileParam = "output"; gyorgyf@60: gyorgyf@60: struct BinaryFeatureWriter::OutputStream gyorgyf@60: { gyorgyf@60: ofstream* stream; gyorgyf@60: bool newtransform; gyorgyf@60: const Transform *transform; gyorgyf@60: OutputStream() : newtransform(true),stream(NULL),transform(NULL) { } gyorgyf@60: ~OutputStream() { if (stream != NULL) {stream->close(); delete stream;} } gyorgyf@60: gyorgyf@60: struct header_t { gyorgyf@66: int16_t BOM16; // 16-bit BOM (FEFF as in UTF-16) 2 bytes gyorgyf@66: int32_t BOM32; // 32-bit BOM (human readable: e.g. ABCD) 4 bytes gyorgyf@64: char major_version; // check for binary compatibility gyorgyf@64: char minor_version; // changes in txt parts only gyorgyf@64: char compression; // use of stream compression (e.g. gzip) gyorgyf@64: char reserved1; // reserved byte gyorgyf@64: char reserved2; // reserved byte gyorgyf@64: char float_size; // size of float gyorgyf@64: char int_size; // size of int gyorgyf@64: char info[160]; // 160 byte text field gyorgyf@64: char null; }; // NULL gyorgyf@60: gyorgyf@60: bool open(string filename, bool append = true) { gyorgyf@60: gyorgyf@60: if (stream) return true; gyorgyf@60: gyorgyf@66: header_t header = {(int16_t) 0xFEFF, (int32_t) 0x41424344,MAJOR_VERSION_PY,2,0,NULL,NULL,(char)sizeof(float),(char)sizeof(int),{NULL},NULL}; gyorgyf@60: char* p_header = reinterpret_cast(&header); gyorgyf@60: gyorgyf@60: if (append) gyorgyf@60: stream = new ofstream(filename.c_str(), fstream::binary | ios_base::out | ios_base::in | ofstream::ate); gyorgyf@60: else gyorgyf@60: stream = new ofstream(filename.c_str(), fstream::binary); gyorgyf@65: gyorgyf@65: if (!stream || !stream->is_open()) { gyorgyf@65: if (append) { gyorgyf@65: cerr << endl << "NOTE: Writing new binary output file: " << filename << endl; gyorgyf@65: delete stream; gyorgyf@65: stream = NULL; gyorgyf@65: return open(filename,false); gyorgyf@65: } else { gyorgyf@65: cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): can't open file " << filename << endl; gyorgyf@65: return false; gyorgyf@65: } gyorgyf@60: } gyorgyf@60: gyorgyf@60: // verify input file format gyorgyf@60: if (append) { gyorgyf@60: ifstream istream; gyorgyf@60: istream.open(filename.c_str(), fstream::binary | ios::in); gyorgyf@60: if (!istream.is_open()) { gyorgyf@60: cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): Can not verify supplied output stream." << endl; gyorgyf@60: return false; gyorgyf@60: } gyorgyf@60: header_t iheader; gyorgyf@60: istream.read(reinterpret_cast(&iheader),sizeof(header_t)); gyorgyf@60: istream.close(); gyorgyf@60: int16_t FEFF = 0xFEFF; gyorgyf@60: if (iheader.BOM16 != FEFF) { gyorgyf@60: if (iheader.BOM16 == (int16_t) 0xFFFE) { gyorgyf@60: cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): This file apperas to have created on a different platform. Can not be appended. " gyorgyf@60: << "Byte order mark: " << iheader.BOM32 << endl; gyorgyf@60: return false; gyorgyf@60: } else { gyorgyf@60: cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): Invalid target file for this writer." << endl; gyorgyf@60: return false; gyorgyf@60: } gyorgyf@60: } gyorgyf@60: if (iheader.major_version != (char) MAJOR_VERSION_PY || iheader.BOM32 != (int32_t) 0x41424344) { gyorgyf@60: cerr << "ERROR: BinaryFeatureWriter::OutputStream::open(): This file is not binary compatible with this version of the writer." gyorgyf@60: << "file version: " << iheader.major_version << " required: " << MAJOR_VERSION_PY << endl; gyorgyf@60: return false; gyorgyf@60: } gyorgyf@60: } gyorgyf@60: gyorgyf@60: if (append) stream->seekp(0,ios_base::end); gyorgyf@60: gyorgyf@60: if (!append) { gyorgyf@60: time_t now = time(0); gyorgyf@60: tm* gmtm = gmtime(&now); gyorgyf@60: QString timestamp; gyorgyf@60: if (gmtm != NULL) gyorgyf@60: timestamp = QString("%1 %2").arg(", Created: ").arg(asctime(gmtm)).trimmed(); gyorgyf@66: string info = QString("_SONIC ANNOTATOR v%1 PYTHON BINARY V0.2, Platform: %2-%3bit%4") gyorgyf@60: .arg(RUNNER_VERSION) gyorgyf@60: .arg(platform) gyorgyf@60: .arg(arch) gyorgyf@60: .arg(timestamp).toStdString(); gyorgyf@60: strncpy(reinterpret_cast(&header.info),info.c_str(), info.length() <= 160 ? info.length() : 160); gyorgyf@60: stream->write(p_header,sizeof(header_t)); gyorgyf@60: } gyorgyf@60: return true; gyorgyf@60: } gyorgyf@60: }; gyorgyf@60: gyorgyf@60: gyorgyf@60: BinaryFeatureWriter::BinaryFeatureWriter() : gyorgyf@60: outputFile("features") gyorgyf@60: { gyorgyf@60: binary = new OutputStream(); gyorgyf@60: } gyorgyf@60: gyorgyf@60: BinaryFeatureWriter::~BinaryFeatureWriter() gyorgyf@60: { gyorgyf@60: if (binary) delete binary; gyorgyf@60: } gyorgyf@60: gyorgyf@60: BinaryFeatureWriter::ParameterList gyorgyf@60: BinaryFeatureWriter::getSupportedParameters() const gyorgyf@60: { gyorgyf@60: ParameterList pl; gyorgyf@60: Parameter p; gyorgyf@60: gyorgyf@60: p.name = outputFileParam; gyorgyf@60: p.description = "Binary output file path"; gyorgyf@60: p.hasArg = true; gyorgyf@60: pl.push_back(p); gyorgyf@60: gyorgyf@60: return pl; gyorgyf@60: } gyorgyf@60: gyorgyf@60: void gyorgyf@60: BinaryFeatureWriter::setParameters(map ¶ms) gyorgyf@60: { gyorgyf@60: if (params.find(outputFileParam) != params.end()) { gyorgyf@60: setOutputFile(params[outputFileParam]); gyorgyf@60: params.erase(outputFileParam); gyorgyf@60: } gyorgyf@60: } gyorgyf@60: gyorgyf@60: void gyorgyf@60: BinaryFeatureWriter::setOutputFile(const string &file) gyorgyf@60: { gyorgyf@60: outputFile = file; gyorgyf@60: } gyorgyf@60: gyorgyf@60: void BinaryFeatureWriter::write(QString trackid, gyorgyf@60: const Transform &transform, gyorgyf@60: const Vamp::Plugin::OutputDescriptor& output, gyorgyf@60: const Vamp::Plugin::FeatureList& featureList, gyorgyf@60: std::string summaryType) gyorgyf@60: { gyorgyf@60: //!!! use summaryType gyorgyf@60: if (summaryType != "") { gyorgyf@60: //!!! IMPLEMENT gyorgyf@60: cerr << "ERROR: BinaryFeatureWriter::write: Writing summaries is not yet implemented!" << endl; gyorgyf@60: exit(1); gyorgyf@60: } gyorgyf@60: gyorgyf@60: // TODO: Consider writing out NumPy arrays directly following this documentation: gyorgyf@60: // https://github.com/numpy/numpy/blob/master/doc/neps/npy-format.txt gyorgyf@60: // and using the .npy format gyorgyf@60: gyorgyf@60: // return if file could not be opened gyorgyf@60: if(!openBinaryFile()) { gyorgyf@60: cerr << "ERROR: BinaryFeatureWriter::write: Error opening binary output file!" << endl; gyorgyf@60: exit(1); gyorgyf@60: } gyorgyf@60: gyorgyf@60: ofstream &ofs = *(binary->stream); gyorgyf@60: gyorgyf@60: // The manager does not call finish() after writing different outputs from the same plugin, but we need this behaviour here: gyorgyf@60: if (!binary->newtransform && binary->transform != NULL && binary->transform != &transform) finish(); gyorgyf@60: gyorgyf@60: // write a python dictionary string containing (some) metadata needed to interpret the results gyorgyf@60: // this can be evaluated in python using the expression : d = eval(f.readline()) gyorgyf@60: // given f is an open file, which should yield a valid dictionary. gyorgyf@60: /* gyorgyf@60: enum SampleType { gyorgyf@60: gyorgyf@60: /// Results from each process() align with that call's block start gyorgyf@60: 0: OneSamplePerStep, gyorgyf@60: gyorgyf@60: /// Results are evenly spaced in time gyorgyf@60: 1: FixedSampleRate, gyorgyf@60: gyorgyf@60: /// Results are unevenly spaced and have individual timestamps gyorgyf@60: 2: VariableSampleRate gyorgyf@60: }; gyorgyf@60: */ gyorgyf@60: gyorgyf@60: if (binary->newtransform) { gyorgyf@60: binary->newtransform = false; gyorgyf@60: output_binCount = output.binCount; gyorgyf@69: feature_count = 0; gyorgyf@69: element_count = 0; gyorgyf@60: binary->transform = &transform; gyorgyf@60: gyorgyf@60: ofs << endl << "{" gyorgyf@60: << "\"track_id\":\"" << trackid << "\"," gyorgyf@60: << "\"transform_id\":\"" << transform.getIdentifier() << "\"," gyorgyf@60: << "\"sample_rate\":" << transform.getSampleRate() << "," gyorgyf@60: << "\"step_size\":" << transform.getStepSize() << "," gyorgyf@60: << "\"block_size\":" << transform.getBlockSize() << "," gyorgyf@60: << "\"window_type\":" << transform.getWindowType() << "," gyorgyf@60: gyorgyf@60: << "\"features_list\":" << featureList.size() << "," gyorgyf@60: << "\"bin_count\":" << output.binCount << "," gyorgyf@60: // << "\"output_description\":\"" << output.description << "\"," gyorgyf@60: << "\"output_sample_type\":" << output.sampleType << "," gyorgyf@60: << "\"output_sample_rate\":" << output.sampleRate << ","; gyorgyf@60: gyorgyf@60: // Write start time and duration if the transform is not for the whole file gyorgyf@60: if (transform.getDuration().toString() != "0.000000000") { gyorgyf@60: ofs << "\"start_time\":\"" << transform.getStartTime().toString() << "\","; gyorgyf@60: ofs << "\"duration\":\"" << transform.getDuration().toString() << "\","; gyorgyf@60: } gyorgyf@60: // Write plugin version if known. (NOTE: using RDF transforms, it remains empty for some reason) gyorgyf@60: if (!transform.getPluginVersion().isEmpty()) gyorgyf@60: ofs << "\"plugin_version\":\"" << transform.getPluginVersion() << "\","; gyorgyf@60: gyorgyf@60: // write transform parameters into a dict: parameters:{"parameter_name":value,...} where value is float gyorgyf@60: ofs << "\"parameters\":{" ; gyorgyf@60: ParameterMap m = transform.getParameters(); gyorgyf@60: gyorgyf@60: for (ParameterMap::const_iterator i = m.begin(); i != m.end(); ++i) gyorgyf@60: // note last comma is ignored by python gyorgyf@60: if (i == m.begin()) gyorgyf@60: ofs << QString("\"%1\":%2").arg(i->first).arg(i->second); gyorgyf@60: else gyorgyf@60: ofs << QString(",\"%1\":%2").arg(i->first).arg(i->second); gyorgyf@60: ofs << "}"; gyorgyf@60: gyorgyf@60: // write the data size last, and close the line. gyorgyf@60: data_size_pos = ofs.tellp(); gyorgyf@60: ofs << " }" << endl; gyorgyf@60: } gyorgyf@60: gyorgyf@68: // BUG: File corruption after about 34000 runs due to beat spectrum plugin returning less data than advertised !!! gyorgyf@68: gyorgyf@60: // write the feature data gyorgyf@60: feature_count += featureList.size(); gyorgyf@60: for (size_t i = 0; i < featureList.size(); ++i) { gyorgyf@69: for (size_t j = 0; j < featureList[i].values.size(); ++j) { gyorgyf@60: ofs.write( (const char*) &featureList[i].values[j], sizeof(featureList[i].values[j]) ); gyorgyf@69: element_count++; gyorgyf@69: } gyorgyf@60: } gyorgyf@60: // ofs << endl; gyorgyf@60: gyorgyf@60: // // write time stamp data gyorgyf@60: // for (int i = 0; i < featureList.size(); ++i) { gyorgyf@60: // for (int j = 0; j < featureList[i].values.size(); ++j) { gyorgyf@60: // // float sec = (int) featureList[i].timestamp.sec; gyorgyf@60: // // float nsec = (int) featureList[i].timestamp.nsec; gyorgyf@60: // // (*dbfiles[id].ofs).write( (const char*) &sec, sizeof(int)); gyorgyf@60: // // (*dbfiles[id].ofs).write( (const char*) &nsec, sizeof(int)); gyorgyf@60: // ofs.write( (const char*) &featureList[i].timestamp.sec, sizeof(int)); gyorgyf@60: // ofs.write( (const char*) &featureList[i].timestamp.nsec, sizeof(int)); gyorgyf@60: // gyorgyf@60: // } gyorgyf@60: // gyorgyf@60: // } gyorgyf@60: gyorgyf@60: // -- UNCOMMENT - TO - HERE -- gyorgyf@60: gyorgyf@60: gyorgyf@60: gyorgyf@60: } gyorgyf@60: gyorgyf@60: bool BinaryFeatureWriter::openBinaryFile() gyorgyf@60: { gyorgyf@64: if (outputFile.rfind(".") == string::npos) gyorgyf@64: return binary->open(outputFile + ".bin"); gyorgyf@64: else gyorgyf@64: return binary->open(outputFile); gyorgyf@60: } gyorgyf@60: gyorgyf@60: void BinaryFeatureWriter::finish() gyorgyf@60: { gyorgyf@60: ofstream &ofs = *(binary->stream); gyorgyf@60: binary->newtransform = true; gyorgyf@60: // ofs << endl; gyorgyf@60: long t = ofs.tellp(); gyorgyf@60: ofs.seekp(data_size_pos); gyorgyf@60: // fill in the missing information in the transform python dict that is required to read the output into an array gyorgyf@60: ofs << ",\"feature_count\":" gyorgyf@60: << feature_count gyorgyf@60: << ",\"data_size\":" gyorgyf@69: // << feature_count * output_binCount * sizeof(float); gyorgyf@69: << element_count * sizeof(float); gyorgyf@69: ofs.seekp(t); gyorgyf@69: if (element_count != feature_count * output_binCount) gyorgyf@69: cerr << "ERROR: The number of feature values written is different from the expected value given by the output bin count and the number of features returned by the plugin. \n" gyorgyf@69: << "Otput bin count: " << output_binCount << ", Feature count: " << feature_count << ", Expected elements: " << feature_count * output_binCount gyorgyf@69: << ", Written elements: " << element_count << ", Resulting float32 data size: " << element_count * sizeof(float) << " bytes." gyorgyf@69: << endl; gyorgyf@60: // FileFeatureWriter::finish(); gyorgyf@60: }