diff options
| author | Comment <tim@gray.(none)> | 2013-07-26 22:46:17 +0100 |
|---|---|---|
| committer | Comment <tim@gray.(none)> | 2013-07-26 22:46:17 +0100 |
| commit | f4170d6bfb763ad0af4002277a37dcd1692534d5 (patch) | |
| tree | db32d9753de780063e3afeb64764e13e5c4f5087 /rotord/src | |
| parent | 3d7eea02aa7a155b84c8c74ecbfd55a1941a9297 (diff) | |
tidy files
Diffstat (limited to 'rotord/src')
29 files changed, 13685 insertions, 0 deletions
diff --git a/rotord/src/Pixels.cpp b/rotord/src/Pixels.cpp new file mode 100644 index 0000000..78f4bbb --- /dev/null +++ b/rotord/src/Pixels.cpp @@ -0,0 +1,90 @@ +#include "Pixels.h" +Pixels::Pixels(){ + pixels=nullptr; + pixelsOwner=false; +} +Pixels::~Pixels(){ + clear(); +} +void Pixels::allocate(int w, int h, int _channels){ + if (w < 0 || h < 0) { + return; + } + + //we check if we are already allocated at the right size + if(bAllocated && w == width && h == height && channels ==_channels){ + return; //we don't need to allocate + } + + //we do need to allocate, clear the data + clear(); + + channels = _channels; + width= w; + height = h; + + pixels = new uint8_t[w * h * channels]; + bAllocated = true; + pixelsOwner = true; +} +void Pixels::clear(){ + if(pixels){ + if(pixelsOwner) delete[] pixels; + pixels = nullptr; + } + + width = 0; + height = 0; + channels = 0; + bAllocated = false; +} +bool Pixels::isAllocated() const{ + return bAllocated; +} + +void Pixels::setFromExternalPixels(uint8_t * newPixels,int w, int h, int _channels){ + clear(); + channels = _channels; + width= w; + height = h; + + pixels = newPixels; + pixelsOwner = false; + bAllocated = true; +} + +uint8_t * Pixels::getPixels(){ + return &pixels[0]; +} + +int Pixels::getWidth() const{ + return width; +} + +int Pixels::getHeight() const{ + return height; +} + +int Pixels::getBytesPerPixel() const{ + return channels; +} + +int Pixels::getNumChannels() const{ + return channels; +} + +void Pixels::swap(Pixels & pix){ + std::swap(pixels,pix.pixels); + std::swap(width, pix.width); + std::swap(height,pix.height); + std::swap(channels,pix.channels); + std::swap(pixelsOwner, pix.pixelsOwner); + std::swap(bAllocated, pix.bAllocated); +} + +void Pixels::set(uint8_t val){ + int size = width * height * channels; + for(int i = 0; i < size; i++){ + pixels[i] = val; + } +}
\ No newline at end of file diff --git a/rotord/src/Pixels.h b/rotord/src/Pixels.h new file mode 100644 index 0000000..b6f5865 --- /dev/null +++ b/rotord/src/Pixels.h @@ -0,0 +1,28 @@ +#include <stdint.h> +#include <algorithm> +//for now always uint8_t* rather than templated + +class Pixels{ + public: + Pixels(); + ~Pixels(); + void allocate(int w, int h, int channels); + bool isAllocated() const; + void setFromExternalPixels(uint8_t * newPixels,int w, int h, int channels); + uint8_t * getPixels(); + int getWidth() const; + int getHeight() const; + void clear(); + void swap(Pixels & pix); + int getBytesPerPixel() const; + int getNumChannels() const; + void set(uint8_t val); + private: + uint8_t * pixels; + int width; + int height; + int channels; + bool bAllocated; + bool pixelsOwner; // if set from external data don't delete it +}; + diff --git a/rotord/src/cvimage.cpp b/rotord/src/cvimage.cpp new file mode 100644 index 0000000..c18c585 --- /dev/null +++ b/rotord/src/cvimage.cpp @@ -0,0 +1,156 @@ +#include "cvimage.h" + +using namespace std; + +namespace Rotor { + Image & Image::operator+=(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + rgb+=other.rgb; + } + return *this; + } + Image & Image::operator*=(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot multiply images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + //rgb/=other.rgb; //does funny glitchy stuff + //could use cv::Mat.mul() here + for (int i=0;i<w*h*3;i++){ + //calculate with tables + rgb.data[i]=pixels.multiply[rgb.data[i]][other.rgb.data[i]]; + } + } + return *this; + } + Image & Image::operator^=(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + rgb^=other.rgb; + } + return *this; + } + Image & Image::add_wrap(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + for (int i=0;i<w*h*3;i++){ + //creates rainbow overload, openCV doesn't do this + rgb.data[i]=(unsigned char)(((int)other.rgb.data[i]+(int)rgb.data[i])); + } + } + return *this; + } + Image & Image::divide_wrap(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + for (int i=0;i<w*h*3;i++){ + //creates rainbow overload, openCV doesn't do this + rgb/=other.rgb; //does funny glitchy stuff + } + } + return *this; + } + //THIS OPENCV VERSION IS SLOWER THAN THE OLDSKOOL VERSION BELOW + Image & Image::alpha_blend_cv(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot blend images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + //why not?? + } + else if (!other.alpha.data){ + //default to full on alpha + rgb=other.rgb.clone(); + } + else { + //overlay the other image based on its alpha values + //https://gist.github.com/Brick85/5009046 - this is a dumb way to do it? + //how to invert a matrix? + //'invert' is matrix invert - different + //subtract from a scalar (1) ? + vector<cv::Mat> ichans,ochans; + vector<cv::Mat> compchans; + cv::split(rgb,ichans); + cv::split(other.rgb,ochans); + uint8_t b=0xFF; + cv::Mat iA=b-other.alpha; + for (int i=0;i<3;i++) { + compchans.push_back(ichans[i].mul(iA,1.0/255.0)+ochans[i].mul(other.alpha,1.0/255.0)); + } + merge(compchans,rgb); + //rgb+=other.rgb; + //for (int i=0;i<w*h*3;i++) { + // rgb.data[i]=(uint8_t)(((((int)rgb.data[i])*(0xFF-other.alpha.data[i/3]))>>8)+((((int)other.rgb.data[i])*((int)other.alpha.data[i/3]))>>8)); + //} + } + + return *this; + } + Image & Image::alpha_blend(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot blend images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + //why not?? + } + else if (!other.alpha.data){ + //default to full on alpha + rgb=other.rgb.clone(); + } + else { + for (int i=0;i<w*h*3;i++) { + rgb.data[i]=(uint8_t)(((((int)rgb.data[i])*(0xFF-other.alpha.data[i/3]))>>8)+((((int)other.rgb.data[i])*((int)other.alpha.data[i/3]))>>8)); + } + } + return *this; + } + Image & Image::alpha_merge(const Image &other) { + //converts the incoming image to monochrome and inserts it into the alpha channel of this image + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot merge alpha with different size! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + cv::cvtColor(other.rgb,alpha,CV_RGB2GRAY); + } + return *this; + } + //channel rearrangement + //RGBAZ - these are channels 0-4 + //HSB - can also have these virtual channels 5-7 + //convert channels- int outChannel[5] - {0,1,2,-5,4} - this mapping sends inverted brightness to alpha + + //scalar operations allocate a new image. + //maybe this could not be the case if the data is owned by this image? + //need to look into auto_ptr + Image & Image::operator*=(const float &amount) { + rgb*=amount; + return *this; + } + Image * Image::operator*(const float &amount) { + Image *other=new Image(w,h); + other->rgb=rgb*amount; + return other; + } + Image * Image::operator+(const float &amount) { + uint8_t amt=(uint8_t)(amount*255.0f); + Image *other=new Image(w,h); + other->rgb=rgb+amt; + return other; + } + Image * Image::operator-(const float &amount) { + uint8_t amt=(uint8_t)(amount*255.0f); + Image *other=new Image(w,h); + other->rgb=rgb-amt; + return other; + } + Image * Image::operator/(const float &amount) { + Image *other=new Image(w,h); + other->rgb=rgb/amount; + return other; + } +} diff --git a/rotord/src/cvimage.h b/rotord/src/cvimage.h new file mode 100644 index 0000000..2f9ed3b --- /dev/null +++ b/rotord/src/cvimage.h @@ -0,0 +1,191 @@ +#ifndef ROTOR_CVIMAGE +#define ROTOR_CVIMAGE + +#include <math.h> +#include <cv.h> + +//converting to use a cv image... +//cv::Mat supports most of what we want here +//need to think +//http://answers.opencv.org/question/8202/using-external-image-data-in-a-cvmat/ + +//all access to the image is presently through a pointer to the data +//cv::Mat supports this + +//how will copying work? +//Rotor::Image will contain a cv::Mat object which may own its data or inherit it +//cv::Mat should take care of reference counting + +//can cv::Mat + +namespace Rotor { + class pixeltables{ + //handy pixel arithmetic lookup tables as nested arrays + //so - pixels.add[0x78][0x66]; will give the precalculated result of adding with saturation + // pixels.mono_weights[0][0x100]; will give the red component to convert to mono + public: + pixeltables(){ + add=new uint8_t*[256]; + multiply=new uint8_t*[256]; + for (int i=0;i<256;i++){ + add[i]=new uint8_t[256]; + multiply[i]=new uint8_t[256]; + for (int j=0;j<256;j++){ + add[i][j]=(uint8_t)std::min(i+j,0xFF); + multiply[i][j]=(uint8_t)((((float)i)/255.0f)*(((float)j)/255.0f)*255.0f); + } + } + mono_weights=new uint8_t*[3]; + float weights[3]={0.2989, 0.5870, 0.1140}; + for (int i=0;i<3;i++) { + mono_weights[i]=new uint8_t[256]; + for (int j=0;j<256;j++){ + mono_weights[i][j]=(uint8_t)(((float)j)*weights[i]); + } + } + } + virtual ~pixeltables(){ + for (int i=0;i<256;i++){ + delete[] add[i]; + delete[] multiply[i]; + } + delete[] add; + delete[] multiply; + for (int i=0;i<3;i++) { + delete[] mono_weights[i]; + } + delete[] mono_weights; + } + uint8_t **add; + uint8_t **multiply; + uint8_t **mono_weights; + }; + static pixeltables pixels; + class Image{ + public: + Image(){ + zero(); + }; + Image(int _w,int _h){ + zero(); + setup(_w,_h); + }; + ~Image() { + free(); + }; + void free(){ + if (RGBdata&&ownsRGBdata) delete[] RGBdata; + if (Adata&&ownsAdata) delete[] Adata; + if (Zdata&&ownsZdata) delete[] Zdata; + zero(); + } + void zero(){ + RGBdata=nullptr; + Adata=nullptr; + Zdata=nullptr; + w=0; + h=0; + ownsRGBdata=ownsAdata=ownsZdata=false; + } + int getStride(){ + return w*3; + } + bool setup(int _w,int _h){ //set up with internal data + rgb.create(_h,_w,CV_8UC3); + RGBdata=rgb.data; //can move to use the bare pointer eventually + ownsRGBdata=false; //will not be necessary + w=_w; + h=_h; + return true; + /* + if (w!=_w||h!=_h||!ownsRGBdata||!ownsAdata||!ownsZdata){ + free(); + w=_w; + h=_h; + RGBdata=new uint8_t[w*h*3]; + Adata=new uint8_t[w*h]; + Zdata=new uint16_t[w*h]; + ownsRGBdata=ownsAdata=ownsZdata=true; + return true; + } + else return false; + */ + } + bool setup_fromRGB(int _w,int _h,uint8_t *pRGBdata,int linepadding=0){ + //here the data belongs to libavcodec or other + //could move to using cv::Mat there also and just passing cv:Mat over + + //linepadding causes crash, but it doesn't seem to be necessary + // Mat::Mat(int rows, int cols, int type, void* data, size_t step=AUTO_STEP) + rgb=cv::Mat(_h,_w,CV_8UC3,pRGBdata,(_w*3)+linepadding); + //std::cerr<<"created cv::Mat with step= "<<rgb.step<<",should be "<<((_w*3)+linepadding)<<std::endl; + + RGBdata=rgb.data; //can move to use the bare pointer eventually + ownsRGBdata=false; //will not be necessary + w=_w; + h=_h; + return true; + /* + if (w!=_w||h!=_h||ownsRGBdata||!ownsAdata||!ownsZdata){ + free(); + w=_w; + h=_h; + RGBdata=pRGBdata; + Adata=new uint8_t[w*h]; + Zdata=new uint16_t[w*h]; + ownsRGBdata=false; + ownsAdata=ownsZdata=true; + return true; + } + return false; + */ + } + bool setup_fromMat(cv::Mat& othermat){ + //here the mat belongs to another Image object + rgb=cv::Mat(othermat); + RGBdata=rgb.data; //can move to use the bare pointer eventually + ownsRGBdata=false; //will not be necessary + w=rgb.rows; + h=rgb.cols; + return true; + } + Image* clone(){ + Image *t=new Image(); + t->rgb=rgb.clone(); + t->w=w; + t->h=h; + t->RGBdata=t->rgb.data; //can move to use the bare pointer eventually + t->ownsRGBdata=false; //will not be necessary + /* + for (int i=0;i<w*h*3;i++) { + t->RGBdata[i]=RGBdata[i]; + } + */ + return t; + } + //believe these still work, don't know if these optimisations are better than opencvs.. + Image & operator+=(const Image &other); + Image & operator*=(const Image &other); + Image & operator^=(const Image &other); + Image & alpha_blend(const Image &other); + Image & alpha_blend_cv(const Image &other); + Image & alpha_merge(const Image &other); + Image & add_wrap(const Image &other); + Image & divide_wrap(const Image &other); + Image & operator*=(const float &amount); + Image * operator*(const float &amount); + Image * operator+(const float &amount); + Image * operator-(const float &amount); + Image * operator/(const float &amount); + uint8_t *RGBdata; + uint8_t *Adata; + uint16_t *Zdata; + int h,w; + bool ownsRGBdata,ownsAdata,ownsZdata; //better done through auto_ptr? + + cv::Mat rgb; + cv::Mat alpha; + }; +} + +#endif
\ No newline at end of file diff --git a/rotord/src/graph.cpp b/rotord/src/graph.cpp new file mode 100644 index 0000000..59f7361 --- /dev/null +++ b/rotord/src/graph.cpp @@ -0,0 +1,150 @@ +#include "rotor.h" + +using namespace Rotor; +const string Graph::toString(){ + string xmlgraph; + if (loaded) { + xml.copyXmlToString(xmlgraph); + return xmlgraph; + } + else return ""; +} +vector<Node*> Graph::find_nodes(const string &type){ + vector<Node*> found; + for (std::unordered_map<string,Node*>::iterator it=nodes.begin();it!=nodes.end();++it) { + if (it->second->type==type) found.push_back(it->second); + } + return found; +}; +Node* Graph::find_node(const string &type){ + for (std::unordered_map<string,Node*>::iterator it=nodes.begin();it!=nodes.end();++it) { + if (it->second->type==type) return it->second; + } + return nullptr; //can be tested against +}; +bool Graph::signal_render(string &signal_xml,const float framerate) { + if (find_node("signal_output")) { + Signal_output *signal_output=dynamic_cast<Signal_output*>(find_node("signal_output")); + return signal_output->render(duration,framerate,signal_xml); + } + cerr<<"Rotor: signal output node not found"<<endl; + + return false; +} +bool Graph::video_render(const string &output_filename,const string &audio_filename,const float framerate,float& progress) { + vector<Node*> loaders=find_nodes("video_loader"); + for (auto i:loaders){ + if (!dynamic_cast<Video_loader*>(i)->isLoaded) { + cerr<<"Rotor: all loaders must be populated before rendering"<<endl; + return false; + } + } + if (find_node("video_output")) { + Video_output *video_output=dynamic_cast<Video_output*>(find_node("video_output")); + return video_output->render(duration,framerate,output_filename,audio_filename,progress,outW,outH); + } + + cerr<<"Rotor: video output node not found"<<endl; + return false; +} +bool Graph::set_resolution(int w,int h){ + if (w>64&&h>48){ + outW=w; + outH=h; + return true; + } + else return false; +} +bool Graph::load(string data){ + if (xml.loadFromBuffer(data)){ + return parseXml(); + } + return false; +} +bool Graph::loadFile(string &filename){ + loaded=false; + printf("loading graph: %s\n",filename.c_str()); + if(xml.loadFile(filename) ){ + return parseXml(); + } + else return false; +} +bool Graph::parseXml(){ + init(xml.getAttribute("patchbay","ID","",0),xml.getValue("patchbay","",0)); + if(xml.pushTag("patchbay")) { + int n1=xml.getNumTags("node"); + for (int i1=0;i1<n1;i1++){ + map<string,string> settings; + vector<string> attrs; + xml.getAttributeNames("node",attrs,i1); + for (auto& attr: attrs) { + settings[attr]=xml.getAttribute("node",attr,"",i1); + //cerr << "Got attribute: " << attr << ":" << xml.getAttribute("node",attr,"",i1) << endl; + } + settings["description"]=xml.getValue("node","",i1); + Node* node=factory.create(settings); + if (node) { + string nodeID=xml.getAttribute("node","ID","",i1); + cerr << "Rotor: created node '"<<nodeID<<"': '"<< xml.getAttribute("node","type","",i1) << "'" << endl; + nodes[nodeID]=node; + if(xml.pushTag("node",i1)) { + int n2=xml.getNumTags("signal_input"); + for (int i2=0;i2<n2;i2++){ + nodes[nodeID]->create_signal_input(xml.getValue("signal_input","",i2)); + string fromID=xml.getAttribute("signal_input","from","",i2); + if (nodes.find(fromID)!=nodes.end()) { + if (!nodes[nodeID]->inputs[i2]->connect((Signal_node*)nodes[fromID])){ + cerr << "Rotor: graph loader cannot connect input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + return false; + } + else cerr << "Rotor: linked input " << i2 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + } + else cerr << "Rotor: linking input " << i2 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; + } + int n3=xml.getNumTags("image_input"); + for (int i3=0;i3<n3;i3++){ + ((Image_node*)nodes[nodeID])->create_image_input(xml.getValue("image_input","",i3)); + string fromID=xml.getAttribute("image_input","from","",i3); + if (nodes.find(fromID)!=nodes.end()) { + if (!(((Image_node*)nodes[nodeID])->image_inputs[i3]->connect((Image_node*)nodes[fromID]))){ + cerr << "Rotor: graph loader cannot connect image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + return false; + } + else cerr << "Rotor: linked image input " << i3 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + } + else cerr << "Rotor: linking image input " << i3 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; + } + int n4=xml.getNumTags("parameter_input"); + for (int i4=0;i4<n4;i4++){ + nodes[nodeID]->create_parameter_input(xml.getAttribute("parameter_input","parameter","",i4),xml.getValue("parameter_input","",i4)); + string fromID=xml.getAttribute("parameter_input","from","",i4); + if (nodes.find(fromID)!=nodes.end()) { + if (!nodes[nodeID]->parameter_inputs[i4]->connect(nodes[fromID])){ + cerr << "Rotor: graph loader cannot connect parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + return false; + } + else cerr << "Rotor: linked parameter input " << i4 << " of node '" << nodeID << "' to node '" << fromID << "'" << endl; + } + else cerr << "Rotor: linking parameter input " << i4 << " of node: '" << nodeID << "', cannot find target '" << fromID << "'" << endl; + } + nodes[nodeID]->link_params(); + //extra key/value pairs that can be specific to sub-settings + int n5=xml.getNumTags("parameter"); + for (int i5=0;i5<n5;i5++){ + nodes[nodeID]->set_parameter(xml.getAttribute("parameter","name","",i5),xml.getAttribute("parameter","value","",i5)); + } + if (n5>0) cerr << "Rotor: found " << n5 << " extra parameters for node '" << nodeID << "'" << endl; + + xml.popTag(); + } + } + else { + cerr << "Rotor: graph loader cannot find node '" << xml.getAttribute("node","type","",i1) << "'" << endl; + return false; + } + } + xml.popTag(); + } + loaded=true; + return true; +}
\ No newline at end of file diff --git a/rotord/src/image.h b/rotord/src/image.h new file mode 100644 index 0000000..68aea09 --- /dev/null +++ b/rotord/src/image.h @@ -0,0 +1,242 @@ +#include <cv.h> + +namespace Rotor { + class pixeltables{ + //handy pixel arithmetic lookup tables as nested arrays + //so - pixels.add[0x78][0x66]; will give the precalculated result of adding with saturation + // pixels.mono_weights[0][0x100]; will give the red component to convert to mono + public: + pixeltables(){ + add=new uint8_t*[256]; + multiply=new uint8_t*[256]; + for (int i=0;i<256;i++){ + add[i]=new uint8_t[256]; + multiply[i]=new uint8_t[256]; + for (int j=0;j<256;j++){ + add[i][j]=(uint8_t)min(i+j,0xFF); + multiply[i][j]=(uint8_t)((((float)i)/255.0f)*(((float)j)/255.0f)*255.0f); + } + } + mono_weights=new uint8_t*[3]; + float weights[3]={0.2989, 0.5870, 0.1140}; + for (int i=0;i<3;i++) { + mono_weights[i]=new uint8_t[256]; + for (int j=0;j<256;j++){ + mono_weights[i][j]=(uint8_t)(((float)j)*weights[i]); + } + } + } + virtual ~pixeltables(){ + for (int i=0;i<256;i++){ + delete[] add[i]; + delete[] multiply[i]; + } + delete[] add; + delete[] multiply; + for (int i=0;i<3;i++) { + delete[] mono_weights[i]; + } + delete[] mono_weights; + } + uint8_t **add; + uint8_t **multiply; + uint8_t **mono_weights; + }; + static pixeltables pixels; + class cvImage{ + public: + bool setup(int _w,int _h){ //set up with internal data + cv::Mat mat =cv::Mat(_w, _h, CV_8UC1); + //is this goint to save time or cost it in the long run? + //wrap cv::Mat or use it directly? + //its reference counted etc.. could save a lot of headaches + + /* + if (w!=_w||h!=_h||!ownsRGBdata||!ownsAdata||!ownsZdata){ + free(); + w=_w; + h=_h; + RGBdata=new uint8_t[w*h*3]; + Adata=new uint8_t[w*h]; + Zdata=new uint16_t[w*h]; + ownsRGBdata=ownsAdata=ownsZdata=true; + return true; + } + else return false; + */ + return false; + } + uint8_t *RGBdata; + uint8_t *Adata; + uint16_t *Zdata; + int h,w; + bool ownsRGBdata,ownsAdata,ownsZdata; //better done through auto_ptr? + }; + class Image{ + public: + Image(){ + zero(); + }; + Image(int _w,int _h){ + zero(); + setup(_w,_h); + }; + ~Image() { + free(); + }; + void free(){ + if (RGBdata&&ownsRGBdata) delete[] RGBdata; + if (Adata&&ownsAdata) delete[] Adata; + if (Zdata&&ownsZdata) delete[] Zdata; + zero(); + } + void zero(){ + RGBdata=nullptr; + Adata=nullptr; + Zdata=nullptr; + w=0; + h=0; + ownsRGBdata=ownsAdata=ownsZdata=false; + } + bool setup(int _w,int _h){ //set up with internal data + if (w!=_w||h!=_h||!ownsRGBdata||!ownsAdata||!ownsZdata){ + free(); + w=_w; + h=_h; + RGBdata=new uint8_t[w*h*3]; + Adata=new uint8_t[w*h]; + Zdata=new uint16_t[w*h]; + ownsRGBdata=ownsAdata=ownsZdata=true; + return true; + } + else return false; + } + bool setup_fromRGB(int _w,int _h,uint8_t *pRGBdata){ //possibility of just resetting pointer? + if (w!=_w||h!=_h||ownsRGBdata||!ownsAdata||!ownsZdata){ + free(); + w=_w; + h=_h; + RGBdata=pRGBdata; + Adata=new uint8_t[w*h]; + Zdata=new uint16_t[w*h]; + ownsRGBdata=false; + ownsAdata=ownsZdata=true; + return true; + } + return false; + } + Image* clone(){ + Image *t=new Image(w,h); + for (int i=0;i<w*h*3;i++) { + t->RGBdata[i]=RGBdata[i]; + } + return t; + } + Image & operator+=(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + for (int i=0;i<w*h*3;i++){ + //calculate with tables + RGBdata[i]=pixels.add[RGBdata[i]][other.RGBdata[i]]; + } + } + return *this; + } + Image & operator*=(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot multiply images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + for (int i=0;i<w*h*3;i++){ + //calculate with tables + uint8_t p1=RGBdata[i]; + uint8_t p2=other.RGBdata[i]; + RGBdata[i]=pixels.multiply[RGBdata[i]][other.RGBdata[i]]; + } + } + return *this; + } + Image & add_wrap(const Image &other) { + if (other.w!=w||other.h!=h) { + cerr<<"Rotor: cannot add images with different sizes! (wanted "<<w<<"x"<<h<<", got "<<other.w<<"x"<<other.h<<")"<<endl; + } + else { + for (int i=0;i<w*h*3;i++){ + //creates rainbow overload + RGBdata[i]=(unsigned char)(((int)other.RGBdata[i]+(int)RGBdata[i])); + } + } + return *this; + } + //scalar operations allocate a new image. + //maybe this could not be the case if the data is owned by this image? + //need to look into auto_ptr + Image & operator*=(const float &amount) { + uint8_t *LUT=new uint8_t[256]; + for (int i=0;i<256;i++) { + LUT[i]=(uint8_t)min(0xFF,max(0,(int)(i*amount))); + } + for (int i=0;i<w*h*3;i++){ + //calculate with table + RGBdata[i]=LUT[RGBdata[i]]; + } + delete[] LUT; + return *this; + } + Image * operator*(const float &amount) { + Image *other=new Image(w,h); + uint8_t *LUT=new uint8_t[256]; + for (int i=0;i<256;i++) { + LUT[i]=(uint8_t)min(0xFF,max(0,(int)(i*amount))); + } + for (int i=0;i<w*h*3;i++){ + other->RGBdata[i]=LUT[RGBdata[i]]; + } + delete[] LUT; + return other; + } + Image * operator+(const float &amount) { + Image *other=new Image(w,h); + uint8_t *LUT=new uint8_t[256]; + for (int i=0;i<256;i++) { + LUT[i]=(uint8_t)min(0xFF,max(0,(int)(i+(amount*255.0f)))); + } + for (int i=0;i<w*h*3;i++){ + other->RGBdata[i]=LUT[RGBdata[i]]; + } + delete[] LUT; + return other; + } + Image * operator-(const float &amount) { + Image *other=new Image(w,h); + uint8_t *LUT=new uint8_t[256]; + for (int i=0;i<256;i++) { + LUT[i]=(uint8_t)min(0xFF,max(0,(int)(i-(amount*255.0f)))); + } + for (int i=0;i<w*h*3;i++){ + other->RGBdata[i]=LUT[RGBdata[i]]; + } + delete[] LUT; + return other; + } + Image * operator/(const float &amount) { + Image *other=new Image(w,h); + uint8_t *LUT=new uint8_t[256]; + for (int i=0;i<256;i++) { + LUT[i]=(uint8_t)min(0xFF,max(0,(int)(i/amount))); + } + for (int i=0;i<w*h*3;i++){ + other->RGBdata[i]=LUT[RGBdata[i]]; + } + delete[] LUT; + return other; + } + uint8_t *RGBdata; + uint8_t *Adata; + uint16_t *Zdata; + int h,w; + bool ownsRGBdata,ownsAdata,ownsZdata; //better done through auto_ptr? + }; +}
\ No newline at end of file diff --git a/rotord/src/libavwrapper.cpp b/rotord/src/libavwrapper.cpp new file mode 100755 index 0000000..a19e01a --- /dev/null +++ b/rotord/src/libavwrapper.cpp @@ -0,0 +1,1657 @@ +#include "libavwrapper.h" + +extern Poco::Mutex mutex; //application wide mutex +static Poco::Mutex mutex; + + +extern "C" +{ +#include <libswscale/swscale.h> +} + + +#include <stdexcept> +#include <iostream> +#include <cassert> + +using namespace std; + +// Translated to C++ by Christopher Bruns May 2012 +// from ffmeg_adapt.c in whisk package by Nathan Clack, Mark Bolstadt, Michael Meeuwisse + + +// Avoid link error on some macs +#ifdef __APPLE__ +extern "C" { +#include <stdlib.h> +#include <errno.h> + +} +#endif + +// Custom read function so FFMPEG does not need to read from a local file by name. +// But rather from a stream derived from a URL or whatever. +extern "C" { + +int readFunction(void* opaque, uint8_t* buf, int buf_size) +{ + //QIODevice* stream = (QIODevice*)opaque; + ifstream* stream = (ifstream*)opaque; + //int numBytes = + stream->read((char*)buf, (streamsize)buf_size); + return stream->gcount(); //?? is this right + //numBytes; //TODO work out +} + +// http://cdry.wordpress.com/2009/09/09/using-custom-io-callbacks-with-ffmpeg/ +int64_t seekFunction(void* opaque, int64_t offset, int whence) +{ + //QIODevice* stream = (QIODevice*)opaque; + ifstream* stream = (ifstream*)opaque; + if (stream == NULL) + return -1; + else if (whence == AVSEEK_SIZE) + return -1; // "size of my handle in bytes" + //else if (stream->isSequential()) + // return -1; // cannot seek a sequential stream //presume this would be certain kind of network stream + else if (whence == SEEK_CUR) { // relative to start of file + if (! stream->seekg(offset,ios_base::cur)) //stream->pos() + offset) ) + return -1; + } + else if (whence == SEEK_END) { // relative to end of file + assert(offset < 0); + if (! stream->seekg(offset,ios_base::end)) //stream->size() + offset) ) + return -1; + } + else if (whence == SEEK_SET) { // relative to start of file + if (! stream->seekg(offset) ) + return -1; + } + else { + assert(false); + } + return stream->tellg(); +} + +} + + +///////////////////////////// +// AVPacketWrapper methods // +///////////////////////////// + + +class AVPacketWrapper +{ +public: + AVPacketWrapper(); + virtual ~AVPacketWrapper(); + void free(); + + AVPacket packet; +}; + + +AVPacketWrapper::AVPacketWrapper() +{ + packet.destruct = NULL; +} + +/* virtual */ +AVPacketWrapper::~AVPacketWrapper() +{ + free(); +} + +void AVPacketWrapper::free() +{ + av_free_packet(&packet); +} + + +//bool libav::b_is_one_time_inited = false; + +///////////////////////// +// decoder methods // +///////////////////////// + +libav::decoder::decoder(PixelFormat pixelFormat) + : isOpen(false) +{ + mutex.lock(); + initialize(); + format = pixelFormat; + mutex.unlock(); +} + + + +void libav::decoder::cleanup(){ + + mutex.lock(); + if (NULL != Sctx) { + sws_freeContext(Sctx); + Sctx = NULL; + } + if (NULL != pRaw) { + av_free(pRaw); + pRaw = NULL; + } + if (NULL != pFrameRGB) { + av_free(pFrameRGB); + pFrameRGB = NULL; + } + if (NULL != pCtx) { + avcodec_close(pCtx); + pCtx = NULL; + } + if (NULL != container) { + avformat_close_input(&container); + container = NULL; + } + if (NULL != buffer) { + av_free(buffer); + buffer = NULL; + } + if (NULL != blank) { + av_free(blank); + blank = NULL; + } + mutex.unlock(); + /* + if (NULL != avioContext) { + av_free(avioContext); + avioContext = NULL; + } + */ + // Don't need to free pCodec? + +} + +/* virtual */ +libav::decoder::~decoder() +{ + cleanup(); +} + + +// file name based method for historical continuity +bool libav::decoder::open(char* fileName, enum PixelFormat formatParam){ + + if (!avtry( avformat_open_input(&container, fileName, NULL, NULL), string(fileName) )) + return false; + return openUsingInitializedContainer(formatParam); +} +bool libav::decoder::open(string& fileName, enum PixelFormat formatParam) +{ + // Open file, check usability + + if (!avtry( avformat_open_input(&container, fileName.c_str(), NULL, NULL), fileName )) + return false; + return openUsingInitializedContainer(formatParam); +} + + +bool libav::decoder::openUsingInitializedContainer(enum PixelFormat formatParam) +{ + format = formatParam; + sc = getNumberOfChannels(); + + if (!avtry( avformat_find_stream_info(container, NULL), "Cannot find stream information." )) + return false; + if (!avtry( videoStream=av_find_best_stream(container, AVMEDIA_TYPE_VIDEO, -1, -1, &pCodec, 0), "Cannot find a video stream." )) + return false; + pCtx=container->streams[videoStream]->codec; + width = pCtx->width; + height = pCtx->height; + if (!avtry( avcodec_open2(pCtx, pCodec, NULL), "Cannot open video decoder." )) + return false; + + /* Frame rate fix for some codecs */ + if( pCtx->time_base.num > 1000 && pCtx->time_base.den == 1 ) + pCtx->time_base.den = 1000; + + //cerr<<"stream frame rate:"<<container->streams[videoStream]->r_frame_rate.num<<"/"<<container->streams[videoStream]->r_frame_rate.den<<endl; + + //cerr<<"video duration: "<<container->duration<<endl; + //cerr<<"video time base: "<<pCtx->time_base.num<<"/"<<pCtx->time_base.den<<endl; + //cerr<<"AV time base: "<<AV_TIME_BASE<<endl; + + /* Compute the total number of frames in the file */ + /* duration is in microsecs */ + //numFrames = (int)(( container->duration / (double)AV_TIME_BASE ) * pCtx->time_base.den + 0.5); + //this approach just seems wrong! + + + + numFrames=container->streams[videoStream]->nb_frames-1; + + if (numFrames<1){ + //some codecs don't keep this info in the header + float fr=((float)container->streams[videoStream]->r_frame_rate.num)/container->streams[videoStream]->r_frame_rate.den; + numFrames = (int)(( container->duration / (double)AV_TIME_BASE ) * fr ); + //this approach still doesn't seem to give quite the right answer- comes out a little too big + //could alternatively just redefine the length if the reader fails + } + + + + init_buffers_and_scaler(); + + /* Give some info on stderr about the file & stream */ + //dump_format(container, 0, fname, 0); + + previousFrameIndex = -1; + return true; +} +bool libav::decoder::reinit_buffers_and_scaler(){ + mutex.lock(); + if (NULL != Sctx) { + sws_freeContext(Sctx); + Sctx = NULL; + } + if (NULL != pRaw) { + av_free(pRaw); + pRaw = NULL; + } + if (NULL != pFrameRGB) { + av_free(pFrameRGB); + pFrameRGB = NULL; + } + mutex.unlock(); + init_buffers_and_scaler(); +} + +bool libav::decoder::init_buffers_and_scaler(){ + /* Get framebuffers */ + if (! (pRaw = avcodec_alloc_frame()) ) + throw std::runtime_error(""); + if (! (pFrameRGB = avcodec_alloc_frame()) ) + throw std::runtime_error(""); + + /* Create data buffer */ + if (format == PIX_FMT_NONE) { + numBytes = 0; + buffer = NULL; + blank = NULL; + pFrameRGB = NULL; + Sctx = NULL; + } + else { + numBytes = avpicture_get_size( format, width, height ); // RGB24 format + if (! (buffer = (uint8_t*)av_malloc(numBytes + FF_INPUT_BUFFER_PADDING_SIZE)) ) // RGB24 format + throw std::runtime_error(""); + if (! (blank = (uint8_t*)av_mallocz(avpicture_get_size(pCtx->pix_fmt,width,height))) ) // native codec format + throw std::runtime_error(""); + + /* Init buffers */ + avpicture_fill( (AVPicture * ) pFrameRGB, buffer, format, + width, height ); + + /* Init scale & convert */ + if (! (Sctx=sws_getContext( + pCtx->width, + pCtx->height, + pCtx->pix_fmt, + width, + height, + format, + SWS_POINT, // fastest? + NULL,NULL,NULL)) ) + throw std::runtime_error(""); + } +} + +bool libav::decoder::fetchFrame(int w, int h,int targetFrameIndex) +{ + if (w!=width||h!=height){ + width=w; + height=h; + cerr<<"libav::decoder reiniting to "<<width<<"x"<<height<<endl; //does not seem to be aware of wrong frame + reinit_buffers_and_scaler(); + } + + //seems to crash out on the last frame, if it can be caught should maybe decrement number of frames + + return fetchFrame(targetFrameIndex); +} + +bool libav::decoder::fetchFrame(int targetFrameIndex) +{ + if ((targetFrameIndex < 0) || (targetFrameIndex > numFrames)) + return false; + if (targetFrameIndex == (previousFrameIndex + 1)) { + if (! readNextFrame(targetFrameIndex+1)) //frame indexing starts at 1 + return false; + } + else { + int64_t response=seekToFrame(targetFrameIndex+1); //frame indexing starts at 1 + if (response < 0) + return false; + if (response!=targetFrameIndex+1) { + cerr<<"libav::decoder asked for "<<targetFrameIndex<<", got "<<(response-1)<<endl; //does not seem to be aware of wrong frame + } + } + previousFrameIndex = targetFrameIndex; + return true; +} + +// \returns current frame on success, otherwise -1 +int libav::decoder::seekToFrame(int targetFrameIndex) +{ + int64_t duration = container->streams[videoStream]->duration; + int64_t ts = av_rescale(duration,targetFrameIndex,numFrames); + int64_t tol = av_rescale(duration,1,2*numFrames); + if ( (targetFrameIndex < 0) || (targetFrameIndex >= numFrames) ) { + return -1; + } + int result = avformat_seek_file( container, //format context + videoStream,//stream id + 0, //min timestamp 0? + ts, //target timestamp + ts, //max timestamp + 0);//flags AVSEEK_FLAG_ANY //doesn't seem to work great + if (result < 0) + return -1; + + avcodec_flush_buffers(pCtx); + if (! readNextFrame(targetFrameIndex)) + return -1; + + return targetFrameIndex; +} + +bool libav::decoder::readNextFrame(int targetFrameIndex) +{ + AVPacket packet = {0}; + av_init_packet(&packet); + bool result = readNextFrameWithPacket(targetFrameIndex, packet, pRaw); + av_free_packet(&packet); + return result; +} + +// WARNING this method can raise an exception +bool libav::decoder::readNextFrameWithPacket(int targetFrameIndex, AVPacket& packet, AVFrame* pYuv) +{ + int finished = 0; + do { + finished = 0; + av_free_packet(&packet); + int result; + //if (!avtry(av_read_frame( container, &packet ), "Failed to read frame")) + if (!avtry(av_read_packet( container, &packet ), "Failed to read packet")) + return false; // !!NOTE: see docs on packet.convergence_duration for proper seeking + if( packet.stream_index != videoStream ) /* Is it what we're trying to parse? */ + continue; + if (!avtry(avcodec_decode_video2( pCtx, pYuv, &finished, &packet ), "Failed to decode video")) + return false; + // handle odd cases and debug + if((pCtx->codec_id==CODEC_ID_RAWVIDEO) && !finished) + { + avpicture_fill( (AVPicture * ) pYuv, blank, pCtx->pix_fmt,width, height ); // set to blank frame + finished = 1; + } +#if 0 // very useful for debugging, very + cout << "Packet - pts:" << (int)packet.pts; + cout << " dts:" << (int)packet.dts; + cout << " - flag: " << packet.flags; + cout << " - finished: " << finished; + cout << " - Frame pts:" << (int)pYuv->pts; + cout << " " << (int)pYuv->best_effort_timestamp; + cout << endl; + /* printf("Packet - pts:%5d dts:%5d (%5d) - flag: %1d - finished: %3d - Frame pts:%5d %5d\n", + (int)packet.pts,(int)packet.dts, + packet.flags,finished, + (int)pYuv->pts,(int)pYuv->best_effort_timestamp); */ +#endif + if(!finished) { + if (packet.pts == AV_NOPTS_VALUE) + packet.pts = 0; + //throw std::runtime_error(""); + //why does it want to throw an error here, isn't the frame succesfully decoded? + // + //when we allow these packets through we get + //[swscaler @ 0x9ef0c80] bad src image pointers + //trying to ignore timestamp below + if (packet.size == 0) // packet.size==0 usually means EOF + break; + } + } while ( (!finished) || (pYuv->best_effort_timestamp < targetFrameIndex)); + // } while (!finished); + + av_free_packet(&packet); + + if (format != PIX_FMT_NONE) { + sws_scale(Sctx, // sws context + pYuv->data, // src slice + pYuv->linesize, // src stride + 0, // src slice origin y + pCtx->height, // src slice height + pFrameRGB->data, // dst + pFrameRGB->linesize ); // dst stride + } + + previousFrameIndex = targetFrameIndex; + return true; +} + +uint8_t libav::decoder::getPixelIntensity(int x, int y, Channel c) const +{ + return *(pFrameRGB->data[0] + y * pFrameRGB->linesize[0] + x * sc + c); +} + +int libav::decoder::getNumberOfFrames() const { return numFrames; } + +int libav::decoder::getWidth() const { return width; } + +int libav::decoder::getHeight() const { return height; } + +int libav::decoder::getNumberOfChannels() const +{ + switch(format) + { + case PIX_FMT_BGRA: + return 4; + break; + case PIX_FMT_RGB24: + return 3; + break; + case PIX_FMT_GRAY8: + return 1; + break; + default: + return 0; + break; + } + return 0; +} + +void libav::decoder::initialize() +{ + Sctx = NULL; + pRaw = NULL; + pFrameRGB = NULL; + pCtx = NULL; + container = NULL; + buffer = NULL; + blank = NULL; + pCodec = NULL; + format = PIX_FMT_NONE; + //network stuff + //reply = NULL; + //ioBuffer = NULL; + //avioContext = NULL; + maybeInitFFMpegLib(); +} + +void libav::maybeInitFFMpegLib() +{ + if (b_is_one_time_inited) + return; + av_register_all(); + avcodec_register_all(); + avformat_network_init(); + b_is_one_time_inited = true; +} + +bool libav::decoder::avtry(int result, const std::string& msg) { + if ((result < 0) && (result != AVERROR_EOF)) { + char buf[1024]; + av_strerror(result, buf, sizeof(buf)); + std::string message = std::string("libav::Error: ") + msg + " "+ buf; + //qDebug() << QString(message.c_str()); + cerr<<message<<endl; + return false; + } + return true; +} + + + + +/////////////////////////// +// encoder methods // +/////////////////////////// + + +libav::encoder::encoder(const char * file_name, int width, int height, float _framerate,enum AVCodecID codec_id) + : picture_yuv(NULL) + , picture_rgb(NULL) + , container(NULL) +{ + //multiply float seconds by this to get pts + timebase=((float)AV_TIME_BASE_Q.den)/(AV_TIME_BASE_Q.num*_framerate*3.125f); //no idea where the 3.125 comes from + + if (0 != (width % 2)) + cerr << "WARNING: Video width is not a multiple of 2" << endl; + if (0 != (height % 2)) + cerr << "WARNING: Video height is not a multiple of 2" << endl; + + maybeInitFFMpegLib(); + + container = avformat_alloc_context(); + if (NULL == container) + throw std::runtime_error("Unable to allocate format context"); + + AVOutputFormat * fmt = av_guess_format(NULL, file_name, NULL); + if (!fmt) + fmt = av_guess_format("mpeg", NULL, NULL); + if (!fmt) + throw std::runtime_error("Unable to deduce video format"); + container->oformat = fmt; + + fmt->video_codec = codec_id; + // fmt->video_codec = CODEC_ID_H264; // fails to write + + video_st = avformat_new_stream(container, NULL); + + pCtx = video_st->codec; + pCtx->codec_id = fmt->video_codec; + pCtx->codec_type = AVMEDIA_TYPE_VIDEO; + // resolution must be a multiple of two + pCtx->width = width; + pCtx->height = height; + + // bit_rate determines image quality + pCtx->bit_rate = width * height * 4; // ? + // pCtx->qmax = 50; // no effect? + + // "high quality" parameters from http://www.cs.ait.ac.th/~on/mplayer/pl/menc-feat-enc-libavcodec.html + // vcodec=mpeg4:mbd=2:mv0:trell:v4mv:cbp:last_pred=3:predia=2:dia=2:vmax_b_frames=2:vb_strategy=1:precmp=2:cmp=2:subcmp=2:preme=2:vme=5:naq:qns=2 + if (false) // does not help + // if (pCtx->codec_id == CODEC_ID_MPEG4) + { + pCtx->mb_decision = 2; + pCtx->last_predictor_count = 3; + pCtx->pre_dia_size = 2; + pCtx->dia_size = 2; + pCtx->max_b_frames = 2; + pCtx->b_frame_strategy = 2; + pCtx->trellis = 2; + pCtx->compression_level = 2; + pCtx->global_quality = 300; + pCtx->pre_me = 2; + pCtx->mv0_threshold = 1; + // pCtx->quantizer_noise_shaping = 2; // deprecated + // TODO + } + + pCtx->time_base = (AVRational){1, 25}; /////TODO FIX TO SUPPORT OTHER RATES + // pCtx->time_base = (AVRational){1, 10}; + pCtx->gop_size = 12; // emit one intra frame every twelve frames + // pCtx->max_b_frames = 0; + pCtx->pix_fmt = PIX_FMT_YUV420P; + if (fmt->flags & AVFMT_GLOBALHEADER) + pCtx->flags |= CODEC_FLAG_GLOBAL_HEADER; + + if (pCtx->codec_id == CODEC_ID_H264) + { + // http://stackoverflow.com/questions/3553003/encoding-h-264-with-libavcodec-x264 + pCtx->coder_type = 1; // coder = 1 + pCtx->flags|=CODEC_FLAG_LOOP_FILTER; // flags=+loop + pCtx->me_cmp|= 1; // cmp=+chroma, where CHROMA = 1 + // pCtx->partitions|=X264_PART_I8X8+X264_PART_I4X4+X264_PART_P8X8+X264_PART_B8X8; // partitions=+parti8x8+parti4x4+partp8x8+partb8x8 + pCtx->me_method=ME_HEX; // me_method=hex + pCtx->me_subpel_quality = 7; // subq=7 + pCtx->me_range = 16; // me_range=16 + pCtx->gop_size = 250; // g=250 + pCtx->keyint_min = 25; // keyint_min=25 + pCtx->scenechange_threshold = 40; // sc_threshold=40 + pCtx->i_quant_factor = 0.71; // i_qfactor=0.71 + pCtx->b_frame_strategy = 1; // b_strategy=1 + pCtx->qcompress = 0.6; // qcomp=0.6 + pCtx->qmin = 10; // qmin=10 + pCtx->qmax = 51; // qmax=51 + pCtx->max_qdiff = 4; // qdiff=4 + pCtx->max_b_frames = 3; // bf=3 + pCtx->refs = 3; // refs=3 + // pCtx->directpred = 1; // directpred=1 + pCtx->trellis = 1; // trellis=1 + // pCtx->flags2|=CODEC_FLAG2_BPYRAMID+CODEC_FLAG2_MIXED_REFS+CODEC_FLAG2_WPRED+CODEC_FLAG2_8X8DCT+CODEC_FLAG2_FASTPSKIP; // flags2=+bpyramid+mixed_refs+wpred+dct8x8+fastpskip + // pCtx->weighted_p_pred = 2; // wpredp=2 + // libx264-main.ffpreset preset + // pCtx->flags2|=CODEC_FLAG2_8X8DCT; + // pCtx->flags2^=CODEC_FLAG2_8X8DCT; // flags2=-dct8x8 + } + + AVCodec * codec = avcodec_find_encoder(pCtx->codec_id); + if (NULL == codec) + throw std::runtime_error("Unable to find Mpeg4 codec"); + if (codec->pix_fmts) + pCtx->pix_fmt = codec->pix_fmts[0]; + { + //QMutexLocker lock(&decoder::mutex); + mutex.lock(); + if (avcodec_open2(pCtx, codec, NULL) < 0) + throw std::runtime_error("Error opening codec"); + mutex.unlock(); + } + + /* Get framebuffers */ + if (! (picture_yuv = avcodec_alloc_frame()) ) // final frame format + throw std::runtime_error(""); + if (! (picture_rgb = avcodec_alloc_frame()) ) // rgb version I can understand easily + throw std::runtime_error(""); + /* the image can be allocated by any means and av_image_alloc() is + * just the most convenient way if av_malloc() is to be used */ + if ( av_image_alloc(picture_yuv->data, picture_yuv->linesize, + pCtx->width, pCtx->height, pCtx->pix_fmt, 1) < 0 ) + throw std::runtime_error("Error allocating YUV frame buffer"); + if ( av_image_alloc(picture_rgb->data, picture_rgb->linesize, + pCtx->width, pCtx->height, PIX_FMT_RGB24, 1) < 0 ) + throw std::runtime_error("Error allocating RGB frame buffer"); + + /* Init scale & convert */ + if (! (Sctx=sws_getContext( + width, + height, + PIX_FMT_RGB24, + pCtx->width, + pCtx->height, + pCtx->pix_fmt, + SWS_BICUBIC,NULL,NULL,NULL)) ) + throw std::runtime_error(""); + +// +// +// added audio init + fmt->audio_codec = AV_CODEC_ID_MP3; + // fmt->video_codec = CODEC_ID_H264; // fails to write + + audio_st = avformat_new_stream(container, NULL); + + aCtx = audio_st->codec; + aCtx->codec_id = fmt->audio_codec; + aCtx->codec_type = AVMEDIA_TYPE_AUDIO; + + aCtx->sample_fmt=AV_SAMPLE_FMT_S16P; //s16p is invalid or not supported by aac: S16 not by mp3 + aCtx->channels=2; + aCtx->sample_rate=44100; + aCtx->channel_layout=AV_CH_LAYOUT_STEREO; + aCtx->bit_rate = 64000; + + + + AVCodec * acodec = avcodec_find_encoder(aCtx->codec_id); + mutex.lock(); + int ret = avcodec_open2(aCtx, acodec, NULL); + mutex.unlock(); + if (ret < 0) { + throw std::runtime_error("Could not open audio codec:"); + + } + + if (aCtx->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE) + audio_input_frame_size = 10000; + else + audio_input_frame_size = aCtx->frame_size; + + + if (container->oformat->flags & AVFMT_GLOBALHEADER) + aCtx->flags |= CODEC_FLAG_GLOBAL_HEADER; + + + audiostep=((float)audio_input_frame_size)/(aCtx->sample_rate); + + + + +// are we supposed to use the same codeccontext? +// + + /* open the output file */ + if (!(fmt->flags & AVFMT_NOFILE)) + { + //QMutexLocker lock(&decoder::mutex); + mutex.lock(); + if (avio_open(&container->pb, file_name, AVIO_FLAG_WRITE) < 0) + throw std::runtime_error("Error opening output video file"); + mutex.unlock(); + } + avformat_write_header(container, NULL); +} + +void libav::encoder::setPixelIntensity(int x, int y, int c, uint8_t value) +{ + uint8_t * ptr = picture_rgb->data[0] + y * picture_rgb->linesize[0] + x * 3 + c; + *ptr = value; +} + +void libav::encoder::write_frame(float seconds,uint8_t *rgbdata) +{ + picture_rgb->data[0]=rgbdata; + + // convert from RGB24 to YUV + sws_scale(Sctx, // sws context + picture_rgb->data, // src slice + picture_rgb->linesize, // src stride + 0, // src slice origin y + pCtx->height, // src slice height + picture_yuv->data, // dst + picture_yuv->linesize ); // dst stride + + /* encode the image */ + // use non-deprecated avcodec_encode_video2(...) + AVPacket packet={0}; + av_init_packet(&packet); + packet.data = NULL; + packet.size = 0; + + //no time stamps as is + //http://dranger.com/ffmpeg/tutorial07.html + + picture_yuv->pts=(uint64_t)(seconds*timebase); // + + int got_packet; + int ret = avcodec_encode_video2(pCtx, + &packet, + picture_yuv, + &got_packet); + + //packet.pts=(uint64_t)(seconds*timebase); //added 0606 + packet.stream_index = video_st->index;; //added 0606 + + if (ret < 0) + throw std::runtime_error("Video encoding failed"); + if (got_packet) + { + // std::cout << "encoding frame" << std::endl; + int result = av_write_frame(container, &packet); + av_destruct_packet(&packet); + } +} +void libav::encoder::write_frame(float seconds,uint16_t *audiodata){ + audio_frame = avcodec_alloc_frame(); + AVPacket pkt = { 0 }; // data and size must be 0; + int got_packet, ret; + av_init_packet(&pkt); + audio_frame->nb_samples = audio_input_frame_size; + uint8_t *sampleptr; + int bufsize=audio_input_frame_size * av_get_bytes_per_sample(aCtx->sample_fmt) *aCtx->channels; + if (audiodata) { + sampleptr=(uint8_t*)audiodata; + } + else { + sampleptr=new uint8_t[bufsize]; + memset(sampleptr,0,bufsize); + } + + audio_frame->pts=(uint64_t)(seconds*timebase); // + + avcodec_fill_audio_frame(audio_frame, aCtx->channels, aCtx->sample_fmt, + sampleptr, + audio_input_frame_size * + av_get_bytes_per_sample(aCtx->sample_fmt) * + aCtx->channels, 0); //; + + + + ret = avcodec_encode_audio2(aCtx, &pkt, audio_frame, &got_packet); + + pkt.stream_index = audio_st->index; //hardcoded stream index added 0606 + //pkt.pts=(uint64_t)(seconds*timebase); //added 060613 + + if (!audiodata) { + delete[] sampleptr; + } + if (ret < 0) { + throw std::runtime_error("Audio encoding failed"); + } + + if (!got_packet) + return; + + // ? pkt.stream_index = st->index; + + ret = av_interleaved_write_frame(container, &pkt); + avcodec_free_frame(&audio_frame); +} + +/* virtual */ +libav::encoder::~encoder() +{ + + //avcodec_flush_buffers(pCtx); ???? from exporter version + + + int result = av_write_frame(container, NULL); // flush + result = av_write_trailer(container); + //QMutexLocker lock(&decoder::mutex); + mutex.lock(); + avio_close(container->pb); + mutex.unlock(); + + //added 0706 + video_st=nullptr; + audio_st=nullptr; + // + + for (int i = 0; i < container->nb_streams; ++i) { + av_freep(container->streams[i]); //CRASHING HERE ON STREAM 1, OUTPUT IS VALID BUT AUDIO INAUDIBLE - 060613 + } + av_free(container); + container = nullptr; + //QMutexLocker lock(&decoder::mutex); + mutex.lock(); + avcodec_close(aCtx); + avcodec_close(pCtx); + mutex.unlock(); + av_free(pCtx); + pCtx = NULL; + av_free(aCtx); + aCtx=nullptr; + av_free(picture_yuv->data[0]); + av_free(picture_yuv); + picture_yuv = NULL; + av_free(picture_rgb->data[0]); + av_free(picture_rgb); + picture_rgb = NULL; + +} + +bool libav::exporter::setup(int w,int h, int bitRate, int frameRate, std::string container){ + + maybeInitFFMpegLib(); + + this->w=w; + this->h=h; + this->bitRate=bitRate; + this->frameRate=frameRate; + this->container=container; + + return true; +} + +bool libav::exporter::record(std::string filename){ + + // allocate the output media context // + avformat_alloc_output_context2(&oc, NULL, NULL, filename.c_str()); + if (!oc) { + printf("Could not deduce output format from file extension: using MPEG.\n"); + avformat_alloc_output_context2(&oc, NULL, "mpeg", filename.c_str()); + } + if (!oc) { + return false; + } + fmt = oc->oformat; + + // Add the audio and video streams using the default format codecs + // * and initialize the codecs. // + video_st = NULL; + audio_st = NULL; + + fmt->video_codec=AV_CODEC_ID_MPEG4; + + if (fmt->video_codec != AV_CODEC_ID_NONE) { + video_st = add_stream(oc, &video_codec, fmt->video_codec); + } + if (fmt->audio_codec != AV_CODEC_ID_NONE) { + audio_st = add_stream(oc, &audio_codec, fmt->audio_codec); + } + + //set initial video params + video_st->codec->width=w; + video_st->codec->height=h; + video_st->codec->time_base.num = 1;//codecCtx->ticks_per_frame; + video_st->codec->time_base.den = frameRate; + video_st->time_base = video_st->codec->time_base; + //audioStream->time_base = codecCtx->time_base; //???has the capability of crashing + + video_st->codec->gop_size = 10; /* emit one intra frame every ten frames */ + video_st->codec->pix_fmt = PIX_FMT_YUV420P; + + // Now that all the parameters are set, we can open the audio and + // * video codecs and allocate the necessary encode buffers. // + if (video_st) + open_video(oc, video_codec, video_st); + if (audio_st) { + audioframesize=open_audio(oc, audio_codec, audio_st); + audiostep=((float)audioframesize)/(audio_st->codec->sample_rate); + std::cerr << "opened audio codec with "<<audioframesize<<" frame size and "<<audiostep<<" seconds per frame"<<std::endl; + } + + + av_dump_format(oc, 0, filename.c_str(), 1); + + // open the output file, if needed // + if (!(fmt->flags & AVFMT_NOFILE)) { + mutex.lock(); + int ret = avio_open(&oc->pb, filename.c_str(), AVIO_FLAG_WRITE); + mutex.unlock(); + if (ret < 0) { + std::cerr <<"Could not open " << filename.c_str() << std::endl; + return false; + } + } + + // Write the stream header, if any. // + int ret = avformat_write_header(oc, NULL); + if (ret < 0) { + //std::cerr <<"Error occurred when opening output file:" << av_err2str(ret) << std::endl; + return false; + } + + if (frame) + frame->pts = 0; + + outputframe=0; + + return true; +} +bool libav::exporter::encodeFrame(unsigned char *pixels,uint16_t *samples){ + // Compute current audio and video time. // + if (audio_st) + audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den; + else + audio_pts = 0.0; + + if (video_st) + video_pts = (double)video_st->pts.val * video_st->time_base.num / + video_st->time_base.den; + else + video_pts = 0.0; + + // write interleaved audio and video frames // + if (!video_st || (video_st && audio_st && audio_pts < video_pts)) { + write_audio_frame(oc, audio_st, samples); + } else { + write_video_frame(oc, video_st, pixels); + + frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base); + } + + //std::cerr << "encoded frame " << outputframe << std::endl; + outputframe++; + + return true; +} +bool libav::exporter::encodeFrame(unsigned char *pixels,AVPacket *audio){ + // Compute current audio and video time. // + if (audio_st) + audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den; + else + audio_pts = 0.0; + + if (video_st) + video_pts = (double)video_st->pts.val * video_st->time_base.num / + video_st->time_base.den; + else + video_pts = 0.0; + + // write interleaved audio and video frames // + if (!video_st || (video_st && audio_st && audio_pts < video_pts)) { + write_audio_frame(oc, audio_st, audio); + } else { + write_video_frame(oc, video_st, pixels); + + frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base); + } + + //std::cerr << "encoded frame " << outputframe << std::endl; + outputframe++; + + return true; +} +bool libav::exporter::encodeFrame(unsigned char *pixels){ + video_pts = (double)video_st->pts.val * video_st->time_base.num / video_st->time_base.den; + write_video_frame(oc, video_st, pixels); + frame->pts += av_rescale_q(1, video_st->codec->time_base, video_st->time_base); + outputframe++; + return true; +} +bool libav::exporter::encodeFrame(uint16_t *samples){ + audio_pts = (double)audio_st->pts.val * audio_st->time_base.num / audio_st->time_base.den; + write_audio_frame(oc, audio_st, samples); + return true; +} +void libav::exporter::finishRecord(){ + + av_write_trailer(oc); + // Close each codec. // + if (video_st) + close_video(oc, video_st); + if (audio_st) + close_audio(oc, audio_st); + + if (!(fmt->flags & AVFMT_NOFILE)) { + // Close the output file. // + mutex.lock(); + avio_close(oc->pb); + mutex.unlock(); + } + + // free the stream // + avformat_free_context(oc); +} + +AVStream* libav::exporter::add_stream(AVFormatContext *oc, AVCodec **codec,enum AVCodecID codec_id) + { + AVCodecContext *c; + AVStream *st; + + // find the encoder // + *codec = avcodec_find_encoder(codec_id); + if (!(*codec)) { + //fprintf(stderr, "Could not find encoder for '%s'\n", + // avcodec_get_name(codec_id)); + exit(1); + } + + st = avformat_new_stream(oc, *codec); + if (!st) { + //fprintf(stderr, "Could not allocate stream\n"); + exit(1); + } + st->id = oc->nb_streams-1; + c = st->codec; + + switch ((*codec)->type) { + case AVMEDIA_TYPE_AUDIO: + st->id = 1; + c->sample_fmt = AV_SAMPLE_FMT_S16; + c->bit_rate = 64000; + c->sample_rate = 44100; + c->channels = 2; + c->channel_layout=AV_CH_LAYOUT_STEREO; + break; + + case AVMEDIA_TYPE_VIDEO: + c->codec_id = codec_id; + + c->bit_rate = 400000; + // Resolution must be a multiple of two. // + c->width = 352; + c->height = 288; + // timebase: This is the fundamental unit of time (in seconds) in terms + // * of which frame timestamps are represented. For fixed-fps content, + // * timebase should be 1/framerate and timestamp increments should be + // * identical to 1. // + c->time_base.den = frameRate; + c->time_base.num = 1; + c->gop_size = 12; // emit one intra frame every twelve frames at most // + c->pix_fmt = AV_PIX_FMT_YUV420P; //ADDED HARDCODED TJR 280513 + if (c->codec_id == AV_CODEC_ID_MPEG2VIDEO) { + // just for testing, we also add B frames // + c->max_b_frames = 2; + } + if (c->codec_id == AV_CODEC_ID_MPEG1VIDEO) { + // Needed to avoid using macroblocks in which some coeffs overflow. + // * This does not happen with normal video, it just happens here as + // * the motion of the chroma plane does not match the luma plane. // + c->mb_decision = 2; + } + break; + + default: + break; + } + + // Some formats want stream headers to be separate. // + if (oc->oformat->flags & AVFMT_GLOBALHEADER) + c->flags |= CODEC_FLAG_GLOBAL_HEADER; + + return st; + } + +void libav::exporter::open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st) + { + int ret; + AVCodecContext *c = st->codec; + + // open the codec // + mutex.lock(); + ret = avcodec_open2(c, codec, NULL); + mutex.unlock(); + if (ret < 0) { + //fprintf(stderr, "Could not open video codec: %s\n", av_err2str(ret)); + exit(1); + } + + // allocate and init a re-usable frame // + frame = avcodec_alloc_frame(); + // moved to constructor and freeing in destructor -- stills crashes the same + if (!frame) { + //fprintf(stderr, "Could not allocate video frame\n"); + exit(1); + } + + // Allocate the encoded raw picture. // + ret = avpicture_alloc(&dst_picture, c->pix_fmt, c->width, c->height); + if (ret < 0) { + //fprintf(stderr, "Could not allocate picture: %s\n", av_err2str(ret)); + exit(1); + } + + // If the output format is not YUV420P, then a temporary YUV420P + // * picture is needed too. It is then converted to the required + // * output format. // + if (c->pix_fmt != AV_PIX_FMT_YUV420P) { + ret = avpicture_alloc(&src_picture, AV_PIX_FMT_RGB24, c->width, c->height); + if (ret < 0) { + //fprintf(stderr, "Could not allocate temporary picture: %s\n", + // av_err2str(ret)); + exit(1); + } + } + + // copy data and linesize picture pointers to frame // + *((AVPicture *)frame) = dst_picture; + + outPixels = (uint8_t*)av_malloc(avpicture_get_size(PIX_FMT_YUV420P, st->codec->width,st->codec->height)); + } + + int libav::exporter::open_audio(AVFormatContext *oc, AVCodec *codec, AVStream *st) + { + AVCodecContext *c; + int ret; + + c = st->codec; + + // open it // + mutex.lock(); + ret = avcodec_open2(c, codec, NULL); + mutex.unlock(); + if (ret < 0) { + //fprintf(stderr, "Could not open audio codec: %s\n", av_err2str(ret)); + exit(1); + } + + // init signal generator // + t = 0; + tincr = 2 * M_PI * 110.0 / c->sample_rate; + // increment frequency by 110 Hz per second // + tincr2 = 2 * M_PI * 110.0 / c->sample_rate / c->sample_rate; + + if (c->codec->capabilities & CODEC_CAP_VARIABLE_FRAME_SIZE) + audio_input_frame_size = 10000; + else + audio_input_frame_size = c->frame_size; + + /* + samples = av_malloc(audio_input_frame_size * + av_get_bytes_per_sample(c->sample_fmt) * + c->channels); + if (!samples) { + //fprintf(stderr, "Could not allocate audio samples buffer\n"); + exit(1); + } + */ + return audio_input_frame_size; + } + + void libav::exporter::write_audio_frame(AVFormatContext *oc, AVStream *st,uint16_t *samples) + { + AVCodecContext *c; + AVPacket pkt = { 0 }; // data and size must be 0; + AVFrame *frame = avcodec_alloc_frame(); + int got_packet, ret; + + av_init_packet(&pkt); + c = st->codec; + + //get_audio_frame(samples, audio_input_frame_size, c->channels); + frame->nb_samples = audio_input_frame_size; + uint8_t *sampleptr; + int bufsize=audio_input_frame_size * av_get_bytes_per_sample(c->sample_fmt) *c->channels; + if (samples) { + sampleptr=(uint8_t*)samples; + } + else { + sampleptr=new uint8_t[bufsize]; + memset(sampleptr,0,bufsize); + } + + avcodec_fill_audio_frame(frame, c->channels, c->sample_fmt, + sampleptr, + audio_input_frame_size * + av_get_bytes_per_sample(c->sample_fmt) * + c->channels, 0); //; + //frame->sample_rate=44100; //hard coded input rate- nope, this doesn't help + //frame->format=AV_SAMPLE_FMT_S16P; + //?? why is ffmpeg reporting fltp as the sample format??? doesn't seem to have an effect to change this though + ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet); + if (!samples) { + delete[] sampleptr; + } + if (ret < 0) { + //fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret)); + exit(1); + } + + if (!got_packet) + return; + + pkt.stream_index = st->index; + + // Write the compressed frame to the media file. // + ret = av_interleaved_write_frame(oc, &pkt); + if (ret != 0) { + //fprintf(stderr, "Error while writing audio frame: %s\n", + // av_err2str(ret)); + exit(1); + } + avcodec_free_frame(&frame); + } + + void libav::exporter::write_audio_frame(AVFormatContext *oc, AVStream *st,AVPacket *pkt) + { + /* + AVCodecContext *c; + AVPacket pkt = { 0 }; // data and size must be 0; + AVFrame *frame = avcodec_alloc_frame(); + int got_packet, ret; + + av_init_packet(&pkt); + c = st->codec; + + //get_audio_frame(samples, audio_input_frame_size, c->channels); + frame->nb_samples = audio_input_frame_size; + uint8_t *sampleptr; + int bufsize=audio_input_frame_size * av_get_bytes_per_sample(c->sample_fmt) *c->channels; + if (samples) { + sampleptr=(uint8_t*)samples; + } + else { + sampleptr=new uint8_t[bufsize]; + memset(sampleptr,0,bufsize); + } + avcodec_fill_audio_frame(frame, c->channels, c->sample_fmt, + sampleptr, + audio_input_frame_size * + av_get_bytes_per_sample(c->sample_fmt) * + c->channels, 1); + + ret = avcodec_encode_audio2(c, &pkt, frame, &got_packet); + if (!samples) { + free(sampleptr); + } + if (ret < 0) { + //fprintf(stderr, "Error encoding audio frame: %s\n", av_err2str(ret)); + exit(1); + } + + if (!got_packet) + return; + */ + + pkt->stream_index = st->index; + + // Write the compressed frame to the media file. // + int ret = av_interleaved_write_frame(oc, pkt); + if (ret != 0) { + //fprintf(stderr, "Error while writing audio frame: %s\n", + // av_err2str(ret)); + exit(1); + } + //avcodec_free_frame(&frame); + av_free_packet(pkt); + } + + void libav::exporter::close_audio(AVFormatContext *oc, AVStream *st) + { + mutex.lock(); + avcodec_close(st->codec); + mutex.unlock(); + + } + + void libav::exporter::write_video_frame(AVFormatContext *oc, AVStream *st, uint8_t *pixels) + { + int ret; + + AVCodecContext *c = st->codec; + +/* + if (frame_count >= STREAM_NB_FRAMES) { + // No more frames to compress. The codec has a latency of a few + // * frames if using B-frames, so we get the last frames by + // * passing the same picture again. // + } else { + if (c->pix_fmt != AV_PIX_FMT_YUV420P) { + // as we only generate a YUV420P picture, we must convert it + // * to the codec pixel format if needed // + if (!sws_ctx) { + sws_ctx = sws_getContext(c->width, c->height, AV_PIX_FMT_YUV420P, + c->width, c->height, c->pix_fmt, + sws_flags, NULL, NULL, NULL); + if (!sws_ctx) { + //fprintf(stderr, + // "Could not initialize the conversion context\n"); + exit(1); + } + } + fill_yuv_image(&src_picture, frame_count, c->width, c->height); + sws_scale(sws_ctx, + (const uint8_t * const *)src_picture.data, src_picture.linesize, + 0, c->height, dst_picture.data, dst_picture.linesize); + } else { + fill_yuv_image(&dst_picture, frame_count, c->width, c->height); + } + } +*/ + //always convert RGB to YUV + //should be context allocated once per render instead of per frame?? + // + // + sws_ctx = sws_getContext(c->width, c->height, AV_PIX_FMT_RGB24, + c->width, c->height, AV_PIX_FMT_YUV420P, + sws_flags, NULL, NULL, NULL); + + avpicture_fill(&src_picture, pixels, PIX_FMT_RGB24, c->width,c->height); + //avpicture_fill(&dst_picture, outPixels, PIX_FMT_YUV420P, c->width,c->height); + + sws_scale(sws_ctx, src_picture.data, src_picture.linesize, 0, c->height, dst_picture.data, dst_picture.linesize); + //fill_yuv_image(&dst_picture, frame_count, c->width, c->height); + if (oc->oformat->flags & AVFMT_RAWPICTURE) { + // Raw video case - directly store the picture in the packet // + AVPacket pkt; + av_init_packet(&pkt); + + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.stream_index = st->index; + pkt.data = dst_picture.data[0]; + pkt.size = sizeof(AVPicture); + + ret = av_interleaved_write_frame(oc, &pkt); + } else { + AVPacket pkt = { 0 }; + int got_packet; + av_init_packet(&pkt); + + // encode the image // + + // 2nd time you render it crashes right after here + + // where the hell is frame being allocated? is the problem caused by it being freed? (see removeal of avframe_free in cleanup) + ret = avcodec_encode_video2(c, &pkt, frame, &got_packet); + if (ret < 0) { + //fprintf(stderr, "Error encoding video frame: %s\n", av_err2str(ret)); + exit(1); + } + // If size is zero, it means the image was buffered. // + + if (!ret && got_packet && pkt.size) { + pkt.stream_index = st->index; + + // Write the compressed frame to the media file. // + ret = av_interleaved_write_frame(oc, &pkt); + } else { + ret = 0; + } + } + + // + // added 22 may in memory leak run + // + sws_freeContext(sws_ctx); //should be done once per render instead of per frame?? + + if (ret != 0) { + //fprintf(stderr, "Error while writing video frame: %s\n", av_err2str(ret)); + exit(1); + } + frame_count++; + + //avcodec_free_frame(&frame); + } + + void libav::exporter::close_video(AVFormatContext *oc, AVStream *st) + { + mutex.lock(); + //avcodec_close(st->codec); //change 0706 to trace 2nd render issue + avcodec_close(audio_st->codec); + avcodec_close(video_st->codec); + // + // + + + //av_free(src_picture.data[0]); //removed to explore weird 2nd render crash.. seems to WORK -- seems that the picture data is owned elsewhere + av_free(dst_picture.data[0]); + av_free(frame); //removed to explore crash 2nd time render + //gives *** Error in `./rotord': corrupted double-linked list: 0x00007fd8b005bd60 *** + //where is frame initialised??? + //moved to destructor + + + av_free(outPixels); //SIGSEV here??? + mutex.unlock(); + } + +bool libav::audioloader::setup(const std::string &filename){ + + maybeInitFFMpegLib(); + + frame = avcodec_alloc_frame(); + if (!frame) + { + std::cout << "Error allocating the frame" << std::endl; + return false; + } + + formatContext = NULL; + mutex.lock(); + if (avformat_open_input(&formatContext, filename.c_str(), NULL, NULL) != 0) + { + av_free(frame); + std::cout << "Error opening the file" << std::endl; + mutex.unlock(); + return false; + } + mutex.unlock(); + + if (avformat_find_stream_info(formatContext, NULL) < 0) + { + mutex.lock(); + av_free(frame); + avformat_close_input(&formatContext); + mutex.unlock(); + std::cout << "Error finding the stream info" << std::endl; + return false; + } + + //use the first audio stream found + + audioStream = NULL; + for (unsigned int i = 0; i < formatContext->nb_streams; ++i) + { + if (formatContext->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) + { + audioStream = formatContext->streams[i]; + break; + } + } + + if (audioStream == NULL) + { + mutex.lock(); + av_free(frame); + avformat_close_input(&formatContext); + mutex.unlock(); + std::cout << "Could not find any audio stream in the file" << std::endl; + return false; + } + + codecContext = audioStream->codec; + + codecContext->codec = avcodec_find_decoder(codecContext->codec_id); + mutex.lock(); + if (codecContext->codec == NULL) + { + + av_free(frame); + avformat_close_input(&formatContext); + mutex.unlock(); + std::cout << "Couldn't find a proper decoder" << std::endl; + return false; + } + else if (avcodec_open2(codecContext, codecContext->codec, NULL) != 0) + { + av_free(frame); + avformat_close_input(&formatContext); + mutex.unlock(); + std::cout << "Couldn't open the context with the decoder" << std::endl; + return false; + } + mutex.unlock(); + + av_dump_format(formatContext, 0, 0, false); //avformat.h line 1256 + int samples = ((formatContext->duration + 5000)*codecContext->sample_rate)/AV_TIME_BASE; + + std::cout << "This stream has " << codecContext->channels << " channels, a sample rate of " << codecContext->sample_rate << "Hz and "<<samples <<" samples" << std::endl; + std::cout << "The data is in format " <<codecContext->sample_fmt<< " (aka "<< av_get_sample_fmt_name(codecContext->sample_fmt) << ") "<<std::endl; + + isPlanar=(av_sample_fmt_is_planar(codecContext->sample_fmt)==1); + + if(isPlanar) { cerr<<"found planar audio"<<endl; } + + + av_init_packet(&packet); + //sample_processed=0; + ready=true; + return true; + } + + AVFrame* libav::audioloader::get_frame() { + + if (!ready) return nullptr; + + int frameFinished = 0; + while (!frameFinished) { + int ret=av_read_frame(formatContext, &packet); + if (ret<0) { + std::cerr << "finished with code "<<ret <<(ret==AVERROR_EOF?" ,EOF":"")<<std::endl; + ready=false; + return nullptr; + } + if (packet.stream_index == audioStream->index) + { + //int bytes = + avcodec_decode_audio4(codecContext, frame, &frameFinished, &packet); + + // Some frames rely on multiple packets, so we have to make sure the frame is finished before + // we can use it + } + // You *must* call av_free_packet() after each call to av_read_frame() or else you'll leak memory + av_free_packet(&packet); + } + return frame; + + } + AVPacket* libav::audioloader::get_packet() { + + if (!ready) return nullptr; + + int ret=av_read_frame(formatContext, &packet); + if (ret<0) { + std::cerr << "finished with code "<<ret <<(ret==AVERROR_EOF?" ,EOF":"")<<std::endl; + ready=false; + return nullptr; + } + //if (packet.stream_index == audioStream->index) + //{ + //int bytes = + // avcodec_decode_audio4(codecContext, frame, &frameFinished, &packet); + + // Some frames rely on multiple packets, so we have to make sure the frame is finished before + // we can use it + //} + // You *must* call av_free_packet() after each call to av_read_frame() or else you'll leak memory + //av_free_packet(&packet);????? + //} + return &packet; + + } + uint16_t* libav::audioloader::get_samples(int num){ //presumes 16bpc here + //std::cerr << "request "<<num<<" samples: "<<(ready?"ready":"not ready")<<std::endl; + //if(!ready) return nullptr; + //shuffle down samples + + if (sample_start>0){ + for (int i=0;i<sample_end-sample_start;i++){ + for (int j=0;j<channels;j++) { + buffer[(i*channels)+j]=buffer[((sample_start+i)*channels)+j]; + } + } + sample_start=sample_end-sample_start; + } + + sample_end=sample_start; + while (sample_end<num) { + frame=get_frame(); + if (frame) { + channels=av_frame_get_channels(frame); //will always reach here 1st + if (((sample_end+std::max(num,frame->nb_samples))*channels)>buffer.size()){ + int m=buffer.size(); + int s=((sample_end+std::max(num,frame->nb_samples))*channels); + buffer.reserve(s); + std::cerr << "audioloader reserved buffer to " << s << std::endl; + for (int i=m;i<s;i++) buffer.push_back(0); + } + for (int i=0;i<frame->nb_samples;i++) { + for (int j=0;j<channels;j++) { + //int frame->format + //format of the frame, -1 if unknown or unset Values correspond to enum AVPixelFormat for video frames, enum AVSampleFormat for audio) + //int ff=frame->format; + //uint64_t frame->channel_layout + //Channel layout of the audio data. + //uint64_t fcl=frame->channel_layout; + //int frame->nb_extended_buf + //Number of elements in extended_buf. + //int fnb=frame->nb_extended_buf; + //int frame->decode_error_flags + //decode error flags of the frame, set to a combination of FF_DECODE_ERROR_xxx flags if the decoder produced a frame, but there were errors during the decoding. + //int fde=frame->decode_error_flags; + + + //uint16_t s=((uint16_t*) frame->buf[j]->data)[i]; + uint16_t s; + if (isPlanar) { + s=((uint16_t*) frame->buf[j]->data)[i]; + }else { + s=((uint16_t*) frame->buf[0]->data)[j*channels+i]; + } + + //where is audio grunge coming from? signed/ unsigned? doesn't seem to be byte order.. + // add +1 to data subscript with no effect + + + //which? must be determined by format or layout of the channels + //ALSO some kind of HEINOUS memory leak?? + buffer[((sample_end+i)*frame->channels)+j]=s; + //buffer[(j*frame->channels)+(sample_end+i)]= ((uint16_t*) frame->buf[j]->data)[i]; ??planar?? nope + } + } + sample_end+=frame->nb_samples; + } + else { + for (int i=sample_end;i<num;i++){ + for (int j=0;j<channels;j++) { + buffer[(channels*i)+j]=0; + } + } + sample_end=num; + } + //std::cerr<<"filling buffer to "<<((sample_end+frame->nb_samples)*frame->channels)<<std::endl; + + + //avcodec_free_frame(&frame); + } + if (sample_end>num) { + sample_start=num; + } + else { + sample_start=0; + } + return (uint16_t*)(&buffer[0]); +} + +bool libav::audioloader::close() { + mutex.lock(); + av_free(frame); + avcodec_close(codecContext); + avformat_close_input(&formatContext); + mutex.unlock(); + ready=false; + sample_start=0; + sample_end=0; + return true; +} diff --git a/rotord/src/libavwrapper.h b/rotord/src/libavwrapper.h new file mode 100755 index 0000000..656f885 --- /dev/null +++ b/rotord/src/libavwrapper.h @@ -0,0 +1,279 @@ +#ifndef libavwrapper_H +#define libavwrapper_H + +/* + * libavwrapper.h + * May 2012 Christopher Bruns + * The libavwrapper class is a C++ wrapper around the poorly documented + * libavcodec movie API used by ffmpeg. I made extensive use of Nathan + * Clack's implemention in the whisk project. + * + * The libavwrapper.h and libavwrapper.cpp files depend only on the libavcodec + * and allied sets of libraries. To compartmentalize and reduce dependencies + * I placed the Vaa3d specific use of this class into a separate set of + * source files: loadV3dFFMpeg.h/cpp + */ + +//////////////////////// +//now that we have guards +//instead of crashing instantly when the 2nd thread tries to encode a frame, we get an error + + //*** Error in `./rotord': corrupted double-linked list: 0x00007f3c31b1b630 *** + + //or + + //*** Error in `./rotord': double free or corruption (out): 0x00007f3bf8210080 *** + /////////////////////// + + +//http://blog.tomaka17.com/2012/03/libavcodeclibavformat-tutorial/ +//great to use c++11 features + +#ifndef UINT64_C +#define UINT64_C(c) (c ## ULL) +#endif + +#include "Poco/Mutex.h" + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavutil/pixfmt.h> +#include <libavutil/opt.h> +#include <libavutil/imgutils.h> +#include <libavutil/samplefmt.h> + +#include <libswscale/swscale.h> //? +} + +/* +#include <QFile> +#include <QNetworkAccessManager> +#include <QMutex> +#include <QUrl> +#include <QBuffer> +*/ + + +#include <string> +#include <stdexcept> +#include <iostream> +#include <fstream> +#include <math.h> +#include <vector> + + + +namespace libav { + + + + static bool b_is_one_time_inited=false; + // Some libavcodec calls are not reentrant + + void maybeInitFFMpegLib(); + + static int sws_flags = SWS_BICUBIC; + +// Translated to C++ by Christopher Bruns May 2012 +// from ffmeg_adapt.c in whisk package by Nathan Clack, Mark Bolstadt, Michael Meeuwisse + class decoder + { + public: + enum Channel { + RED = 0, + GRAY = 0, + GREEN = 1, + BLUE = 2, + ALPHA = 3 + }; + + + decoder(PixelFormat pixelFormat=PIX_FMT_RGB24); + //decoder(QUrl url, PixelFormat pixelFormat=PIX_FMT_RGB24); + void cleanup(); + virtual ~decoder(); + //bool open(QUrl url, enum PixelFormat formatParam = PIX_FMT_RGB24); + //bool open(QIODevice& fileStream, QString& fileName, enum PixelFormat formatParam = PIX_FMT_RGB24); + bool reinit_buffers_and_scaler(); + bool init_buffers_and_scaler(); + uint8_t getPixelIntensity(int x, int y, Channel c = GRAY) const; + bool fetchFrame(int targetFrameIndex = 0); + bool fetchFrame(int w,int h,int targetFrameIndex = 0); + int getNumberOfFrames() const; + int getWidth() const; + int getHeight() const; + int getNumberOfChannels() const; + bool readNextFrame(int targetFrameIndex = 0); + bool readNextFrameWithPacket(int targetFrameIndex, AVPacket& packet, AVFrame* pYuv); + int seekToFrame(int targetFrameIndex = 0); + + // make certain members public, for use by Fast3DTexture class + AVFrame *pFrameRGB; + AVFrame *pRaw; + AVFormatContext *container; + AVCodecContext *pCtx; + int videoStream; + int previousFrameIndex; + bool isOpen; + + bool open(std::string& fileName, enum PixelFormat formatParam = PIX_FMT_RGB24); + bool open(char* fileName, enum PixelFormat formatParam = PIX_FMT_RGB24); + + protected: + + + void initialize(); + + bool openUsingInitializedContainer(enum PixelFormat formatParam = PIX_FMT_RGB24 ); + static bool avtry(int result, const std::string& msg); + + AVCodec *pCodec; + uint8_t *buffer, + *blank; + //struct + SwsContext *Sctx; + int width, height; + PixelFormat format; + size_t numBytes; + int numFrames; + int sc; // number of color channels + + // For loading from URL + /* + static const int ioBufferSize = 32768; + unsigned char * ioBuffer; + QNetworkAccessManager networkManager; + AVIOContext* avioContext; + QFile fileStream; + QNetworkReply* reply; + QBuffer fileBuffer; + QByteArray byteArray; + */ + }; + + + // TODO - finish refactoring based on + // http://svn.gnumonks.org/trunk/21c3-video/ffmpeg/ffmpeg-0.4.9-pre1/output_example.c + class encoder + { + public: + //typedef encoder::Channel Channel; + + encoder(const char * file_name, int width, int height, float _framerate=25.0f, enum AVCodecID codec_id = CODEC_ID_H264); + virtual ~encoder(); + void setPixelIntensity(int x, int y, int c, uint8_t value); + void write_frame(float seconds,uint8_t *rgbdata); + void write_frame(float seconds,uint16_t *audiodata); + int get_audio_framesize(){ return audio_input_frame_size; } + float get_audio_step(){return audiostep;}; + + protected: + AVFormatContext *container; + AVCodecContext *pCtx; + AVFrame *picture_yuv; + AVFrame *picture_rgb; + AVFrame *audio_frame; + float timebase; + struct SwsContext *Sctx; + + AVStream *audio_st; + AVStream *video_st; + + AVCodecContext *aCtx; + int audio_input_frame_size; + float audiostep; + }; + + + class exporter { + public: + virtual ~exporter(){}; + bool setup(int w,int h, int bitRate, int frameRate, std::string container); + bool record(std::string filename); + bool encodeFrame(unsigned char *pixels, uint16_t *samples); + bool encodeFrame(unsigned char *pixels,AVPacket *audiopkt); //is possible to just copy the packets? + bool encodeFrame(unsigned char *pixels); + bool encodeFrame(uint16_t *samples); + void finishRecord(); + int get_audio_framesize(){return audioframesize;}; + float get_audio_step(){return audiostep;}; + + AVStream *add_stream(AVFormatContext *oc, AVCodec **codec,enum AVCodecID codec_id); + void open_video(AVFormatContext *oc, AVCodec *codec, AVStream *st); + int open_audio(AVFormatContext *oc, AVCodec *codec, AVStream *st); + + void write_audio_frame(AVFormatContext *oc, AVStream *st,uint16_t *samples); + void write_audio_frame(AVFormatContext *oc, AVStream *st,AVPacket *pkt); + void close_audio(AVFormatContext *oc, AVStream *st); + + void write_video_frame(AVFormatContext *oc, AVStream *st, uint8_t *pixels); + void close_video(AVFormatContext *oc, AVStream *st); + + private: + AVOutputFormat *fmt; + AVFormatContext *oc; + AVStream *audio_st, *video_st; + AVCodec *audio_codec, *video_codec; + double audio_pts, video_pts; + + struct SwsContext *sws_ctx; + + int audioframesize; + float audiostep; + int w; + int h; + int bitRate; + int frameRate; + std::string container; + + int outputframe; + + // video output // + + AVFrame *frame; + AVPicture src_picture, dst_picture; + int frame_count; + uint8_t *outPixels; + + + //************************************************************// + // audio output // + + float t, tincr, tincr2; + int audio_input_frame_size; + + + }; + + class audioloader{ + public: + audioloader(){ready=false;sample_start=0;sample_end=0;}; + bool setup(const std::string &filename); + AVFrame* get_frame(); + uint16_t* get_samples(int num); + AVPacket* get_packet(); + bool close(); + bool ready; + + AVCodecContext* codecContext; + AVFormatContext* formatContext; + int channels; //necessary to handle final packet -- unititialised after load/ problem? + private: + std::vector<uint16_t> buffer; + AVFrame* frame; + + AVStream* audioStream; + + AVPacket packet; + int sample_end; + int sample_start; + bool isPlanar; + + }; + +} + + + +#endif // libavwrapper_H diff --git a/rotord/src/nodes_audio_analysis.h b/rotord/src/nodes_audio_analysis.h new file mode 100644 index 0000000..e6c1e65 --- /dev/null +++ b/rotord/src/nodes_audio_analysis.h @@ -0,0 +1,47 @@ +#ifndef ROTOR_NODES_AUDIO_ANALYSIS +#define ROTOR_NODES_AUDIO_ANALYSIS + +#include "rotor.h" +#include "vampHost.h" + +namespace Rotor { + class Audio_analysis: public Base_audio_processor { + public: + Audio_analysis(){}; + Audio_analysis(map<string,string> &settings) { + base_settings(settings); + soname=find_setting(settings,"soname"); + id=find_setting(settings,"id"); + outputNo=find_setting(settings,"outputNo",0); + }; + Audio_analysis* clone(map<string,string> &_settings) { return new Audio_analysis(_settings);}; + bool init(int _channels,int _bits,int _samples,int _rate); + void cleanup(); + void set_parameter(const std::string &key,const std::string &value){params[key]=ofToFloat(value);}; + int process_frame(uint8_t *data,int samples_in_frame); + const float output(const Time_spec &time) { + if (analyser.features.size()) { + auto i=analyser.features.upper_bound(time.time); //the first element in the container whose key is considered to go after k + if (i!=analyser.features.end()){ + float uk=i->first; + i--; + float lk=i->first; + int ln=i->second; + return (((time.time-lk)/(uk-lk))+ln); + } + } + return 0.0f; + } + void print_features(); + void print_summary(){ + cerr<<"vamp plugin "<<id<<" of library "<<soname<<" found "<<analyser.features.size()<<" features "<<endl; + }; + private: + string soname,id; + int outputNo; + vampHost::Analyser analyser; + map <string,float> params; + }; +} + +#endif
\ No newline at end of file diff --git a/rotord/src/nodes_drawing.h b/rotord/src/nodes_drawing.h new file mode 100644 index 0000000..11df2d6 --- /dev/null +++ b/rotord/src/nodes_drawing.h @@ -0,0 +1,43 @@ +#ifndef ROTOR_NODES_DRAWING +#define ROTOR_NODES_DRAWING + +#include "rotor.h" +#include <cairo.h> + +namespace Rotor { + class Draw: public Image_node { + public: + Draw(){image=nullptr;}; + Draw(map<string,string> &settings) { + base_settings(settings); + }; + ~Draw(){ if (image) delete image;}; + Draw* clone(map<string,string> &_settings) { return new Draw(_settings);}; + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + //copy incoming image **writable + if (image) delete image; + image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); + } + else image->setup(frame.w,frame.h); + } + else image->setup(frame.w,frame.h); //do this twice or use a goto + //draw onto new or input image + cairo_surface_t * cs = cairo_image_surface_create_for_data (image->RGBdata, + CAIRO_FORMAT_RGB24, + image->w, + image->h, + image->getStride()); + cairo_t *c=cairo_create(cs); + cairo_rectangle(c, image->w/2, image->h/2, image->w, image->h); + cairo_set_source_rgb(c, 1.0, 0.0, 0.0); + cairo_fill(c); + return image; + } + private: + Image *image; //is an image generator + }; +} + +#endif
\ No newline at end of file diff --git a/rotord/src/ofUtils.cpp b/rotord/src/ofUtils.cpp new file mode 100755 index 0000000..7d7d9cc --- /dev/null +++ b/rotord/src/ofUtils.cpp @@ -0,0 +1,745 @@ +#include "ofUtils.h" +//#include "ofImage.h" +//#include "ofTypes.h" +//#include "ofGraphics.h" +//#include "ofAppRunner.h" + +#include "Poco/String.h" +#include "Poco/LocalDateTime.h" +#include "Poco/DateTimeFormatter.h" + +#include <cctype> // for toupper + + + +/* +#ifdef TARGET_WIN32 + #ifndef _MSC_VER + #include <unistd.h> // this if for MINGW / _getcwd + #include <sys/param.h> // for MAXPATHLEN + #endif +#endif + + +#if defined(TARGET_OF_IPHONE) || defined(TARGET_OSX ) || defined(TARGET_LINUX) + #include <sys/time.h> +#endif + +#ifdef TARGET_OSX + #ifndef TARGET_OF_IPHONE + #include <mach-o/dyld.h> + #include <sys/param.h> // for MAXPATHLEN + #endif +#endif + +#ifdef TARGET_WIN32 + #include <mmsystem.h> + #ifdef _MSC_VER + #include <direct.h> + #endif + +#endif +*/ + +#ifndef MAXPATHLEN + #define MAXPATHLEN 1024 +#endif + + +static bool enableDataPath = true; +//static unsigned long startTime = ofGetSystemTime(); // better at the first frame ?? (currently, there is some delay from static init, to running. +//static unsigned long startTimeMicros = ofGetSystemTimeMicros(); + +/* +//-------------------------------------- +unsigned long ofGetElapsedTimeMillis(){ + return ofGetSystemTime() - startTime; +} + +//-------------------------------------- +unsigned long ofGetElapsedTimeMicros(){ + return ofGetSystemTimeMicros() - startTimeMicros; +} + +//-------------------------------------- +float ofGetElapsedTimef(){ + return ofGetElapsedTimeMicros() / 1000000.0f; +} + +//-------------------------------------- +void ofResetElapsedTimeCounter(){ + startTime = ofGetSystemTime(); + startTimeMicros = ofGetSystemTimeMicros(); +} +*/ +//======================================= +// this is from freeglut, and used internally: +/* Platform-dependent time in milliseconds, as an unsigned 32-bit integer. + * This value wraps every 49.7 days, but integer overflows cancel + * when subtracting an initial start time, unless the total time exceeds + * 32-bit, where the GLUT API return value is also overflowed. + */ +/* +unsigned long ofGetSystemTime( ) { + #ifndef TARGET_WIN32 + struct timeval now; + gettimeofday( &now, NULL ); + return now.tv_usec/1000 + now.tv_sec*1000; + #else + #if defined(_WIN32_WCE) + return GetTickCount(); + #else + return timeGetTime(); + #endif + #endif +} +*/ + +/* +unsigned long ofGetSystemTimeMicros( ) { + #ifndef TARGET_WIN32 + struct timeval now; + gettimeofday( &now, NULL ); + return now.tv_usec + now.tv_sec*1000000; + #else + #if defined(_WIN32_WCE) + return GetTickCount()*1000; + #else + return timeGetTime()*1000; + #endif + #endif +} +*/ +//-------------------------------------------------- +unsigned int ofGetUnixTime(){ + return (unsigned int)time(NULL); +} + +//default ofGetTimestampString returns in this format: 2011-01-15-18-29-35-299 +//-------------------------------------------------- +string ofGetTimestampString(){ + string timeFormat = "%Y-%m-%d-%H-%M-%S-%i"; + Poco::LocalDateTime now; + return Poco::DateTimeFormatter::format(now, timeFormat); +} + +//specify the string format - eg: %Y-%m-%d-%H-%M-%S-%i ( 2011-01-15-18-29-35-299 ) +//-------------------------------------------------- +string ofGetTimestampString(string timestampFormat){ + Poco::LocalDateTime now; + return Poco::DateTimeFormatter::format(now, timestampFormat); +} + +//-------------------------------------------------- +int ofGetSeconds(){ + time_t curr; + tm local; + time(&curr); + local =*(localtime(&curr)); + return local.tm_sec; +} + +//-------------------------------------------------- +int ofGetMinutes(){ + time_t curr; + tm local; + time(&curr); + local =*(localtime(&curr)); + return local.tm_min; +} + +//-------------------------------------------------- +int ofGetHours(){ + time_t curr; + tm local; + time(&curr); + local =*(localtime(&curr)); + return local.tm_hour; +} + +//-------------------------------------------------- +int ofGetYear(){ + time_t curr; + tm local; + time(&curr); + local =*(localtime(&curr)); + int year = local.tm_year + 1900; + return year; +} + +//-------------------------------------------------- +int ofGetMonth(){ + time_t curr; + tm local; + time(&curr); + local =*(localtime(&curr)); + int month = local.tm_mon + 1; + return month; +} + +//-------------------------------------------------- +int ofGetDay(){ + time_t curr; + tm local; + time(&curr); + local =*(localtime(&curr)); + return local.tm_mday; +} + +//-------------------------------------------------- +int ofGetWeekday(){ + time_t curr; + tm local; + time(&curr); + local =*(localtime(&curr)); + return local.tm_wday; +} + +//-------------------------------------------------- +void ofEnableDataPath(){ + enableDataPath = true; +} + +//-------------------------------------------------- +void ofDisableDataPath(){ + enableDataPath = false; +} + +//-------------------------------------------------- +//use ofSetDataPathRoot() to override this +static string & dataPathRoot(){ +#if defined TARGET_OSX + static string * dataPathRoot = new string("../../../data/"); +#elif defined TARGET_ANDROID + static string * dataPathRoot = new string("sdcard/"); +#elif defined(TARGET_LINUX) + static string * dataPathRoot = new string(ofFilePath::join(ofFilePath::getCurrentExeDir(), "data/")); +#else + static string * dataPathRoot = new string("data/"); +#endif + return *dataPathRoot; +} + +static bool & isDataPathSet(){ + static bool * dataPathSet = new bool(false); + return * dataPathSet; +} + +//-------------------------------------------------- +void ofSetDataPathRoot(string newRoot){ + string newPath = ""; + + #ifdef TARGET_OSX + #ifndef TARGET_OF_IPHONE + char path[MAXPATHLEN]; + uint32_t size = sizeof(path); + + if (_NSGetExecutablePath(path, &size) == 0){ + //printf("executable path is %s\n", path); + string pathStr = string(path); + + //theo: check this with having '/' as a character in a folder name - OSX treats the '/' as a ':' + //checked with spaces too! + + vector < string> pathBrokenUp = ofSplitString( pathStr, "/"); + + newPath = ""; + + for(int i = 0; i < pathBrokenUp.size()-1; i++){ + newPath += pathBrokenUp[i]; + newPath += "/"; + } + + //cout << newPath << endl; // some sanity checks here + //system( "pwd" ); + + chdir ( newPath.c_str() ); + //system("pwd"); + }else{ + ofLog(OF_LOG_FATAL_ERROR, "buffer too small; need size %u\n", size); + } + #endif + #endif + + dataPathRoot() = newRoot; + isDataPathSet() = true; +} + +//-------------------------------------------------- +string ofToDataPath(string path, bool makeAbsolute){ + + if (!isDataPathSet()) + ofSetDataPathRoot(dataPathRoot()); + + if( enableDataPath ){ + + //check if absolute path has been passed or if data path has already been applied + //do we want to check for C: D: etc ?? like substr(1, 2) == ':' ?? + if( path.length()==0 || (path.substr(0,1) != "/" && path.substr(1,1) != ":" && path.substr(0,dataPathRoot().length()) != dataPathRoot())){ + path = dataPathRoot()+path; + } + + if(makeAbsolute && (path.length()==0 || path.substr(0,1) != "/")){ + /* + #if !defined( TARGET_OF_IPHONE) & !defined(TARGET_ANDROID) + + #ifndef TARGET_WIN32 + char currDir[1024]; + path = "/"+path; + path = getcwd(currDir, 1024)+path; + + #else + + char currDir[1024]; + path = "\\"+path; + path = _getcwd(currDir, 1024)+path; + std::replace( path.begin(), path.end(), '/', '\\' ); // fix any unixy paths... + + + #endif + + + #else + //do we need iphone specific code here? + #endif + */ + } + + } + return path; +} + +//---------------------------------------- +template <> +string ofToHex(const string& value) { + ostringstream out; + // how many bytes are in the string + int numBytes = value.size(); + for(int i = 0; i < numBytes; i++) { + // print each byte as a 2-character wide hex value + out << setfill('0') << setw(2) << hex << (unsigned int) ((unsigned char)value[i]); + } + return out.str(); +} + +//---------------------------------------- +string ofToHex(const char* value) { + // this function is necessary if you want to print a string + // using a syntax like ofToHex("test") + return ofToHex((string) value); +} + +//---------------------------------------- +int ofToInt(const string& intString) { + int x = 0; + istringstream cur(intString); + cur >> x; + return x; +} + +//---------------------------------------- +int ofHexToInt(const string& intHexString) { + int x = 0; + istringstream cur(intHexString); + cur >> hex >> x; + return x; +} + +//---------------------------------------- +char ofHexToChar(const string& charHexString) { + int x = 0; + istringstream cur(charHexString); + cur >> hex >> x; + return (char) x; +} + +//---------------------------------------- +float ofHexToFloat(const string& floatHexString) { + union intFloatUnion { + int x; + float f; + } myUnion; + myUnion.x = 0; + istringstream cur(floatHexString); + cur >> hex >> myUnion.x; + return myUnion.f; +} + +//---------------------------------------- +string ofHexToString(const string& stringHexString) { + stringstream out; + stringstream stream(stringHexString); + // a hex string has two characters per byte + int numBytes = stringHexString.size() / 2; + for(int i = 0; i < numBytes; i++) { + string curByte; + // grab two characters from the hex string + stream >> setw(2) >> curByte; + // prepare to parse the two characters + stringstream curByteStream(curByte); + int cur = 0; + // parse the two characters as a hex-encoded int + curByteStream >> hex >> cur; + // add the int as a char to our output stream + out << (char) cur; + } + return out.str(); +} + +//---------------------------------------- +float ofToFloat(const string& floatString) { + float x = 0; + istringstream cur(floatString); + cur >> x; + return x; +} + +//---------------------------------------- +bool ofToBool(const string& boolString) { + static const string trueString = "true"; + static const string falseString = "false"; + string lower = Poco::toLower(boolString); + if(lower == trueString) { + return true; + } + if(lower == falseString) { + return false; + } + bool x = false; + istringstream cur(lower); + cur >> x; + return x; +} + +//---------------------------------------- +char ofToChar(const string& charString) { + char x = '\0'; + istringstream cur(charString); + cur >> x; + return x; +} + +//---------------------------------------- +template <> string ofToBinary(const string& value) { + stringstream out; + int numBytes = value.size(); + for(int i = 0; i < numBytes; i++) { + bitset<8> bitBuffer(value[i]); + out << bitBuffer; + } + return out.str(); +} + +//---------------------------------------- +string ofToBinary(const char* value) { + // this function is necessary if you want to print a string + // using a syntax like ofToBinary("test") + return ofToBinary((string) value); +} + +//---------------------------------------- +int ofBinaryToInt(const string& value) { + const int intSize = sizeof(int) * 8; + bitset<intSize> binaryString(value); + return (int) binaryString.to_ulong(); +} + +//---------------------------------------- +char ofBinaryToChar(const string& value) { + const int charSize = sizeof(char) * 8; + bitset<charSize> binaryString(value); + return (char) binaryString.to_ulong(); +} + +//---------------------------------------- +float ofBinaryToFloat(const string& value) { + const int floatSize = sizeof(float) * 8; + bitset<floatSize> binaryString(value); + union ulongFloatUnion { + unsigned long result; + float f; + } myUFUnion; + myUFUnion.result = binaryString.to_ulong(); + return myUFUnion.f; +} +//---------------------------------------- +string ofBinaryToString(const string& value) { + ostringstream out; + stringstream stream(value); + bitset<8> byteString; + int numBytes = value.size() / 8; + for(int i = 0; i < numBytes; i++) { + stream >> byteString; + out << (char) byteString.to_ulong(); + } + return out.str(); +} + +//-------------------------------------------------- +vector <string> ofSplitString(const string & source, const string & delimiter, bool ignoreEmpty, bool trim) { + vector<string> result; + if (delimiter.empty()) { + result.push_back(source); + return result; + } + string::const_iterator substart = source.begin(), subend; + while (true) { + subend = search(substart, source.end(), delimiter.begin(), delimiter.end()); + string sub(substart, subend); + if(trim) { + Poco::trimInPlace(sub); + } + if (!ignoreEmpty || !sub.empty()) { + result.push_back(sub); + } + if (subend == source.end()) { + break; + } + substart = subend + delimiter.size(); + } + return result; +} + +//-------------------------------------------------- +string ofJoinString(vector <string> stringElements, const string & delimiter){ + string resultString = ""; + int numElements = stringElements.size(); + + for(int k = 0; k < numElements; k++){ + if( k < numElements-1 ){ + resultString += stringElements[k] + delimiter; + } else { + resultString += stringElements[k]; + } + } + + return resultString; +} + +//-------------------------------------------------- +void ofStringReplace(string& input, string searchStr, string replaceStr){ + size_t uPos = 0; + size_t uFindLen = searchStr.length(); + size_t uReplaceLen = replaceStr.length(); + + if( uFindLen == 0 ){ + return; + } + + for( ;(uPos = input.find( searchStr, uPos )) != std::string::npos; ){ + input.replace( uPos, uFindLen, replaceStr ); + uPos += uReplaceLen; + } +} + +//-------------------------------------------------- +bool ofIsStringInString(string haystack, string needle){ + return ( strstr(haystack.c_str(), needle.c_str() ) != NULL ); +} + +//-------------------------------------------------- +string ofToLower(const string & src){ + string dst(src); + transform(src.begin(),src.end(),dst.begin(),::tolower); + return dst; +} + +//-------------------------------------------------- +string ofToUpper(const string & src){ + string dst(src); + transform(src.begin(),src.end(),dst.begin(),::toupper); + return dst; +} + +//-------------------------------------------------- +string ofVAArgsToString(const char * format, ...){ + // variadic args to string: + // http://www.codeproject.com/KB/string/string_format.aspx + static char aux_buffer[10000]; + string retStr(""); + if (NULL != format){ + + va_list marker; + + // initialize variable arguments + va_start(marker, format); + + // Get formatted string length adding one for NULL + size_t len = vsprintf(aux_buffer, format, marker) + 1; + + // Reset variable arguments + va_end(marker); + + if (len > 0) + { + va_list args; + + // initialize variable arguments + va_start(args, format); + + // Create a char vector to hold the formatted string. + vector<char> buffer(len, '\0'); + vsprintf(&buffer[0], format, args); + retStr = &buffer[0]; + va_end(args); + } + + } + return retStr; +} + +string ofVAArgsToString(const char * format, va_list args){ + // variadic args to string: + // http://www.codeproject.com/KB/string/string_format.aspx + char aux_buffer[10000]; + string retStr(""); + if (NULL != format){ + + // Get formatted string length adding one for NULL + vsprintf(aux_buffer, format, args); + retStr = aux_buffer; + + } + return retStr; +} + +/* +//-------------------------------------------------- +void ofLaunchBrowser(string url){ + + // http://support.microsoft.com/kb/224816 + + //make sure it is a properly formatted url + if(Poco::icompare(url.substr(0,7), "http://") != 0 && + Poco::icompare(url.substr(0,8), "https://") != 0) { + ofLog(OF_LOG_WARNING, "ofLaunchBrowser: url must begin http:// or https://"); + return; + } + + //---------------------------- + #ifdef TARGET_WIN32 + //---------------------------- + + #if (_MSC_VER) + // microsoft visual studio yaks about strings, wide chars, unicode, etc + ShellExecuteA(NULL, "open", url.c_str(), + NULL, NULL, SW_SHOWNORMAL); + #else + ShellExecute(NULL, "open", url.c_str(), + NULL, NULL, SW_SHOWNORMAL); + #endif + + //---------------------------- + #endif + //---------------------------- + + //-------------------------------------- + #ifdef TARGET_OSX + //-------------------------------------- + // ok gotta be a better way then this, + // this is what I found... + string commandStr = "open "+url; + system(commandStr.c_str()); + //---------------------------- + #endif + //---------------------------- + + //-------------------------------------- + #ifdef TARGET_LINUX + //-------------------------------------- + string commandStr = "xdg-open "+url; + int ret = system(commandStr.c_str()); + if(ret!=0) ofLog(OF_LOG_ERROR,"ofLaunchBrowser: couldn't open browser"); + //---------------------------- + #endif + //---------------------------- +} + +//-------------------------------------------------- +string ofGetVersionInfo(){ + string version; + stringstream sstr; + sstr << "of version: " << OF_VERSION << endl; + return sstr.str(); +} +*/ +//---- new to 006 +//from the forums http://www.openframeworks.cc/forum/viewtopic.php?t=1413 +/* +//-------------------------------------------------- +void ofSaveScreen(string filename) { + ofImage screen; + screen.allocate(ofGetWidth(), ofGetHeight(), OF_IMAGE_COLOR); + screen.grabScreen(0, 0, ofGetWidth(), ofGetHeight()); + screen.saveImage(filename); +} + +//-------------------------------------------------- +void ofSaveViewport(string filename) { + // because ofSaveScreen doesn't related to viewports + ofImage screen; + ofRectangle view = ofGetCurrentViewport(); + screen.allocate(view.width, view.height, OF_IMAGE_COLOR); + screen.grabScreen(0, 0, view.width, view.height); + screen.saveImage(filename); +} + +//-------------------------------------------------- +int saveImageCounter = 0; +void ofSaveFrame(bool bUseViewport){ + string fileName = ofToString(saveImageCounter) + ".png"; + if (bUseViewport){ + ofSaveViewport(fileName); + } else { + ofSaveScreen(fileName); + } + saveImageCounter++; +} + +//-------------------------------------------------- +string ofSystem(string command){ + FILE * ret = NULL; +#ifdef TARGET_WIN32 + ret = _popen(command.c_str(),"r"); +#else + ret = popen(command.c_str(),"r"); +#endif + + string strret; + char c; + + if (ret == NULL){ + ofLogError() << "ofSystem: error opening return file"; + }else{ + do { + c = fgetc (ret); + strret += c; + } while (c != EOF); + fclose (ret); + } + + return strret; +} + +//-------------------------------------------------- +ofTargetPlatform ofGetTargetPlatform(){ +#ifdef TARGET_LINUX + if(ofSystem("uname -m").find("x86_64")==0) + return OF_TARGET_LINUX64; + else + return OF_TARGET_LINUX; +#elif defined(TARGET_OSX) + return OF_TARGET_OSX; +#elif defined(TARGET_WIN32) + #if (_MSC_VER) + return OF_TARGET_WINVS; + #else + return OF_TARGET_WINGCC; + #endif +#elif defined(TARGET_ANDROID) + return OF_TARGET_ANDROID; +#elif defined(TARGET_OF_IPHONE) + return OF_TARGET_IPHONE; +#endif +} +*/
\ No newline at end of file diff --git a/rotord/src/ofUtils.h b/rotord/src/ofUtils.h new file mode 100755 index 0000000..0567e22 --- /dev/null +++ b/rotord/src/ofUtils.h @@ -0,0 +1,223 @@ +#pragma once + +//#include "ofConstants.h" + + +// core: --------------------------- +#include <cstdio> +#include <cstdarg> +#include <cmath> +#include <ctime> +#include <cstdlib> +#include <string> +#include <iostream> +#include <vector> +#include <cstring> +#include <sstream> //for ostringsream +#include <iomanip> //for setprecision +#include <fstream> +#include <algorithm> + +#include <bitset> // for ofToBinary + + + + +//#include "ofLog.h" + +/*#ifdef TARGET_WIN32 // for ofLaunchBrowser + #include <shellapi.h> +#endif +*/ + + +using namespace std; + +int ofNextPow2(int input); + +void ofResetElapsedTimeCounter(); // this happens on the first frame +float ofGetElapsedTimef(); +unsigned long ofGetElapsedTimeMillis(); +unsigned long ofGetElapsedTimeMicros(); +int ofGetFrameNum(); + +int ofGetSeconds(); +int ofGetMinutes(); +int ofGetHours(); + +//number of seconds since 1970 +unsigned int ofGetUnixTime(); + + +/* +unsigned long ofGetSystemTime( ); // system time in milliseconds; +unsigned long ofGetSystemTimeMicros( ); // system time in microseconds; + + //returns +string ofGetTimestampString(); +string ofGetTimestampString(string timestampFormat); + + +int ofGetYear(); +int ofGetMonth(); +int ofGetDay(); +int ofGetWeekday(); + +void ofLaunchBrowser(string url); +*/ +void ofEnableDataPath(); +void ofDisableDataPath(); +string ofToDataPath(string path, bool absolute=false); + +template<class T> +void ofRandomize(vector<T>& values) { + random_shuffle(values.begin(), values.end()); +} + +template<class T, class BoolFunction> +void ofRemove(vector<T>& values, BoolFunction shouldErase) { + values.erase(remove_if(values.begin(), values.end(), shouldErase), values.end()); +} + +template<class T> +void ofSort(vector<T>& values) { + sort(values.begin(), values.end()); +} +template<class T, class BoolFunction> +void ofSort(vector<T>& values, BoolFunction compare) { + sort(values.begin(), values.end(), compare); +} + +template <class T> +unsigned int ofFind(const vector<T>& values, const T& target) { + return distance(values.begin(), find(values.begin(), values.end(), target)); +} + +template <class T> +bool ofContains(const vector<T>& values, const T& target) { + return ofFind(values, target) != values.size(); +} + +//set the root path that ofToDataPath will use to search for files relative to the app +//the path must have a trailing slash (/) !!!! +void ofSetDataPathRoot( string root ); + +template <class T> +string ofToString(const T& value){ + ostringstream out; + out << value; + return out.str(); +} + +/// like sprintf "%4f" format, in this example precision=4 +template <class T> +string ofToString(const T& value, int precision){ + ostringstream out; + out << fixed << setprecision(precision) << value; + return out.str(); +} + +/// like sprintf "% 4d" or "% 4f" format, in this example width=4, fill=' ' +template <class T> +string ofToString(const T& value, int width, char fill ){ + ostringstream out; + out << fixed << setfill(fill) << setw(width) << value; + return out.str(); +} + +/// like sprintf "%04.2d" or "%04.2f" format, in this example precision=2, width=4, fill='0' +template <class T> +string ofToString(const T& value, int precision, int width, char fill ){ + ostringstream out; + out << fixed << setfill(fill) << setw(width) << setprecision(precision) << value; + return out.str(); +} + +template<class T> +string ofToString(const vector<T>& values) { + stringstream out; + int n = values.size(); + out << "{"; + if(n > 0) { + for(int i = 0; i < n - 1; i++) { + out << values[i] << ", "; + } + out << values[n - 1]; + } + out << "}"; + return out.str(); +} + +template <class T> +string ofToHex(const T& value) { + ostringstream out; + // pretend that the value is a bunch of bytes + unsigned char* valuePtr = (unsigned char*) &value; + // the number of bytes is determined by the datatype + int numBytes = sizeof(T); + // the bytes are stored backwards (least significant first) + for(int i = numBytes - 1; i >= 0; i--) { + // print each byte out as a 2-character wide hex value + out << setfill('0') << setw(2) << hex << (int) valuePtr[i]; + } + return out.str(); +} +template <> +string ofToHex(const string& value); +string ofToHex(const char* value); + +int ofHexToInt(const string& intHexString); +char ofHexToChar(const string& charHexString); +float ofHexToFloat(const string& floatHexString); +string ofHexToString(const string& stringHexString); + +int ofToInt(const string& intString); +char ofToChar(const string& charString); +float ofToFloat(const string& floatString); +bool ofToBool(const string& boolString); + +template <class T> +string ofToBinary(const T& value) { + ostringstream out; + const char* data = (const char*) &value; + // the number of bytes is determined by the datatype + int numBytes = sizeof(T); + // the bytes are stored backwards (least significant first) + for(int i = numBytes - 1; i >= 0; i--) { + bitset<8> cur(data[i]); + out << cur; + } + return out.str(); +} +template <> +string ofToBinary(const string& value); +string ofToBinary(const char* value); + +int ofBinaryToInt(const string& value); +char ofBinaryToChar(const string& value); +float ofBinaryToFloat(const string& value); +string ofBinaryToString(const string& value); + +string ofGetVersionInfo(); + +void ofSaveScreen(string filename); +void ofSaveFrame(bool bUseViewport = false); +void ofSaveViewport(string filename); + +//-------------------------------------------------- +vector <string> ofSplitString(const string & source, const string & delimiter, bool ignoreEmpty = false, bool trim = false); +string ofJoinString(vector <string> stringElements, const string & delimiter); +void ofStringReplace(string& input, string searchStr, string replaceStr); +bool ofIsStringInString(string haystack, string needle); + +string ofToLower(const string & src); +string ofToUpper(const string & src); + +string ofVAArgsToString(const char * format, ...); +string ofVAArgsToString(const char * format, va_list args); + +string ofSystem(string command); + +//ofTargetPlatform ofGetTargetPlatform(); + + diff --git a/rotord/src/params.h b/rotord/src/params.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/rotord/src/params.h diff --git a/rotord/src/rendercontext.cpp b/rotord/src/rendercontext.cpp new file mode 100644 index 0000000..7362d64 --- /dev/null +++ b/rotord/src/rendercontext.cpp @@ -0,0 +1,386 @@ +#include "rotor.h" + + +using namespace Rotor; +void Render_context::runTask() { + while (!isCancelled()) { + int cmd=0; + mutex.lock(); + if (work_queue.size()){ + cmd=work_queue[0]; + work_queue.pop_front(); + } + mutex.unlock(); + if(cmd==ANALYSE_AUDIO) { + state=ANALYSING_AUDIO; + vector<Base_audio_processor*> processors; + processors.push_back(audio_thumb); + vector<Node*> analysers=graph.find_nodes("audio_analysis"); + for (auto a: analysers) { + processors.push_back(dynamic_cast<Base_audio_processor*>(a)); + } + if (load_audio(audio_filename,processors)) { + audio_loaded=true; + state=IDLE; + } + else { + //an error occurred: TODO have to clean up allocated data. autoptr? + audio_loaded=false; + state=IDLE; + } + } + if(cmd==RENDER) { + state=RENDERING; + if(graph.video_render(output_filename,audio_filename,output_framerate,progress)){ + state=IDLE; + } + else { + //an error occurred: TODO have to clean up allocated data. autoptr? + cerr<<"Rotor: render failed"<<endl; + state=IDLE; + } + } + sleep(100); + } + printf("Rotor: stopping thread\n"); +} +void Render_context::add_queue(int item) { + mutex.lock(); + work_queue.push_back(item); + mutex.unlock(); +} +void Render_context::session_command(const std::vector<std::string>& command,xmlIO& XML,HTTPResponse::HTTPStatus& status){ + Logger& logger = Logger::get("Rotor"); + status=HTTPResponse::HTTP_BAD_REQUEST; //error by default + if (command[2]=="resolution") { + if (command[0]=="PUT") { + if (command.size()>2) { + if (state==IDLE) { + Poco::StringTokenizer t1(command[3],","); + if (t1.count()>1){ + int w=ofToInt(t1[0]); + int h=ofToInt(t1[1]); + if (graph.set_resolution(w,h)){ + logger.information("resolution set to "+t1[0]+"x"+t1[1]); + XML.addValue("status","resolution set to "+t1[0]+"x"+t1[1]); + status=HTTPResponse::HTTP_OK; + } + else { + logger.error("ERROR: invalid resolution request: "+t1[0]+"x"+t1[1]); + XML.addValue("error","invalid resolution request: "+t1[0]+"x"+t1[1]); + } + } + } + else { + XML.addValue("error","session busy"); + } + } + } + } + if (command[2]=="audio") { + if (command[0]=="PUT") { //get audio file location and initiate analysis + if (command.size()>2) { + if (state==IDLE) { + audio_filename=media_dir+command[3]; //for now, store session variables in memory //check file exists + Poco::File f=Poco::File(audio_filename); + if (f.exists()) { + //pass to worker thread ??if engine is ready?? ??what if engine has finished but results aren't read?? + add_queue(ANALYSE_AUDIO); + status=HTTPResponse::HTTP_OK; + logger.information("Starting audio analysis: "+command[3]); + XML.addValue("status","Starting audio analysis: "+command[3]); + } + else { + status=HTTPResponse::HTTP_NOT_FOUND; + logger.error("ERROR: audio file "+command[3]+" not found"); + XML.addValue("error",command[3]+" not found"); + } + + } + else { + logger.error("ERROR: Session busy"); + XML.addValue("error","Session busy"); + } + } + } + if (command[0]=="GET") { + if (state==ANALYSING_AUDIO) { + status=HTTPResponse::HTTP_OK; + XML.addValue("status","Analysing audio"); + char c[20]; + sprintf(c,"%02f",progress); + XML.addValue("progress",string(c)); + } + else if (audio_loaded) { + //not sure about this-- should this state be retained? + //can the data only be read once? + //for now + status=HTTPResponse::HTTP_OK; + XML.addValue("status","Audio ready"); + XML.addValue("audio",audio_thumb->print()); + } + else { + logger.error("ERROR: audio thumbnail requested but no audio loaded"); + XML.addValue("error","No audio loaded"); + } + } + if (command[0]=="DELETE") { + if (state==IDLE) { + audio_filename=""; + logger.information("Audio deleted"); + XML.addValue("status","Audio deleted"); + status=HTTPResponse::HTTP_OK; + } + else { + logger.error("ERROR: Session busy"); + XML.addValue("error","Session busy"); + } + } + } + if (command[2]=="graph") { + if (command[0]=="GET") { + if (graph.loaded) { + status=HTTPResponse::HTTP_OK; + //XML.addValue("patchbay",graph.toString()); + logger.information("Requested graph"); + XML.loadFromBuffer(graph.toString()); + } + else { + logger.error("ERROR: graph not loaded: check XML"); + XML.addValue("error","graph not loaded: check XML"); + } + } + if (command[0]=="PUT") { //get new graph from file + if (command.size()>2) { + //should interrupt whatever is happening? + //before begining to load from xml + if (state==IDLE) { //eventually not like this + if (graph.load(command[3])) { + status=HTTPResponse::HTTP_OK; + logger.information("Loaded graph from http PUT body"); + XML.addValue("status","Loaded graph from PUT body"); + if (audio_loaded) { + add_queue(ANALYSE_AUDIO); + status=HTTPResponse::HTTP_OK; + logger.information("Starting audio analysis for graph: "+command[3]); + XML.addValue("status","Starting audio analysis for graph: "+command[3]); + } + } + else { + string graph_filename=graph_dir+command[3]; + Poco::File f=Poco::File(graph_filename); + if (f.exists()) { + if (graph.loadFile(graph_filename)) { + status=HTTPResponse::HTTP_OK; + //XML.addValue("patchbay",graph.toString()); + //XML.loadFromBuffer(graph.toString()); + XML=graph.xml; + //the graph could actually contain an xml object and we could just print it here? + //or could our nodes even be subclassed from xml nodes? + //the graph or the audio could load first- have to analyse the audio with vamp after the graph is loaded + //for now the graph must load 1st + if (audio_loaded) { + add_queue(ANALYSE_AUDIO); + status=HTTPResponse::HTTP_OK; + logger.information("Starting audio analysis for graph: "+command[3]); + XML.addValue("status","Starting audio analysis for graph: "+command[3]); + } + } + else { + status=HTTPResponse::HTTP_INTERNAL_SERVER_ERROR; //~/sources/poco-1.4.6-all/Net/include/Poco/Net/HTTPResponse.h + logger.error("ERROR: graph not loaded: check XML"); + XML.addValue("error","graph not loaded: check XML"); + } + } + else { + status=HTTPResponse::HTTP_NOT_FOUND; + logger.error("ERROR: "+command[3]+" not found"); + XML.addValue("error",command[3]+" not found"); + } + } + } + } + } + if (command[0]=="DELETE") { + //for now + graph=Graph(); + logger.information("graph deleted"); + XML.addValue("status","graph deleted"); + status=HTTPResponse::HTTP_OK; + } + } + if (command[2]=="signal") { + if (command[0]=="GET") { //generate xml from 1st signal output + if (state==IDLE) { + //direct call for testing + float framerate=25.0f; + //if (command.size()>2) { + // framerate=ofToFloat(command[3]); + //} + string signal_xml; + if (graph.signal_render(signal_xml,framerate)){ + status=HTTPResponse::HTTP_OK; + logger.information("rendering signal to xml"); + XML.addValue("signal",signal_xml); //this doesn't work >> pseudo xml + } + else { + status=HTTPResponse::HTTP_INTERNAL_SERVER_ERROR; + logger.error("ERROR: could not render output signal"); + XML.addValue("error","could not render output signal"); + } + //else { + // status=HTTPResponse::HTTP_NOT_FOUND; + // XML.addValue("error","Signal output not found in graph"); + //} + } + else { + status=HTTPResponse::HTTP_SERVICE_UNAVAILABLE; + logger.error("ERROR: context busy"); + XML.addValue("error","Context busy"); + } + } + } + if (command[2]=="video") { + if (command[0]=="PUT") { //get vide file location and initiate analysis + if (command.size()>4) { //there should be a filename + a destination node + if (state==IDLE) { + string video_filename=media_dir+command[4]; + //check file exists + Poco::File f=Poco::File(video_filename); + if (f.exists()) { + if (load_video(command[3],video_filename)) { + //pass to worker thread ??if engine is ready?? ??what if engine has finished but results aren't read?? + //DUMMY RESPONSE + status=HTTPResponse::HTTP_OK; + logger.information("Succesfully loaded "+command[4]+" into video node "+command[3]); + XML.addValue("status","Succesfully loaded "+command[4]+" into video node "+command[3]); + } + else { + status=HTTPResponse::HTTP_INTERNAL_SERVER_ERROR; + logger.error("ERROR: could not load "+command[4]+" into video node "+command[3]); + XML.addValue("error","could not load "+command[4]+" into video node "+command[3]); + } + } + else { + status=HTTPResponse::HTTP_NOT_FOUND; + logger.error("ERROR: "+command[4]+" not found"); + XML.addValue("error",command[4]+" not found"); + } + } + else { + status=HTTPResponse::HTTP_BAD_REQUEST; + logger.error("ERROR: Session busy"); + XML.addValue("error","Session busy"); + } + } + else { + status=HTTPResponse::HTTP_BAD_REQUEST; + logger.error("ERROR: Bad request"); + XML.addValue("error","Bad request"); + } + } + } + if (command[2]=="render") { + if (command[0]=="GET") { + if(state==RENDERING){ + status=HTTPResponse::HTTP_OK; + XML.addValue("status","Rendering video"); + XML.addValue("progress",ofToString(progress)); + } + else { + logger.error("ERROR: Render progress requested but not rendering"); + XML.addValue("error","Not rendering"); + } + } + if (command[0]=="PUT") { + if (command.size()>2) { + if (state==IDLE) { + output_filename=output_dir+command[3]; + if (command.size()>3) { +// output_framerate=ofToFloat(command[4]); + } + add_queue(RENDER); + status=HTTPResponse::HTTP_OK; + logger.information("Starting render: "+command[3]); + XML.addValue("status","Starting render: "+command[3]); + } + else { + status=HTTPResponse::HTTP_BAD_REQUEST; + logger.error("ERROR: Session busy"); + XML.addValue("error","Session busy"); + } + } + else { + status=HTTPResponse::HTTP_BAD_REQUEST; + logger.error("ERROR: No output file specified"); + XML.addValue("error","No output file specified"); + } + } + if (command[0]=="DELETE") { + status=HTTPResponse::HTTP_OK; + logger.error("ERROR: Not implemented"); + XML.addValue("status","DUMMY RESPONSE: cancelling render"); + } + } +} + +bool Render_context::load_audio(const string &filename,vector<Base_audio_processor*> processors){ + Logger& logger = Logger::get("Rotor"); + logger.information("Starting audio analysis"); + + libav::audioloader loader; + loader.setup(filename); + + graph.duration=((float)loader.formatContext->duration)/AV_TIME_BASE; + + int rate = loader.codecContext->sample_rate; + int samples = ((loader.formatContext->duration + 5000)*rate)/AV_TIME_BASE; //why 5000 more? + int channels= loader.codecContext->channels; + int bits = loader.codecContext->bits_per_raw_sample; + + for (auto p: processors) { + if(!p->init(channels,bits,samples,rate) ){ + logger.error("ERROR: Audio plugin failed to initialse"); + return false; + } + } + + AVFrame* frame=loader.get_frame(); + int sample_processed=0; + + while (frame) + { + //now we can pass the data to the processor(s) + for (auto p: processors) { + p->process_frame(frame->data[0],frame->nb_samples); + } + sample_processed+=frame->nb_samples; + //mutex.lock(); + progress=((float)sample_processed)/samples; //atomic on 64 bit? + //mutex.unlock(); + + frame=loader.get_frame(); + } + + loader.close(); + + for (auto p: processors) { + p->cleanup(); + p->print_summary(); + } + + logger.information("Finished audio analysis"); + return true; +} +bool Render_context::load_video(const string &nodeID,const string &filename){ + //this is a good standard example of how to find + //a node of a specific type by ID and do something + if (graph.nodes.find(nodeID)!=graph.nodes.end()){ + if (graph.nodes[nodeID]->type=="video_loader") { + if (((Video_loader*)graph.nodes[nodeID])->load(filename)) { + return true; + } + } + } + return false; +} diff --git a/rotord/src/rotor.cpp b/rotord/src/rotor.cpp new file mode 100755 index 0000000..8b72c50 --- /dev/null +++ b/rotord/src/rotor.cpp @@ -0,0 +1,361 @@ +#include "rotor.h" +#include "nodes_audio_analysis.h" +#include "nodes_drawing.h" + +using namespace Rotor; +Node_factory::Node_factory(){ + //for now, statically load prototype map in constructor + add_type("audio_analysis",new Audio_analysis()); + add_type("divide",new Signal_divide()); + add_type("bang",new Is_new_integer()); + add_type("signal_output",new Signal_output()); + add_type("testcard",new Testcard()); + add_type("video_output",new Video_output()); + add_type("video_loader",new Video_loader()); + add_type("on_off",new On_off()); + add_type("invert",new Invert()); + add_type("video_cycler",new Video_cycler()); + add_type("luma_levels",new Luma_levels()); + add_type("echo_trails",new Echo_trails()); + add_type("time",new Time()); + add_type("track_time",new Track_time()); + add_type("comparison",new Comparison()); //TODO: alias to symbols + add_type("arithmetic",new Arithmetic()); //TODO: alias to symbols + add_type("signal_colour",new Signal_colour()); + add_type("signal_greyscale",new Signal_greyscale()); + add_type("image_arithmetic",new Image_arithmetic()); + add_type("random",new Random()); + add_type("blend",new Blend()); + add_type("mirror",new Mirror()); + add_type("monochrome",new Monochrome()); + add_type("transform",new Transform()); + add_type("alpha_merge",new Alpha_merge()); + add_type("draw",new Draw()); +} + +bool Signal_input::connect(Signal_node* source) { + if (source->output_type=="signal") { + connection=(Node*)source; + return true; + } + else return false; +} +void Parameter_input::update(const Time_spec& time){ //gets input and updates variable + if (receiver){ + *receiver=((Signal_node*)connection)->get_output(time); + } +} +bool Image_input::connect(Image_node* source) { + if (source->output_type=="image") { + connection=(Node*)source; + return true; + } + else return false; +} +void Node::update_params(const Time_spec& time){ //compute connected parameters + for (auto p:parameter_inputs){ + p->update(time); + } +} +bool Signal_output::render(const float duration, const float framerate,string &xml_out){ + //testing signal routes + cerr << "Rotor: Signal_output rendering " << duration << " seconds at " << framerate << " frames per second" << endl; + float step=1.0f/framerate; + float v=0.0f; + float min=10000000.0f; + float max=-10000000.0f; + for (float f=0.0f;f<duration;f+=step) { + float u=get_output(Time_spec(f,framerate,duration)); + if (!fequal(u,v)) { + xml_out+=("<signal time='"+ofToString(f)+"'>"+ofToString(u)+"</signal>\n"); + v=u; + if (v>max) max=v; + if (v<min) min=v; + } + } + xml_out+=("<signal_finished min='"+ofToString(min)+"' max='"+ofToString(max)+"'/>\n"); + return true; +} + +bool Audio_thumbnailer::init(int _channels,int _bits,int _samples,int _rate) { + //base_audio_processor::init(_channels,_bits,_samples); + channels=_channels; + bits=_bits; + samples=_samples; + samples_per_column=samples/width; + column=0; //point thumbnail bitmap + out_sample=0; //sample in whole track + offset=0x1<<(bits-1); //signed audio + scale=1.0/offset; + sample=0; + samples=0; + accum=0.0; + return true; +} +int Audio_thumbnailer::process_frame(uint8_t *_data,int samples_in_frame){ + //begin by processing remaining samples + //samples per column could be larger than a frame! (probably is) + //but all we are doing is averaging + int bytes=(bits>>3); + int stride=channels*bytes; + int in_sample=0; + while (in_sample<samples_in_frame&&column<width) { + //continue the column + while (sample<samples_per_column&&in_sample<samples_in_frame) { + //accumulate samples for this column until we run out of samples + for (int i=0;i<channels;i++) { + unsigned int this_val=0; + for (int j=0;j<bytes;j++) { + this_val+=_data[(in_sample*stride)+(i*bytes)+j]<<(j*8); + } + //convert from integer data format - i.e s16p - to audio signal in -1..1 range + //presume 16 bits for now... + double val=((double)((int16_t)this_val))*scale; + accum+=val*val; + samples++; + } + in_sample++; + sample++; + out_sample++; + } + if (sample==samples_per_column) { //finished a column + //get root-mean + double mean=pow(accum/samples,0.5); + //if (column==0) { + // cerr << "first column total: "<< accum << " in " << samples << " samples, average " << (accum/samples)<<endl; + //} + int colheight=height*mean*0.5; + int hh=height>>1; + for (int i=0;i<height;i++) { + data[i*width+column]=abs(i-hh)<colheight?0xff:0x00; + } + column++; + sample=0; + samples=0; + accum=0.0; + } + } + return out_sample; +} +string Audio_thumbnailer::print(){ + //base64 encode the image data output it + + stringstream output; + Poco::Base64Encoder *enc=new Poco::Base64Encoder(output); + + enc->write((char*)data,width*height); + //tring output; + /* + for (int j=0;j<height;j++) { + for (int i=0;i<width;i++) { + output+=data[j*width+i]<0x7f?"0":"1"; + } + output +="\n"; + } + */ + enc->close(); + delete enc; + return output.str(); +} +bool Audio_analysis::init(int _channels,int _bits,int _samples, int _rate) { + //need these to make sense of data + channels=_channels; + bits=_bits; + samples=_samples; + + return analyser.init(soname,id,_channels,_bits,_samples,_rate,outputNo,params); + + + //attempt to load vamp plugin and prepare to receive frames of data + //should the audio analysis contain a vamphost or should it inherit? + //maybe neater to contain it in terms of headers etc + +} +int Audio_analysis::process_frame(uint8_t *data,int samples_in_frame) { + analyser.process_frame(data,samples_in_frame); + return 1; +} +void Audio_analysis::cleanup() { + analyser.cleanup(); + //print_features(); +} +void Audio_analysis::print_features(){ + for (auto i: analyser.features) { + cerr<<" ["<<i.second<<":"<<i.first<<"]"; + } + cerr<<endl; +} + +bool Video_output::render(const float duration, const float framerate,const string &output_filename,const string &audio_filename,float& progress,int outW,int outH){ + + // + //setup defaults + int bitRate=5000000; + AVCodecID codecId=AV_CODEC_ID_H264; //MPEG4; + std::string container ="mp4"; + + + //at the moment it crashes if you render before audio is loaded and also on 2nd render + libav::exporter exporter; + + float spct=100.0f/duration; + + if (exporter.setup(outW,outH,bitRate,framerate,container)) { //codecId, + if (exporter.record(output_filename)) { + + libav::audioloader audioloader; + + bool usingaudio=audioloader.setup(audio_filename); + + cerr << "Rotor: Video_output rendering " << duration << " seconds at " << framerate << " fps, audio frame size: " << exporter.get_audio_framesize()<<endl; + //25fps video and 43.06640625fps audio? hmm + //how to get the timecodes correct for the interleaved files + + struct timeval start, end; + + gettimeofday(&start, NULL); + + + float vstep=1.0f/framerate; + float v=0.0f; + float vf=0.0f; + float af=0.0f; + while (vf<duration){ //-vstep) { + if (usingaudio) { + while (!fless(af,vf)) { + //insert audio frames until we are ahead of the video + exporter.encodeFrame(audioloader.get_samples(exporter.get_audio_framesize())); + af+=exporter.get_audio_step(); + + } + } + + + //[mp3 @ 0x7fffe40330e0] max_analyze_duration 5000000 reached at 5015510 microseconds + //[mp3 @ 0x7fffe4033ec0] Insufficient thread locking around avcodec_open/close() + //[mp3 @ 0x7fffe40330e0] Estimating duration from bitrate, this may be inaccurate + //[libx264 @ 0x7fffe8003940] using cpu capabilities: MMX2 SSE2Fast SSSE3 FastShuffle SSE4.2 + //[libx264 @ 0x7fffe8003940] profile High, level 3.0 + //[libx264 @ 0x7fffe8003940] 264 - core 123 r2189 35cf912 - H.264/MPEG-4 AVC codec - Copyleft 2003-2012 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=-2 threads=12 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=10 keyint_min=1 scenecut=40 intra_refresh=0 rc_lookahead=10 rc=abr mbtree=1 bitrate=400 ratetol=1.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00 + //Assertion ff_avcodec_locked failed at libavcodec/utils.c:2967 + + //cerr<<"videoloader: "<<vf<<" seconds, vstep "<<vstep<<" ,asking for frame "<<((int)((vf*framerate)+0.5))<<endl; + + Image* i=get_output(Frame_spec(vf,framerate,duration,outW,outH)); + if (i) { + exporter.encodeFrame(i->RGBdata); + + } + vf+=vstep; + progress=vf/duration; + } + + exporter.finishRecord(); + + gettimeofday(&end, NULL); + + float mtime = ((end.tv_sec-start.tv_sec) + (end.tv_usec-start.tv_usec)/1000000.0) + 0.5; + + printf("Rotor Video_output: rendered in %02f seconds\n", mtime); + + if (usingaudio) audioloader.close(); + + return true; + } + } + + return false; +} + +bool Video_loader::load(const string &filename){ + /* + //gstreamer needs absolute paths ALWAYS + //string uri="file:///home/tim/workspace/rotor/rotord/"+filename; + Poco::Path path; + string uri="file://"+path.current()+filename; + //cerr << "video input: loading "<<uri<<endl; + if (player->loadMovie(uri)){ + player->play(); + player->setPaused(true); + player->setFrameByFrame(true); + player->update(); + cerr<<"Rotor::Video_loader: "<<filename<<", "<<player->getDuration()<<" seconds "<<", "<<player->getWidth()<<"x"<<player->getHeight()<<endl; + image->setup_fromRGB(player->getWidth(),player->getHeight(),(uint8_t*) player->getPixels()); + return true; + } + */ + if (isLoaded) { + player.cleanup(); ///should be in decoder class? + isLoaded=false; + } + Poco::Path path; + string uri="file://"+filename; + isLoaded=player.open(uri); + if (isLoaded){ + cerr<<"Rotor::Video_loader: "<<filename<<", "<<player.getNumberOfFrames()<<" frames "<<", "<<player.getWidth()<<"x"<<player.getHeight()<<endl; + return true; + } + cerr<<"Rotor::Video_loader: failed to load "<<filename<<endl; + return false; +} +Image* Video_loader::output(const Frame_spec &frame){ + //wonder about the actual mechanism used by gstreamer + //have to implment callback when seek is ready? + //presume gstreamer caches a loaded frame? + + + //deal with reolution: swscale from avcodec or put scaler in pipeline? + //can image node point to buffer in gst rather than copying the pixels? + + //to test using fp time to seek: need a short movie with synced audio + + //fix actual duration and audio file + //trace frame that is being read + /* + if (player->isLoaded()){ + //player->setPosition(frame.time); + int wanted=((int) (frame.time*frame.framerate))%(player->getTotalNumFrames()-2); //-2?? + player->setFrame(wanted); + //while (player->getCurrentFrame()!=wanted){ + // cerr << "seeking to "<<wanted<<" :"<<player->getCurrentFrame()<<endl; + //player->setFrame(wanted); + //player->update(); + // sleep(.001); + //} + player->update(); + image->RGBdata=player->getPixels(); //don't really know why this is needed every frame + //cerr<<"Video_loader: retrieving frame "<<((int) (frame.time*frame.framerate))<<endl; + return image; + } + */ + + if (isLoaded){ + int wanted=(((int) ((frame.time*frame.framerate)+0.5))%(player.getNumberOfFrames())); //+1 is necessary because 1st frame in a video is number 1? + + + //if (wanted==99){ + // cerr<<"videoloader: near the end"<<endl; + //} + + //cerr<<"videoloader: requesting frame "<<wanted<<endl; + //if (wanted==68) { + // int nothing=0; + //} + + if (!player.fetchFrame(frame.w,frame.h,wanted)) { //seek fail + cerr<<"Rotor: failed to seek frame"<<endl; + if (image.w>0) return ℑ //just return the previous frame if possible + else return nullptr; + }; + //cerr<<"Video_loader: setting up frame: lineoffset="<<(player.pFrameRGB->linesize[0]-(frame.w*3))<<endl; + image.setup_fromRGB(frame.w,frame.h,player.pFrameRGB->data[0],player.pFrameRGB->linesize[0]-(frame.w*3)); + return ℑ + } + + //confusingly, crashes with files not made with short files? + //seems to be on last frame? - returns nullptr - still tries to clone? + //can't really return 1st frame instead, should get # of frames right 1st? + //think about what echo trails does on the last frame + + return nullptr; +}; diff --git a/rotord/src/rotor.h b/rotord/src/rotor.h new file mode 100755 index 0000000..f922fcf --- /dev/null +++ b/rotord/src/rotor.h @@ -0,0 +1,1391 @@ +#ifndef ROTOR_H +#define ROTOR_H + +/* +nodes can have many inputs but only 1 output + +image nodes that use an image as input can pass on the incoming image only if its unchanged. + +TODO - parameter class that automatically links variable to correctly named inputs +TODO - use try.. catch and dynamic_cast to verify node connections rather than checking 'type' tag + +TODO - put the boilerplate code for checking inputs into the base class, finally call checked_output + +http://stackoverflow.com/questions/5261658/how-to-seek-in-ffmpeg-c-c +*/ + +#include <unordered_map> +#include <deque> +#include <math.h> +#include <memory> +#include <sys/time.h> +#include <iostream> + +#include "Poco/Net/HTTPServer.h" +#include "Poco/Net/HTTPResponse.h" +#include "Poco/UUID.h" +#include "Poco/UUIDGenerator.h" +#include "Poco/Notification.h" +#include "Poco/NotificationCenter.h" +#include "Poco/Observer.h" +#include "Poco/ThreadPool.h" +#include "Poco/Thread.h" +#include "Poco/Task.h" +#include "Poco/Runnable.h" +#include "Poco/Mutex.h" +#include "Poco/Random.h" +#include "Poco/AutoPtr.h" +#include "Poco/File.h" +#include "Poco/Base64Encoder.h" +#include "Poco/Path.h" +#include "Poco/StringTokenizer.h" +#include "Poco/Logger.h" + + +using Poco::UUID; +using Poco::UUIDGenerator; +using Poco::Net::HTTPResponse; +using Poco::Logger; + +/* +extern "C" { + #include <libavcodec/avcodec.h> + #include <libavformat/avformat.h> + #include <libavutil/opt.h> + #include <libavutil/channel_layout.h> + #include <libavutil/common.h> + #include <libavutil/imgutils.h> + #include <libavutil/mathematics.h> + #include <libavutil/samplefmt.h> + + #include <libavutil/dict.h> + //#include <libavutil/dict.c> stops the compiler error but causes a linker error. does libavcodec need to be statically linked? + #include <libavutil/imgutils.h> + #include <libavutil/samplefmt.h> + //#include <libavutil/timestamp.h> +} +*/ + + +#define AUDIO_INBUF_SIZE 20480 +#define AUDIO_REFILL_THRESH 4096 + +#include "xmlIO.h" +#include "utils.h" //fequal +#include "libavwrapper.h" +#include "cvimage.h" + +namespace Rotor { + #define IDLE 0 + #define ANALYSING_AUDIO 1 + #define AUDIO_READY 2 + #define CREATING_PREVIEW 3 + #define PREVIEW_READY 4 + #define RENDERING 5 + #define RENDER_READY 6 + + #define ANALYSE_AUDIO 1 + #define PREVIEW 2 + #define RENDER 3 + + //forward declaration + class Node; + class Signal_node; + class Image_node; + class Parameter_input; + + //http://blog.tomaka17.com/2012/03/libavcodeclibavformat-tutorial/ + /* struct Packet { + explicit Packet(AVFormatContext* ctxt = nullptr) { + av_init_packet(&packet); + packet.data = nullptr; + packet.size=0; + if (ctxt) reset(ctxt); + } + + Packet(Packet&& other) : packet(std::move(other.packet)) { + other.packet.data = nullptr; + } + + ~Packet() { + if (packet.data) + av_free_packet(&packet); + } + + void reset(AVFormatContext* ctxt) { + if (packet.data) + av_free_packet(&packet); + if (av_read_frame(ctxt, &packet) < 0) + packet.data = nullptr; + } + + AVPacket packet; + }; + */ + class Time_spec{ + public: + Time_spec(){}; + Time_spec(float _time,float _framerate,float _duration){ time=_time; framerate=_framerate; duration=_duration;}; + float time; + float framerate; + float duration; + Time_spec lastframe() const{ + return Time_spec(time-(1.0f/framerate),framerate,duration); + } + }; + class Frame_spec: public Time_spec{ + public: + Frame_spec(float _time,float _framerate,float _duration,int _w,int _h){ time=_time; framerate=_framerate; duration=_duration; w=_w; h=_h;}; + Frame_spec(int _frame,float _framerate,float _duration,int _w,int _h){ time=((float)_frame)/_framerate; framerate=_framerate; duration=_duration; w=_w; h=_h;}; + //Frame_spec(time,_framerate,_duration,_w,_h);}; + + //float time; //this hould probably be implemented with a num/denom scheme eventually for accuracy + //float framerate; + int h,w; + //Frame_spec lastframe(){ + // return Frame_spec(time-(1.0f/framerate),framerate,w,h); + //} + int frame(){ + return (int)((time*framerate)+0.5); //rounded to the nearest frame + } + }; + class Colour{ + public: + Colour(){ + r=g=b=0; + } + Colour(int c){ + r=c&0xFF; + g=(c&0xFF00)>>8; + b=(c&0xFF0000)>>16; + } + Colour(std::string s){ + r=(uint8_t)ofHexToChar(s.substr(0,2)); + g=(uint8_t)ofHexToChar(s.substr(2,2)); + b=(uint8_t)ofHexToChar(s.substr(4,2)); + } + uint8_t r,g,b; + }; + + class Render_status{ + public: + int id; + float progress; + }; + class Render_requirements{ + public: + int num_performances; + int num_clips; + }; + class Command_response{ + public: + Command_response() { status=Poco::Net::HTTPResponse::HTTP_OK; } + std::string description; + Poco::Net::HTTPResponse::HTTPStatus status; + }; + class Input{ + public: + Input(const string &_desc): connection(nullptr),description(_desc){}; + Node* connection; + string description; + }; + class Image_input: public Input{ + public: + bool connect(Image_node *source); + Image_input(const string &_desc): Input(_desc){}; + }; + class Signal_input: public Input{ + public: + bool connect(Signal_node *source); + Signal_input(const string &_desc): Input(_desc){}; + }; + class Parameter_input: public Signal_input{ + public: + Parameter_input(const string &_param,const string &_desc): Signal_input(_desc),receiver(nullptr),parameter(_param){}; + float *receiver; + void update(const Time_spec& time); + string parameter; + }; + class Node{ + public: + virtual Node* clone(map<string,string> &_settings)=0; + virtual ~Node(){}; + UUID uid; //every usable node has a UUID + int id; + vector<Signal_input*> inputs; //simple node can have signal inputs, output depends on node type + vector<Parameter_input*> parameter_inputs; //linked parameters can convert from settings to inputs + void create_signal_input(const string &description) {inputs.push_back(new Signal_input(description));}; + void create_parameter_input(const string ¶meter,const string &description) {parameter_inputs.push_back(new Parameter_input(parameter,description));}; + string description; + string type; + string output_type; + string ID; + string find_setting(map<string,string> &settings,string key,string def=""){ if (settings.find(key)!=settings.end()) return settings[key]; else return def;}; + float find_setting(map<string,string> &settings,string key,float def){ if (settings.find(key)!=settings.end()) return ofToFloat(settings[key]); else return def;}; + int find_setting(map<string,string> &settings,string key,int def){ if (settings.find(key)!=settings.end()) return ofToInt(settings[key]); else return def;}; + void base_settings(map<string,string> &settings) { + description=find_setting(settings,"description"); + type=find_setting(settings,"type"); + output_type=find_setting(settings,"output"); + ID=find_setting(settings,"ID"); + } + virtual void set_parameter(const std::string &key,const std::string &value){}; + virtual void link_params(){}; //TODO make param classes that link automatically + void update_params(const Time_spec& time); + }; + class Signal_node: public Node{ + public: + virtual ~Signal_node(){}; + const float get_output(const Time_spec &time) { update_params(time); return output(time); }; + virtual const float output(const Time_spec &time) { return 0.0f; }; + }; + class Image_node: public Node{ + public: + virtual ~Image_node(){}; + vector<Image_input*> image_inputs; //image node also has image inputs and outputs + void create_image_input(const string &description) {image_inputs.push_back(new Image_input(description));}; + Image *get_output(const Frame_spec &frame) { update_params((Time_spec)frame); return output(frame); }; + virtual const Image *output(const Frame_spec &frame)=0; + Image *get_preview(const Frame_spec &frame); + Image *image; //this can be privately allocated or just passed on as the node see fit + private: + float image_time; + }; + class Base_audio_processor: public Signal_node { + public: + virtual ~Base_audio_processor(){}; + virtual int process_frame(uint8_t *data,int samples)=0; + virtual bool init(int _channels,int _bits,int _samples,int _rate)=0; + virtual void cleanup()=0; + virtual void print_summary(){}; + int channels,bits,samples,rate; + }; + //actual nodes------------------------------------------------- + class Time: public Signal_node { + public: + Time(){}; + Time(map<string,string> &settings) { + base_settings(settings); + }; + Time* clone(map<string,string> &_settings) { return new Time(_settings);}; + const float output(const Time_spec &time) { + return time.time; + } + }; + class Track_time: public Signal_node { + public: + Track_time(){}; + Track_time(map<string,string> &settings) { + base_settings(settings); + }; + Track_time* clone(map<string,string> &_settings) { return new Track_time(_settings);}; + const float output(const Time_spec &time) { + return time.time/time.duration; + } + }; +#define COMPARISON_Equal 1 +#define COMPARISON_Not_equal 2 +#define COMPARISON_Greater 3 +#define COMPARISON_Less 4 +#define COMPARISON_Greater_or_equal 5 +#define COMPARISON_Less_or_equal 6 + class Comparison: public Signal_node { + public: + Comparison(){}; + Comparison(map<string,string> &settings) { + base_settings(settings); + value=find_setting(settings,"value",0.0f); + string _op=find_setting(settings,"operator","=="); + if (_op=="==") op=COMPARISON_Equal; + if (_op=="!=") op=COMPARISON_Not_equal; + if (_op==">") op=COMPARISON_Greater; + if (_op=="<") op=COMPARISON_Less; + if (_op==">=") op=COMPARISON_Greater_or_equal; + if (_op=="<=") op=COMPARISON_Less_or_equal; + } + void link_params() { + for (auto p:parameter_inputs){ + if (p->parameter=="value") p->receiver=&value; + } + }; + Comparison* clone(map<string,string> &_settings) { return new Comparison(_settings);}; + const float output(const Time_spec &time) { + if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml + if (inputs[0]->connection) { + float in= (((Signal_node*)inputs[0]->connection)->get_output(time)); + switch (op) { + case COMPARISON_Equal: + return fequal(value,in)?1.0f:0.0f; + break; + case COMPARISON_Not_equal: + return fequal(value,in)?0.0f:1.0f; + break; + case COMPARISON_Greater: + return fgreater(value,in)?1.0f:0.0f; + break; + case COMPARISON_Less: + return fless(value,in)?1.0f:0.0f; + break; + case COMPARISON_Greater_or_equal: + return fgreater_or_equal(value,in)?1.0f:0.0f; + break; + case COMPARISON_Less_or_equal: + return fless_or_equal(value,in)?1.0f:0.0f; + break; + } + } + } + return 0.0f; + } + int op; + float value; + }; +#define ARITHMETIC_plus 1 +#define ARITHMETIC_minus 2 +#define ARITHMETIC_multiply 3 +#define ARITHMETIC_divide 4 +#define ARITHMETIC_modulo 5 + class Arithmetic: public Signal_node { + public: + Arithmetic(){}; + Arithmetic(map<string,string> &settings) { + base_settings(settings); + value=find_setting(settings,"value",0.0f); + string _op=find_setting(settings,"operator","+"); + if (_op=="+") op=ARITHMETIC_plus; + if (_op=="-") op=ARITHMETIC_minus; + if (_op=="*") op=ARITHMETIC_multiply; + if (_op=="/") op=ARITHMETIC_divide; + if (_op=="%") op=ARITHMETIC_modulo; + } + void link_params() { + for (auto p:parameter_inputs){ + p->receiver=nullptr; + if (p->parameter=="value") p->receiver=&value; + } + }; + Arithmetic* clone(map<string,string> &_settings) { return new Arithmetic(_settings);}; + const float output(const Time_spec &time) { + if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml + if (inputs[0]->connection) { + float in= (((Signal_node*)inputs[0]->connection)->get_output(time)); + switch (op) { + case ARITHMETIC_plus: + return in+value; + break; + case ARITHMETIC_minus: + return in-value; + break; + case ARITHMETIC_multiply: + return in*value; + break; + case ARITHMETIC_divide: + return in/value; + break; + case ARITHMETIC_modulo: + return fmod(in,value); + break; + } + } + } + return 0.0f; + } + int op; + float value; + }; + class Signal_divide: public Signal_node { + public: + Signal_divide(){}; + Signal_divide(map<string,string> &settings) { + base_settings(settings); + divide_amount=ofToFloat(find_setting(settings,"amount")); + for (auto p:parameter_inputs){ + if (p->parameter=="amount") p->receiver=÷_amount; + } + }; + Signal_divide* clone(map<string,string> &_settings) { return new Signal_divide(_settings);}; + const float output(const Time_spec &time) { + if (inputs.size()) { //there should there be a way to specify number of inputs in the code rather than in xml + if (inputs[0]->connection) { + return (((Signal_node*)inputs[0]->connection)->get_output(time))/divide_amount; + } + } + return 0.0f; + } + float divide_amount; + }; + class Is_new_integer: public Signal_node { + public: + Is_new_integer(){}; + Is_new_integer(map<string,string> &settings) { + base_settings(settings); + }; + Is_new_integer* clone(map<string,string> &_settings) { return new Is_new_integer(_settings);}; + const float output(const Time_spec &time) { + if (inputs[0]->connection) { + float s1=(((Signal_node*)(inputs[0]->connection))->get_output(time)); + float s2=(((Signal_node*)(inputs[0]->connection))->get_output(time.lastframe())); + if (((int)s1)>((int)s2)) { + return 1.0f; + } + } + return 0.0f; + } + }; + class On_off: public Signal_node { + public: + On_off(){}; + On_off(map<string,string> &settings) { + base_settings(settings); + }; + On_off* clone(map<string,string> &_settings) { return new On_off(_settings);}; + const float output(const Time_spec &time) { + if (inputs[0]->connection) { + float s1=(((Signal_node*)(inputs[0]->connection))->get_output(time)); + if ((int)s1%2) return 1.0f; + } + return 0.0f; + } + }; + //pseudo random repeatable hash function + //http://create.stephan-brumme.com/fnv-hash/ + const uint32_t Prime = 0x01000193; // 16777619 + const uint32_t Seed = 0x811C9DC5; // 2166136261 + /// hash a byte + inline uint32_t fnv1a(unsigned char oneByte, uint32_t hash = Seed) + { + return (oneByte ^ hash) * Prime; + } + /// hash a short (two bytes) + inline uint32_t fnv1a(unsigned short twoBytes, uint32_t hash = Seed) + { + const unsigned char* ptr = (const unsigned char*) &twoBytes; + hash = fnv1a(*ptr++, hash); + return fnv1a(*ptr , hash); + } + /// hash a 32 bit integer (four bytes) + inline uint32_t fnv1a(uint32_t fourBytes, uint32_t hash = Seed) + { + const unsigned char* ptr = (const unsigned char*) &fourBytes; + hash = fnv1a(*ptr++, hash); + hash = fnv1a(*ptr++, hash); + hash = fnv1a(*ptr++, hash); + return fnv1a(*ptr , hash); + } + class Random: public Signal_node { + public: + Random(){}; + Random(map<string,string> &settings) { + base_settings(settings); + seed=(Seed+find_setting(settings,"seed",0)); + cerr<<"random:: seed "<<seed<<" ("<<Seed<<")"<<endl; + }; + Random* clone(map<string,string> &_settings) { return new Random(_settings);}; + const float output(const Time_spec &time) { + if (inputs.size()) { + if (inputs[0]->connection) { + + //hash the integer part and add the fractional part back on + float o=(((Signal_node*)inputs[0]->connection)->get_output(time)); + uint32_t m=(int)o; + return ((float)(fnv1a(m,seed)%((uint32_t)time.duration)))+(o-m); + } + } + return 0.0f; + } + uint32_t seed; + private: + }; + class Signal_output: public Signal_node { + public: + Signal_output(){}; + Signal_output(map<string,string> &settings) { + base_settings(settings); + }; + Signal_output* clone(map<string,string> &_settings) { return new Signal_output(_settings);}; + bool render(const float duration, const float framerate,string &xml_out); + const float output(const Time_spec &time) { + if (inputs[0]->connection) { + return ((Signal_node*)(inputs[0]->connection))->get_output(time); + } + else return 0.0f; + } + }; + class Testcard: public Image_node { + public: + Testcard(){image=nullptr;}; + Testcard(map<string,string> &settings) { + base_settings(settings); + image=new Image(); + }; + ~Testcard(){ if (image) delete image;}; + Testcard* clone(map<string,string> &_settings) { return new Testcard(_settings);}; + Image *output(const Frame_spec &frame){ + if (image->setup(frame.w,frame.h)) { + + } + //always create testcard + //float ws=(255.0f/frame.w); + float hs=(255.0f/frame.h); + for (int i=0;i<frame.h;i++){ + for (int j=0;j<frame.w;j++){ + image->RGBdata[(i*frame.w+j)*3]=(uint8_t)((int)((i+(frame.time*25.0f)*hs))%255); + image->RGBdata[((i*frame.w+j)*3)+1]=(uint8_t)((int)((j+(frame.time*100.0f)*hs))%255); + image->RGBdata[((i*frame.w+j)*3)+2]=(uint8_t)(0); + //image->Adata[i*frame.w+j]=(uint8_t)255; + //image->Zdata[i*frame.w+j]=(uint16_t)512; //1.0 in fixed point 8.8 bits + } + } + return image; + } + private: + Image *image; //is an image generator + }; + class Invert: public Image_node { + public: + Invert(){image=nullptr;}; + Invert(map<string,string> &settings) { + base_settings(settings); + image=new Image(); + }; + ~Invert(){ if (image) delete image;}; + Invert* clone(map<string,string> &_settings) { return new Invert(_settings);}; + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + if (inputs[0]->connection) { + if (fgreater_or_equal(1.0f,(((Signal_node*)inputs[0]->connection)->get_output((Time_spec)frame)))) { + Image *in=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); + if (in){ + image->setup(frame.w,frame.h); + for (int i=0;i<in->w*in->h*3;i++) { + image->RGBdata[i]=255-in->RGBdata[i]; + } + return image; + } + } + } + return (((Image_node*)image_inputs[0]->connection)->get_output(frame)); + } + + } + if (image_inputs[0]->connection) { + return image; + } + return nullptr; + } + private: + Image *image; //is an image generator + //bool invert; + }; + class Video_output: public Image_node { + public: + Video_output(){}; + Video_output(map<string,string> &settings) { + base_settings(settings); + }; + ~Video_output(){ }; + Image *output(const Frame_spec &frame){ + if (image_inputs[0]->connection) { + return ((Image_node*)(image_inputs[0]->connection))->get_output(frame); + } + else return nullptr; + }; + Video_output* clone(map<string,string> &_settings) { return new Video_output(_settings);}; + bool render(const float duration, const float framerate,const string &output_filename,const string &audio_filename,float& progress,int w,int h); + + private: + + }; + class Video_loader: public Image_node { + public: + Video_loader(){}; + Video_loader(map<string,string> &settings) { + base_settings(settings); + isLoaded=false; + }; + ~Video_loader(){}; + bool load(const string &filename); + Image *output(const Frame_spec &frame); + Video_loader* clone(map<string,string> &_settings) { return new Video_loader(_settings);}; + bool isLoaded; + private: + libav::decoder player; + Image image; + }; + class Video_cycler: public Image_node { + //cycles through video inputs in order + public: + Video_cycler(){}; + Video_cycler(map<string,string> &settings) { + base_settings(settings); + }; + ~Video_cycler(){}; + bool load(const string &filename); + Image *output(const Frame_spec &frame){ + int which_input=0; + if (inputs[0]->connection) { + which_input=((int)((Signal_node*)inputs[0]->connection)->get_output((Time_spec)frame))%image_inputs.size(); + } + if (image_inputs.size()) { + if (image_inputs[which_input]->connection){ + return (((Image_node*)image_inputs[which_input]->connection)->get_output(frame)); + } + } + return nullptr; + } + Video_cycler* clone(map<string,string> &_settings) { return new Video_cycler(_settings);}; + private: + }; + class Signal_colour: public Image_node { + //cycles through video inputs in order + public: + Signal_colour(){}; + Signal_colour(map<string,string> &settings) { + base_settings(settings); + string colours=find_setting(settings,"palette",""); + for (int i=0;i<colours.size()/6;i++){ + palette.push_back(Colour(colours.substr(i*6,6))); + } + for (auto i: palette) { + cerr << "Signal_colour found palette colour: "<<(int)i.r<<" "<<(int)i.g<<" "<<(int)i.b<<endl; + } + prevcol=-1; + }; + ~Signal_colour(){}; + Image *output(const Frame_spec &frame){ + if (palette.size()) { + if (inputs.size()) { + if (inputs[0]->connection){ + int col= ((int)(((Signal_node*)inputs[0]->connection)->get_output(frame)))%palette.size(); + if (col!=prevcol||image.w!=frame.w||image.h!=frame.h){ + image.setup(frame.w,frame.h); + for (int i=0;i<image.w*image.h;i++){ + image.RGBdata[i*3]=palette[col].r; + image.RGBdata[i*3+1]=palette[col].g; + image.RGBdata[i*3+2]=palette[col].b; + } + prevcol=col; + } + return ℑ + } + } + } + return nullptr; + } + Signal_colour* clone(map<string,string> &_settings) { return new Signal_colour(_settings);}; + private: + vector<Rotor::Colour> palette; + Image image; + int prevcol; + }; + class Signal_greyscale: public Image_node { + //Draws signal bars in greyscale + public: + Signal_greyscale(){}; + Signal_greyscale(map<string,string> &settings) { + base_settings(settings); + prevcol=-1; + }; + ~Signal_greyscale(){}; + Image *output(const Frame_spec &frame){ + if (inputs.size()) { + if (inputs[0]->connection){ + float sig= ((((Signal_node*)inputs[0]->connection)->get_output(frame))); + uint8_t col=255-((uint8_t)(sig*255.0f)); + if (col!=prevcol||image.w!=frame.w||image.h!=frame.h){ + image.setup(frame.w,frame.h); + for (int i=0;i<image.w*image.h*3;i++){ + image.RGBdata[i]=col; + } + prevcol=col; + } + return ℑ + } + } + return nullptr; + } + Signal_greyscale* clone(map<string,string> &_settings) { return new Signal_greyscale(_settings);}; + private: + Image image; + uint8_t prevcol; + }; + class Image_arithmetic: public Image_node { + //Draws signal bars in greyscale + public: + Image_arithmetic(){image=nullptr;}; + Image_arithmetic(map<string,string> &settings) { + base_settings(settings); + value=find_setting(settings,"value",0.0f); + string _op=find_setting(settings,"operator","+"); + if (_op=="+") op=ARITHMETIC_plus; + if (_op=="-") op=ARITHMETIC_minus; + if (_op=="*") op=ARITHMETIC_multiply; + if (_op=="/") op=ARITHMETIC_divide; + //if (_op=="%") op=ARITHMETIC_modulo; ??what would this even mean? + image=nullptr; + cerr<<"image_arithmetic: mode "<<op<<", value "<<value<<endl; + } + void link_params() { + for (auto p:parameter_inputs){ + if (p->parameter=="value") { + p->receiver=&value; + } + } + + }; + ~Image_arithmetic(){if (image) delete image;}; + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + if (image) delete image; //from the previous frame- this may not be ideal + //because operator* made a new image it should be deleted + Image *in=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); + switch (op) { + case ARITHMETIC_plus: + image=(*in)+value; + break; + case ARITHMETIC_minus: + image=(*in)-value; + break; + case ARITHMETIC_multiply: + image=(*in)*value; + break; + case ARITHMETIC_divide: + image=(*in)/value; + break; + } + return image; + } + } + return nullptr; + } + Image_arithmetic* clone(map<string,string> &_settings) { return new Image_arithmetic(_settings);}; + private: + Image *image; + float value; + int op; + }; + + class Luma_levels: public Image_node { + //applies LUT To RGB channels equally + public: + Luma_levels(){LUT=nullptr;image=nullptr;}; + Luma_levels(map<string,string> &settings) { + base_settings(settings); + levels_settings(settings); + image=new Image(); + } + void link_params() { + for (auto p:parameter_inputs){ + if (p->parameter=="black_in") p->receiver=&black_in; + if (p->parameter=="white_in") p->receiver=&white_in; + if (p->parameter=="gamma") p->receiver=γ + if (p->parameter=="black_out") p->receiver=&black_out; + if (p->parameter=="white_out") p->receiver=&white_out; + } + }; + ~Luma_levels(){if (LUT) {delete[] LUT;} if (image) delete image; }; + void levels_settings(map<string,string> &settings){ + black_in=find_setting(settings,"black_in",0.0f); + white_in=find_setting(settings,"white_in",1.0f); + gamma=find_setting(settings,"gamma",1.0f); + black_out=find_setting(settings,"black_out",0.0f); + white_out=find_setting(settings,"white_out",1.0f); + LUT=nullptr; + generate_LUT(); + } + void generate_LUT(){ //check this + if (LUT) delete[] LUT; + LUT=new unsigned char[256]; + float fltmax=(255.0f/256.0f); + for (int i=0;i<256;i++){ + LUT[i]=(unsigned char)(((pow(min(fltmax,max(0.0f,(((((float)i)/256.0f)-black_in)/(white_in-black_in)))),(1.0/gamma))*(white_out-black_out))+black_out)*256.0f); + } + } + void apply_LUT(const Image& in){ + apply_LUT(in,*image); + } + void apply_LUT(const Image& in,Image &out){ //facility to apply to other images for inherited classes + out.setup(in.w,in.h); + for (int i=0;i<out.w*out.h*3;i++){ + out.RGBdata[i]=LUT[in.RGBdata[i]]; + } + } + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + if (LUT) { + apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(frame))); + return image; + } + } + } + return nullptr; + } + Luma_levels* clone(map<string,string> &_settings) { return new Luma_levels(_settings);}; + protected: + unsigned char *LUT; + Image *image; + float black_in,white_in,gamma,black_out,white_out; + }; + class Echo_trails: public Luma_levels { + //draw trail frames additively that fade off over time + //the hard thing here is how to cache frames, if its done cleverly it could have no impact when + //used linearly + //Image needs to overload operator+ + //need a clever data structure to cache frames - maybe a map of Image pointers + + //we know the frames we want to overlay as offsets ie -25,-20,-15,-10,-5 + //do we keep 25 frames loaded in order to benefit? 25 PAL frames is 60MB so probably so + //OK so: + //make a new set of pointers + //identify if any of the new pointers can inherit old frames + //delete unneeded old frames + //load new frames + //do the calculations + + //new set of pointers? or track frames by absolute frame number? + //with relative pointers and switching frames, could use auto_ptr? + + //this cache mechanism should maybe be inheritable too? + + //it could be hugely beneficial to only do the LUT once? + //although maybe the way to do the fading is to have a LUT for each frame? + + //or is it actually best to use alpha keying after all! + public: + Echo_trails(){image=nullptr;}; + Echo_trails(map<string,string> &settings) { + base_settings(settings); + //duration=find_setting(settings,"duration",1.0f); + number=find_setting(settings,"number",1); + fadeto=find_setting(settings,"fadeto",1.0f); + levels_settings(settings); + image=nullptr; + lastframe=-1; + mode=find_setting(settings,"mode",0.0f); + } + void link_params() { + for (auto p:parameter_inputs){ + if (p->parameter=="black_in") p->receiver=&black_in; + if (p->parameter=="white_in") p->receiver=&white_in; + if (p->parameter=="gamma") p->receiver=γ + if (p->parameter=="black_out") p->receiver=&black_out; + if (p->parameter=="white_out") p->receiver=&white_out; + + //TODO: control an integer + if (p->parameter=="mode") p->receiver=&mode; + } + }; + //~Echo_trails(){if (image) {delete image;} }; + ~Echo_trails(){ + if (image) delete image; + for (auto i:images) {if (image) delete i.second;} + }; + Image *output(const Frame_spec &frame){ + //check if cache is valid + if (images.size()){ + if (frame.w!=image->w||frame.h!=image->h){ //or framerate changed? + //clear cache and start over + images.clear(); + lastframe=-1; + //calculate frame interval + //interval=(int)(((duration/number)*frame.framerate)+0.5); + //total=interval*number; + } + } + int thisframe=frame.frame(); + //iterate cache and throw out any obsolete frames + auto i = std::begin(images); + while (i != std::end(images)) { + // check if the image is in the range we need + if (thisframe-(*i).first>number||thisframe-(*i).first<0) { + delete (*i).second; + i = images.erase(i); + } + else + ++i; + } + //if frame has already been calculated just return it + if (thisframe==lastframe) { + return image; + } + else { + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + if (LUT) { + //need a better strategy here, should be able to get each image once + //copy incoming image **writable + if (image) image->free(); + image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); + images[thisframe]=new Image(frame.w,frame.h); + apply_LUT(*(image),*(images[thisframe])); + for (int i=1;i<number;i++){ + //check echo frame isn't at negative time + int absframe=thisframe-i; + if (absframe>-1){ + //check if image is in the cache + if (images.find(absframe)==images.end()){ + images[absframe]=new Image(frame.w,frame.h); + Frame_spec wanted=Frame_spec(absframe,frame.framerate,frame.duration,frame.w,frame.h); + apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(wanted)),*(images[absframe])); + } + //cerr<<"Rotor: about to apply image ("<<images[absframe].w<<"x"<<images[absframe].h<<")"<<endl; + if (fless(1.0f,fadeto)){ + float amount=((((float)number-i)/number)*(1.0f-fadeto))+(1.0f-fadeto); + Image *temp=*images[absframe]*amount; + if (mode<0.5) { + (*image)+=*temp; + } + else { + image->add_wrap(*temp); + } + delete temp; + } + else { + if (mode<0.5) (*image)+=*(images[absframe]); + else (*image)=image->add_wrap(*(images[absframe])); + } + } + } + //for (int i=0;i<frame.w*frame.h*3;i++){ + // image->RGBdata[i]=LUT[in->RGBdata[i]]; + //} + lastframe=thisframe; + return image; + } + } + } + } + return nullptr; + } + Echo_trails* clone(map<string,string> &_settings) { return new Echo_trails(_settings);}; + protected: + float duration,fadeto; + int number; + int interval,total,lastframe; //number of frames between displayed echoes + unordered_map<int,Image*> images; + float mode; //TODO make int, enum string parameter types + }; + #define BLEND_screen 1 + #define BLEND_multiply 2 + #define BLEND_blend 3 + #define BLEND_alpha 4 + #define BLEND_screen_wrap 5 + #define BLEND_multiply_wrap 6 + #define BLEND_xor 7 + class Blend: public Image_node { + public: + Blend(){image=nullptr;}; + Blend(map<string,string> &settings) { + base_settings(settings); + image=nullptr; + amount=find_setting(settings,"amount",1.0f); + string _mode=find_setting(settings,"mode","screen"); + if (_mode=="screen") mode=BLEND_screen; + if (_mode=="multiply") mode=BLEND_multiply; + if (_mode=="blend") mode=BLEND_blend; + if (_mode=="alpha") mode=BLEND_alpha; + if (_mode=="screen_wrap") mode=BLEND_screen_wrap; + if (_mode=="multiply_wrap") mode=BLEND_multiply_wrap; + if (_mode=="xor") mode=BLEND_xor; + }; + void link_params() { + for (auto p:parameter_inputs){ + if (p->parameter=="amount") p->receiver=&amount; + } + }; + ~Blend(){ if (image) delete image;}; + Blend* clone(map<string,string> &_settings) { return new Blend(_settings);}; + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + if (image_inputs.size()>1) { + if (image_inputs[1]->connection) { + //copy incoming image **writable + if (image) delete image; + image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); + switch(mode){ + case BLEND_screen: + (*image)+=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); + break; + case BLEND_multiply: + (*image)*=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); + break; + case BLEND_xor: + (*image)^=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); + break; + case BLEND_alpha: + (*image)=(*image).alpha_blend(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); + break; + case BLEND_screen_wrap: + (*image)=(*image).add_wrap(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); + break; + case BLEND_multiply_wrap: + (*image)=(*image).divide_wrap(*(((Image_node*)image_inputs[1]->connection)->get_output(frame))); + break; + case BLEND_blend: //has to be last because of initialser of *in? go figure + (*image)*=(1.0f-amount); + Image *in=(*(((Image_node*)image_inputs[1]->connection)->get_output(frame)))*amount; + (*image)+=(*in); + delete in; + break; + + } + return image; + } + } + //if there aren't 2 image inputs connected just return the first + return (((Image_node*)image_inputs[0]->connection)->get_output(frame)); + } + } + return nullptr; + } + private: + Image *image; //is an image generator + int mode; + float amount; //for blend + }; + #define MIRROR_horiz 1 + #define MIRROR_vert 2 + #define MIRROR_horizR 3 + #define MIRROR_vertR 4 + class Mirror: public Image_node { + public: + Mirror(){image=nullptr;}; + Mirror(map<string,string> &settings) { + base_settings(settings); + image=nullptr; + string _mode=find_setting(settings,"mode","horiz"); + if (_mode=="horiz") mode=MIRROR_horiz; + if (_mode=="vert") mode=MIRROR_vert; + if (_mode=="horizR") mode=MIRROR_horizR; + if (_mode=="vertR") mode=MIRROR_vertR; + }; + ~Mirror(){ if (image) delete image;}; + Mirror* clone(map<string,string> &_settings) { return new Mirror(_settings);}; + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + //copy incoming image **writable + if (image) delete image; + image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); + switch (mode) { + case MIRROR_horiz: + for (int i=0;i<image->w/2;i++){ + for (int j=0;j<image->h;j++){ + for (int k=0;k<3;k++){ + image->RGBdata[(((j*image->w)+((image->w/2)+i))*3)+k]=image->RGBdata[(((j*image->w)+((image->w/2)-i))*3)+k]; + } + } + } + break; + case MIRROR_vert: + for (int i=0;i<image->w;i++){ + for (int j=0;j<image->h/2;j++){ + for (int k=0;k<3;k++){ + image->RGBdata[((((image->h/2+j)*image->w)+i)*3)+k]=image->RGBdata[((((image->h/2-j)*image->w)+i)*3)+k]; + } + } + } + break; + case MIRROR_horizR: + for (int i=0;i<image->w/2;i++){ + for (int j=0;j<image->h;j++){ + for (int k=0;k<3;k++){ + image->RGBdata[(((j*image->w)+((image->w/2)-i))*3)+k]=image->RGBdata[(((j*image->w)+((image->w/2)+i))*3)+k]; + } + } + } + break; + case MIRROR_vertR: + for (int i=0;i<image->w;i++){ + for (int j=0;j<image->h/2;j++){ + for (int k=0;k<3;k++){ + image->RGBdata[((((image->h/2-j)*image->w)+i)*3)+k]=image->RGBdata[((((image->h/2+j)*image->w)+i)*3)+k]; + } + } + } + break; + } + return image; + } + } + return nullptr; + } + private: + Image *image; //is an image generator + int mode; + }; + class Monochrome: public Image_node { + public: + Monochrome(){}; + Monochrome(map<string,string> &settings) { + base_settings(settings); + }; + ~Monochrome(){ + }; + Monochrome* clone(map<string,string> &_settings) { return new Monochrome(_settings);}; + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + Image *other=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); + image.setup(other->w,other->h); + for (int i=0;i<image.w;i++){ + for (int j=0;j<image.h;j++){ + uint8_t luma=0; + for (int l=0;l<3;l++) luma+=pixels.mono_weights[l][other->RGBdata[(((j*image.w)+i)*3)+l]]; + for (int k=0;k<3;k++) image.RGBdata[(((j*image.w)+i)*3)+k]=luma; + } + } + return ℑ + } + } + return nullptr; + } + private: + Image image; + }; + class Transform: public Image_node { + //what is the best coordinate system to use? + //origin: corner or centre + //units: pixel or fractional + //aspect: scaled or homogenous + public: + Transform(){}; + Transform(map<string,string> &settings) { + base_settings(settings); + tX=find_setting(settings,"transformX",0.0f); + tY=find_setting(settings,"transformY",0.0f); + oX=find_setting(settings,"originX",0.5f); + oY=find_setting(settings,"originX",0.5f); + r=find_setting(settings,"rotation",0.0f); + s=find_setting(settings,"scale",1.0f); + }; + ~Transform(){ + }; + void link_params() { + for (auto p:parameter_inputs){ + if (p->parameter=="scale") p->receiver=&s; + if (p->parameter=="rotation") p->receiver=&r; + if (p->parameter=="transformX") p->receiver=&tX; + if (p->parameter=="transformY") p->receiver=&tY; + if (p->parameter=="originX") p->receiver=&oX; + if (p->parameter=="originY") p->receiver=&oY; + } + }; + Transform* clone(map<string,string> &_settings) { return new Transform(_settings);}; + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + Image *other=(((Image_node*)image_inputs[0]->connection)->get_output(frame)); + if (other) { + image.setup(other->w,other->h); + //do opencv transform + cv::Point2f srcTri[3], dstTri[3]; + cv::Mat rot_mat(2,3,CV_32FC1); + cv::Mat trans_mat(2,3,CV_32FC1); + + + Image inter; + inter.setup(other->w,other->h); + // Compute matrix by creating triangle and transforming + //is there a better way - combine the 2? Just a bit of geometry + srcTri[0].x=0; + srcTri[0].y=0; + srcTri[1].x=other->w-1; + srcTri[1].y=0; + srcTri[2].x=0; + srcTri[2].y=other->h-1; + for (int i=0;i<3;i++){ + dstTri[i].x=srcTri[i].x+(tX*other->w); + dstTri[i].y=srcTri[i].y+(tY*other->h); + } + trans_mat=getAffineTransform( srcTri, dstTri ); + warpAffine( other->rgb, inter.rgb, trans_mat, inter.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP); + + + // Compute rotation matrix + // + cv::Point centre = cv::Point( oX*other->w, oY*other->h ); + + rot_mat = getRotationMatrix2D( centre, r, s ); + // Do the transformation + // + warpAffine( inter.rgb, image.rgb, rot_mat, image.rgb.size(), cv::INTER_LINEAR, cv::BORDER_WRAP); + //BORDER_WRAP + + //INTER_NEAREST - a nearest-neighbor interpolation + //INTER_LINEAR - a bilinear interpolation (used by default) + //INTER_AREA - resampling using pixel area relation. It may be a preferred method for image decimation, as it gives moire’-free results. But when the image is zoomed, it is similar to the INTER_NEAREST method. + //INTER_CUBIC - a bicubic interpolation over 4x4 pixel neighborhood + //INTER_LANCZOS4 - a Lanczos interpolation over 8x8 pixel neighborhood + + return ℑ + } + } + } + return nullptr; + } + private: + Image image; + float tX,tY,oX,oY,r,s; + //todo - quality settings + }; + class Alpha_merge: public Image_node { + public: + Alpha_merge(){image=nullptr;}; + Alpha_merge(map<string,string> &settings) { + base_settings(settings); + }; + ~Alpha_merge(){ if (image) delete image;}; + Alpha_merge* clone(map<string,string> &_settings) { return new Alpha_merge(_settings);}; + Image *output(const Frame_spec &frame){ + if (image_inputs.size()) { + if (image_inputs[0]->connection){ + //copy incoming image **writable + if (image) delete image; + image=(((Image_node*)image_inputs[0]->connection)->get_output(frame))->clone(); + if (image_inputs.size()>1) { + if (image_inputs[1]->connection) { + image->alpha_merge(*((Image_node*)image_inputs[1]->connection)->get_output(frame)); + } + } + //if there aren't 2 image inputs connected just return the first + return image; + } + } + return nullptr; + } + private: + Image *image; //is an image generator + }; + //------------------------------------------------------------------- + class Node_factory{ + public: + Node_factory(); + ~Node_factory(){ + for (auto t:type_map) delete t.second; + } + void add_type(string type,Node* proto){ + type_map[type]=proto; + }; + Node *create(map<string,string> &settings){ + if (settings.find("type")!=settings.end()) { + if (type_map.find(settings["type"])!=type_map.end()) { + return type_map[settings["type"]]->clone(settings); + } + } + return NULL; + }; + private: + unordered_map<string,Node*> type_map; + }; + class Graph{ + public: + Graph(){duration=20.0f;loaded = false;outW=640;outH=360;}; + Graph(const string& _uid,const string& _desc){init(_uid,_desc);}; + void init(const string& _uid,const string& _desc){ uid=_uid;description=_desc;duration=20.0f;}; + string uid; //every version of a graph has a UUID, no particular need to actually read its data(?) + //?? is it faster than using strings?? + string description; + std::unordered_map<string,Node*> nodes; + vector<Node*> find_nodes(const string &type); //could be a way of finding a set based on capabilities? + Node* find_node(const string &type); + bool signal_render(string &signal_xml,const float framerate); + bool video_render(const string &output_filename,const string &audio_filename,const float framerate,float& progress); + int load(Poco::UUID uid); + bool load(string data); + bool loadFile(string &filename); + bool parseXml(); + bool set_resolution(int w,int h); + UUID save(); //save to DB, returns UUID of saved graph + bool loaded; + float duration; + const string toString(); + xmlIO xml; + private: + Node_factory factory; + int outW,outH; + }; + class Audio_thumbnailer: public Base_audio_processor { + public: + Audio_thumbnailer(){ + height=128; + width=512; //fit + data=new uint8_t[height*width]; + memset(data,0,height*width); + }; + ~Audio_thumbnailer(){ + delete[] data; + }; + Audio_thumbnailer* clone(map<string,string> &_settings) { return new Audio_thumbnailer();}; + bool init(int _channels,int _bits,int _samples,int _rate); + void cleanup(){}; + int process_frame(uint8_t *data,int samples_in_frame); + string print(); + uint8_t *data; + int height,width,samples_per_column; + int column,out_sample,sample,samples; + int offset; + double scale,accum; + }; + class Render_context: public Poco::Task { //Poco task object + //manages a 'patchbay' + //high level interfaces for the wizard + //and low level interface onto the graph + public: + Render_context(const std::string& name): Task(name) { + audio_thumb=new Audio_thumbnailer(); + state=IDLE; + output_framerate=25.0f; + audio_loaded=false; + + xmlIO xml; + if(xml.loadFile("settings.xml") ){ + graph_dir=xml.getAttribute("Rotor","graph_dir","",0); + media_dir=xml.getAttribute("Rotor","media_dir","",0); + output_dir=xml.getAttribute("Rotor","output_dir","",0); + } + else cerr<<"Rotor: settings.xml not found, using defaults"<<endl; + }; + ~Render_context(){delete audio_thumb;}; + void runTask(); + void add_queue(int item); + Command_response session_command(const std::vector<std::string>& command); + void session_command(const std::vector<std::string>& command,xmlIO& XML,HTTPResponse::HTTPStatus& status); + Render_status get_status(); + void cancel(); //interrupt locking process + int make_preview(int nodeID, float time); //starts a frame preview - returns status code - how to retrieve? + bool load_audio(const string &filename,vector<Base_audio_processor*> processors); + bool _load_audio(const string &filename,vector<Base_audio_processor*> processors); + Render_requirements get_requirements(); + bool load_video(const string &nodeID,const string &filename);//can be performance or clip + private: + int state; + float progress; //for a locking process: audio analysis or rendering + //thread only does one thing at once + std::deque<int> work_queue; + Poco::Mutex mutex; //lock for access from parent thread + std::string audio_filename; + std::string output_filename; + std::string graph_dir; + std::string media_dir; + std::string output_dir; + + Audio_thumbnailer *audio_thumb; + Graph graph; + Node_factory factory; + float output_framerate; + bool audio_loaded; + + }; +} + +/* +coding style +Types begin with capitals 'New_type' +variables/ instances use lower case with underscore as a seperator +*/ +#endif
\ No newline at end of file diff --git a/rotord/src/rotord.cpp b/rotord/src/rotord.cpp new file mode 100755 index 0000000..6f2d765 --- /dev/null +++ b/rotord/src/rotord.cpp @@ -0,0 +1,230 @@ +#include "rotord.h" + +RenderContextHandler::RenderContextHandler(const std::string _content,const HTTPServerResponse::HTTPStatus _status){ + content=_content; + status=_status; +} + + +void RenderContextHandler::handleRequest(HTTPServerRequest& request,HTTPServerResponse& response) { + + response.setChunkedTransferEncoding(true); + response.setContentType("text/html"); + response.setStatus(status); + + std::ostream& ostr = response.send(); + + ostr << "<?xml version='1.0' encoding='ISO-8859-1'?>\n"; //this is the mysterious extra header + ostr << content; + +} + + +HTTPRequestHandler* RotorRequestHandlerFactory::createRequestHandler(const HTTPServerRequest& request){ + + + Poco::URI theuri=Poco::URI(request.getURI()); + std::vector <std::string> command; + theuri.getPathSegments(command); + + Logger& logger = Logger::get("Rotor"); + logger.information(request.clientAddress().toString()+" "+request.getMethod()); + + HTTPResponse::HTTPStatus status=HTTPResponse::HTTP_BAD_REQUEST; //by default + + std::string body; + std::ostringstream os; + os<<request.stream().rdbuf(); + body=os.str(); + + xmlIO XML; //xml object handles the servers responses + XML.addTag("rotor"); + + //can we create a tinyxml object here and pass a pointer to it to the render context? + //can tinyxml output to a string? is there any reason to use poco instead? + + if (command.size()) { + if (command[0]=="new") { + XML.pushTag("rotor"); + if (request.getMethod()=="GET") { + string sID=idGen.createOne().toString(); //create() seems to cause problems + //Creates a new time-based UUID, using the MAC address of one of the system's ethernet adapters. + //Throws a SystemException if no MAC address can be obtained. + // + //seems to hang, to me + logger.information("starting thread "+sID); + manager.start(new Rotor::Render_context(sID)); + //XML.addTag("sID"); + XML.addValue("sID",sID); + status=HTTPResponse::HTTP_OK; + } + if (request.getMethod()=="PUT") { //unofficial manual thread name + if (body.size()) { + string sID=body; + bool found=false; + for (auto& task: manager.taskList()) { + if(task->name()==sID) { + logger.error("ERROR: tried to create thread with existing name "+sID); + XML.addValue("error","Render context /"+sID+"/ exists already"); + found=true; + } + } + if (!found){ + logger.information("starting thread "+sID); + manager.start(new Rotor::Render_context(sID)); + XML.addValue("sID",sID); + status=HTTPResponse::HTTP_OK; + } + } + } + } + else if (command[0]=="list") { + XML.pushTag("rotor"); + if (request.getMethod()=="GET") { + logger.information("sending tasklist"); + //std::list < Poco::AutoPtr < Poco::Task > >::iterator it; + //it=manager.taskList().begin(); + //for (it=manager.taskList().begin();it !=manager.taskList().end();++it) { + //content+="<sID>"+(*it)->name()+"</sID>\n"; + //} + + //massive problems making an iterator for the tasklist, the above crashes + //solution: auto type range-based for-loop + //this is c++11 specific but works + + for (auto& task: manager.taskList()) { //c++11 + XML.addValue("sID",task->name()); + } + status=HTTPResponse::HTTP_OK; + } + } + else if (command[0]=="exit") { + logger.information("exiting"); + exit(0); + } + else { + bool found=false; + for (auto& task: manager.taskList()) { //c++11 + if(task->name()==command[0]) { + //valid session command + found=true; + XML.addAttribute("rotor","context",task->name(),0); + XML.pushTag("rotor"); + if (command.size()==1) { + //just invoking sID + if (request.getMethod()=="DELETE") { + task->cancel(); + status=HTTPResponse::HTTP_OK; + logger.information("deleted context "+command[0]); + XML.addValue("status","context deleted successfully"); + } + else { + logger.error("ERROR: Render context invoked with no command: "+command[0]); + XML.addValue("error","Render context invoked with no command"); + } + } + else { //session modifier command- to be passed to render context + //some commands need to return error codes + //ie where the audio file isn't found + //on the other hand, some commands need to know state of the renderer? + + + vector<string> sc; //method,id,command1,{command2,}{body} + sc.push_back(request.getMethod()); + for (auto& i: command){ + sc.push_back(i); + } + sc.push_back(body); + + ((Poco::AutoPtr<Rotor::Render_context>)task)->session_command(sc,XML,status); + + } + } + } + if (!found) { + status=HTTPResponse::HTTP_NOT_FOUND; + logger.error("ERROR: context not found: "+command[0]); + XML.pushTag("rotor"); + XML.addValue("error","Render context not found"); + } + } + } + else { + logger.error("ERROR: Empty request"); + XML.addValue("error","Empty request"); + } + string content; + XML.copyXmlToString(content); + return new RenderContextHandler(content, status); +} + + +RotorServer::RotorServer(): _helpRequested(false) +{ +} + +RotorServer::~RotorServer() +{ +} + +void RotorServer::initialize(Application& self){ + loadConfiguration(); + ServerApplication::initialize(self); +} + +void RotorServer::uninitialize(){ + ServerApplication::uninitialize(); +} + +void RotorServer::defineOptions(OptionSet& options) { + ServerApplication::defineOptions(options); + options.addOption( + Option("help", "h", "display argument help information") + .required(false) + .repeatable(false) + .callback(OptionCallback<RotorServer>(this, &RotorServer::handleHelp) + ) + ); +} + +void RotorServer::handleHelp(const std::string& name, const std::string& value){ + HelpFormatter helpFormatter(options()); + helpFormatter.setCommand(commandName()); + helpFormatter.setUsage("OPTIONS"); + helpFormatter.setHeader( + "Rotor"); + helpFormatter.format(std::cout); + stopOptionsProcessing(); + _helpRequested = true; +} + +int RotorServer::main(const std::vector<std::string>& args){ + if (!_helpRequested) { + + unsigned short port; + + Logger& logger = Logger::get("Rotor"); + + xmlIO xml; + if(xml.loadFile("settings.xml") ){ + port=xml.getAttribute("Rotor","port",9000,0); + } + else logger.information("settings.xml not found, using defaults"); + + logger.information("rotord running on port "+ofToString(port)); + + port = (unsigned short) config().getInt("port", port); //override from command line + + std::string format(config().getString("format", DateTimeFormat::SORTABLE_FORMAT)); + + + + ServerSocket svs(port); + HTTPServer srv(new RotorRequestHandlerFactory(),svs, new HTTPServerParams); + srv.start(); + waitForTerminationRequest(); + srv.stop(); + } + return Application::EXIT_OK; +} +
\ No newline at end of file diff --git a/rotord/src/rotord.h b/rotord/src/rotord.h new file mode 100755 index 0000000..7656c28 --- /dev/null +++ b/rotord/src/rotord.h @@ -0,0 +1,136 @@ +#include "Poco/Net/HTTPServer.h" +#include "Poco/Net/HTTPRequestHandler.h" +#include "Poco/Net/HTTPRequestHandlerFactory.h" +#include "Poco/Net/HTTPServerParams.h" +#include "Poco/Net/HTTPServerRequest.h" +#include "Poco/Net/HTTPServerResponse.h" +#include "Poco/Net/HTTPServerParams.h" +#include "Poco/Net/ServerSocket.h" +#include "Poco/Timestamp.h" +#include "Poco/DateTimeFormatter.h" +#include "Poco/DateTimeFormat.h" +#include "Poco/Exception.h" +#include "Poco/ThreadPool.h" +#include "Poco/Task.h" +#include "Poco/NotificationCenter.h" +#include "Poco/TaskManager.h" +#include "Poco/Util/ServerApplication.h" +#include "Poco/Util/Option.h" +#include "Poco/Util/OptionSet.h" +#include "Poco/Util/HelpFormatter.h" +#include "Poco/FileStream.h" +#include "Poco/StreamCopier.h" +#include "Poco/Net/HTTPStreamFactory.h" +#include <iostream> + +#include <sstream> +#include "Poco/URI.h" +#include "Poco/Channel.h" +#include "Poco/SplitterChannel.h" +#include "Poco/ConsoleChannel.h" +#include "Poco/FormattingChannel.h" +#include "Poco/FileChannel.h" +#include "Poco/Message.h" +#include "Poco/Formatter.h" +#include "Poco/PatternFormatter.h" +#include "Poco/AutoPtr.h" + +using Poco::Net::ServerSocket; +using Poco::Net::HTTPResponse; +using Poco::Net::HTTPRequestHandler; +using Poco::Net::HTTPRequestHandlerFactory; +using Poco::Net::HTTPServer; +using Poco::Net::HTTPServerRequest; +using Poco::Net::HTTPServerResponse; +using Poco::Net::HTTPServerParams; +using Poco::Timestamp; +using Poco::DateTimeFormatter; +using Poco::DateTimeFormat; +using Poco::ThreadPool; +using Poco::TaskManager; +using Poco::Util::ServerApplication; +using Poco::Util::Application; +using Poco::Util::Option; +using Poco::Util::OptionSet; +using Poco::Util::OptionCallback; +using Poco::Util::HelpFormatter; +using Poco::Net::HTTPStreamFactory; +using Poco::Logger; +using Poco::Channel; +using Poco::SplitterChannel; +using Poco::ConsoleChannel; +using Poco::FormattingChannel; +using Poco::Formatter; +using Poco::PatternFormatter; +using Poco::FileChannel; +using Poco::Message; +using Poco::AutoPtr; + + +#include "rotor.h" + + +class RenderContextHandler: public HTTPRequestHandler +{ + public: + RenderContextHandler(string _content,HTTPServerResponse::HTTPStatus _status); + void handleRequest(HTTPServerRequest& request,HTTPServerResponse& response); + private: + std::string content; + HTTPServerResponse::HTTPStatus status; +}; + +class RotorRequestHandlerFactory: public HTTPRequestHandlerFactory +{ + public: + HTTPRequestHandler* createRequestHandler(const HTTPServerRequest& request); + private: + + std::unordered_map<std::string,Rotor::Render_context> context; + Poco::UUIDGenerator idGen; + Poco::TaskManager manager; +}; + +class RotorServer: public Poco::Util::ServerApplication +{ + public: + RotorServer(); + ~RotorServer(); + protected: + void initialize(Application& self); + void uninitialize(); + void defineOptions(OptionSet& options); + void handleHelp(const std::string& name, const std::string& value); + int main(const std::vector<std::string>& args); + private: + bool _helpRequested; +}; + +RotorServer app; //needs to be global for logger + +int main(int argc, char** argv) +{ + AutoPtr<SplitterChannel> splitterChannel(new SplitterChannel()); + AutoPtr<Channel> consoleChannel(new ConsoleChannel()); + AutoPtr<Channel> fileChannel(new FileChannel("Rotord.log")); + AutoPtr<FileChannel> rotatedFileChannel(new FileChannel("Rotord_R.log")); + + rotatedFileChannel->setProperty("rotation", "100"); + rotatedFileChannel->setProperty("archive", "timestamp"); + + splitterChannel->addChannel(consoleChannel); + splitterChannel->addChannel(fileChannel); + splitterChannel->addChannel(rotatedFileChannel); + + AutoPtr<Formatter> formatter(new PatternFormatter("%d-%m-%Y %H:%M:%S %s: %t")); + AutoPtr<Channel> formattingChannel(new FormattingChannel(formatter, splitterChannel)); + + Logger& sLog = Logger::create("Rotor", formattingChannel, Message::PRIO_TRACE); + + Logger& logger = Logger::get("Rotor"); + logger.information("starting rendering daemon"); + + HTTPStreamFactory::registerFactory(); + + return app.run(argc, argv); +} diff --git a/rotord/src/system.h b/rotord/src/system.h new file mode 100644 index 0000000..15aa8c1 --- /dev/null +++ b/rotord/src/system.h @@ -0,0 +1,75 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + +#ifndef _SYSTEM_H_ +#define _SYSTEM_H_ + +#ifdef _WIN32 + +#include <windows.h> + +#define DLOPEN(a,b) LoadLibrary((a).c_str()) +#define DLSYM(a,b) GetProcAddress((HINSTANCE)(a),(b)) +#define DLCLOSE(a) FreeLibrary((HINSTANCE)(a)) +#define DLERROR() "" + +#define PLUGIN_SUFFIX "dll" + +#else + +#include <dlfcn.h> + +#define DLOPEN(a,b) dlopen((a).c_str(),(b)) +#define DLSYM(a,b) dlsym((a),(b)) +#define DLCLOSE(a) dlclose((a)) +#define DLERROR() dlerror() + +#ifdef __APPLE__ + +#define PLUGIN_SUFFIX "dylib" +#define HAVE_OPENDIR 1 + +#else + +#define PLUGIN_SUFFIX "so" +#define HAVE_OPENDIR 1 + +#endif /* __APPLE__ */ + +#endif /* ! _WIN32 */ + +#endif + diff --git a/rotord/src/tinyxml.cpp b/rotord/src/tinyxml.cpp new file mode 100755 index 0000000..5de21f6 --- /dev/null +++ b/rotord/src/tinyxml.cpp @@ -0,0 +1,1888 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include <ctype.h> + +#ifdef TIXML_USE_STL +#include <sstream> +#include <iostream> +#endif + +#include "tinyxml.h" + + +bool TiXmlBase::condenseWhiteSpace = true; + +// Microsoft compiler security +FILE* TiXmlFOpen( const char* filename, const char* mode ) +{ + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + FILE* fp = 0; + errno_t err = fopen_s( &fp, filename, mode ); + if ( !err && fp ) + return fp; + return 0; + #else + return fopen( filename, mode ); + #endif +} + +void TiXmlBase::EncodeString( const TIXML_STRING& str, TIXML_STRING* outString ) +{ + int i=0; + + while( i<(int)str.length() ) + { + unsigned char c = (unsigned char) str[i]; + + if ( c == '&' + && i < ( (int)str.length() - 2 ) + && str[i+1] == '#' + && str[i+2] == 'x' ) + { + // Hexadecimal character reference. + // Pass through unchanged. + // © -- copyright symbol, for example. + // + // The -1 is a bug fix from Rob Laveaux. It keeps + // an overflow from happening if there is no ';'. + // There are actually 2 ways to exit this loop - + // while fails (error case) and break (semicolon found). + // However, there is no mechanism (currently) for + // this function to return an error. + while ( i<(int)str.length()-1 ) + { + outString->append( str.c_str() + i, 1 ); + ++i; + if ( str[i] == ';' ) + break; + } + } + else if ( c == '&' ) + { + outString->append( entity[0].str, entity[0].strLength ); + ++i; + } + else if ( c == '<' ) + { + outString->append( entity[1].str, entity[1].strLength ); + ++i; + } + else if ( c == '>' ) + { + outString->append( entity[2].str, entity[2].strLength ); + ++i; + } + else if ( c == '\"' ) + { + outString->append( entity[3].str, entity[3].strLength ); + ++i; + } + else if ( c == '\'' ) + { + outString->append( entity[4].str, entity[4].strLength ); + ++i; + } + else if ( c < 32 ) + { + // Easy pass at non-alpha/numeric/symbol + // Below 32 is symbolic. + char buf[ 32 ]; + + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "&#x%02X;", (unsigned) ( c & 0xff ) ); + #else + sprintf( buf, "&#x%02X;", (unsigned) ( c & 0xff ) ); + #endif + + //*ME: warning C4267: convert 'size_t' to 'int' + //*ME: Int-Cast to make compiler happy ... + outString->append( buf, (int)strlen( buf ) ); + ++i; + } + else + { + //char realc = (char) c; + //outString->append( &realc, 1 ); + *outString += (char) c; // somewhat more efficient function call. + ++i; + } + } +} + + +TiXmlNode::TiXmlNode( NodeType _type ) : TiXmlBase() +{ + parent = 0; + type = _type; + firstChild = 0; + lastChild = 0; + prev = 0; + next = 0; +} + + +TiXmlNode::~TiXmlNode() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } +} + + +void TiXmlNode::CopyTo( TiXmlNode* target ) const +{ + target->SetValue (value.c_str() ); + target->userData = userData; +} + + +void TiXmlNode::Clear() +{ + TiXmlNode* node = firstChild; + TiXmlNode* temp = 0; + + while ( node ) + { + temp = node; + node = node->next; + delete temp; + } + + firstChild = 0; + lastChild = 0; +} + + +TiXmlNode* TiXmlNode::LinkEndChild( TiXmlNode* node ) +{ + assert( node->parent == 0 || node->parent == this ); + assert( node->GetDocument() == 0 || node->GetDocument() == this->GetDocument() ); + + if ( node->Type() == TiXmlNode::DOCUMENT ) + { + delete node; + if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + node->parent = this; + + node->prev = lastChild; + node->next = 0; + + if ( lastChild ) + lastChild->next = node; + else + firstChild = node; // it was an empty list. + + lastChild = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertEndChild( const TiXmlNode& addThis ) +{ + if ( addThis.Type() == TiXmlNode::DOCUMENT ) + { + if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + + return LinkEndChild( node ); +} + + +TiXmlNode* TiXmlNode::InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ) +{ + if ( !beforeThis || beforeThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::DOCUMENT ) + { + if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->next = beforeThis; + node->prev = beforeThis->prev; + if ( beforeThis->prev ) + { + beforeThis->prev->next = node; + } + else + { + assert( firstChild == beforeThis ); + firstChild = node; + } + beforeThis->prev = node; + return node; +} + + +TiXmlNode* TiXmlNode::InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ) +{ + if ( !afterThis || afterThis->parent != this ) { + return 0; + } + if ( addThis.Type() == TiXmlNode::DOCUMENT ) + { + if ( GetDocument() ) GetDocument()->SetError( TIXML_ERROR_DOCUMENT_TOP_ONLY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + TiXmlNode* node = addThis.Clone(); + if ( !node ) + return 0; + node->parent = this; + + node->prev = afterThis; + node->next = afterThis->next; + if ( afterThis->next ) + { + afterThis->next->prev = node; + } + else + { + assert( lastChild == afterThis ); + lastChild = node; + } + afterThis->next = node; + return node; +} + + +TiXmlNode* TiXmlNode::ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ) +{ + if ( replaceThis->parent != this ) + return 0; + + TiXmlNode* node = withThis.Clone(); + if ( !node ) + return 0; + + node->next = replaceThis->next; + node->prev = replaceThis->prev; + + if ( replaceThis->next ) + replaceThis->next->prev = node; + else + lastChild = node; + + if ( replaceThis->prev ) + replaceThis->prev->next = node; + else + firstChild = node; + + delete replaceThis; + node->parent = this; + return node; +} + + +bool TiXmlNode::RemoveChild( TiXmlNode* removeThis ) +{ + if ( removeThis->parent != this ) + { + assert( 0 ); + return false; + } + + if ( removeThis->next ) + removeThis->next->prev = removeThis->prev; + else + lastChild = removeThis->prev; + + if ( removeThis->prev ) + removeThis->prev->next = removeThis->next; + else + firstChild = removeThis->next; + + delete removeThis; + return true; +} + +const TiXmlNode* TiXmlNode::FirstChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = firstChild; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::LastChild( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = lastChild; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild(); + } + else + { + assert( previous->parent == this ); + return previous->NextSibling(); + } +} + + +const TiXmlNode* TiXmlNode::IterateChildren( const char * val, const TiXmlNode* previous ) const +{ + if ( !previous ) + { + return FirstChild( val ); + } + else + { + assert( previous->parent == this ); + return previous->NextSibling( val ); + } +} + + +const TiXmlNode* TiXmlNode::NextSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = next; node; node = node->next ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +const TiXmlNode* TiXmlNode::PreviousSibling( const char * _value ) const +{ + const TiXmlNode* node; + for ( node = prev; node; node = node->prev ) + { + if ( strcmp( node->Value(), _value ) == 0 ) + return node; + } + return 0; +} + + +void TiXmlElement::RemoveAttribute( const char * name ) +{ + #ifdef TIXML_USE_STL + TIXML_STRING str( name ); + TiXmlAttribute* node = attributeSet.Find( str ); + #else + TiXmlAttribute* node = attributeSet.Find( name ); + #endif + if ( node ) + { + attributeSet.Remove( node ); + delete node; + } +} + +const TiXmlElement* TiXmlNode::FirstChildElement() const +{ + const TiXmlNode* node; + + for ( node = FirstChild(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::FirstChildElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = FirstChild( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement() const +{ + const TiXmlNode* node; + + for ( node = NextSibling(); + node; + node = node->NextSibling() ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlElement* TiXmlNode::NextSiblingElement( const char * _value ) const +{ + const TiXmlNode* node; + + for ( node = NextSibling( _value ); + node; + node = node->NextSibling( _value ) ) + { + if ( node->ToElement() ) + return node->ToElement(); + } + return 0; +} + + +const TiXmlDocument* TiXmlNode::GetDocument() const +{ + const TiXmlNode* node; + + for( node = this; node; node = node->parent ) + { + if ( node->ToDocument() ) + return node->ToDocument(); + } + return 0; +} + + +TiXmlElement::TiXmlElement (const char * _value) + : TiXmlNode( TiXmlNode::ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} + + +#ifdef TIXML_USE_STL +TiXmlElement::TiXmlElement( const std::string& _value ) + : TiXmlNode( TiXmlNode::ELEMENT ) +{ + firstChild = lastChild = 0; + value = _value; +} +#endif + + +TiXmlElement::TiXmlElement( const TiXmlElement& copy) + : TiXmlNode( TiXmlNode::ELEMENT ) +{ + firstChild = lastChild = 0; + copy.CopyTo( this ); +} + + +void TiXmlElement::operator=( const TiXmlElement& base ) +{ + ClearThis(); + base.CopyTo( this ); +} + + +TiXmlElement::~TiXmlElement() +{ + ClearThis(); +} + + +void TiXmlElement::ClearThis() +{ + Clear(); + while( attributeSet.First() ) + { + TiXmlAttribute* node = attributeSet.First(); + attributeSet.Remove( node ); + delete node; + } +} + + +const char* TiXmlElement::Attribute( const char* name ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( node ) + return node->Value(); + return 0; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( node ) + return &node->ValueStr(); + return 0; +} +#endif + + +const char* TiXmlElement::Attribute( const char* name, int* i ) const +{ + const char* s = Attribute( name ); + if ( i ) + { + if ( s ) { + *i = atoi( s ); + } + else { + *i = 0; + } + } + return s; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name, int* i ) const +{ + const std::string* s = Attribute( name ); + if ( i ) + { + if ( s ) { + *i = atoi( s->c_str() ); + } + else { + *i = 0; + } + } + return s; +} +#endif + + +const char* TiXmlElement::Attribute( const char* name, double* d ) const +{ + const char* s = Attribute( name ); + if ( d ) + { + if ( s ) { + *d = atof( s ); + } + else { + *d = 0; + } + } + return s; +} + + +#ifdef TIXML_USE_STL +const std::string* TiXmlElement::Attribute( const std::string& name, double* d ) const +{ + const std::string* s = Attribute( name ); + if ( d ) + { + if ( s ) { + *d = atof( s->c_str() ); + } + else { + *d = 0; + } + } + return s; +} +#endif + + +int TiXmlElement::QueryIntAttribute( const char* name, int* ival ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + return node->QueryIntValue( ival ); +} + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryIntAttribute( const std::string& name, int* ival ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + return node->QueryIntValue( ival ); +} +#endif + + +int TiXmlElement::QueryDoubleAttribute( const char* name, double* dval ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + return node->QueryDoubleValue( dval ); +} + + +#ifdef TIXML_USE_STL +int TiXmlElement::QueryDoubleAttribute( const std::string& name, double* dval ) const +{ + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + return node->QueryDoubleValue( dval ); +} +#endif + + +void TiXmlElement::SetAttribute( const char * name, int val ) +{ + char buf[64]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "%d", val ); + #else + sprintf( buf, "%d", val ); + #endif + SetAttribute( name, buf ); +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetAttribute( const std::string& name, int val ) +{ + std::ostringstream oss; + oss << val; + SetAttribute( name, oss.str() ); +} +#endif + + +void TiXmlElement::SetDoubleAttribute( const char * name, double val ) +{ + char buf[256]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "%f", val ); + #else + sprintf( buf, "%f", val ); + #endif + SetAttribute( name, buf ); +} + + +void TiXmlElement::SetAttribute( const char * cname, const char * cvalue ) +{ + #ifdef TIXML_USE_STL + TIXML_STRING _name( cname ); + TIXML_STRING _value( cvalue ); + #else + const char* _name = cname; + const char* _value = cvalue; + #endif + + TiXmlAttribute* node = attributeSet.Find( _name ); + if ( node ) + { + node->SetValue( _value ); + return; + } + + TiXmlAttribute* attrib = new TiXmlAttribute( cname, cvalue ); + if ( attrib ) + { + attributeSet.Add( attrib ); + } + else + { + TiXmlDocument* document = GetDocument(); + if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); + } +} + + +#ifdef TIXML_USE_STL +void TiXmlElement::SetAttribute( const std::string& name, const std::string& _value ) +{ + TiXmlAttribute* node = attributeSet.Find( name ); + if ( node ) + { + node->SetValue( _value ); + return; + } + + TiXmlAttribute* attrib = new TiXmlAttribute( name, _value ); + if ( attrib ) + { + attributeSet.Add( attrib ); + } + else + { + TiXmlDocument* document = GetDocument(); + if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); + } +} +#endif + + +void TiXmlElement::Print( FILE* cfile, int depth ) const +{ + int i; + assert( cfile ); + for ( i=0; i<depth; i++ ) { + fprintf( cfile, " " ); + } + + fprintf( cfile, "<%s", value.c_str() ); + + const TiXmlAttribute* attrib; + for ( attrib = attributeSet.First(); attrib; attrib = attrib->Next() ) + { + fprintf( cfile, " " ); + attrib->Print( cfile, depth ); + } + + // There are 3 different formatting approaches: + // 1) An element without children is printed as a <foo /> node + // 2) An element with only a text child is printed as <foo> text </foo> + // 3) An element with children is printed on multiple lines. + TiXmlNode* node; + if ( !firstChild ) + { + fprintf( cfile, " />" ); + } + else if ( firstChild == lastChild && firstChild->ToText() ) + { + fprintf( cfile, ">" ); + firstChild->Print( cfile, depth + 1 ); + fprintf( cfile, "</%s>", value.c_str() ); + } + else + { + fprintf( cfile, ">" ); + + for ( node = firstChild; node; node=node->NextSibling() ) + { + if ( !node->ToText() ) + { + fprintf( cfile, "\n" ); + } + node->Print( cfile, depth+1 ); + } + fprintf( cfile, "\n" ); + for( i=0; i<depth; ++i ) { + fprintf( cfile, " " ); + } + fprintf( cfile, "</%s>", value.c_str() ); + } +} + + +void TiXmlElement::CopyTo( TiXmlElement* target ) const +{ + // superclass: + TiXmlNode::CopyTo( target ); + + // Element class: + // Clone the attributes, then clone the children. + const TiXmlAttribute* attribute = 0; + for( attribute = attributeSet.First(); + attribute; + attribute = attribute->Next() ) + { + target->SetAttribute( attribute->Name(), attribute->Value() ); + } + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + +bool TiXmlElement::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this, attributeSet.First() ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +TiXmlNode* TiXmlElement::Clone() const +{ + TiXmlElement* clone = new TiXmlElement( Value() ); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +const char* TiXmlElement::GetText() const +{ + const TiXmlNode* child = this->FirstChild(); + if ( child ) { + const TiXmlText* childText = child->ToText(); + if ( childText ) { + return childText->Value(); + } + } + return 0; +} + + +TiXmlDocument::TiXmlDocument() : TiXmlNode( TiXmlNode::DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + ClearError(); +} + +TiXmlDocument::TiXmlDocument( const char * documentName ) : TiXmlNode( TiXmlNode::DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} + + +#ifdef TIXML_USE_STL +TiXmlDocument::TiXmlDocument( const std::string& documentName ) : TiXmlNode( TiXmlNode::DOCUMENT ) +{ + tabsize = 4; + useMicrosoftBOM = false; + value = documentName; + ClearError(); +} +#endif + + +TiXmlDocument::TiXmlDocument( const TiXmlDocument& copy ) : TiXmlNode( TiXmlNode::DOCUMENT ) +{ + copy.CopyTo( this ); +} + + +void TiXmlDocument::operator=( const TiXmlDocument& copy ) +{ + Clear(); + copy.CopyTo( this ); +} + + +bool TiXmlDocument::LoadFile( TiXmlEncoding encoding ) +{ + // See STL_STRING_BUG below. + //StringToBuffer buf( value ); + + return LoadFile( Value(), encoding ); +} + + +bool TiXmlDocument::SaveFile() const +{ + // See STL_STRING_BUG below. +// StringToBuffer buf( value ); +// +// if ( buf.buffer && SaveFile( buf.buffer ) ) +// return true; +// +// return false; + return SaveFile( Value() ); +} + +bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding ) +{ + // There was a really terrifying little bug here. The code: + // value = filename + // in the STL case, cause the assignment method of the std::string to + // be called. What is strange, is that the std::string had the same + // address as it's c_str() method, and so bad things happen. Looks + // like a bug in the Microsoft STL implementation. + // Add an extra string to avoid the crash. + TIXML_STRING filename( _filename ); + value = filename; + + // reading in binary mode so that tinyxml can normalize the EOL + FILE* file = TiXmlFOpen( value.c_str (), "rb" ); + + if ( file ) + { + bool result = LoadFile( file, encoding ); + fclose( file ); + return result; + } + else + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } +} + +bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding ) +{ + if ( !file ) + { + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // Delete the existing data: + Clear(); + location.Clear(); + + // Get the file size, so we can pre-allocate the string. HUGE speed impact. + long length = 0; + fseek( file, 0, SEEK_END ); + length = ftell( file ); + fseek( file, 0, SEEK_SET ); + + // Strange case, but good to handle up front. + if ( length <= 0 ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // If we have a file, assume it is all one big XML file, and read it in. + // The document parser may decide the document ends sooner than the entire file, however. + TIXML_STRING data; + data.reserve( length ); + + // Subtle bug here. TinyXml did use fgets. But from the XML spec: + // 2.11 End-of-Line Handling + // <snip> + // <quote> + // ...the XML processor MUST behave as if it normalized all line breaks in external + // parsed entities (including the document entity) on input, before parsing, by translating + // both the two-character sequence #xD #xA and any #xD that is not followed by #xA to + // a single #xA character. + // </quote> + // + // It is not clear fgets does that, and certainly isn't clear it works cross platform. + // Generally, you expect fgets to translate from the convention of the OS to the c/unix + // convention, and not work generally. + + /* + while( fgets( buf, sizeof(buf), file ) ) + { + data += buf; + } + */ + + char* buf = new char[ length+1 ]; + buf[0] = 0; + + if ( fread( buf, length, 1, file ) != 1 ) { + delete [] buf; + SetError( TIXML_ERROR_OPENING_FILE, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + const char* lastPos = buf; + const char* p = buf; + + buf[length] = 0; + while( *p ) { + assert( p < (buf+length) ); + if ( *p == 0xa ) { + // Newline character. No special rules for this. Append all the characters + // since the last string, and include the newline. + data.append( lastPos, (p-lastPos+1) ); // append, include the newline + ++p; // move past the newline + lastPos = p; // and point to the new buffer (may be 0) + assert( p <= (buf+length) ); + } + else if ( *p == 0xd ) { + // Carriage return. Append what we have so far, then + // handle moving forward in the buffer. + if ( (p-lastPos) > 0 ) { + data.append( lastPos, p-lastPos ); // do not add the CR + } + data += (char)0xa; // a proper newline + + if ( *(p+1) == 0xa ) { + // Carriage return - new line sequence + p += 2; + lastPos = p; + assert( p <= (buf+length) ); + } + else { + // it was followed by something else...that is presumably characters again. + ++p; + lastPos = p; + assert( p <= (buf+length) ); + } + } + else { + ++p; + } + } + // Handle any left over characters. + if ( p-lastPos ) { + data.append( lastPos, p-lastPos ); + } + delete [] buf; + buf = 0; + + Parse( data.c_str(), 0, encoding ); + + if ( Error() ) + return false; + else + return true; +} + + +bool TiXmlDocument::SaveFile( const char * filename ) const +{ + // The old c stuff lives on... + FILE* fp = TiXmlFOpen( filename, "w" ); + if ( fp ) + { + bool result = SaveFile( fp ); + fclose( fp ); + return result; + } + return false; +} + + +bool TiXmlDocument::SaveFile( FILE* fp ) const +{ + if ( useMicrosoftBOM ) + { + const unsigned char TIXML_UTF_LEAD_0 = 0xefU; + const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; + const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + + fputc( TIXML_UTF_LEAD_0, fp ); + fputc( TIXML_UTF_LEAD_1, fp ); + fputc( TIXML_UTF_LEAD_2, fp ); + } + Print( fp, 0 ); + return (ferror(fp) == 0); +} + + +void TiXmlDocument::CopyTo( TiXmlDocument* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->error = error; + target->errorId = errorId; + target->errorDesc = errorDesc; + target->tabsize = tabsize; + target->errorLocation = errorLocation; + target->useMicrosoftBOM = useMicrosoftBOM; + + TiXmlNode* node = 0; + for ( node = firstChild; node; node = node->NextSibling() ) + { + target->LinkEndChild( node->Clone() ); + } +} + + +TiXmlNode* TiXmlDocument::Clone() const +{ + TiXmlDocument* clone = new TiXmlDocument(); + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlDocument::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + node->Print( cfile, depth ); + fprintf( cfile, "\n" ); + } +} + + +bool TiXmlDocument::Accept( TiXmlVisitor* visitor ) const +{ + if ( visitor->VisitEnter( *this ) ) + { + for ( const TiXmlNode* node=FirstChild(); node; node=node->NextSibling() ) + { + if ( !node->Accept( visitor ) ) + break; + } + } + return visitor->VisitExit( *this ); +} + + +const TiXmlAttribute* TiXmlAttribute::Next() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} + +/* +TiXmlAttribute* TiXmlAttribute::Next() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( next->value.empty() && next->name.empty() ) + return 0; + return next; +} +*/ + +const TiXmlAttribute* TiXmlAttribute::Previous() const +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} + +/* +TiXmlAttribute* TiXmlAttribute::Previous() +{ + // We are using knowledge of the sentinel. The sentinel + // have a value or name. + if ( prev->value.empty() && prev->name.empty() ) + return 0; + return prev; +} +*/ + +void TiXmlAttribute::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + TIXML_STRING n, v; + + EncodeString( name, &n ); + EncodeString( value, &v ); + + if (value.find ('\"') == TIXML_STRING::npos) { + if ( cfile ) { + fprintf (cfile, "%s=\"%s\"", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "=\""; (*str) += v; (*str) += "\""; + } + } + else { + if ( cfile ) { + fprintf (cfile, "%s='%s'", n.c_str(), v.c_str() ); + } + if ( str ) { + (*str) += n; (*str) += "='"; (*str) += v; (*str) += "'"; + } + } +} + + +int TiXmlAttribute::QueryIntValue( int* ival ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%d", ival ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +int TiXmlAttribute::QueryDoubleValue( double* dval ) const +{ + if ( TIXML_SSCANF( value.c_str(), "%lf", dval ) == 1 ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; +} + +void TiXmlAttribute::SetIntValue( int _value ) +{ + char buf [64]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF(buf, sizeof(buf), "%d", _value); + #else + sprintf (buf, "%d", _value); + #endif + SetValue (buf); +} + +void TiXmlAttribute::SetDoubleValue( double _value ) +{ + char buf [256]; + #if defined(TIXML_SNPRINTF) + TIXML_SNPRINTF( buf, sizeof(buf), "%lf", _value); + #else + sprintf (buf, "%lf", _value); + #endif + SetValue (buf); +} + +int TiXmlAttribute::IntValue() const +{ + return atoi (value.c_str ()); +} + +double TiXmlAttribute::DoubleValue() const +{ + return atof (value.c_str ()); +} + + +TiXmlComment::TiXmlComment( const TiXmlComment& copy ) : TiXmlNode( TiXmlNode::COMMENT ) +{ + copy.CopyTo( this ); +} + + +void TiXmlComment::operator=( const TiXmlComment& base ) +{ + Clear(); + base.CopyTo( this ); +} + + +void TiXmlComment::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + for ( int i=0; i<depth; i++ ) + { + fprintf( cfile, " " ); + } + fprintf( cfile, "<!--%s-->", value.c_str() ); +} + + +void TiXmlComment::CopyTo( TiXmlComment* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlComment::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlComment::Clone() const +{ + TiXmlComment* clone = new TiXmlComment(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlText::Print( FILE* cfile, int depth ) const +{ + assert( cfile ); + if ( cdata ) + { + int i; + fprintf( cfile, "\n" ); + for ( i=0; i<depth; i++ ) { + fprintf( cfile, " " ); + } + fprintf( cfile, "<![CDATA[%s]]>\n", value.c_str() ); // unformatted output + } + else + { + TIXML_STRING buffer; + EncodeString( value, &buffer ); + fprintf( cfile, "%s", buffer.c_str() ); + } +} + + +void TiXmlText::CopyTo( TiXmlText* target ) const +{ + TiXmlNode::CopyTo( target ); + target->cdata = cdata; +} + + +bool TiXmlText::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlText::Clone() const +{ + TiXmlText* clone = 0; + clone = new TiXmlText( "" ); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlDeclaration::TiXmlDeclaration( const char * _version, + const char * _encoding, + const char * _standalone ) + : TiXmlNode( TiXmlNode::DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} + + +#ifdef TIXML_USE_STL +TiXmlDeclaration::TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ) + : TiXmlNode( TiXmlNode::DECLARATION ) +{ + version = _version; + encoding = _encoding; + standalone = _standalone; +} +#endif + + +TiXmlDeclaration::TiXmlDeclaration( const TiXmlDeclaration& copy ) + : TiXmlNode( TiXmlNode::DECLARATION ) +{ + copy.CopyTo( this ); +} + + +void TiXmlDeclaration::operator=( const TiXmlDeclaration& copy ) +{ + Clear(); + copy.CopyTo( this ); +} + + +void TiXmlDeclaration::Print( FILE* cfile, int /*depth*/, TIXML_STRING* str ) const +{ + if ( cfile ) fprintf( cfile, "<?xml " ); + if ( str ) (*str) += "<?xml "; + + if ( !version.empty() ) { + if ( cfile ) fprintf (cfile, "version=\"%s\" ", version.c_str ()); + if ( str ) { (*str) += "version=\""; (*str) += version; (*str) += "\" "; } + } + if ( !encoding.empty() ) { + if ( cfile ) fprintf (cfile, "encoding=\"%s\" ", encoding.c_str ()); + if ( str ) { (*str) += "encoding=\""; (*str) += encoding; (*str) += "\" "; } + } + if ( !standalone.empty() ) { + if ( cfile ) fprintf (cfile, "standalone=\"%s\" ", standalone.c_str ()); + if ( str ) { (*str) += "standalone=\""; (*str) += standalone; (*str) += "\" "; } + } + if ( cfile ) fprintf( cfile, "?>" ); + if ( str ) (*str) += "?>"; +} + + +void TiXmlDeclaration::CopyTo( TiXmlDeclaration* target ) const +{ + TiXmlNode::CopyTo( target ); + + target->version = version; + target->encoding = encoding; + target->standalone = standalone; +} + + +bool TiXmlDeclaration::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlDeclaration::Clone() const +{ + TiXmlDeclaration* clone = new TiXmlDeclaration(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +void TiXmlUnknown::Print( FILE* cfile, int depth ) const +{ + for ( int i=0; i<depth; i++ ) + fprintf( cfile, " " ); + fprintf( cfile, "<%s>", value.c_str() ); +} + + +void TiXmlUnknown::CopyTo( TiXmlUnknown* target ) const +{ + TiXmlNode::CopyTo( target ); +} + + +bool TiXmlUnknown::Accept( TiXmlVisitor* visitor ) const +{ + return visitor->Visit( *this ); +} + + +TiXmlNode* TiXmlUnknown::Clone() const +{ + TiXmlUnknown* clone = new TiXmlUnknown(); + + if ( !clone ) + return 0; + + CopyTo( clone ); + return clone; +} + + +TiXmlAttributeSet::TiXmlAttributeSet() +{ + sentinel.next = &sentinel; + sentinel.prev = &sentinel; +} + + +TiXmlAttributeSet::~TiXmlAttributeSet() +{ + assert( sentinel.next == &sentinel ); + assert( sentinel.prev == &sentinel ); +} + + +void TiXmlAttributeSet::Add( TiXmlAttribute* addMe ) +{ + #ifdef TIXML_USE_STL + assert( !Find( TIXML_STRING( addMe->Name() ) ) ); // Shouldn't be multiply adding to the set. + #else + assert( !Find( addMe->Name() ) ); // Shouldn't be multiply adding to the set. + #endif + + addMe->next = &sentinel; + addMe->prev = sentinel.prev; + + sentinel.prev->next = addMe; + sentinel.prev = addMe; +} + +void TiXmlAttributeSet::Remove( TiXmlAttribute* removeMe ) +{ + TiXmlAttribute* node; + + for( node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node == removeMe ) + { + node->prev->next = node->next; + node->next->prev = node->prev; + node->next = 0; + node->prev = 0; + return; + } + } + assert( 0 ); // we tried to remove a non-linked attribute. +} + + +#ifdef TIXML_USE_STL +const TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) const +{ + for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node->name == name ) + return node; + } + return 0; +} + +/* +TiXmlAttribute* TiXmlAttributeSet::Find( const std::string& name ) +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( node->name == name ) + return node; + } + return 0; +} +*/ +#endif + + +const TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) const +{ + for( const TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( strcmp( node->name.c_str(), name ) == 0 ) + return node; + } + return 0; +} + +/* +TiXmlAttribute* TiXmlAttributeSet::Find( const char* name ) +{ + for( TiXmlAttribute* node = sentinel.next; node != &sentinel; node = node->next ) + { + if ( strcmp( node->name.c_str(), name ) == 0 ) + return node; + } + return 0; +} +*/ + +#ifdef TIXML_USE_STL +std::istream& operator>> (std::istream & in, TiXmlNode & base) +{ + TIXML_STRING tag; + tag.reserve( 8 * 1000 ); + base.StreamIn( &in, &tag ); + + base.Parse( tag.c_str(), 0, TIXML_DEFAULT_ENCODING ); + return in; +} +#endif + + +#ifdef TIXML_USE_STL +std::ostream& operator<< (std::ostream & out, const TiXmlNode & base) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out << printer.Str(); + + return out; +} + + +std::string& operator<< (std::string& out, const TiXmlNode& base ) +{ + TiXmlPrinter printer; + printer.SetStreamPrinting(); + base.Accept( &printer ); + out.append( printer.Str() ); + + return out; +} +#endif + + +TiXmlHandle TiXmlHandle::FirstChild() const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChild( const char * value ) const +{ + if ( node ) + { + TiXmlNode* child = node->FirstChild( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement() const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement(); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::FirstChildElement( const char * value ) const +{ + if ( node ) + { + TiXmlElement* child = node->FirstChildElement( value ); + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild(); + for ( i=0; + child && i<count; + child = child->NextSibling(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::Child( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlNode* child = node->FirstChild( value ); + for ( i=0; + child && i<count; + child = child->NextSibling( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement(); + for ( i=0; + child && i<count; + child = child->NextSiblingElement(), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +TiXmlHandle TiXmlHandle::ChildElement( const char* value, int count ) const +{ + if ( node ) + { + int i; + TiXmlElement* child = node->FirstChildElement( value ); + for ( i=0; + child && i<count; + child = child->NextSiblingElement( value ), ++i ) + { + // nothing + } + if ( child ) + return TiXmlHandle( child ); + } + return TiXmlHandle( 0 ); +} + + +bool TiXmlPrinter::VisitEnter( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitExit( const TiXmlDocument& ) +{ + return true; +} + +bool TiXmlPrinter::VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ) +{ + DoIndent(); + buffer += "<"; + buffer += element.Value(); + + for( const TiXmlAttribute* attrib = firstAttribute; attrib; attrib = attrib->Next() ) + { + buffer += " "; + attrib->Print( 0, 0, &buffer ); + } + + if ( !element.FirstChild() ) + { + buffer += " />"; + DoLineBreak(); + } + else + { + buffer += ">"; + if ( element.FirstChild()->ToText() + && element.LastChild() == element.FirstChild() + && element.FirstChild()->ToText()->CDATA() == false ) + { + simpleTextPrint = true; + // no DoLineBreak()! + } + else + { + DoLineBreak(); + } + } + ++depth; + return true; +} + + +bool TiXmlPrinter::VisitExit( const TiXmlElement& element ) +{ + --depth; + if ( !element.FirstChild() ) + { + // nothing. + } + else + { + if ( simpleTextPrint ) + { + simpleTextPrint = false; + } + else + { + DoIndent(); + } + buffer += "</"; + buffer += element.Value(); + buffer += ">"; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlText& text ) +{ + if ( text.CDATA() ) + { + DoIndent(); + buffer += "<![CDATA["; + buffer += text.Value(); + buffer += "]]>"; + DoLineBreak(); + } + else if ( simpleTextPrint ) + { + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + } + else + { + DoIndent(); + TIXML_STRING str; + TiXmlBase::EncodeString( text.ValueTStr(), &str ); + buffer += str; + DoLineBreak(); + } + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlDeclaration& declaration ) +{ + DoIndent(); + declaration.Print( 0, 0, &buffer ); + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlComment& comment ) +{ + DoIndent(); + buffer += "<!--"; + buffer += comment.Value(); + buffer += "-->"; + DoLineBreak(); + return true; +} + + +bool TiXmlPrinter::Visit( const TiXmlUnknown& unknown ) +{ + DoIndent(); + buffer += "<"; + buffer += unknown.Value(); + buffer += ">"; + DoLineBreak(); + return true; +} + diff --git a/rotord/src/tinyxml.h b/rotord/src/tinyxml.h new file mode 100755 index 0000000..7958de5 --- /dev/null +++ b/rotord/src/tinyxml.h @@ -0,0 +1,1807 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#ifndef TINYXML_INCLUDED +#define TINYXML_INCLUDED + +#ifdef _MSC_VER +#pragma warning( push ) +#pragma warning( disable : 4530 ) +#pragma warning( disable : 4786 ) +#endif + +#include <ctype.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +// Help out windows: +#if defined( _DEBUG ) && !defined( DEBUG ) +#define DEBUG +#endif + +#define TIXML_USE_STL // for now, OFXML will use STL for string stuff + +#ifdef TIXML_USE_STL + #include <string> + #include <iostream> + #include <sstream> + #define TIXML_STRING std::string +#else + #include "tinystr.h" + #define TIXML_STRING TiXmlString +#endif + +// Deprecated library function hell. Compilers want to use the +// new safe versions. This probably doesn't fully address the problem, +// but it gets closer. There are too many compilers for me to fully +// test. If you get compilation troubles, undefine TIXML_SAFE +#define TIXML_SAFE + +#ifdef TIXML_SAFE + #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) + // Microsoft visual studio, version 2005 and higher. + #define TIXML_SNPRINTF _snprintf_s + #define TIXML_SNSCANF _snscanf_s + #define TIXML_SSCANF sscanf_s + #elif defined(_MSC_VER) && (_MSC_VER >= 1200 ) + // Microsoft visual studio, version 6 and higher. + //#pragma message( "Using _sn* functions." ) + #define TIXML_SNPRINTF _snprintf + #define TIXML_SNSCANF _snscanf + #define TIXML_SSCANF sscanf + #elif defined(__GNUC__) && (__GNUC__ >= 3 ) + // GCC version 3 and higher.s + //#warning( "Using sn* functions." ) + #define TIXML_SNPRINTF snprintf + #define TIXML_SNSCANF snscanf + #define TIXML_SSCANF sscanf + #else + #define TIXML_SSCANF sscanf + #endif +#endif + +class TiXmlDocument; +class TiXmlElement; +class TiXmlComment; +class TiXmlUnknown; +class TiXmlAttribute; +class TiXmlText; +class TiXmlDeclaration; +class TiXmlParsingData; + +const int TIXML_MAJOR_VERSION = 2; +const int TIXML_MINOR_VERSION = 5; +const int TIXML_PATCH_VERSION = 3; + +/* Internal structure for tracking location of items + in the XML file. +*/ +struct TiXmlCursor +{ + TiXmlCursor() { Clear(); } + void Clear() { row = col = -1; } + + int row; // 0 based. + int col; // 0 based. +}; + + +/** + If you call the Accept() method, it requires being passed a TiXmlVisitor + class to handle callbacks. For nodes that contain other nodes (Document, Element) + you will get called with a VisitEnter/VisitExit pair. Nodes that are always leaves + are simple called with Visit(). + + If you return 'true' from a Visit method, recursive parsing will continue. If you return + false, <b>no children of this node or its sibilings</b> will be Visited. + + All flavors of Visit methods have a default implementation that returns 'true' (continue + visiting). You need to only override methods that are interesting to you. + + Generally Accept() is called on the TiXmlDocument, although all nodes suppert Visiting. + + You should never change the document from a callback. + + @sa TiXmlNode::Accept() +*/ +class TiXmlVisitor +{ +public: + virtual ~TiXmlVisitor() {} + + /// Visit a document. + virtual bool VisitEnter( const TiXmlDocument& /*doc*/ ) { return true; } + /// Visit a document. + virtual bool VisitExit( const TiXmlDocument& /*doc*/ ) { return true; } + + /// Visit an element. + virtual bool VisitEnter( const TiXmlElement& /*element*/, const TiXmlAttribute* /*firstAttribute*/ ) { return true; } + /// Visit an element. + virtual bool VisitExit( const TiXmlElement& /*element*/ ) { return true; } + + /// Visit a declaration + virtual bool Visit( const TiXmlDeclaration& /*declaration*/ ) { return true; } + /// Visit a text node + virtual bool Visit( const TiXmlText& /*text*/ ) { return true; } + /// Visit a comment node + virtual bool Visit( const TiXmlComment& /*comment*/ ) { return true; } + /// Visit an unknow node + virtual bool Visit( const TiXmlUnknown& /*unknown*/ ) { return true; } +}; + +// Only used by Attribute::Query functions +enum +{ + TIXML_SUCCESS, + TIXML_NO_ATTRIBUTE, + TIXML_WRONG_TYPE +}; + + +// Used by the parsing routines. +enum TiXmlEncoding +{ + TIXML_ENCODING_UNKNOWN, + TIXML_ENCODING_UTF8, + TIXML_ENCODING_LEGACY +}; + +const TiXmlEncoding TIXML_DEFAULT_ENCODING = TIXML_ENCODING_UNKNOWN; + +/** TiXmlBase is a base class for every class in TinyXml. + It does little except to establish that TinyXml classes + can be printed and provide some utility functions. + + In XML, the document and elements can contain + other elements and other types of nodes. + + @verbatim + A Document can contain: Element (container or leaf) + Comment (leaf) + Unknown (leaf) + Declaration( leaf ) + + An Element can contain: Element (container or leaf) + Text (leaf) + Attributes (not on tree) + Comment (leaf) + Unknown (leaf) + + A Decleration contains: Attributes (not on tree) + @endverbatim +*/ +class TiXmlBase +{ + friend class TiXmlNode; + friend class TiXmlElement; + friend class TiXmlDocument; + +public: + TiXmlBase() : userData(0) {} + virtual ~TiXmlBase() {} + + /** All TinyXml classes can print themselves to a filestream + or the string class (TiXmlString in non-STL mode, std::string + in STL mode.) Either or both cfile and str can be null. + + This is a formatted print, and will insert + tabs and newlines. + + (For an unformatted stream, use the << operator.) + */ + virtual void Print( FILE* cfile, int depth ) const = 0; + + /** The world does not agree on whether white space should be kept or + not. In order to make everyone happy, these global, static functions + are provided to set whether or not TinyXml will condense all white space + into a single space or not. The default is to condense. Note changing this + value is not thread safe. + */ + static void SetCondenseWhiteSpace( bool condense ) { condenseWhiteSpace = condense; } + + /// Return the current white space setting. + static bool IsWhiteSpaceCondensed() { return condenseWhiteSpace; } + + /** Return the position, in the original source file, of this node or attribute. + The row and column are 1-based. (That is the first row and first column is + 1,1). If the returns values are 0 or less, then the parser does not have + a row and column value. + + Generally, the row and column value will be set when the TiXmlDocument::Load(), + TiXmlDocument::LoadFile(), or any TiXmlNode::Parse() is called. It will NOT be set + when the DOM was created from operator>>. + + The values reflect the initial load. Once the DOM is modified programmatically + (by adding or changing nodes and attributes) the new values will NOT update to + reflect changes in the document. + + There is a minor performance cost to computing the row and column. Computation + can be disabled if TiXmlDocument::SetTabSize() is called with 0 as the value. + + @sa TiXmlDocument::SetTabSize() + */ + int Row() const { return location.row + 1; } + int Column() const { return location.col + 1; } ///< See Row() + + void SetUserData( void* user ) { userData = user; } ///< Set a pointer to arbitrary user data. + void* GetUserData() { return userData; } ///< Get a pointer to arbitrary user data. + const void* GetUserData() const { return userData; } ///< Get a pointer to arbitrary user data. + + // Table that returs, for a given lead byte, the total number of bytes + // in the UTF-8 sequence. + static const int utf8ByteTable[256]; + + virtual const char* Parse( const char* p, + TiXmlParsingData* data, + TiXmlEncoding encoding /*= TIXML_ENCODING_UNKNOWN */ ) = 0; + + /** Expands entities in a string. Note this should not contian the tag's '<', '>', etc, + or they will be transformed into entities! + */ + static void EncodeString( const TIXML_STRING& str, TIXML_STRING* out ); + + enum + { + TIXML_NO_ERROR = 0, + TIXML_ERROR, + TIXML_ERROR_OPENING_FILE, + TIXML_ERROR_OUT_OF_MEMORY, + TIXML_ERROR_PARSING_ELEMENT, + TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, + TIXML_ERROR_READING_ELEMENT_VALUE, + TIXML_ERROR_READING_ATTRIBUTES, + TIXML_ERROR_PARSING_EMPTY, + TIXML_ERROR_READING_END_TAG, + TIXML_ERROR_PARSING_UNKNOWN, + TIXML_ERROR_PARSING_COMMENT, + TIXML_ERROR_PARSING_DECLARATION, + TIXML_ERROR_DOCUMENT_EMPTY, + TIXML_ERROR_EMBEDDED_NULL, + TIXML_ERROR_PARSING_CDATA, + TIXML_ERROR_DOCUMENT_TOP_ONLY, + + TIXML_ERROR_STRING_COUNT + }; + +protected: + + static const char* SkipWhiteSpace( const char*, TiXmlEncoding encoding ); + inline static bool IsWhiteSpace( char c ) + { + return ( isspace( (unsigned char) c ) || c == '\n' || c == '\r' ); + } + inline static bool IsWhiteSpace( int c ) + { + if ( c < 256 ) + return IsWhiteSpace( (char) c ); + return false; // Again, only truly correct for English/Latin...but usually works. + } + + #ifdef TIXML_USE_STL + static bool StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ); + static bool StreamTo( std::istream * in, int character, TIXML_STRING * tag ); + #endif + + /* Reads an XML name into the string provided. Returns + a pointer just past the last character of the name, + or 0 if the function has an error. + */ + static const char* ReadName( const char* p, TIXML_STRING* name, TiXmlEncoding encoding ); + + /* Reads text. Returns a pointer past the given end tag. + Wickedly complex options, but it keeps the (sensitive) code in one place. + */ + static const char* ReadText( const char* in, // where to start + TIXML_STRING* text, // the string read + bool ignoreWhiteSpace, // whether to keep the white space + const char* endTag, // what ends this text + bool ignoreCase, // whether to ignore case in the end tag + TiXmlEncoding encoding ); // the current encoding + + // If an entity has been found, transform it into a character. + static const char* GetEntity( const char* in, char* value, int* length, TiXmlEncoding encoding ); + + // Get a character, while interpreting entities. + // The length can be from 0 to 4 bytes. + inline static const char* GetChar( const char* p, char* _value, int* length, TiXmlEncoding encoding ) + { + assert( p ); + if ( encoding == TIXML_ENCODING_UTF8 ) + { + *length = utf8ByteTable[ *((const unsigned char*)p) ]; + assert( *length >= 0 && *length < 5 ); + } + else + { + *length = 1; + } + + if ( *length == 1 ) + { + if ( *p == '&' ) + return GetEntity( p, _value, length, encoding ); + *_value = *p; + return p+1; + } + else if ( *length ) + { + //strncpy( _value, p, *length ); // lots of compilers don't like this function (unsafe), + // and the null terminator isn't needed + for( int i=0; p[i] && i<*length; ++i ) { + _value[i] = p[i]; + } + return p + (*length); + } + else + { + // Not valid text. + return 0; + } + } + + // Return true if the next characters in the stream are any of the endTag sequences. + // Ignore case only works for english, and should only be relied on when comparing + // to English words: StringEqual( p, "version", true ) is fine. + static bool StringEqual( const char* p, + const char* endTag, + bool ignoreCase, + TiXmlEncoding encoding ); + + static const char* errorString[ TIXML_ERROR_STRING_COUNT ]; + + TiXmlCursor location; + + /// Field containing a generic user pointer + void* userData; + + // None of these methods are reliable for any language except English. + // Good for approximation, not great for accuracy. + static int IsAlpha( unsigned char anyByte, TiXmlEncoding encoding ); + static int IsAlphaNum( unsigned char anyByte, TiXmlEncoding encoding ); + inline static int ToLower( int v, TiXmlEncoding encoding ) + { + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( v < 128 ) return tolower( v ); + return v; + } + else + { + return tolower( v ); + } + } + static void ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ); + +private: + TiXmlBase( const TiXmlBase& ); // not implemented. + void operator=( const TiXmlBase& base ); // not allowed. + + struct Entity + { + const char* str; + unsigned int strLength; + char chr; + }; + enum + { + NUM_ENTITY = 5, + MAX_ENTITY_LENGTH = 6 + + }; + static Entity entity[ NUM_ENTITY ]; + static bool condenseWhiteSpace; +}; + + +/** The parent class for everything in the Document Object Model. + (Except for attributes). + Nodes have siblings, a parent, and children. A node can be + in a document, or stand on its own. The type of a TiXmlNode + can be queried, and it can be cast to its more defined type. +*/ +class TiXmlNode : public TiXmlBase +{ + friend class TiXmlDocument; + friend class TiXmlElement; + +public: + #ifdef TIXML_USE_STL + + /** An input stream operator, for every class. Tolerant of newlines and + formatting, but doesn't expect them. + */ + friend std::istream& operator >> (std::istream& in, TiXmlNode& base); + + /** An output stream operator, for every class. Note that this outputs + without any newlines or formatting, as opposed to Print(), which + includes tabs and new lines. + + The operator<< and operator>> are not completely symmetric. Writing + a node to a stream is very well defined. You'll get a nice stream + of output, without any extra whitespace or newlines. + + But reading is not as well defined. (As it always is.) If you create + a TiXmlElement (for example) and read that from an input stream, + the text needs to define an element or junk will result. This is + true of all input streams, but it's worth keeping in mind. + + A TiXmlDocument will read nodes until it reads a root element, and + all the children of that root element. + */ + friend std::ostream& operator<< (std::ostream& out, const TiXmlNode& base); + + /// Appends the XML node or attribute to a std::string. + friend std::string& operator<< (std::string& out, const TiXmlNode& base ); + + #endif + + /** The types of XML nodes supported by TinyXml. (All the + unsupported types are picked up by UNKNOWN.) + */ + enum NodeType + { + DOCUMENT, + ELEMENT, + COMMENT, + UNKNOWN, + TEXT, + DECLARATION, + TYPECOUNT + }; + + virtual ~TiXmlNode(); + + /** The meaning of 'value' changes for the specific type of + TiXmlNode. + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + + The subclasses will wrap this function. + */ + const char *Value() const { return value.c_str (); } + + #ifdef TIXML_USE_STL + /** Return Value() as a std::string. If you only use STL, + this is more efficient than calling Value(). + Only available in STL mode. + */ + const std::string& ValueStr() const { return value; } + #endif + + const TIXML_STRING& ValueTStr() const { return value; } + + /** Changes the value of the node. Defined as: + @verbatim + Document: filename of the xml file + Element: name of the element + Comment: the comment text + Unknown: the tag contents + Text: the text string + @endverbatim + */ + void SetValue(const char * _value) { value = _value;} + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Delete all the children of this node. Does not affect 'this'. + void Clear(); + + /// One step up the DOM. + TiXmlNode* Parent() { return parent; } + const TiXmlNode* Parent() const { return parent; } + + const TiXmlNode* FirstChild() const { return firstChild; } ///< The first child of this node. Will be null if there are no children. + TiXmlNode* FirstChild() { return firstChild; } + const TiXmlNode* FirstChild( const char * value ) const; ///< The first child of this node with the matching 'value'. Will be null if none found. + /// The first child of this node with the matching 'value'. Will be null if none found. + TiXmlNode* FirstChild( const char * _value ) { + // Call through to the const version - safe since nothing is changed. Exiting syntax: cast this to a const (always safe) + // call the method, cast the return back to non-const. + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->FirstChild( _value )); + } + const TiXmlNode* LastChild() const { return lastChild; } /// The last child of this node. Will be null if there are no children. + TiXmlNode* LastChild() { return lastChild; } + + const TiXmlNode* LastChild( const char * value ) const; /// The last child of this node matching 'value'. Will be null if there are no children. + TiXmlNode* LastChild( const char * _value ) { + return const_cast< TiXmlNode* > ((const_cast< const TiXmlNode* >(this))->LastChild( _value )); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* FirstChild( const std::string& _value ) const { return FirstChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* FirstChild( const std::string& _value ) { return FirstChild (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* LastChild( const std::string& _value ) const { return LastChild (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* LastChild( const std::string& _value ) { return LastChild (_value.c_str ()); } ///< STL std::string form. + #endif + + /** An alternate way to walk the children of a node. + One way to iterate over nodes is: + @verbatim + for( child = parent->FirstChild(); child; child = child->NextSibling() ) + @endverbatim + + IterateChildren does the same thing with the syntax: + @verbatim + child = 0; + while( child = parent->IterateChildren( child ) ) + @endverbatim + + IterateChildren takes the previous child as input and finds + the next one. If the previous child is null, it returns the + first. IterateChildren will return null when done. + */ + const TiXmlNode* IterateChildren( const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( previous ) ); + } + + /// This flavor of IterateChildren searches for children with a particular 'value' + const TiXmlNode* IterateChildren( const char * value, const TiXmlNode* previous ) const; + TiXmlNode* IterateChildren( const char * _value, const TiXmlNode* previous ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->IterateChildren( _value, previous ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) const { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + TiXmlNode* IterateChildren( const std::string& _value, const TiXmlNode* previous ) { return IterateChildren (_value.c_str (), previous); } ///< STL std::string form. + #endif + + /** Add a new node related to this. Adds a child past the LastChild. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertEndChild( const TiXmlNode& addThis ); + + + /** Add a new node related to this. Adds a child past the LastChild. + + NOTE: the node to be added is passed by pointer, and will be + henceforth owned (and deleted) by tinyXml. This method is efficient + and avoids an extra copy, but should be used with care as it + uses a different memory model than the other insert functions. + + @sa InsertEndChild + */ + TiXmlNode* LinkEndChild( TiXmlNode* addThis ); + + /** Add a new node related to this. Adds a child before the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertBeforeChild( TiXmlNode* beforeThis, const TiXmlNode& addThis ); + + /** Add a new node related to this. Adds a child after the specified child. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* InsertAfterChild( TiXmlNode* afterThis, const TiXmlNode& addThis ); + + /** Replace a child of this node. + Returns a pointer to the new object or NULL if an error occured. + */ + TiXmlNode* ReplaceChild( TiXmlNode* replaceThis, const TiXmlNode& withThis ); + + /// Delete a child of this node. + bool RemoveChild( TiXmlNode* removeThis ); + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling() const { return prev; } + TiXmlNode* PreviousSibling() { return prev; } + + /// Navigate to a sibling node. + const TiXmlNode* PreviousSibling( const char * ) const; + TiXmlNode* PreviousSibling( const char *_prev ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->PreviousSibling( _prev ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlNode* PreviousSibling( const std::string& _value ) const { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* PreviousSibling( const std::string& _value ) { return PreviousSibling (_value.c_str ()); } ///< STL std::string form. + const TiXmlNode* NextSibling( const std::string& _value) const { return NextSibling (_value.c_str ()); } ///< STL std::string form. + TiXmlNode* NextSibling( const std::string& _value) { return NextSibling (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Navigate to a sibling node. + const TiXmlNode* NextSibling() const { return next; } + TiXmlNode* NextSibling() { return next; } + + /// Navigate to a sibling node with the given 'value'. + const TiXmlNode* NextSibling( const char * ) const; + TiXmlNode* NextSibling( const char* _next ) { + return const_cast< TiXmlNode* >( (const_cast< const TiXmlNode* >(this))->NextSibling( _next ) ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement() const; + TiXmlElement* NextSiblingElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement() ); + } + + /** Convenience function to get through elements. + Calls NextSibling and ToElement. Will skip all non-Element + nodes. Returns 0 if there is not another element. + */ + const TiXmlElement* NextSiblingElement( const char * ) const; + TiXmlElement* NextSiblingElement( const char *_next ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->NextSiblingElement( _next ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* NextSiblingElement( const std::string& _value) const { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* NextSiblingElement( const std::string& _value) { return NextSiblingElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement() const; + TiXmlElement* FirstChildElement() { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement() ); + } + + /// Convenience function to get through elements. + const TiXmlElement* FirstChildElement( const char * _value ) const; + TiXmlElement* FirstChildElement( const char * _value ) { + return const_cast< TiXmlElement* >( (const_cast< const TiXmlNode* >(this))->FirstChildElement( _value ) ); + } + + #ifdef TIXML_USE_STL + const TiXmlElement* FirstChildElement( const std::string& _value ) const { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + TiXmlElement* FirstChildElement( const std::string& _value ) { return FirstChildElement (_value.c_str ()); } ///< STL std::string form. + #endif + + /** Query the type (as an enumerated value, above) of this node. + The possible types are: DOCUMENT, ELEMENT, COMMENT, + UNKNOWN, TEXT, and DECLARATION. + */ + int Type() const { return type; } + + /** Return a pointer to the Document this node lives in. + Returns null if not in a document. + */ + const TiXmlDocument* GetDocument() const; + TiXmlDocument* GetDocument() { + return const_cast< TiXmlDocument* >( (const_cast< const TiXmlNode* >(this))->GetDocument() ); + } + + /// Returns true if this node has no children. + bool NoChildren() const { return !firstChild; } + + virtual const TiXmlDocument* ToDocument() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlElement* ToElement() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlComment* ToComment() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlUnknown* ToUnknown() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlText* ToText() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual const TiXmlDeclaration* ToDeclaration() const { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + virtual TiXmlDocument* ToDocument() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlElement* ToElement() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlComment* ToComment() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlText* ToText() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return 0; } ///< Cast to a more defined type. Will return null if not of the requested type. + + /** Create an exact duplicate of this node and return it. The memory must be deleted + by the caller. + */ + virtual TiXmlNode* Clone() const = 0; + + /** Accept a hierchical visit the nodes in the TinyXML DOM. Every node in the + XML tree will be conditionally visited and the host will be called back + via the TiXmlVisitor interface. + + This is essentially a SAX interface for TinyXML. (Note however it doesn't re-parse + the XML for the callbacks, so the performance of TinyXML is unchanged by using this + interface versus any other.) + + The interface has been based on ideas from: + + - http://www.saxproject.org/ + - http://c2.com/cgi/wiki?HierarchicalVisitorPattern + + Which are both good references for "visiting". + + An example of using Accept(): + @verbatim + TiXmlPrinter printer; + tinyxmlDoc.Accept( &printer ); + const char* xmlcstr = printer.CStr(); + @endverbatim + */ + virtual bool Accept( TiXmlVisitor* visitor ) const = 0; + +protected: + TiXmlNode( NodeType _type ); + + // Copy to the allocated object. Shared functionality between Clone, Copy constructor, + // and the assignment operator. + void CopyTo( TiXmlNode* target ) const; + + #ifdef TIXML_USE_STL + // The real work of the input operator. + virtual void StreamIn( std::istream* in, TIXML_STRING* tag ) = 0; + #endif + + // Figure out what is at *p, and parse it. Returns null if it is not an xml node. + TiXmlNode* Identify( const char* start, TiXmlEncoding encoding ); + + TiXmlNode* parent; + NodeType type; + + TiXmlNode* firstChild; + TiXmlNode* lastChild; + + TIXML_STRING value; + + TiXmlNode* prev; + TiXmlNode* next; + +private: + TiXmlNode( const TiXmlNode& ); // not implemented. + void operator=( const TiXmlNode& base ); // not allowed. +}; + + +/** An attribute is a name-value pair. Elements have an arbitrary + number of attributes, each with a unique name. + + @note The attributes are not TiXmlNodes, since they are not + part of the tinyXML document object model. There are other + suggested ways to look at this problem. +*/ +class TiXmlAttribute : public TiXmlBase +{ + friend class TiXmlAttributeSet; + +public: + /// Construct an empty attribute. + TiXmlAttribute() : TiXmlBase() + { + document = 0; + prev = next = 0; + } + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlAttribute( const std::string& _name, const std::string& _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + #endif + + /// Construct an attribute with a name and value. + TiXmlAttribute( const char * _name, const char * _value ) + { + name = _name; + value = _value; + document = 0; + prev = next = 0; + } + + const char* Name() const { return name.c_str(); } ///< Return the name of this attribute. + const char* Value() const { return value.c_str(); } ///< Return the value of this attribute. + #ifdef TIXML_USE_STL + const std::string& ValueStr() const { return value; } ///< Return the value of this attribute. + #endif + int IntValue() const; ///< Return the value of this attribute, converted to an integer. + double DoubleValue() const; ///< Return the value of this attribute, converted to a double. + + // Get the tinyxml string representation + const TIXML_STRING& NameTStr() const { return name; } + + /** QueryIntValue examines the value string. It is an alternative to the + IntValue() method with richer error checking. + If the value is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. + + A specialized but useful call. Note that for success it returns 0, + which is the opposite of almost all other TinyXml calls. + */ + int QueryIntValue( int* _value ) const; + /// QueryDoubleValue examines the value string. See QueryIntValue(). + int QueryDoubleValue( double* _value ) const; + + void SetName( const char* _name ) { name = _name; } ///< Set the name of this attribute. + void SetValue( const char* _value ) { value = _value; } ///< Set the value. + + void SetIntValue( int _value ); ///< Set the value from an integer. + void SetDoubleValue( double _value ); ///< Set the value from a double. + + #ifdef TIXML_USE_STL + /// STL std::string form. + void SetName( const std::string& _name ) { name = _name; } + /// STL std::string form. + void SetValue( const std::string& _value ) { value = _value; } + #endif + + /// Get the next sibling attribute in the DOM. Returns null at end. + const TiXmlAttribute* Next() const; + TiXmlAttribute* Next() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Next() ); + } + + /// Get the previous sibling attribute in the DOM. Returns null at beginning. + const TiXmlAttribute* Previous() const; + TiXmlAttribute* Previous() { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttribute* >(this))->Previous() ); + } + + bool operator==( const TiXmlAttribute& rhs ) const { return rhs.name == name; } + bool operator<( const TiXmlAttribute& rhs ) const { return name < rhs.name; } + bool operator>( const TiXmlAttribute& rhs ) const { return name > rhs.name; } + + /* Attribute parsing starts: first letter of the name + returns: the next char after the value end quote + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + // Prints this Attribute to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + + // [internal use] + // Set the document pointer so the attribute can report errors. + void SetDocument( TiXmlDocument* doc ) { document = doc; } + +private: + TiXmlAttribute( const TiXmlAttribute& ); // not implemented. + void operator=( const TiXmlAttribute& base ); // not allowed. + + TiXmlDocument* document; // A pointer back to a document, for error reporting. + TIXML_STRING name; + TIXML_STRING value; + TiXmlAttribute* prev; + TiXmlAttribute* next; +}; + + +/* A class used to manage a group of attributes. + It is only used internally, both by the ELEMENT and the DECLARATION. + + The set can be changed transparent to the Element and Declaration + classes that use it, but NOT transparent to the Attribute + which has to implement a next() and previous() method. Which makes + it a bit problematic and prevents the use of STL. + + This version is implemented with circular lists because: + - I like circular lists + - it demonstrates some independence from the (typical) doubly linked list. +*/ +class TiXmlAttributeSet +{ +public: + TiXmlAttributeSet(); + ~TiXmlAttributeSet(); + + void Add( TiXmlAttribute* attribute ); + void Remove( TiXmlAttribute* attribute ); + + const TiXmlAttribute* First() const { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + TiXmlAttribute* First() { return ( sentinel.next == &sentinel ) ? 0 : sentinel.next; } + const TiXmlAttribute* Last() const { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + TiXmlAttribute* Last() { return ( sentinel.prev == &sentinel ) ? 0 : sentinel.prev; } + + const TiXmlAttribute* Find( const char* _name ) const; + TiXmlAttribute* Find( const char* _name ) { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) ); + } + #ifdef TIXML_USE_STL + const TiXmlAttribute* Find( const std::string& _name ) const; + TiXmlAttribute* Find( const std::string& _name ) { + return const_cast< TiXmlAttribute* >( (const_cast< const TiXmlAttributeSet* >(this))->Find( _name ) ); + } + + #endif + +private: + //*ME: Because of hidden/disabled copy-construktor in TiXmlAttribute (sentinel-element), + //*ME: this class must be also use a hidden/disabled copy-constructor !!! + TiXmlAttributeSet( const TiXmlAttributeSet& ); // not allowed + void operator=( const TiXmlAttributeSet& ); // not allowed (as TiXmlAttribute) + + TiXmlAttribute sentinel; +}; + + +/** The element is a container class. It has a value, the element name, + and can contain other elements, text, comments, and unknowns. + Elements also contain an arbitrary number of attributes. +*/ +class TiXmlElement : public TiXmlNode +{ +public: + /// Construct an element. + TiXmlElement (const char * in_value); + + #ifdef TIXML_USE_STL + /// std::string constructor. + TiXmlElement( const std::string& _value ); + #endif + + TiXmlElement( const TiXmlElement& ); + + void operator=( const TiXmlElement& base ); + + virtual ~TiXmlElement(); + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + */ + const char* Attribute( const char* name ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an integer, + the integer value will be put in the return 'i', if 'i' + is non-null. + */ + const char* Attribute( const char* name, int* i ) const; + + /** Given an attribute name, Attribute() returns the value + for the attribute of that name, or null if none exists. + If the attribute exists and can be converted to an double, + the double value will be put in the return 'd', if 'd' + is non-null. + */ + const char* Attribute( const char* name, double* d ) const; + + /** QueryIntAttribute examines the attribute - it is an alternative to the + Attribute() method with richer error checking. + If the attribute is an integer, it is stored in 'value' and + the call returns TIXML_SUCCESS. If it is not + an integer, it returns TIXML_WRONG_TYPE. If the attribute + does not exist, then TIXML_NO_ATTRIBUTE is returned. + */ + int QueryIntAttribute( const char* name, int* _value ) const; + /// QueryDoubleAttribute examines the attribute - see QueryIntAttribute(). + int QueryDoubleAttribute( const char* name, double* _value ) const; + /// QueryFloatAttribute examines the attribute - see QueryIntAttribute(). + int QueryFloatAttribute( const char* name, float* _value ) const { + double d; + int result = QueryDoubleAttribute( name, &d ); + if ( result == TIXML_SUCCESS ) { + *_value = (float)d; + } + return result; + } + + #ifdef TIXML_USE_STL + /** Template form of the attribute query which will try to read the + attribute into the specified type. Very easy, very powerful, but + be careful to make sure to call this with the correct type. + + NOTE: This method doesn't work correctly for 'string' types. + + @return TIXML_SUCCESS, TIXML_WRONG_TYPE, or TIXML_NO_ATTRIBUTE + */ + template< typename T > int QueryValueAttribute( const std::string& name, T* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + + std::stringstream sstream( node->ValueStr() ); + sstream >> *outValue; + if ( !sstream.fail() ) + return TIXML_SUCCESS; + return TIXML_WRONG_TYPE; + } + /* + This is - in theory - a bug fix for "QueryValueAtribute returns truncated std::string" + but template specialization is hard to get working cross-compiler. Leaving the bug for now. + + // The above will fail for std::string because the space character is used as a seperator. + // Specialize for strings. Bug [ 1695429 ] QueryValueAtribute returns truncated std::string + template<> int QueryValueAttribute( const std::string& name, std::string* outValue ) const + { + const TiXmlAttribute* node = attributeSet.Find( name ); + if ( !node ) + return TIXML_NO_ATTRIBUTE; + *outValue = node->ValueStr(); + return TIXML_SUCCESS; + } + */ + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char* name, const char * _value ); + + #ifdef TIXML_USE_STL + const std::string* Attribute( const std::string& name ) const; + const std::string* Attribute( const std::string& name, int* i ) const; + const std::string* Attribute( const std::string& name, double* d ) const; + int QueryIntAttribute( const std::string& name, int* _value ) const; + int QueryDoubleAttribute( const std::string& name, double* _value ) const; + + /// STL std::string form. + void SetAttribute( const std::string& name, const std::string& _value ); + ///< STL std::string form. + void SetAttribute( const std::string& name, int _value ); + #endif + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetAttribute( const char * name, int value ); + + /** Sets an attribute of name to a given value. The attribute + will be created if it does not exist, or changed if it does. + */ + void SetDoubleAttribute( const char * name, double value ); + + /** Deletes an attribute with the given name. + */ + void RemoveAttribute( const char * name ); + #ifdef TIXML_USE_STL + void RemoveAttribute( const std::string& name ) { RemoveAttribute (name.c_str ()); } ///< STL std::string form. + #endif + + const TiXmlAttribute* FirstAttribute() const { return attributeSet.First(); } ///< Access the first attribute in this element. + TiXmlAttribute* FirstAttribute() { return attributeSet.First(); } + const TiXmlAttribute* LastAttribute() const { return attributeSet.Last(); } ///< Access the last attribute in this element. + TiXmlAttribute* LastAttribute() { return attributeSet.Last(); } + + /** Convenience function for easy access to the text inside an element. Although easy + and concise, GetText() is limited compared to getting the TiXmlText child + and accessing it directly. + + If the first child of 'this' is a TiXmlText, the GetText() + returns the character string of the Text node, else null is returned. + + This is a convenient method for getting the text of simple contained text: + @verbatim + <foo>This is text</foo> + const char* str = fooElement->GetText(); + @endverbatim + + 'str' will be a pointer to "This is text". + + Note that this function can be misleading. If the element foo was created from + this XML: + @verbatim + <foo><b>This is text</b></foo> + @endverbatim + + then the value of str would be null. The first child node isn't a text node, it is + another element. From this XML: + @verbatim + <foo>This is <b>text</b></foo> + @endverbatim + GetText() will return "This is ". + + WARNING: GetText() accesses a child node - don't become confused with the + similarly named TiXmlHandle::Text() and TiXmlNode::ToText() which are + safe type casts on the referenced node. + */ + const char* GetText() const; + + /// Creates a new Element and returns it - the returned element is a copy. + virtual TiXmlNode* Clone() const; + // Print the Element to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: next char past '<' + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlElement* ToElement() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlElement* ToElement() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + + void CopyTo( TiXmlElement* target ) const; + void ClearThis(); // like clear, but initializes 'this' object as well + + // Used to be public [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + /* [internal use] + Reads the "value" of the element -- another element, or text. + This should terminate with the current end tag. + */ + const char* ReadValue( const char* in, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + +private: + + TiXmlAttributeSet attributeSet; +}; + + +/** An XML comment. +*/ +class TiXmlComment : public TiXmlNode +{ +public: + /// Constructs an empty comment. + TiXmlComment() : TiXmlNode( TiXmlNode::COMMENT ) {} + /// Construct a comment from text. + TiXmlComment( const char* _value ) : TiXmlNode( TiXmlNode::COMMENT ) { + SetValue( _value ); + } + TiXmlComment( const TiXmlComment& ); + void operator=( const TiXmlComment& base ); + + virtual ~TiXmlComment() {} + + /// Returns a copy of this Comment. + virtual TiXmlNode* Clone() const; + // Write this Comment to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /* Attribtue parsing starts: at the ! of the !-- + returns: next char past '>' + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlComment* ToComment() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlComment* ToComment() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlComment* target ) const; + + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif +// virtual void StreamOut( TIXML_OSTREAM * out ) const; + +private: + +}; + + +/** XML text. A text node can have 2 ways to output the next. "normal" output + and CDATA. It will default to the mode it was parsed from the XML file and + you generally want to leave it alone, but you can change the output mode with + SetCDATA() and query it with CDATA(). +*/ +class TiXmlText : public TiXmlNode +{ + friend class TiXmlElement; +public: + /** Constructor for text element. By default, it is treated as + normal, encoded text. If you want it be output as a CDATA text + element, set the parameter _cdata to 'true' + */ + TiXmlText (const char * initValue ) : TiXmlNode (TiXmlNode::TEXT) + { + SetValue( initValue ); + cdata = false; + } + virtual ~TiXmlText() {} + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlText( const std::string& initValue ) : TiXmlNode (TiXmlNode::TEXT) + { + SetValue( initValue ); + cdata = false; + } + #endif + + TiXmlText( const TiXmlText& copy ) : TiXmlNode( TiXmlNode::TEXT ) { copy.CopyTo( this ); } + void operator=( const TiXmlText& base ) { base.CopyTo( this ); } + + // Write this text object to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + /// Queries whether this represents text using a CDATA section. + bool CDATA() const { return cdata; } + /// Turns on or off a CDATA representation of text. + void SetCDATA( bool _cdata ) { cdata = _cdata; } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlText* ToText() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlText* ToText() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + /// [internal use] Creates a new Element and returns it. + virtual TiXmlNode* Clone() const; + void CopyTo( TiXmlText* target ) const; + + bool Blank() const; // returns true if all white space and new lines + // [internal use] + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + bool cdata; // true if this should be input and output as a CDATA style text element +}; + + +/** In correct XML the declaration is the first entry in the file. + @verbatim + <?xml version="1.0" standalone="yes"?> + @endverbatim + + TinyXml will happily read or write files without a declaration, + however. There are 3 possible attributes to the declaration: + version, encoding, and standalone. + + Note: In this version of the code, the attributes are + handled as special cases, not generic attributes, simply + because there can only be at most 3 and they are always the same. +*/ +class TiXmlDeclaration : public TiXmlNode +{ +public: + /// Construct an empty declaration. + TiXmlDeclaration() : TiXmlNode( TiXmlNode::DECLARATION ) {} + +#ifdef TIXML_USE_STL + /// Constructor. + TiXmlDeclaration( const std::string& _version, + const std::string& _encoding, + const std::string& _standalone ); +#endif + + /// Construct. + TiXmlDeclaration( const char* _version, + const char* _encoding, + const char* _standalone ); + + TiXmlDeclaration( const TiXmlDeclaration& copy ); + void operator=( const TiXmlDeclaration& copy ); + + virtual ~TiXmlDeclaration() {} + + /// Version. Will return an empty string if none was found. + const char *Version() const { return version.c_str (); } + /// Encoding. Will return an empty string if none was found. + const char *Encoding() const { return encoding.c_str (); } + /// Is this a standalone document? + const char *Standalone() const { return standalone.c_str (); } + + /// Creates a copy of this Declaration and returns it. + virtual TiXmlNode* Clone() const; + // Print this declaration to a FILE stream. + virtual void Print( FILE* cfile, int depth, TIXML_STRING* str ) const; + virtual void Print( FILE* cfile, int depth ) const { + Print( cfile, depth, 0 ); + } + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlDeclaration* ToDeclaration() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDeclaration* ToDeclaration() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* visitor ) const; + +protected: + void CopyTo( TiXmlDeclaration* target ) const; + // used to be public + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + + TIXML_STRING version; + TIXML_STRING encoding; + TIXML_STRING standalone; +}; + + +/** Any tag that tinyXml doesn't recognize is saved as an + unknown. It is a tag of text, but should not be modified. + It will be written back to the XML, unchanged, when the file + is saved. + + DTD tags get thrown into TiXmlUnknowns. +*/ +class TiXmlUnknown : public TiXmlNode +{ +public: + TiXmlUnknown() : TiXmlNode( TiXmlNode::UNKNOWN ) {} + virtual ~TiXmlUnknown() {} + + TiXmlUnknown( const TiXmlUnknown& copy ) : TiXmlNode( TiXmlNode::UNKNOWN ) { copy.CopyTo( this ); } + void operator=( const TiXmlUnknown& copy ) { copy.CopyTo( this ); } + + /// Creates a copy of this Unknown and returns it. + virtual TiXmlNode* Clone() const; + // Print this Unknown to a FILE stream. + virtual void Print( FILE* cfile, int depth ) const; + + virtual const char* Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ); + + virtual const TiXmlUnknown* ToUnknown() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlUnknown* ToUnknown() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected: + void CopyTo( TiXmlUnknown* target ) const; + + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + +}; + + +/** Always the top level node. A document binds together all the + XML pieces. It can be saved, loaded, and printed to the screen. + The 'value' of a document node is the xml file name. +*/ +class TiXmlDocument : public TiXmlNode +{ +public: + /// Create an empty document, that has no name. + TiXmlDocument(); + /// Create a document with a name. The name of the document is also the filename of the xml. + TiXmlDocument( const char * documentName ); + + // Altered header + bool ReadFromMemory( const char* pBuf, size_t sz, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + + #ifdef TIXML_USE_STL + /// Constructor. + TiXmlDocument( const std::string& documentName ); + #endif + + TiXmlDocument( const TiXmlDocument& copy ); + void operator=( const TiXmlDocument& copy ); + + virtual ~TiXmlDocument() {} + + /** Load a file using the current document value. + Returns true if successful. Will delete any existing + document data before loading. + */ + bool LoadFile( TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the current document value. Returns true if successful. + bool SaveFile() const; + /// Load a file using the given filename. Returns true if successful. + bool LoadFile( const char * filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given filename. Returns true if successful. + bool SaveFile( const char * filename ) const; + /** Load a file using the given FILE*. Returns true if successful. Note that this method + doesn't stream - the entire object pointed at by the FILE* + will be interpreted as an XML file. TinyXML doesn't stream in XML from the current + file location. Streaming may be added in the future. + */ + bool LoadFile( FILE*, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + /// Save a file using the given FILE*. Returns true if successful. + bool SaveFile( FILE* ) const; + + #ifdef TIXML_USE_STL + bool LoadFile( const std::string& filename, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ) ///< STL std::string version. + { +// StringToBuffer f( filename ); +// return ( f.buffer && LoadFile( f.buffer, encoding )); + return LoadFile( filename.c_str(), encoding ); + } + bool SaveFile( const std::string& filename ) const ///< STL std::string version. + { +// StringToBuffer f( filename ); +// return ( f.buffer && SaveFile( f.buffer )); + return SaveFile( filename.c_str() ); + } + #endif + + /** Parse the given null terminated block of xml data. Passing in an encoding to this + method (either TIXML_ENCODING_LEGACY or TIXML_ENCODING_UTF8 will force TinyXml + to use that encoding, regardless of what TinyXml might otherwise try to detect. + */ + virtual const char* Parse( const char* p, TiXmlParsingData* data = 0, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING ); + + /** Get the root element -- the only top level element -- of the document. + In well formed XML, there should only be one. TinyXml is tolerant of + multiple elements at the document level. + */ + const TiXmlElement* RootElement() const { return FirstChildElement(); } + TiXmlElement* RootElement() { return FirstChildElement(); } + + /** If an error occurs, Error will be set to true. Also, + - The ErrorId() will contain the integer identifier of the error (not generally useful) + - The ErrorDesc() method will return the name of the error. (very useful) + - The ErrorRow() and ErrorCol() will return the location of the error (if known) + */ + bool Error() const { return error; } + + /// Contains a textual (english) description of the error if one occurs. + const char * ErrorDesc() const { return errorDesc.c_str (); } + + /** Generally, you probably want the error string ( ErrorDesc() ). But if you + prefer the ErrorId, this function will fetch it. + */ + int ErrorId() const { return errorId; } + + /** Returns the location (if known) of the error. The first column is column 1, + and the first row is row 1. A value of 0 means the row and column wasn't applicable + (memory errors, for example, have no row/column) or the parser lost the error. (An + error in the error reporting, in that case.) + + @sa SetTabSize, Row, Column + */ + int ErrorRow() const { return errorLocation.row+1; } + int ErrorCol() const { return errorLocation.col+1; } ///< The column where the error occured. See ErrorRow() + + /** SetTabSize() allows the error reporting functions (ErrorRow() and ErrorCol()) + to report the correct values for row and column. It does not change the output + or input in any way. + + By calling this method, with a tab size + greater than 0, the row and column of each node and attribute is stored + when the file is loaded. Very useful for tracking the DOM back in to + the source file. + + The tab size is required for calculating the location of nodes. If not + set, the default of 4 is used. The tabsize is set per document. Setting + the tabsize to 0 disables row/column tracking. + + Note that row and column tracking is not supported when using operator>>. + + The tab size needs to be enabled before the parse or load. Correct usage: + @verbatim + TiXmlDocument doc; + doc.SetTabSize( 8 ); + doc.Load( "myfile.xml" ); + @endverbatim + + @sa Row, Column + */ + void SetTabSize( int _tabsize ) { tabsize = _tabsize; } + + int TabSize() const { return tabsize; } + + /** If you have handled the error, it can be reset with this call. The error + state is automatically cleared if you Parse a new XML block. + */ + void ClearError() { error = false; + errorId = 0; + errorDesc = ""; + errorLocation.row = errorLocation.col = 0; + //errorLocation.last = 0; + } + + /** Write the document to standard out using formatted printing ("pretty print"). */ + void Print() const { Print( stdout, 0 ); } + + /* Write the document to a string using formatted printing ("pretty print"). This + will allocate a character array (new char[]) and return it as a pointer. The + calling code pust call delete[] on the return char* to avoid a memory leak. + */ + //char* PrintToMemory() const; + + /// Print this Document to a FILE stream. + virtual void Print( FILE* cfile, int depth = 0 ) const; + // [internal use] + void SetError( int err, const char* errorLocation, TiXmlParsingData* prevData, TiXmlEncoding encoding ); + + virtual const TiXmlDocument* ToDocument() const { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + virtual TiXmlDocument* ToDocument() { return this; } ///< Cast to a more defined type. Will return null not of the requested type. + + /** Walk the XML tree visiting this node and all of its children. + */ + virtual bool Accept( TiXmlVisitor* content ) const; + +protected : + // [internal use] + virtual TiXmlNode* Clone() const; + #ifdef TIXML_USE_STL + virtual void StreamIn( std::istream * in, TIXML_STRING * tag ); + #endif + +private: + void CopyTo( TiXmlDocument* target ) const; + + bool error; + int errorId; + TIXML_STRING errorDesc; + int tabsize; + TiXmlCursor errorLocation; + bool useMicrosoftBOM; // the UTF-8 BOM were found when read. Note this, and try to write. +}; + + +/** + A TiXmlHandle is a class that wraps a node pointer with null checks; this is + an incredibly useful thing. Note that TiXmlHandle is not part of the TinyXml + DOM structure. It is a separate utility class. + + Take an example: + @verbatim + <Document> + <Element attributeA = "valueA"> + <Child attributeB = "value1" /> + <Child attributeB = "value2" /> + </Element> + <Document> + @endverbatim + + Assuming you want the value of "attributeB" in the 2nd "Child" element, it's very + easy to write a *lot* of code that looks like: + + @verbatim + TiXmlElement* root = document.FirstChildElement( "Document" ); + if ( root ) + { + TiXmlElement* element = root->FirstChildElement( "Element" ); + if ( element ) + { + TiXmlElement* child = element->FirstChildElement( "Child" ); + if ( child ) + { + TiXmlElement* child2 = child->NextSiblingElement( "Child" ); + if ( child2 ) + { + // Finally do something useful. + @endverbatim + + And that doesn't even cover "else" cases. TiXmlHandle addresses the verbosity + of such code. A TiXmlHandle checks for null pointers so it is perfectly safe + and correct to use: + + @verbatim + TiXmlHandle docHandle( &document ); + TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement(); + if ( child2 ) + { + // do something useful + @endverbatim + + Which is MUCH more concise and useful. + + It is also safe to copy handles - internally they are nothing more than node pointers. + @verbatim + TiXmlHandle handleCopy = handle; + @endverbatim + + What they should not be used for is iteration: + + @verbatim + int i=0; + while ( true ) + { + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", i ).ToElement(); + if ( !child ) + break; + // do something + ++i; + } + @endverbatim + + It seems reasonable, but it is in fact two embedded while loops. The Child method is + a linear walk to find the element, so this code would iterate much more than it needs + to. Instead, prefer: + + @verbatim + TiXmlElement* child = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).FirstChild( "Child" ).ToElement(); + + for( child; child; child=child->NextSiblingElement() ) + { + // do something + } + @endverbatim +*/ +class TiXmlHandle +{ +public: + /// Create a handle from any node (at any depth of the tree.) This can be a null pointer. + TiXmlHandle( TiXmlNode* _node ) { this->node = _node; } + /// Copy constructor + TiXmlHandle( const TiXmlHandle& ref ) { this->node = ref.node; } + TiXmlHandle operator=( const TiXmlHandle& ref ) { this->node = ref.node; return *this; } + + /// Return a handle to the first child node. + TiXmlHandle FirstChild() const; + /// Return a handle to the first child node with the given name. + TiXmlHandle FirstChild( const char * value ) const; + /// Return a handle to the first child element. + TiXmlHandle FirstChildElement() const; + /// Return a handle to the first child element with the given name. + TiXmlHandle FirstChildElement( const char * value ) const; + + /** Return a handle to the "index" child with the given name. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( const char* value, int index ) const; + /** Return a handle to the "index" child. + The first child is 0, the second 1, etc. + */ + TiXmlHandle Child( int index ) const; + /** Return a handle to the "index" child element with the given name. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( const char* value, int index ) const; + /** Return a handle to the "index" child element. + The first child element is 0, the second 1, etc. Note that only TiXmlElements + are indexed: other types are not counted. + */ + TiXmlHandle ChildElement( int index ) const; + + #ifdef TIXML_USE_STL + TiXmlHandle FirstChild( const std::string& _value ) const { return FirstChild( _value.c_str() ); } + TiXmlHandle FirstChildElement( const std::string& _value ) const { return FirstChildElement( _value.c_str() ); } + + TiXmlHandle Child( const std::string& _value, int index ) const { return Child( _value.c_str(), index ); } + TiXmlHandle ChildElement( const std::string& _value, int index ) const { return ChildElement( _value.c_str(), index ); } + #endif + + /** Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* ToNode() const { return node; } + /** Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* ToElement() const { return ( ( node && node->ToElement() ) ? node->ToElement() : 0 ); } + /** Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* ToText() const { return ( ( node && node->ToText() ) ? node->ToText() : 0 ); } + /** Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* ToUnknown() const { return ( ( node && node->ToUnknown() ) ? node->ToUnknown() : 0 ); } + + /** @deprecated use ToNode. + Return the handle as a TiXmlNode. This may return null. + */ + TiXmlNode* Node() const { return ToNode(); } + /** @deprecated use ToElement. + Return the handle as a TiXmlElement. This may return null. + */ + TiXmlElement* Element() const { return ToElement(); } + /** @deprecated use ToText() + Return the handle as a TiXmlText. This may return null. + */ + TiXmlText* Text() const { return ToText(); } + /** @deprecated use ToUnknown() + Return the handle as a TiXmlUnknown. This may return null. + */ + TiXmlUnknown* Unknown() const { return ToUnknown(); } + +private: + TiXmlNode* node; +}; + + +/** Print to memory functionality. The TiXmlPrinter is useful when you need to: + + -# Print to memory (especially in non-STL mode) + -# Control formatting (line endings, etc.) + + When constructed, the TiXmlPrinter is in its default "pretty printing" mode. + Before calling Accept() you can call methods to control the printing + of the XML document. After TiXmlNode::Accept() is called, the printed document can + be accessed via the CStr(), Str(), and Size() methods. + + TiXmlPrinter uses the Visitor API. + @verbatim + TiXmlPrinter printer; + printer.SetIndent( "\t" ); + + doc.Accept( &printer ); + fprintf( stdout, "%s", printer.CStr() ); + @endverbatim +*/ +class TiXmlPrinter : public TiXmlVisitor +{ +public: + TiXmlPrinter() : depth( 0 ), simpleTextPrint( false ), + buffer(), indent( " " ), lineBreak( "\n" ) {} + + virtual bool VisitEnter( const TiXmlDocument& doc ); + virtual bool VisitExit( const TiXmlDocument& doc ); + + virtual bool VisitEnter( const TiXmlElement& element, const TiXmlAttribute* firstAttribute ); + virtual bool VisitExit( const TiXmlElement& element ); + + virtual bool Visit( const TiXmlDeclaration& declaration ); + virtual bool Visit( const TiXmlText& text ); + virtual bool Visit( const TiXmlComment& comment ); + virtual bool Visit( const TiXmlUnknown& unknown ); + + /** Set the indent characters for printing. By default 4 spaces + but tab (\t) is also useful, or null/empty string for no indentation. + */ + void SetIndent( const char* _indent ) { indent = _indent ? _indent : "" ; } + /// Query the indention string. + const char* Indent() { return indent.c_str(); } + /** Set the line breaking string. By default set to newline (\n). + Some operating systems prefer other characters, or can be + set to the null/empty string for no indenation. + */ + void SetLineBreak( const char* _lineBreak ) { lineBreak = _lineBreak ? _lineBreak : ""; } + /// Query the current line breaking string. + const char* LineBreak() { return lineBreak.c_str(); } + + /** Switch over to "stream printing" which is the most dense formatting without + linebreaks. Common when the XML is needed for network transmission. + */ + void SetStreamPrinting() { indent = ""; + lineBreak = ""; + } + /// Return the result. + const char* CStr() { return buffer.c_str(); } + /// Return the length of the result string. + size_t Size() { return buffer.size(); } + + #ifdef TIXML_USE_STL + /// Return the result. + const std::string& Str() { return buffer; } + #endif + +private: + void DoIndent() { + for( int i=0; i<depth; ++i ) + buffer += indent; + } + void DoLineBreak() { + buffer += lineBreak; + } + + int depth; + bool simpleTextPrint; + TIXML_STRING buffer; + TIXML_STRING indent; + TIXML_STRING lineBreak; +}; + + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif + diff --git a/rotord/src/tinyxmlerror.cpp b/rotord/src/tinyxmlerror.cpp new file mode 100755 index 0000000..d24f63b --- /dev/null +++ b/rotord/src/tinyxmlerror.cpp @@ -0,0 +1,53 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code (2.0 and earlier )copyright (c) 2000-2006 Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "tinyxml.h" + +// The goal of the seperate error file is to make the first +// step towards localization. tinyxml (currently) only supports +// english error messages, but the could now be translated. +// +// It also cleans up the code a bit. +// + +const char* TiXmlBase::errorString[ TIXML_ERROR_STRING_COUNT ] = +{ + "No error", + "Error", + "Failed to open file", + "Memory allocation failed.", + "Error parsing Element.", + "Failed to read Element name", + "Error reading Element value.", + "Error reading Attributes.", + "Error: empty tag.", + "Error reading end tag.", + "Error parsing Unknown.", + "Error parsing Comment.", + "Error parsing Declaration.", + "Error document empty.", + "Error null (0) or unexpected EOF found in input stream.", + "Error parsing CDATA.", + "Error when TiXmlDocument added to document, because TiXmlDocument can only be at the root.", +}; diff --git a/rotord/src/tinyxmlparser.cpp b/rotord/src/tinyxmlparser.cpp new file mode 100755 index 0000000..c672283 --- /dev/null +++ b/rotord/src/tinyxmlparser.cpp @@ -0,0 +1,1719 @@ +/* +www.sourceforge.net/projects/tinyxml +Original code (2.0 and earlier )copyright (c) 2000-2002 Lee Thomason (www.grinninglizard.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include <ctype.h> +#include <stddef.h> + +#include "tinyxml.h" + +//#define DEBUG_PARSER +#if defined( DEBUG_PARSER ) +# if defined( DEBUG ) && defined( _MSC_VER ) +# include <windows.h> +# define TIXML_LOG OutputDebugString +# else +# define TIXML_LOG printf +# endif +#endif + +// Note tha "PutString" hardcodes the same list. This +// is less flexible than it appears. Changing the entries +// or order will break putstring. +TiXmlBase::Entity TiXmlBase::entity[ NUM_ENTITY ] = +{ + { "&", 5, '&' }, + { "<", 4, '<' }, + { ">", 4, '>' }, + { """, 6, '\"' }, + { "'", 6, '\'' } +}; + +// Bunch of unicode info at: +// http://www.unicode.org/faq/utf_bom.html +// Including the basic of this table, which determines the #bytes in the +// sequence from the lead byte. 1 placed for invalid sequences -- +// although the result will be junk, pass it through as much as possible. +// Beware of the non-characters in UTF-8: +// ef bb bf (Microsoft "lead bytes") +// ef bf be +// ef bf bf + +const unsigned char TIXML_UTF_LEAD_0 = 0xefU; +const unsigned char TIXML_UTF_LEAD_1 = 0xbbU; +const unsigned char TIXML_UTF_LEAD_2 = 0xbfU; + +const int TiXmlBase::utf8ByteTable[256] = +{ + // 0 1 2 3 4 5 6 7 8 9 a b c d e f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x00 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x10 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x30 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 End of ASCII range + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 0x80 to 0xc1 invalid + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 + 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 0xc2 to 0xdf 2 byte + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 0xe0 to 0xef 3 byte + 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 // 0xf0 0xf0 to 0xf4 4 byte, 0xf5 and higher invalid +}; + + +void TiXmlBase::ConvertUTF32ToUTF8( unsigned long input, char* output, int* length ) +{ + const unsigned long BYTE_MASK = 0xBF; + const unsigned long BYTE_MARK = 0x80; + const unsigned long FIRST_BYTE_MARK[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + if (input < 0x80) + *length = 1; + else if ( input < 0x800 ) + *length = 2; + else if ( input < 0x10000 ) + *length = 3; + else if ( input < 0x200000 ) + *length = 4; + else + { *length = 0; return; } // This code won't covert this correctly anyway. + + output += *length; + + // Scary scary fall throughs. + switch (*length) + { + case 4: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 3: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 2: + --output; + *output = (char)((input | BYTE_MARK) & BYTE_MASK); + input >>= 6; + case 1: + --output; + *output = (char)(input | FIRST_BYTE_MARK[*length]); + } +} + + +/*static*/ int TiXmlBase::IsAlpha( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalpha( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalpha( anyByte ); +// } +} + + +/*static*/ int TiXmlBase::IsAlphaNum( unsigned char anyByte, TiXmlEncoding /*encoding*/ ) +{ + // This will only work for low-ascii, everything else is assumed to be a valid + // letter. I'm not sure this is the best approach, but it is quite tricky trying + // to figure out alhabetical vs. not across encoding. So take a very + // conservative approach. + +// if ( encoding == TIXML_ENCODING_UTF8 ) +// { + if ( anyByte < 127 ) + return isalnum( anyByte ); + else + return 1; // What else to do? The unicode set is huge...get the english ones right. +// } +// else +// { +// return isalnum( anyByte ); +// } +} + + +class TiXmlParsingData +{ + friend class TiXmlDocument; + public: + void Stamp( const char* now, TiXmlEncoding encoding ); + + const TiXmlCursor& Cursor() { return cursor; } + + private: + // Only used by the document! + TiXmlParsingData( const char* start, int _tabsize, int row, int col ) + { + assert( start ); + stamp = start; + tabsize = _tabsize; + cursor.row = row; + cursor.col = col; + } + + TiXmlCursor cursor; + const char* stamp; + int tabsize; +}; + + +void TiXmlParsingData::Stamp( const char* now, TiXmlEncoding encoding ) +{ + assert( now ); + + // Do nothing if the tabsize is 0. + if ( tabsize < 1 ) + { + return; + } + + // Get the current row, column. + int row = cursor.row; + int col = cursor.col; + const char* p = stamp; + assert( p ); + + while ( p < now ) + { + // Treat p as unsigned, so we have a happy compiler. + const unsigned char* pU = (const unsigned char*)p; + + // Code contributed by Fletcher Dunn: (modified by lee) + switch (*pU) { + case 0: + // We *should* never get here, but in case we do, don't + // advance past the terminating null character, ever + return; + + case '\r': + // bump down to the next line + ++row; + col = 0; + // Eat the character + ++p; + + // Check for \r\n sequence, and treat this as a single character + if (*p == '\n') { + ++p; + } + break; + + case '\n': + // bump down to the next line + ++row; + col = 0; + + // Eat the character + ++p; + + // Check for \n\r sequence, and treat this as a single + // character. (Yes, this bizarre thing does occur still + // on some arcane platforms...) + if (*p == '\r') { + ++p; + } + break; + + case '\t': + // Eat the character + ++p; + + // Skip to next tab stop + col = (col / tabsize + 1) * tabsize; + break; + + case TIXML_UTF_LEAD_0: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + if ( *(p+1) && *(p+2) ) + { + // In these cases, don't advance the column. These are + // 0-width spaces. + if ( *(pU+1)==TIXML_UTF_LEAD_1 && *(pU+2)==TIXML_UTF_LEAD_2 ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbeU ) + p += 3; + else if ( *(pU+1)==0xbfU && *(pU+2)==0xbfU ) + p += 3; + else + { p +=3; ++col; } // A normal character. + } + } + else + { + ++p; + ++col; + } + break; + + default: + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // Eat the 1 to 4 byte utf8 character. + int step = TiXmlBase::utf8ByteTable[*((const unsigned char*)p)]; + if ( step == 0 ) + step = 1; // Error case from bad encoding, but handle gracefully. + p += step; + + // Just advance one column, of course. + ++col; + } + else + { + ++p; + ++col; + } + break; + } + } + cursor.row = row; + cursor.col = col; + assert( cursor.row >= -1 ); + assert( cursor.col >= -1 ); + stamp = p; + assert( stamp ); +} + + +const char* TiXmlBase::SkipWhiteSpace( const char* p, TiXmlEncoding encoding ) +{ + if ( !p || !*p ) + { + return 0; + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + while ( *p ) + { + const unsigned char* pU = (const unsigned char*)p; + + // Skip the stupid Microsoft UTF-8 Byte order marks + if ( *(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==TIXML_UTF_LEAD_1 + && *(pU+2)==TIXML_UTF_LEAD_2 ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbeU ) + { + p += 3; + continue; + } + else if(*(pU+0)==TIXML_UTF_LEAD_0 + && *(pU+1)==0xbfU + && *(pU+2)==0xbfU ) + { + p += 3; + continue; + } + + if ( IsWhiteSpace( *p ) || *p == '\n' || *p =='\r' ) // Still using old rules for white space. + ++p; + else + break; + } + } + else + { + while ( (*p && IsWhiteSpace( *p )) || *p == '\n' || *p =='\r' ) + ++p; + } + + return p; +} + +#ifdef TIXML_USE_STL +/*static*/ bool TiXmlBase::StreamWhiteSpace( std::istream * in, TIXML_STRING * tag ) +{ + for( ;; ) + { + if ( !in->good() ) return false; + + int c = in->peek(); + // At this scope, we can't get to a document. So fail silently. + if ( !IsWhiteSpace( c ) || c <= 0 ) + return true; + + *tag += (char) in->get(); + } +} + +/*static*/ bool TiXmlBase::StreamTo( std::istream * in, int character, TIXML_STRING * tag ) +{ + //assert( character > 0 && character < 128 ); // else it won't work in utf-8 + while ( in->good() ) + { + int c = in->peek(); + if ( c == character ) + return true; + if ( c <= 0 ) // Silent failure: can't get document at this scope + return false; + + in->get(); + *tag += (char) c; + } + return false; +} +#endif + +// One of TinyXML's more performance demanding functions. Try to keep the memory overhead down. The +// "assign" optimization removes over 10% of the execution time. +// +const char* TiXmlBase::ReadName( const char* p, TIXML_STRING * name, TiXmlEncoding encoding ) +{ + // Oddly, not supported on some comilers, + //name->clear(); + // So use this: + *name = ""; + assert( p ); + + // Names start with letters or underscores. + // Of course, in unicode, tinyxml has no idea what a letter *is*. The + // algorithm is generous. + // + // After that, they can be letters, underscores, numbers, + // hyphens, or colons. (Colons are valid ony for namespaces, + // but tinyxml can't tell namespaces from names.) + if ( p && *p + && ( IsAlpha( (unsigned char) *p, encoding ) || *p == '_' ) ) + { + const char* start = p; + while( p && *p + && ( IsAlphaNum( (unsigned char ) *p, encoding ) + || *p == '_' + || *p == '-' + || *p == '.' + || *p == ':' ) ) + { + //(*name) += *p; // expensive + ++p; + } + if ( p-start > 0 ) { + name->assign( start, p-start ); + } + return p; + } + return 0; +} + +const char* TiXmlBase::GetEntity( const char* p, char* value, int* length, TiXmlEncoding encoding ) +{ + // Presume an entity, and pull it out. + TIXML_STRING ent; + int i; + *length = 0; + + if ( *(p+1) && *(p+1) == '#' && *(p+2) ) + { + unsigned long ucs = 0; + ptrdiff_t delta = 0; + unsigned mult = 1; + + if ( *(p+2) == 'x' ) + { + // Hexadecimal. + if ( !*(p+3) ) return 0; + + const char* q = p+3; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != 'x' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else if ( *q >= 'a' && *q <= 'f' ) + ucs += mult * (*q - 'a' + 10); + else if ( *q >= 'A' && *q <= 'F' ) + ucs += mult * (*q - 'A' + 10 ); + else + return 0; + mult *= 16; + --q; + } + } + else + { + // Decimal. + if ( !*(p+2) ) return 0; + + const char* q = p+2; + q = strchr( q, ';' ); + + if ( !q || !*q ) return 0; + + delta = q-p; + --q; + + while ( *q != '#' ) + { + if ( *q >= '0' && *q <= '9' ) + ucs += mult * (*q - '0'); + else + return 0; + mult *= 10; + --q; + } + } + if ( encoding == TIXML_ENCODING_UTF8 ) + { + // convert the UCS to UTF-8 + ConvertUTF32ToUTF8( ucs, value, length ); + } + else + { + *value = (char)ucs; + *length = 1; + } + return p + delta + 1; + } + + // Now try to match it. + for( i=0; i<NUM_ENTITY; ++i ) + { + if ( strncmp( entity[i].str, p, entity[i].strLength ) == 0 ) + { + assert( strlen( entity[i].str ) == entity[i].strLength ); + *value = entity[i].chr; + *length = 1; + return ( p + entity[i].strLength ); + } + } + + // So it wasn't an entity, its unrecognized, or something like that. + *value = *p; // Don't put back the last one, since we return it! + //*length = 1; // Leave unrecognized entities - this doesn't really work. + // Just writes strange XML. + return p+1; +} + + +bool TiXmlBase::StringEqual( const char* p, + const char* tag, + bool ignoreCase, + TiXmlEncoding encoding ) +{ + assert( p ); + assert( tag ); + if ( !p || !*p ) + { + assert( 0 ); + return false; + } + + const char* q = p; + + if ( ignoreCase ) + { + while ( *q && *tag && ToLower( *q, encoding ) == ToLower( *tag, encoding ) ) + { + ++q; + ++tag; + } + + if ( *tag == 0 ) + return true; + } + else + { + while ( *q && *tag && *q == *tag ) + { + ++q; + ++tag; + } + + if ( *tag == 0 ) // Have we found the end of the tag, and everything equal? + return true; + } + return false; +} + +const char* TiXmlBase::ReadText( const char* p, + TIXML_STRING * text, + bool trimWhiteSpace, + const char* endTag, + bool caseInsensitive, + TiXmlEncoding encoding ) +{ + *text = ""; + if ( !trimWhiteSpace // certain tags always keep whitespace + || !condenseWhiteSpace ) // if true, whitespace is always kept + { + // Keep all the white space. + while ( p && *p + && !StringEqual( p, endTag, caseInsensitive, encoding ) + ) + { + int len; + char cArr[4] = { 0, 0, 0, 0 }; + p = GetChar( p, cArr, &len, encoding ); + text->append( cArr, len ); + } + } + else + { + bool whitespace = false; + + // Remove leading white space: + p = SkipWhiteSpace( p, encoding ); + while ( p && *p + && !StringEqual( p, endTag, caseInsensitive, encoding ) ) + { + if ( *p == '\r' || *p == '\n' ) + { + whitespace = true; + ++p; + } + else if ( IsWhiteSpace( *p ) ) + { + whitespace = true; + ++p; + } + else + { + // If we've found whitespace, add it before the + // new character. Any whitespace just becomes a space. + if ( whitespace ) + { + (*text) += ' '; + whitespace = false; + } + int len; + char cArr[4] = { 0, 0, 0, 0 }; + p = GetChar( p, cArr, &len, encoding ); + if ( len == 1 ) + (*text) += cArr[0]; // more efficient + else + text->append( cArr, len ); + } + } + } + if ( p ) + p += strlen( endTag ); + return p; +} + +#ifdef TIXML_USE_STL + +void TiXmlDocument::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + // The basic issue with a document is that we don't know what we're + // streaming. Read something presumed to be a tag (and hope), then + // identify it, and call the appropriate stream method on the tag. + // + // This "pre-streaming" will never read the closing ">" so the + // sub-tag can orient itself. + + if ( !StreamTo( in, '<', tag ) ) + { + SetError( TIXML_ERROR_PARSING_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + while ( in->good() ) + { + int tagIndex = (int) tag->length(); + while ( in->good() && in->peek() != '>' ) + { + int c = in->get(); + if ( c <= 0 ) + { + SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + break; + } + (*tag) += (char) c; + } + + if ( in->good() ) + { + // We now have something we presume to be a node of + // some sort. Identify it, and call the node to + // continue streaming. + TiXmlNode* node = Identify( tag->c_str() + tagIndex, TIXML_DEFAULT_ENCODING ); + + if ( node ) + { + node->StreamIn( in, tag ); + bool isElement = node->ToElement() != 0; + delete node; + node = 0; + + // If this is the root element, we're done. Parsing will be + // done by the >> operator. + if ( isElement ) + { + return; + } + } + else + { + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + } + } + // We should have returned sooner. + SetError( TIXML_ERROR, 0, 0, TIXML_ENCODING_UNKNOWN ); +} + +#endif + +const char* TiXmlDocument::Parse( const char* p, TiXmlParsingData* prevData, TiXmlEncoding encoding ) +{ + ClearError(); + + // Parse away, at the document level. Since a document + // contains nothing but other tags, most of what happens + // here is skipping white space. + if ( !p || !*p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + // Note that, for a document, this needs to come + // before the while space skip, so that parsing + // starts from the pointer we are given. + location.Clear(); + if ( prevData ) + { + location.row = prevData->cursor.row; + location.col = prevData->cursor.col; + } + else + { + location.row = 0; + location.col = 0; + } + TiXmlParsingData data( p, TabSize(), location.row, location.col ); + location = data.Cursor(); + + if ( encoding == TIXML_ENCODING_UNKNOWN ) + { + // Check for the Microsoft UTF-8 lead bytes. + const unsigned char* pU = (const unsigned char*)p; + if ( *(pU+0) && *(pU+0) == TIXML_UTF_LEAD_0 + && *(pU+1) && *(pU+1) == TIXML_UTF_LEAD_1 + && *(pU+2) && *(pU+2) == TIXML_UTF_LEAD_2 ) + { + encoding = TIXML_ENCODING_UTF8; + useMicrosoftBOM = true; + } + } + + p = SkipWhiteSpace( p, encoding ); + if ( !p ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return 0; + } + + while ( p && *p ) + { + TiXmlNode* node = Identify( p, encoding ); + if ( node ) + { + p = node->Parse( p, &data, encoding ); + LinkEndChild( node ); + } + else + { + break; + } + + // Did we get encoding info? + if ( encoding == TIXML_ENCODING_UNKNOWN + && node->ToDeclaration() ) + { + TiXmlDeclaration* dec = node->ToDeclaration(); + const char* enc = dec->Encoding(); + assert( enc ); + + if ( *enc == 0 ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF-8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; + else if ( StringEqual( enc, "UTF8", true, TIXML_ENCODING_UNKNOWN ) ) + encoding = TIXML_ENCODING_UTF8; // incorrect, but be nice + else + encoding = TIXML_ENCODING_LEGACY; + } + + p = SkipWhiteSpace( p, encoding ); + } + + // Was this empty? + if ( !firstChild ) { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, encoding ); + return 0; + } + + // All is well. + return p; +} + +void TiXmlDocument::SetError( int err, const char* pError, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + // The first error in a chain is more accurate - don't set again! + if ( error ) + return; + + assert( err > 0 && err < TIXML_ERROR_STRING_COUNT ); + error = true; + errorId = err; + errorDesc = errorString[ errorId ]; + + errorLocation.Clear(); + if ( pError && data ) + { + data->Stamp( pError, encoding ); + errorLocation = data->Cursor(); + } +} + + +TiXmlNode* TiXmlNode::Identify( const char* p, TiXmlEncoding encoding ) +{ + TiXmlNode* returnNode = 0; + + p = SkipWhiteSpace( p, encoding ); + if( !p || !*p || *p != '<' ) + { + return 0; + } + + TiXmlDocument* doc = GetDocument(); + p = SkipWhiteSpace( p, encoding ); + + if ( !p || !*p ) + { + return 0; + } + + // What is this thing? + // - Elements start with a letter or underscore, but xml is reserved. + // - Comments: <!-- + // - Decleration: <?xml + // - Everthing else is unknown to tinyxml. + // + + const char* xmlHeader = { "<?xml" }; + const char* commentHeader = { "<!--" }; + const char* dtdHeader = { "<!" }; + const char* cdataHeader = { "<![CDATA[" }; + + if ( StringEqual( p, xmlHeader, true, encoding ) ) + { + #ifdef DEBUG_PARSER + TIXML_LOG( "XML parsing Declaration\n" ); + #endif + returnNode = new TiXmlDeclaration(); + } + else if ( StringEqual( p, commentHeader, false, encoding ) ) + { + #ifdef DEBUG_PARSER + TIXML_LOG( "XML parsing Comment\n" ); + #endif + returnNode = new TiXmlComment(); + } + else if ( StringEqual( p, cdataHeader, false, encoding ) ) + { + #ifdef DEBUG_PARSER + TIXML_LOG( "XML parsing CDATA\n" ); + #endif + TiXmlText* text = new TiXmlText( "" ); + text->SetCDATA( true ); + returnNode = text; + } + else if ( StringEqual( p, dtdHeader, false, encoding ) ) + { + #ifdef DEBUG_PARSER + TIXML_LOG( "XML parsing Unknown(1)\n" ); + #endif + returnNode = new TiXmlUnknown(); + } + else if ( IsAlpha( *(p+1), encoding ) + || *(p+1) == '_' ) + { + #ifdef DEBUG_PARSER + TIXML_LOG( "XML parsing Element\n" ); + #endif + returnNode = new TiXmlElement( "" ); + } + else + { + #ifdef DEBUG_PARSER + TIXML_LOG( "XML parsing Unknown(2)\n" ); + #endif + returnNode = new TiXmlUnknown(); + } + + if ( returnNode ) + { + // Set the parent, so it can report errors + returnNode->parent = this; + } + else + { + if ( doc ) + doc->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, TIXML_ENCODING_UNKNOWN ); + } + return returnNode; +} + +#ifdef TIXML_USE_STL + +void TiXmlElement::StreamIn (std::istream * in, TIXML_STRING * tag) +{ + // We're called with some amount of pre-parsing. That is, some of "this" + // element is in "tag". Go ahead and stream to the closing ">" + while( in->good() ) + { + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + (*tag) += (char) c ; + + if ( c == '>' ) + break; + } + + if ( tag->length() < 3 ) return; + + // Okay...if we are a "/>" tag, then we're done. We've read a complete tag. + // If not, identify and stream. + + if ( tag->at( tag->length() - 1 ) == '>' + && tag->at( tag->length() - 2 ) == '/' ) + { + // All good! + return; + } + else if ( tag->at( tag->length() - 1 ) == '>' ) + { + // There is more. Could be: + // text + // cdata text (which looks like another node) + // closing tag + // another node. + for ( ;; ) + { + StreamWhiteSpace( in, tag ); + + // Do we have text? + if ( in->good() && in->peek() != '<' ) + { + // Yep, text. + TiXmlText text( "" ); + text.StreamIn( in, tag ); + + // What follows text is a closing tag or another node. + // Go around again and figure it out. + continue; + } + + // We now have either a closing tag...or another node. + // We should be at a "<", regardless. + if ( !in->good() ) return; + assert( in->peek() == '<' ); + int tagIndex = (int) tag->length(); + + bool closingTag = false; + bool firstCharFound = false; + + for( ;; ) + { + if ( !in->good() ) + return; + + int c = in->peek(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + if ( c == '>' ) + break; + + *tag += (char) c; + in->get(); + + // Early out if we find the CDATA id. + if ( c == '[' && tag->size() >= 9 ) + { + size_t len = tag->size(); + const char* start = tag->c_str() + len - 9; + if ( strcmp( start, "<![CDATA[" ) == 0 ) { + assert( !closingTag ); + break; + } + } + + if ( !firstCharFound && c != '<' && !IsWhiteSpace( c ) ) + { + firstCharFound = true; + if ( c == '/' ) + closingTag = true; + } + } + // If it was a closing tag, then read in the closing '>' to clean up the input stream. + // If it was not, the streaming will be done by the tag. + if ( closingTag ) + { + if ( !in->good() ) + return; + + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + assert( c == '>' ); + *tag += (char) c; + + // We are done, once we've found our closing tag. + return; + } + else + { + // If not a closing tag, id it, and stream. + const char* tagloc = tag->c_str() + tagIndex; + TiXmlNode* node = Identify( tagloc, TIXML_DEFAULT_ENCODING ); + if ( !node ) + return; + node->StreamIn( in, tag ); + delete node; + node = 0; + + // No return: go around from the beginning: text, closing tag, or node. + } + } + } +} +#endif + +const char* TiXmlElement::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + p = SkipWhiteSpace( p, encoding ); + TiXmlDocument* document = GetDocument(); + + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, 0, 0, encoding ); + return 0; + } + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + + if ( *p != '<' ) + { + if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, p, data, encoding ); + return 0; + } + + p = SkipWhiteSpace( p+1, encoding ); + + // Read the name. + const char* pErr = p; + + p = ReadName( p, &value, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_FAILED_TO_READ_ELEMENT_NAME, pErr, data, encoding ); + return 0; + } + + TIXML_STRING endTag ("</"); + endTag += value; + endTag += ">"; + + // Check for and read attributes. Also look for an empty + // tag or an end tag. + while ( p && *p ) + { + pErr = p; + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding ); + return 0; + } + if ( *p == '/' ) + { + ++p; + // Empty tag. + if ( *p != '>' ) + { + if ( document ) document->SetError( TIXML_ERROR_PARSING_EMPTY, p, data, encoding ); + return 0; + } + return (p+1); + } + else if ( *p == '>' ) + { + // Done with attributes (if there were any.) + // Read the value -- which can include other + // elements -- read the end tag, and return. + ++p; + p = ReadValue( p, data, encoding ); // Note this is an Element method, and will set the error if one happens. + if ( !p || !*p ) { + // We were looking for the end tag, but found nothing. + // Fix for [ 1663758 ] Failure to report error on bad XML + if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding ); + return 0; + } + + // We should find the end tag now + if ( StringEqual( p, endTag.c_str(), false, encoding ) ) + { + p += endTag.length(); + return p; + } + else + { + if ( document ) document->SetError( TIXML_ERROR_READING_END_TAG, p, data, encoding ); + return 0; + } + } + else + { + // Try to read an attribute: + TiXmlAttribute* attrib = new TiXmlAttribute(); + if ( !attrib ) + { + if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, pErr, data, encoding ); + return 0; + } + + attrib->SetDocument( document ); + pErr = p; + p = attrib->Parse( p, data, encoding ); + + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_PARSING_ELEMENT, pErr, data, encoding ); + delete attrib; + return 0; + } + + // Handle the strange case of double attributes: + #ifdef TIXML_USE_STL + TiXmlAttribute* node = attributeSet.Find( attrib->NameTStr() ); + #else + TiXmlAttribute* node = attributeSet.Find( attrib->Name() ); + #endif + if ( node ) + { + node->SetValue( attrib->Value() ); + delete attrib; + return 0; + } + + attributeSet.Add( attrib ); + } + } + return p; +} + + +const char* TiXmlElement::ReadValue( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + TiXmlDocument* document = GetDocument(); + + // Read in text and elements in any order. + const char* pWithWhiteSpace = p; + p = SkipWhiteSpace( p, encoding ); + + while ( p && *p ) + { + if ( *p != '<' ) + { + // Take what we have, make a text element. + TiXmlText* textNode = new TiXmlText( "" ); + + if ( !textNode ) + { + if ( document ) document->SetError( TIXML_ERROR_OUT_OF_MEMORY, 0, 0, encoding ); + return 0; + } + + if ( TiXmlBase::IsWhiteSpaceCondensed() ) + { + p = textNode->Parse( p, data, encoding ); + } + else + { + // Special case: we want to keep the white space + // so that leading spaces aren't removed. + p = textNode->Parse( pWithWhiteSpace, data, encoding ); + } + + if ( !textNode->Blank() ) + LinkEndChild( textNode ); + else + delete textNode; + } + else + { + // We hit a '<' + // Have we hit a new element or an end tag? This could also be + // a TiXmlText in the "CDATA" style. + if ( StringEqual( p, "</", false, encoding ) ) + { + return p; + } + else + { + TiXmlNode* node = Identify( p, encoding ); + if ( node ) + { + p = node->Parse( p, data, encoding ); + LinkEndChild( node ); + } + else + { + return 0; + } + } + } + pWithWhiteSpace = p; + p = SkipWhiteSpace( p, encoding ); + } + + if ( !p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ELEMENT_VALUE, 0, 0, encoding ); + } + return p; +} + + +#ifdef TIXML_USE_STL +void TiXmlUnknown::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + (*tag) += (char) c; + + if ( c == '>' ) + { + // All is well. + return; + } + } +} +#endif + + +const char* TiXmlUnknown::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + TiXmlDocument* document = GetDocument(); + p = SkipWhiteSpace( p, encoding ); + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + if ( !p || !*p || *p != '<' ) + { + if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, p, data, encoding ); + return 0; + } + ++p; + value = ""; + + while ( p && *p && *p != '>' ) + { + value += *p; + ++p; + } + + if ( !p ) + { + if ( document ) document->SetError( TIXML_ERROR_PARSING_UNKNOWN, 0, 0, encoding ); + } + if ( *p == '>' ) + return p+1; + return p; +} + +#ifdef TIXML_USE_STL +void TiXmlComment::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + (*tag) += (char) c; + + if ( c == '>' + && tag->at( tag->length() - 2 ) == '-' + && tag->at( tag->length() - 3 ) == '-' ) + { + // All is well. + return; + } + } +} +#endif + + +const char* TiXmlComment::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + TiXmlDocument* document = GetDocument(); + value = ""; + + p = SkipWhiteSpace( p, encoding ); + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + const char* startTag = "<!--"; + const char* endTag = "-->"; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + document->SetError( TIXML_ERROR_PARSING_COMMENT, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // [ 1475201 ] TinyXML parses entities in comments + // Oops - ReadText doesn't work, because we don't want to parse the entities. + // p = ReadText( p, &value, false, endTag, false, encoding ); + // + // from the XML spec: + /* + [Definition: Comments may appear anywhere in a document outside other markup; in addition, + they may appear within the document type declaration at places allowed by the grammar. + They are not part of the document's character data; an XML processor MAY, but need not, + make it possible for an application to retrieve the text of comments. For compatibility, + the string "--" (double-hyphen) MUST NOT occur within comments.] Parameter entity + references MUST NOT be recognized within comments. + + An example of a comment: + + <!-- declarations for <head> & <body> --> + */ + + value = ""; + // Keep all the white space. + while ( p && *p && !StringEqual( p, endTag, false, encoding ) ) + { + value.append( p, 1 ); + ++p; + } + if ( p ) + p += strlen( endTag ); + + return p; +} + + +const char* TiXmlAttribute::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) return 0; + +// int tabsize = 4; +// if ( document ) +// tabsize = document->TabSize(); + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + // Read the name, the '=' and the value. + const char* pErr = p; + p = ReadName( p, &name, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, pErr, data, encoding ); + return 0; + } + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p || *p != '=' ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + ++p; // skip '=' + p = SkipWhiteSpace( p, encoding ); + if ( !p || !*p ) + { + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + + const char* end; + const char SINGLE_QUOTE = '\''; + const char DOUBLE_QUOTE = '\"'; + + if ( *p == SINGLE_QUOTE ) + { + ++p; + end = "\'"; // single quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else if ( *p == DOUBLE_QUOTE ) + { + ++p; + end = "\""; // double quote in string + p = ReadText( p, &value, false, end, false, encoding ); + } + else + { + // All attribute values should be in single or double quotes. + // But this is such a common error that the parser will try + // its best, even without them. + value = ""; + while ( p && *p // existence + && !IsWhiteSpace( *p ) && *p != '\n' && *p != '\r' // whitespace + && *p != '/' && *p != '>' ) // tag end + { + if ( *p == SINGLE_QUOTE || *p == DOUBLE_QUOTE ) { + // [ 1451649 ] Attribute values with trailing quotes not handled correctly + // We did not have an opening quote but seem to have a + // closing one. Give up and throw an error. + if ( document ) document->SetError( TIXML_ERROR_READING_ATTRIBUTES, p, data, encoding ); + return 0; + } + value += *p; + ++p; + } + } + return p; +} + +#ifdef TIXML_USE_STL +void TiXmlText::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->peek(); + if ( !cdata && (c == '<' ) ) + { + return; + } + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + + (*tag) += (char) c; + in->get(); // "commits" the peek made above + + if ( cdata && c == '>' && tag->size() >= 3 ) { + size_t len = tag->size(); + if ( (*tag)[len-2] == ']' && (*tag)[len-3] == ']' ) { + // terminator of cdata. + return; + } + } + } +} +#endif + +const char* TiXmlText::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding encoding ) +{ + value = ""; + TiXmlDocument* document = GetDocument(); + + if ( data ) + { + data->Stamp( p, encoding ); + location = data->Cursor(); + } + + const char* const startTag = "<![CDATA["; + const char* const endTag = "]]>"; + + if ( cdata || StringEqual( p, startTag, false, encoding ) ) + { + cdata = true; + + if ( !StringEqual( p, startTag, false, encoding ) ) + { + document->SetError( TIXML_ERROR_PARSING_CDATA, p, data, encoding ); + return 0; + } + p += strlen( startTag ); + + // Keep all the white space, ignore the encoding, etc. + while ( p && *p + && !StringEqual( p, endTag, false, encoding ) + ) + { + value += *p; + ++p; + } + + TIXML_STRING dummy; + p = ReadText( p, &dummy, false, endTag, false, encoding ); + return p; + } + else + { + bool ignoreWhite = true; + + const char* end = "<"; + p = ReadText( p, &value, ignoreWhite, end, false, encoding ); + if ( p ) + return p-1; // don't truncate the '<' + return 0; + } +} + +#ifdef TIXML_USE_STL +void TiXmlDeclaration::StreamIn( std::istream * in, TIXML_STRING * tag ) +{ + while ( in->good() ) + { + int c = in->get(); + if ( c <= 0 ) + { + TiXmlDocument* document = GetDocument(); + if ( document ) + document->SetError( TIXML_ERROR_EMBEDDED_NULL, 0, 0, TIXML_ENCODING_UNKNOWN ); + return; + } + (*tag) += (char) c; + + if ( c == '>' ) + { + // All is well. + return; + } + } +} +#endif + +const char* TiXmlDeclaration::Parse( const char* p, TiXmlParsingData* data, TiXmlEncoding _encoding ) +{ + p = SkipWhiteSpace( p, _encoding ); + // Find the beginning, find the end, and look for + // the stuff in-between. + TiXmlDocument* document = GetDocument(); + if ( !p || !*p || !StringEqual( p, "<?xml", true, _encoding ) ) + { + if ( document ) document->SetError( TIXML_ERROR_PARSING_DECLARATION, 0, 0, _encoding ); + return 0; + } + if ( data ) + { + data->Stamp( p, _encoding ); + location = data->Cursor(); + } + p += 5; + + version = ""; + encoding = ""; + standalone = ""; + + while ( p && *p ) + { + if ( *p == '>' ) + { + ++p; + return p; + } + + p = SkipWhiteSpace( p, _encoding ); + if ( StringEqual( p, "version", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + version = attrib.Value(); + } + else if ( StringEqual( p, "encoding", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + encoding = attrib.Value(); + } + else if ( StringEqual( p, "standalone", true, _encoding ) ) + { + TiXmlAttribute attrib; + p = attrib.Parse( p, data, _encoding ); + standalone = attrib.Value(); + } + else + { + // Read over whatever it is. + while( p && *p && *p != '>' && !IsWhiteSpace( *p ) ) + ++p; + } + } + return 0; +} + +bool TiXmlText::Blank() const +{ + for ( unsigned i=0; i<value.length(); i++ ) + if ( !IsWhiteSpace( value[i] ) ) + return false; + return true; +} + + +bool TiXmlDocument::ReadFromMemory( const char* pBuf, size_t sz, TiXmlEncoding encoding) +{ + // Delete the existing data: + Clear(); + location.Clear(); + + // Get the file size, so we can pre-allocate the string. HUGE speed impact. + long length = (long) sz; + + // Strange case, but good to handle up front. + if ( length == 0 ) + { + SetError( TIXML_ERROR_DOCUMENT_EMPTY, 0, 0, TIXML_ENCODING_UNKNOWN ); + return false; + } + + // If we have a file, assume it is all one big XML file, and read it in. + // The document parser may decide the document ends sooner than the entire file, however. + TIXML_STRING data; + data.reserve( length ); + + + char* buf = new char[ length+1 ]; + memset(buf,0,length+1); + + memcpy(buf, pBuf, length); + + const char* lastPos = buf; + const char* p = buf; + + buf[length] = 0; + while( *p ) { + assert( p < (buf+length) ); + if ( *p == 0xa ) { + // Newline character. No special rules for this. Append all the characters + // since the last string, and include the newline. + data.append( lastPos, (p-lastPos+1) ); // append, include the newline + ++p; // move past the newline + lastPos = p; // and point to the new buffer (may be 0) + assert( p <= (buf+length) ); + } + else if ( *p == 0xd ) { + // Carriage return. Append what we have so far, then + // handle moving forward in the buffer. + if ( (p-lastPos) > 0 ) { + data.append( lastPos, p-lastPos ); // do not add the CR + } + data += (char)0xa; // a proper newline + + if ( *(p+1) == 0xa ) { + // Carriage return - new line sequence + p += 2; + lastPos = p; + assert( p <= (buf+length) ); + } + else { + // it was followed by something else...that is presumably characters again. + ++p; + lastPos = p; + assert( p <= (buf+length) ); + } + } + else { + ++p; + } + } + // Handle any left over characters. + if ( p-lastPos ) { + data.append( lastPos, p-lastPos ); + } + delete [] buf; + buf = 0; + + Parse( data.c_str(), 0, encoding ); + + if ( Error() ) + return false; + else + return true; +} diff --git a/rotord/src/utils.cpp b/rotord/src/utils.cpp new file mode 100644 index 0000000..9828124 --- /dev/null +++ b/rotord/src/utils.cpp @@ -0,0 +1,29 @@ +#include "utils.h" + +using namespace std; + +//float equality +bool fequal(const float u,const float v){ + if (abs(u-v)<FLOAT_THRESHOLD) return true; + else return false; +}; +bool fless_or_equal(const float u,const float v){ + //v is less or equal to u + if (u-v>-FLOAT_THRESHOLD) return true; + else return false; +}; +bool fgreater_or_equal(const float u,const float v){ + //v is more or equal to u + if (v-u>-FLOAT_THRESHOLD) return true; + else return false; +}; +bool fless(const float u,const float v){ + //v is less than u + if (u-v>FLOAT_THRESHOLD) return true; + else return false; +}; +bool fgreater(const float u,const float v){ + //v is greater than u + if (v-u>FLOAT_THRESHOLD) return true; + else return false; +}; diff --git a/rotord/src/utils.h b/rotord/src/utils.h new file mode 100644 index 0000000..3859afe --- /dev/null +++ b/rotord/src/utils.h @@ -0,0 +1,10 @@ +#include <cmath> + +#define FLOAT_THRESHOLD .001f + +//float equality +bool fequal(const float u,const float v); +bool fless_or_equal(const float u,const float v); +bool fgreater_or_equal(const float u,const float v); +bool fless(const float u,const float v); +bool fgreater(const float u,const float v); diff --git a/rotord/src/vampHost.cpp b/rotord/src/vampHost.cpp new file mode 100644 index 0000000..65755eb --- /dev/null +++ b/rotord/src/vampHost.cpp @@ -0,0 +1,815 @@ +#include "vampHost.h" + +int vampHost::runPlugin(string myname, string soname, string id, string output, + int outputNo, string inputFile, ostream& out, bool useFrames) +{ + PluginLoader *loader = PluginLoader::getInstance(); + + PluginLoader::PluginKey key = loader->composePluginKey(soname, id); + + SNDFILE *sndfile; + SF_INFO sfinfo; + memset(&sfinfo, 0, sizeof(SF_INFO)); + + sndfile = sf_open(inputFile.c_str(), SFM_READ, &sfinfo); + if (!sndfile) { + cerr << myname << ": ERROR: Failed to open input file \"" + << inputFile << "\": " << sf_strerror(sndfile) << endl; + return 1; + } + + Plugin *plugin = loader->loadPlugin + (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE); + if (!plugin) { + cerr << myname << ": ERROR: Failed to load plugin \"" << id + << "\" from library \"" << soname << "\"" << endl; + sf_close(sndfile); + return 1; + } + + cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl; + + // Note that the following would be much simpler if we used a + // PluginBufferingAdapter as well -- i.e. if we had passed + // PluginLoader::ADAPT_ALL to loader->loadPlugin() above, instead + // of ADAPT_ALL_SAFE. Then we could simply specify our own block + // size, keep the step size equal to the block size, and ignore + // the plugin's bleatings. However, there are some issues with + // using a PluginBufferingAdapter that make the results sometimes + // technically different from (if effectively the same as) the + // un-adapted plugin, so we aren't doing that here. See the + // PluginBufferingAdapter documentation for details. + + int blockSize = plugin->getPreferredBlockSize(); + int stepSize = plugin->getPreferredStepSize(); + + if (blockSize == 0) { + blockSize = 1024; + } + if (stepSize == 0) { + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + stepSize = blockSize/2; + } else { + stepSize = blockSize; + } + } else if (stepSize > blockSize) { + cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to "; + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + blockSize = stepSize * 2; + } else { + blockSize = stepSize; + } + cerr << blockSize << endl; + } + int overlapSize = blockSize - stepSize; + sf_count_t currentStep = 0; + int finalStepsRemaining = max(1, (blockSize / stepSize) - 1); // at end of file, this many part-silent frames needed after we hit EOF + + int channels = sfinfo.channels; + + float *filebuf = new float[blockSize * channels]; + float **plugbuf = new float*[channels]; + for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2]; + + cerr << "Using block size = " << blockSize << ", step size = " + << stepSize << endl; + + // The channel queries here are for informational purposes only -- + // a PluginChannelAdapter is being used automatically behind the + // scenes, and it will take case of any channel mismatch + + int minch = plugin->getMinChannelCount(); + int maxch = plugin->getMaxChannelCount(); + cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl; + cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl; + + Plugin::OutputList outputs = plugin->getOutputDescriptors(); + Plugin::OutputDescriptor od; + + int returnValue = 1; + int progress = 0; + + RealTime rt; + PluginWrapper *wrapper = 0; + RealTime adjustment = RealTime::zeroTime; + + if (outputs.empty()) { + cerr << "ERROR: Plugin has no outputs!" << endl; + goto done; + } + + if (outputNo < 0) { + + for (size_t oi = 0; oi < outputs.size(); ++oi) { + if (outputs[oi].identifier == output) { + outputNo = oi; + break; + } + } + + if (outputNo < 0) { + cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl; + goto done; + } + + } else { + + if (int(outputs.size()) <= outputNo) { + cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl; + goto done; + } + } + + od = outputs[outputNo]; + cerr << "Output is: \"" << od.identifier << "\"" << endl; + + if (!plugin->initialise(channels, stepSize, blockSize)) { + cerr << "ERROR: Plugin initialise (channels = " << channels + << ", stepSize = " << stepSize << ", blockSize = " + << blockSize << ") failed." << endl; + goto done; + } + + wrapper = dynamic_cast<PluginWrapper *>(plugin); + if (wrapper) { + // See documentation for + // PluginInputDomainAdapter::getTimestampAdjustment + PluginInputDomainAdapter *ida = + wrapper->getWrapper<PluginInputDomainAdapter>(); + if (ida) adjustment = ida->getTimestampAdjustment(); + } + + // Here we iterate over the frames, avoiding asking the numframes in case it's streaming input. + do { + + int count; + + if ((blockSize==stepSize) || (currentStep==0)) { + // read a full fresh block + if ((count = sf_readf_float(sndfile, filebuf, blockSize)) < 0) { + cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; + break; + } + if (count != blockSize) --finalStepsRemaining; + } else { + // otherwise shunt the existing data down and read the remainder. + memmove(filebuf, filebuf + (stepSize * channels), overlapSize * channels * sizeof(float)); + if ((count = sf_readf_float(sndfile, filebuf + (overlapSize * channels), stepSize)) < 0) { + cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; + break; + } + if (count != stepSize) --finalStepsRemaining; + count += overlapSize; + } + + for (int c = 0; c < channels; ++c) { + int j = 0; + while (j < count) { + plugbuf[c][j] = filebuf[j * sfinfo.channels + c]; + ++j; + } + while (j < blockSize) { + plugbuf[c][j] = 0.0f; + ++j; + } + } + + rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate); + + vampHost::printFeatures + (RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate), + sfinfo.samplerate, outputNo, plugin->process(plugbuf, rt), + out, useFrames); + + if (sfinfo.frames > 0){ + int pp = progress; + progress = lrintf((float(currentStep * stepSize) / sfinfo.frames) * 100.f); + if (progress != pp && out) { + cerr << "\r" << progress << "%"; + } + } + + ++currentStep; + + } while (finalStepsRemaining > 0); + + if (out) cerr << "\rDone" << endl; + + rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate); + + vampHost::printFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate), + sfinfo.samplerate, outputNo, + plugin->getRemainingFeatures(), out, useFrames); + + returnValue = 0; + +done: + delete plugin; + sf_close(sndfile); + return returnValue; +} + +int vampHost::rotorRunPlugin(string soname, string id, string output, + int outputNo, string inputFile, vector<float>& out, float& progress) +{ + PluginLoader *loader = PluginLoader::getInstance(); + + PluginLoader::PluginKey key = loader->composePluginKey(soname, id); + + SNDFILE *sndfile; + SF_INFO sfinfo; + memset(&sfinfo, 0, sizeof(SF_INFO)); + + sndfile = sf_open(inputFile.c_str(), SFM_READ, &sfinfo); + if (!sndfile) { + cerr << ": ERROR: Failed to open input file \"" + << inputFile << "\": " << sf_strerror(sndfile) << endl; + return 1; + } + + Plugin *plugin = loader->loadPlugin + (key, sfinfo.samplerate, PluginLoader::ADAPT_ALL_SAFE); + if (!plugin) { + cerr << ": ERROR: Failed to load plugin \"" << id + << "\" from library \"" << soname << "\"" << endl; + sf_close(sndfile); + return 1; + } + + cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"..." << endl; + + // Note that the following would be much simpler if we used a + // PluginBufferingAdapter as well -- i.e. if we had passed + // PluginLoader::ADAPT_ALL to loader->loadPlugin() above, instead + // of ADAPT_ALL_SAFE. Then we could simply specify our own block + // size, keep the step size equal to the block size, and ignore + // the plugin's bleatings. However, there are some issues with + // using a PluginBufferingAdapter that make the results sometimes + // technically different from (if effectively the same as) the + // un-adapted plugin, so we aren't doing that here. See the + // PluginBufferingAdapter documentation for details. + + int blockSize = plugin->getPreferredBlockSize(); + int stepSize = plugin->getPreferredStepSize(); + + if (blockSize == 0) { + blockSize = 1024; + } + if (stepSize == 0) { + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + stepSize = blockSize/2; + } else { + stepSize = blockSize; + } + } else if (stepSize > blockSize) { + cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to "; + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + blockSize = stepSize * 2; + } else { + blockSize = stepSize; + } + cerr << blockSize << endl; + } + int overlapSize = blockSize - stepSize; + sf_count_t currentStep = 0; + int finalStepsRemaining = max(1, (blockSize / stepSize) - 1); // at end of file, this many part-silent frames needed after we hit EOF + + int channels = sfinfo.channels; + + float *filebuf = new float[blockSize * channels]; + float **plugbuf = new float*[channels]; + for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2]; + + cerr << "Using block size = " << blockSize << ", step size = " + << stepSize << endl; + + // The channel queries here are for informational purposes only -- + // a PluginChannelAdapter is being used automatically behind the + // scenes, and it will take case of any channel mismatch + + int minch = plugin->getMinChannelCount(); + int maxch = plugin->getMaxChannelCount(); + cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl; + cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl; + + Plugin::OutputList outputs = plugin->getOutputDescriptors(); + Plugin::OutputDescriptor od; + + int returnValue = 1; + int prog = 0; + + RealTime rt; + PluginWrapper *wrapper = 0; + RealTime adjustment = RealTime::zeroTime; + + if (outputs.empty()) { + cerr << "ERROR: Plugin has no outputs!" << endl; + goto done; + } + + if (outputNo < 0) { + + for (size_t oi = 0; oi < outputs.size(); ++oi) { + if (outputs[oi].identifier == output) { + outputNo = oi; + break; + } + } + + if (outputNo < 0) { + cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl; + goto done; + } + + } else { + + if (int(outputs.size()) <= outputNo) { + cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl; + goto done; + } + } + + od = outputs[outputNo]; + cerr << "Output is: \"" << od.identifier << "\"" << endl; + + if (!plugin->initialise(channels, stepSize, blockSize)) { + cerr << "ERROR: Plugin initialise (channels = " << channels + << ", stepSize = " << stepSize << ", blockSize = " + << blockSize << ") failed." << endl; + goto done; + } + + wrapper = dynamic_cast<PluginWrapper *>(plugin); + if (wrapper) { + // See documentation for + // PluginInputDomainAdapter::getTimestampAdjustment + PluginInputDomainAdapter *ida = + wrapper->getWrapper<PluginInputDomainAdapter>(); + if (ida) adjustment = ida->getTimestampAdjustment(); + } + + // Here we iterate over the frames, avoiding asking the numframes in case it's streaming input. + do { + + int count; + + if ((blockSize==stepSize) || (currentStep==0)) { + // read a full fresh block + if ((count = sf_readf_float(sndfile, filebuf, blockSize)) < 0) { + cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; + break; + } + if (count != blockSize) --finalStepsRemaining; + } else { + // otherwise shunt the existing data down and read the remainder. + memmove(filebuf, filebuf + (stepSize * channels), overlapSize * channels * sizeof(float)); + if ((count = sf_readf_float(sndfile, filebuf + (overlapSize * channels), stepSize)) < 0) { + cerr << "ERROR: sf_readf_float failed: " << sf_strerror(sndfile) << endl; + break; + } + if (count != stepSize) --finalStepsRemaining; + count += overlapSize; + } + + for (int c = 0; c < channels; ++c) { + int j = 0; + while (j < count) { + plugbuf[c][j] = filebuf[j * sfinfo.channels + c]; + ++j; + } + while (j < blockSize) { + plugbuf[c][j] = 0.0f; + ++j; + } + } + + rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate); + + vampHost::rotorGetFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate),sfinfo.samplerate, outputNo,plugin->getRemainingFeatures(), out, progress); + + if (sfinfo.frames > 0){ + int pp = prog; + prog = lrintf((float(currentStep * stepSize) / sfinfo.frames) * 100.f); + if (prog != pp ) { + cerr << "\r" << progress << "%"; + } + } + + ++currentStep; + + } while (finalStepsRemaining > 0); + + cerr << "\rDone" << endl; + + rt = RealTime::frame2RealTime(currentStep * stepSize, sfinfo.samplerate); + + vampHost::rotorGetFeatures(RealTime::realTime2Frame(rt + adjustment, sfinfo.samplerate),sfinfo.samplerate, outputNo,plugin->getRemainingFeatures(), out, progress); + + returnValue = 0; + +done: + delete plugin; + sf_close(sndfile); + return returnValue; +} + +void vampHost::printFeatures(int frame, int sr, int output, + Plugin::FeatureSet features, ostream& out, bool useFrames) +{ + if (features[output].size()) { + cout << "." << features[output].size(); + } + for (unsigned int i = 0; i < features[output].size(); ++i) { + + if (useFrames) { + + int displayFrame = frame; + + if (features[output][i].hasTimestamp) { + displayFrame = RealTime::realTime2Frame + (features[output][i].timestamp, sr); + } + + out << displayFrame; + + if (features[output][i].hasDuration) { + displayFrame = RealTime::realTime2Frame + (features[output][i].duration, sr); + out << "," << displayFrame; + } + + out << ":"; + + } else { + + RealTime rt = RealTime::frame2RealTime(frame, sr); + + if (features[output][i].hasTimestamp) { + rt = features[output][i].timestamp; + } + + out << rt.toString(); + + if (features[output][i].hasDuration) { + rt = features[output][i].duration; + out<< "," << rt.toString(); + } + + out << ":"; + } + + for (unsigned int j = 0; j < features[output][i].values.size(); ++j) { + out<< " " << features[output][i].values[j]; + } + out << " " << features[output][i].label; + + out << endl; + } + +} + + + +void vampHost::rotorGetFeatures(int frame, int sr, int output,Plugin::FeatureSet features, vector<float>& out, float& progress) +{ + if (features[output].size()) { + cout << "." << features[output].size(); + } + for (unsigned int i = 0; i < features[output].size(); ++i) { + + + + int displayFrame = frame; + + if (features[output][i].hasTimestamp) { + displayFrame = RealTime::realTime2Frame + (features[output][i].timestamp, sr); + } + + cout << displayFrame; + + + cout << endl; + } + +} + + +int vampHost::QMAnalyser::process(const string inputFile){ + //vampHost::runPlugin("",settings.soname,settings.filtername, "",0, settings.inputFile, ostr,true); + //would run the plugin, outputting progress to cerr and the data to ostr + // + //int runPlugin(string myname, string soname, string id, string output,int outputNo, string inputFile, ostream& out, bool frames); + + + //we want to run a specific plugin, outputting progress to a mutex-protected passed variable + //and ultimately passing the data back as a string? + //or capture it as an array of floats? + //get the progress as a float + //how to handle errors? + + //debugger fucking up! program stalls after 1 request in debug!? + + string soname="qm-vamp-plugins"; + string id="qm-tempotracker"; + string myname=""; + string output=""; + int outputNo=0; + + vampHost::rotorRunPlugin(soname,id,output,outputNo,inputFile,beats,progress); + +} + +void vampHost::getTimestamps(int output,Plugin::FeatureSet features, vector<float>& out){ + + /* + vamp-simple-host qm-vamp-plugins:qm-tempotracker 01.wav + + 0.046439908: 156.61 bpm + 0.429569160: 156.61 bpm + 0.812698412: 161.50 bpm + 1.184217686: 152.00 bpm + + + vamp-simple-host qm-vamp-plugins:qm-segmenter 01.wav + + 0.000000000: 4 4 + 23.800000000: 6 6 + 44.600000000: 5 5 + 55.000000000: 7 7 + 72.800000000: 1 1 + 90.600000000: 2 2 + 109.200000000: 5 5 + 116.000000000: 3 3 + 143.800000000: 5 5 + 153.400000000: 3 3 + 163.000000000: 8 8 + + seems to be FP seconds then another metric + for now we can just take the first part + + features[output][i].timestamp is of type RealTime: represents time values to nanosecond precision + int sec; + int nsec; + 1 sec = 10^9 nanosec + + actually maybe this would be the way to go for rotor- avoiding rounding errors etc + for now - ideally will get a float representation + + features[output][i].values is a vector of floats + a description + WE DON'T CARE ABOUT ANYTHING <.01 seconds + + static long realTime2Frame(const RealTime &r, unsigned int sampleRate); + + get a vector of floats out, using frames, presuming data has a timestamp + + + this is crashing with "Aborted (core dumped)" + if we check for timestamp + + */ + + cout << "." << features[output].size(); + + //if (!features[output][0].hasTimestamp) { + // cerr << output << " channel, getTimestamps: error, featureset doesn't support timestamp" << endl; + //}_ + //else { + for (unsigned int i = 0; i < features[output].size(); ++i) { + out.push_back( ((float)RealTime::realTime2Frame(features[output][i].timestamp, 1000))*.001f); + cout << "feature found.\n"; + } + //} +} +float vampHost::QMAnalyser::get_progress(){ + float p; + mutex.lock(); + p=progress; + mutex.unlock(); + return p; +} +bool vampHost::Analyser::init(const string &soname,const string &id,const int &_channels,const int &_bits,const int &_samples,const int &_rate,int _outputNo,const map<string,float> ¶ms){ + + //stuff that only happens once + channels =_channels; + samples=_samples; + rate=_rate; + bits=_bits; + outputNo=_outputNo; + //output=_output; + + //http://www.mega-nerd.com/libsndfile/api.html#note1 + //libsndfile returns -1..1 for fp data + bytes=(bits>>3); + stride=channels*bytes; + scale=(1.0f/pow(2.0f,bits)); + + features.clear(); //in case of reuse + features[0.0f]=0; + + loader = PluginLoader::getInstance(); + key = loader->composePluginKey(soname, id); + plugin = loader->loadPlugin(key, _rate, PluginLoader::ADAPT_ALL_SAFE); + if (!plugin) { + cerr << ": ERROR: Failed to load plugin \"" << id + << "\" from library \"" << soname << "\"" << endl; + return false; + } + + cerr << "Running plugin: \"" << plugin->getIdentifier() << "\"... Domain:"; + + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + cerr << "frequency" << endl; + } + else { + + cerr << "time" << endl; + + } + + blockSize = plugin->getPreferredBlockSize(); + stepSize = plugin->getPreferredStepSize(); + + if (blockSize == 0) { + blockSize = 1024; + } + if (stepSize == 0) { + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + stepSize = blockSize/2; + } else { + stepSize = blockSize; + } + } + else if (stepSize > blockSize) { + cerr << "WARNING: stepSize " << stepSize << " > blockSize " << blockSize << ", resetting blockSize to "; + if (plugin->getInputDomain() == Plugin::FrequencyDomain) { + blockSize = stepSize * 2; + } else { + blockSize = stepSize; + } + cerr << blockSize << endl; + } + overlapSize = blockSize - stepSize; + currentStep = 0; + finalStepsRemaining = max(1, (blockSize / stepSize) - 1); // at end of file, this many part-silent frames needed after we hit EOF + + plugbuf = new float*[channels]; + for (int c = 0; c < channels; ++c) plugbuf[c] = new float[blockSize + 2]; + + cerr << "Using block size = " << blockSize << ", step size = " + << stepSize << endl; + + // The channel queries here are for informational purposes only -- + // a PluginChannelAdapter is being used automatically behind the + // scenes, and it will take case of any channel mismatch + + int minch = plugin->getMinChannelCount(); + int maxch = plugin->getMaxChannelCount(); + cerr << "Plugin accepts " << minch << " -> " << maxch << " channel(s)" << endl; + cerr << "Sound file has " << channels << " (will mix/augment if necessary)" << endl; + + Plugin::OutputList outputs = plugin->getOutputDescriptors(); + Plugin::OutputDescriptor od; + + int returnValue = 1; + int prog = 0; + + RealTime rt; + PluginWrapper *wrapper = 0; + RealTime adjustment = RealTime::zeroTime; + + if (outputs.empty()) { + cerr << "ERROR: Plugin has no outputs!" << endl; + return false; + } + + if (outputNo < 0) { + for (size_t oi = 0; oi < outputs.size(); ++oi) { + if (outputs[oi].identifier == output) { + outputNo = oi; + break; + } + } + if (outputNo < 0) { + cerr << "ERROR: Non-existent output \"" << output << "\" requested" << endl; + return false; + } + } + else { + if (int(outputs.size()) <= outputNo) { + cerr << "ERROR: Output " << outputNo << " requested, but plugin has only " << outputs.size() << " output(s)" << endl; + return false; + } + } + od = outputs[outputNo]; + cerr << "Output number "<<outputNo<<": \"" << od.identifier << "\"" << endl; + + + for (auto i:params){ + plugin->setParameter(i.first,i.second); + cerr << "Set plugin parameter: "<<i.first<<" : "<<i.second<<endl; + } + + if (!plugin->initialise(channels, stepSize, blockSize)) { + cerr << "ERROR: Plugin initialise (channels = " << channels + << ", stepSize = " << stepSize << ", blockSize = " + << blockSize << ") failed." << endl; + return false; + } + + wrapper = dynamic_cast<PluginWrapper *>(plugin); + if (wrapper) { + // See documentation for + // PluginInputDomainAdapter::getTimestampAdjustment + PluginInputDomainAdapter *ida =wrapper->getWrapper<PluginInputDomainAdapter>(); + if (ida) adjustment = ida->getTimestampAdjustment(); + } + + //everything is prepared to start consuming data in blocks + + in_block=0; + blocks_processed=0; + currentStep=0; + + featureNo=1; + + return true; +} +void vampHost::Analyser::process_frame(uint8_t *data,int samples_in_frame){ + int sample=0; + + uint16_t *_data=(uint16_t*)data; + //process the whole frame which may be f>1<f blocks + //when the frame is finished leave the partial block for the next frame + while(sample<samples_in_frame) { + while(sample<samples_in_frame&&in_block<blockSize) { + for (int i=0;i<channels;i++) { + //unsigned int this_val=0; + // this_val+=data[(sample*stride)+(i*bytes)+j]<<((1-j)*8); + //} + //plugbuf[i][in_block]=((float)((int16_t)this_val))*scale; + plugbuf[i][in_block]=((float)_data[sample])*scale; + } + in_block++; + sample++; + } + if (in_block==blockSize) { + //block is ready to be processed + //cerr<<plugin->getIdentifier()<<" processed block "<<blocks_processed<<endl; + + //I /think/ that the vamp plugin keeps processing through the plugbuf until it encounters 0s + rt = RealTime::frame2RealTime(currentStep * stepSize, rate); //48000); //setting different rate doesn't affect it + + Plugin::FeatureSet feat=plugin->process(plugbuf, rt); + + for (unsigned int i = 0; i < feat[outputNo].size(); ++i) { + features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=featureNo; + featureNo++; + } + + //shunt it down + for (int i=0;i<blockSize-stepSize;i++){ + for (int j=0;j<channels;j++){ + plugbuf[j][i]=plugbuf[j][i+stepSize]; + } + } + + in_block-=stepSize; + currentStep++; + } + } +} +void vampHost::Analyser::cleanup(){ + + //process final block + while(in_block<blockSize) { + for (int i=0;i<channels;i++) { + plugbuf[i][in_block]=0.0f; + } + in_block++; + } + + rt = RealTime::frame2RealTime(currentStep * stepSize, rate); // //setting different + + Plugin::FeatureSet feat=plugin->process(plugbuf, rt); + + for (unsigned int i = 0; i < feat[outputNo].size(); ++i) { + features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=featureNo; + featureNo++; + } + + feat=plugin->getRemainingFeatures(); + + for (unsigned int i = 0; i < feat[outputNo].size(); ++i) { + features[((float)feat[outputNo][i].timestamp.sec)+(((float)feat[outputNo][i].timestamp.nsec)*.000000001)]=featureNo; + featureNo++; + } + + //cerr<<plugin->getIdentifier()<<" found "<<(features.size()-1)<<" features"<<endl; + //deal with left over data? + for (int c = 0; c < channels; ++c) { + delete[] plugbuf[c]; + } + delete[] plugbuf; + delete plugin; +} diff --git a/rotord/src/vampHost.h b/rotord/src/vampHost.h new file mode 100644 index 0000000..d1dfc81 --- /dev/null +++ b/rotord/src/vampHost.h @@ -0,0 +1,92 @@ +#include <vamp-hostsdk/PluginHostAdapter.h> +#include <vamp-hostsdk/PluginInputDomainAdapter.h> +#include <vamp-hostsdk/PluginLoader.h> + +#include "Poco/Mutex.h" + +#include <iostream> +#include <fstream> +#include <set> +#include <sndfile.h> + +#include <cstring> +#include <cstdlib> + +#include "system.h" + +#include <cmath> + +/* +line 366: is returnValue the fail/succeed return value? +*/ + +using namespace std; + +using Vamp::Plugin; +using Vamp::PluginHostAdapter; +using Vamp::RealTime; +using Vamp::HostExt::PluginLoader; +using Vamp::HostExt::PluginWrapper; +using Vamp::HostExt::PluginInputDomainAdapter; + +#define HOST_VERSION "1.5" + +namespace vampHost { + + class Settings{ + public: + Settings(string _so="",string _filter="",string _input="") { + soname=_so; + filtername=_filter; + inputFile=_input; + } + string soname; + string filtername; + string inputFile; + }; + class QMAnalyser{ + public: + int process(const string soundfile); + float get_progress(); + vector<float> beats; + private: + float progress; + Poco::Mutex mutex; //lock for progress data + }; + class Analyser{ + //can load any vamp analysis plugin and present its data with a unified interface + public: + bool init(const string &soname,const string &id,const int &_channels,const int &_bits,const int &_samples,const int &_rate,int outputNo,const map<string,float> ¶ms); + void process_frame(uint8_t *data,int samples_in_frame); + void cleanup(); + + map<double,int> features; + //map<time,featureNo> + //this is the best way to store features: because map allows to search for the key below and above the present time + + private: + PluginLoader *loader; + PluginLoader::PluginKey key; + Plugin *plugin; + RealTime rt; + int channels,bits,samples,rate; + int bytes,stride; + float scale; + int blockSize,stepSize,overlapSize,finalStepsRemaining,currentStep,outputNo; + int in_block,blocks_processed; + string output; + float **plugbuf; + + int featureNo; + + }; + + string getQMBeats(const string soundfile); + void printFeatures(int, int, int, Plugin::FeatureSet, ostream &, bool frames); + void getTimestamps(int output,Plugin::FeatureSet features, vector<float>& out); + int runPlugin(string myname, string soname, string id, string output,int outputNo, string inputFile, ostream& out, bool frames); + + int rotorRunPlugin(string soname, string id, string output,int outputNo, string inputFile, vector<float>& out, float& progress); + void rotorGetFeatures(int frame, int sr, int output,Plugin::FeatureSet features, vector<float>& out, float& progress); + +} diff --git a/rotord/src/xmlIO.cpp b/rotord/src/xmlIO.cpp new file mode 100755 index 0000000..3a7ec61 --- /dev/null +++ b/rotord/src/xmlIO.cpp @@ -0,0 +1,673 @@ +#include "xmlIO.h" + +#include <vector> +#include <string> +#include <iostream> + +//---------------------------------------- +// a pretty useful tokenization system: +static vector<string> tokenize(const string & str, const string & delim); +static vector<string> tokenize(const string & str, const string & delim) +{ + vector<string> tokens; + + size_t p0 = 0, p1 = string::npos; + while(p0 != string::npos) + { + p1 = str.find_first_of(delim, p0); + if(p1 != p0) + { + string token = str.substr(p0, p1 - p0); + tokens.push_back(token); + } + p0 = str.find_first_not_of(delim, p1); + } + return tokens; +} +//---------------------------------------- + +//---------------------------------------- +xmlIO::xmlIO(): + storedHandle(NULL) +{ + level = 0; + //we do this so that we have a valid handle + //without the need for loadFile + storedHandle = TiXmlHandle(&doc); +} + +//---------------------------------------- +xmlIO::xmlIO(const string& xmlFile): + storedHandle(NULL) +{ + level = 0; + //we do this so that we have a valid handle + //without the need for loadFile + storedHandle = TiXmlHandle(&doc); + loadFile(xmlFile); +} + +//--------------------------------------------------------- +xmlIO::~xmlIO() +{ +} + +//--------------------------------------------------------- +void xmlIO::setVerbose(bool _verbose){ +} + +//--------------------------------------------------------- +void xmlIO::clear(){ + //we clear from our root level + //this is usually the document + //but if we are pushed - it could + //be all the tags inside of the pushed + //node - including the node itself! + + storedHandle.ToNode()->Clear(); +} + +//--------------------------------------------------------- +bool xmlIO::loadFile(const string& xmlFile){ + + //string fullXmlFile = ofToDataPath(xmlFile); + + bool loadOkay = doc.LoadFile(xmlFile); + + //theo removed bool check as it would + //return false if the file exists but was + //empty + + //our push pop level should be set to 0! + level = 0; + + storedHandle = TiXmlHandle(&doc); + return loadOkay; +} + +//--------------------------------------------------------- +bool xmlIO::saveFile(const string& xmlFile){ + + //string fullXmlFile = ofToDataPath(xmlFile); + return doc.SaveFile(xmlFile); +} + +//--------------------------------------------------------- +bool xmlIO::saveFile(){ + return doc.SaveFile(); +} + +//--------------------------------------------------------- +void xmlIO::clearTagContents(const string& tag, int which){ + //we check it first to see if it exists + //otherwise setValue will make a new empty tag + if( tagExists(tag, which) )setValue(tag, "", which); +} + +//--------------------------------------------------------- +void xmlIO::removeTag(const string& tag, int which){ + + vector<string> tokens = tokenize(tag,":"); + + //no tags so we return + if( tokens.size() == 0 ) return; + + //grab the handle from the level we are at + //normally this is the doc but could be a pushed node + TiXmlHandle tagHandle = storedHandle; + + if(which < 0) which = 0; + + for(int x=0;x<(int)tokens.size();x++){ + + //we only support multi tags + //with same name at root level + if(x > 0) which = 0; + + TiXmlHandle isRealHandle = tagHandle.ChildElement( tokens.at(x), which); + + if ( !isRealHandle.ToNode() ) break; + else{ + if (x == (int)tokens.size()-1){ + //if we are at the last tag and it exists + //we use its parent to remove it - haha + tagHandle.ToNode()->RemoveChild( isRealHandle.ToNode() ); + } + tagHandle = isRealHandle; + } + } +} + +//--------------------------------------------------------- +int xmlIO::getValue(const string& tag, int defaultValue, int which){ + TiXmlHandle valHandle(NULL); + if (readTag(tag, valHandle, which)){ + return ofToInt(valHandle.ToText()->Value()); + } + return defaultValue; +} + +//--------------------------------------------------------- +double xmlIO::getValue(const string& tag, double defaultValue, int which){ + TiXmlHandle valHandle(NULL); + if (readTag(tag, valHandle, which)){ + return ofToFloat(valHandle.ToText()->Value()); + } + return defaultValue; +} + +//--------------------------------------------------------- +string xmlIO::getValue(const string& tag, const string& defaultValue, int which){ + TiXmlHandle valHandle(NULL); + if (readTag(tag, valHandle, which)){ + return valHandle.ToText()->ValueStr(); + } + return defaultValue; +} + +//--------------------------------------------------------- +bool xmlIO::readTag(const string& tag, TiXmlHandle& valHandle, int which){ + + vector<string> tokens = tokenize(tag,":"); + + TiXmlHandle tagHandle = storedHandle; + for(int x=0;x<(int)tokens.size();x++){ + if(x == 0)tagHandle = tagHandle.ChildElement(tokens.at(x), which); + else tagHandle = tagHandle.FirstChildElement( tokens.at(x) ); + } + + // once we've walked, let's get that value... + valHandle = tagHandle.Child( 0 ); + return (valHandle.ToText() != NULL); +} + + +//--------------------------------------------------------- +bool xmlIO::pushTag(const string& tag, int which){ + + int pos = tag.find(":"); + + // Either find the tag specified, or the first tag if colon-seperated. + string tagToFind((pos > 0) ? tag.substr(0,pos) :tag); + + //we only allow to push one tag at a time. + TiXmlHandle isRealHandle = storedHandle.ChildElement(tagToFind, which); + + if( isRealHandle.ToNode() ){ + storedHandle = isRealHandle; + level++; + return true; + }else{ + //ofLog( OF_LOG_ERROR, "pushTag - <" + tag + "> tag not found"); + } + + return false; +} + +//--------------------------------------------------------- +int xmlIO::popTag(){ + + if(level >= 1){ + TiXmlHandle parent( (storedHandle.ToNode() )->Parent() ); + storedHandle = parent; + level--; + }else{ + storedHandle = TiXmlHandle(&doc); + level = 0; + } + + return level; +} + +//--------------------------------------------------------- +int xmlIO::getPushLevel(){ + return level; +} + +//--------------------------------------------------------- +bool xmlIO::tagExists(const string& tag, int which){ + + vector<string> tokens = tokenize(tag,":"); + + bool found = false; + + //grab the handle from the level we are at + //normally this is the doc but could be a pushed node + TiXmlHandle tagHandle = storedHandle; + + if(which < 0) which = 0; + + for(int x=0;x<(int)tokens.size();x++){ + + //we only support multi tags + //with same name at root level + if(x > 0) which = 0; + + TiXmlHandle isRealHandle = tagHandle.ChildElement( tokens.at(x), which); + + //as soon as we find a tag that doesn't exist + //we return false; + if ( !isRealHandle.ToNode() ){ + found = false; + break; + } + else{ + found = true; + tagHandle = isRealHandle; + } + } + + return found; +} + + +//--------------------------------------------------------- +int xmlIO::getNumTags(const string& tag){ + //this only works for tags at the current root level + + int pos = tag.find(":"); + + // Either find the tag specified, or the first tag if colon-seperated. + string tagToFind((pos > 0) ? tag.substr(0,pos) :tag); + + //grab the handle from the level we are at + //normally this is the doc but could be a pushed node + TiXmlHandle tagHandle = storedHandle; + + int count = 0; + + //ripped from tinyXML as doing this ourselves once is a LOT! faster + //than having this called n number of times in a while loop - we go from n*n iterations to n iterations + + TiXmlElement* child = ( storedHandle.FirstChildElement( tagToFind ) ).ToElement(); + for (count = 0; child; child = child->NextSiblingElement( tagToFind ), ++count){ + //nothing + } + + return count; +} + + + +//--------------------------------------------------------- +int xmlIO::writeTag(const string& tag, const string& valueStr, int which){ + + vector<string> tokens = tokenize(tag,":"); + + // allocate on the stack + vector<TiXmlElement> elements; + elements.reserve(tokens.size()); + for(int x=0;x<(int)tokens.size();x++) + elements.push_back(tokens.at(x)); + + + TiXmlText Value(valueStr); + + // search our way up - do these tags exist? + // find the first that DOESNT exist, then move backwards... + TiXmlHandle tagHandle = storedHandle; + + bool addNewTag = false; + if(which == -1)addNewTag = true; + + for(int x=0;x<(int)tokens.size();x++){ + + if( x > 0 ){ + //multi tags of same name + //only for the root level + which = 0; + addNewTag = false; + } + + TiXmlHandle isRealHandle = tagHandle.ChildElement( tokens.at(x), which); + + if ( !isRealHandle.ToNode() || addNewTag){ + + for(int i=(int)tokens.size()-1;i>=x;i--){ + if (i == (int)tokens.size()-1){ + elements[i].InsertEndChild(Value); + } else { + elements[i].InsertEndChild(elements[i+1]); + } + } + + tagHandle.ToNode()->InsertEndChild(elements[x]); + + break; + + } else { + tagHandle = isRealHandle; + if (x == (int)tokens.size()-1){ + // what we want to change : TiXmlHandle valHandle = tagHandle.Child( 0 ); + tagHandle.ToNode()->Clear(); + tagHandle.ToNode()->InsertEndChild(Value); + } + } + } + + + //lets count how many tags with our name exist so we can return an index + + //ripped from tinyXML as doing this ourselves once is a LOT! faster + //than having this called n number of times in a while loop - we go from n*n iterations to n iterations + int numSameTags; + TiXmlElement* child = ( storedHandle.FirstChildElement( tokens.at(0) ) ).ToElement(); + for (numSameTags = 0; child; child = child->NextSiblingElement( tokens.at(0) ), ++numSameTags){ + //nothing + } + + return numSameTags; +} + +//--------------------------------------------------------- +int xmlIO::setValue(const string& tag, int value, int which){ + int tagID = writeTag(tag, ofToString(value).c_str(), which) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::setValue(const string& tag, double value, int which){ + int tagID = writeTag(tag, ofToString(value).c_str(), which) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::setValue(const string& tag, const string& value, int which){ + int tagID = writeTag(tag, value, which) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::addValue(const string& tag, int value){ + int tagID = writeTag(tag, ofToString(value).c_str(), -1) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::addValue(const string& tag, double value){ + int tagID = writeTag(tag, ofToString(value).c_str(), -1) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::addValue(const string& tag, const string& value){ + int tagID = writeTag(tag, value, -1) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::addTag(const string& tag){ + int tagID = writeTag(tag, "", -1) -1; + return tagID; +} + +/******************* +* Attribute addons * +*******************/ + +//--------------------------------------------------------- +int xmlIO::addAttribute(const string& tag, const string& attribute, int value, int which){ + int tagID = writeAttribute(tag, attribute, ofToString(value).c_str(), which) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::addAttribute(const string& tag, const string& attribute, int value){ + return addAttribute(tag,attribute,value,-1); +} + +//--------------------------------------------------------- +int xmlIO::addAttribute(const string& tag, const string& attribute, double value, int which){ + int tagID = writeAttribute(tag, attribute, ofToString(value).c_str(), which) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::addAttribute(const string& tag, const string& attribute, double value){ + return addAttribute(tag,attribute,value,-1); +} + +//--------------------------------------------------------- +int xmlIO::addAttribute(const string& tag, const string& attribute, const string& value, int which){ + int tagID = writeAttribute(tag, attribute, value, which) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::addAttribute(const string& tag, const string& attribute, const string& value){ + return addAttribute(tag,attribute,value,-1); +} + +//--------------------------------------------------------- +void xmlIO::removeAttribute(const string& tag, const string& attribute, int which){ + vector<string> tokens = tokenize(tag,":"); + TiXmlHandle tagHandle = storedHandle; + for (int x = 0; x < (int)tokens.size(); x++) { + if (x == 0) + tagHandle = tagHandle.ChildElement(tokens.at(x), which); + else + tagHandle = tagHandle.FirstChildElement(tokens.at(x)); + } + + if (tagHandle.ToElement()) { + TiXmlElement* elem = tagHandle.ToElement(); + elem->RemoveAttribute(attribute); + } +} + +//--------------------------------------------------------- +void xmlIO::clearTagAttributes(const string& tag, int which){ + vector<string> names; + getAttributeNames( tag, names, which ); + for (vector<string>::iterator i = names.begin(); i != names.end(); i++) + removeAttribute(tag, *i, which); +} + +//--------------------------------------------------------- +int xmlIO::getNumAttributes(const string& tag, int which){ + vector<string> tokens = tokenize(tag,":"); + TiXmlHandle tagHandle = storedHandle; + for (int x = 0; x < (int)tokens.size(); x++) { + if (x == 0) + tagHandle = tagHandle.ChildElement(tokens.at(x), which); + else + tagHandle = tagHandle.FirstChildElement(tokens.at(x)); + } + + if (tagHandle.ToElement()) { + TiXmlElement* elem = tagHandle.ToElement(); + + // Do stuff with the element here + TiXmlAttribute* first = elem->FirstAttribute(); + if (first) { + int count = 1; + for (TiXmlAttribute* curr = first; curr != elem->LastAttribute(); curr = curr->Next()) + count++; + return count; + } + } + return 0; +} + +//--------------------------------------------------------- +bool xmlIO::attributeExists(const string& tag, const string& attribute, int which){ + vector<string> tokens = tokenize(tag,":"); + TiXmlHandle tagHandle = storedHandle; + for (int x = 0; x < (int)tokens.size(); x++) { + if (x == 0) + tagHandle = tagHandle.ChildElement(tokens.at(x), which); + else + tagHandle = tagHandle.FirstChildElement(tokens.at(x)); + } + + if (tagHandle.ToElement()) { + TiXmlElement* elem = tagHandle.ToElement(); + + // Do stuff with the element here + for (TiXmlAttribute* a = elem->FirstAttribute(); a; a = a->Next()) { + if (a->Name() == attribute) + return true; + } + } + return false; +} + +//--------------------------------------------------------- +bool xmlIO::getAttributeNames(const string& tag, vector<string>& outNames, int which){ + vector<string> tokens = tokenize(tag,":"); + TiXmlHandle tagHandle = storedHandle; + for (int x = 0; x < (int)tokens.size(); x++) { + if (x == 0) + tagHandle = tagHandle.ChildElement(tokens.at(x), which); + else + tagHandle = tagHandle.FirstChildElement(tokens.at(x)); + } + + if (tagHandle.ToElement()) { + TiXmlElement* elem = tagHandle.ToElement(); + + // Do stuff with the element here + for (TiXmlAttribute* a = elem->FirstAttribute(); a; a = a->Next()) + outNames.push_back( string(a->Name()) ); + } + return !outNames.empty(); +} + +//--------------------------------------------------------- +int xmlIO::getAttribute(const string& tag, const string& attribute, int defaultValue, int which){ + int value = defaultValue; + readIntAttribute(tag, attribute, value, which); + return value; +} + +//--------------------------------------------------------- +double xmlIO::getAttribute(const string& tag, const string& attribute, double defaultValue, int which){ + double value = defaultValue; + readDoubleAttribute(tag, attribute, value, which); + return value; +} + +//--------------------------------------------------------- +string xmlIO::getAttribute(const string& tag, const string& attribute, const string& defaultValue, int which){ + string value = defaultValue; + readStringAttribute(tag, attribute, value, which); + return value; +} + +//--------------------------------------------------------- +int xmlIO::setAttribute(const string& tag, const string& attribute, int value, int which){ + char valueStr[255]; + sprintf(valueStr, "%i", value); + int tagID = writeAttribute(tag, attribute, valueStr, which) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::setAttribute(const string& tag, const string& attribute, double value, int which){ + char valueStr[255]; + sprintf(valueStr, "%lf", value); + int tagID = writeAttribute(tag, attribute, valueStr, which) -1; + return tagID; +} + +//--------------------------------------------------------- +int xmlIO::setAttribute(const string& tag, const string& attribute, const string& value, int which){ + int tagID = writeAttribute(tag, attribute, value, which) -1; + return tagID; +} + +//--------------------------------------------------------- +TiXmlElement* xmlIO::getElementForAttribute(const string& tag, int which){ + vector<string> tokens = tokenize(tag,":"); + TiXmlHandle tagHandle = storedHandle; + for (int x = 0; x < (int)tokens.size(); x++) { + if (x == 0) + tagHandle = tagHandle.ChildElement(tokens.at(x), which); + else + tagHandle = tagHandle.FirstChildElement(tokens.at(x)); + } + return tagHandle.ToElement(); +} + +//--------------------------------------------------------- +bool xmlIO::readIntAttribute(const string& tag, const string& attribute, int& outValue, int which){ + + TiXmlElement* elem = getElementForAttribute(tag, which); + if (elem) + return (elem->QueryIntAttribute(attribute, &outValue) == TIXML_SUCCESS); + return false; +} + +//--------------------------------------------------------- +bool xmlIO::readDoubleAttribute(const string& tag, const string& attribute, double& outValue, int which){ + + TiXmlElement* elem = getElementForAttribute(tag, which); + if (elem) + return (elem->QueryDoubleAttribute(attribute, &outValue) == TIXML_SUCCESS); + return false; +} + +//--------------------------------------------------------- +bool xmlIO::readStringAttribute(const string& tag, const string& attribute, string& outValue, int which){ + + TiXmlElement* elem = getElementForAttribute(tag, which); + if (elem) + { + const string* value = elem->Attribute(attribute); + if (value) + { + outValue = *value; + return true; + } + } + return false; +} + +//--------------------------------------------------------- +int xmlIO::writeAttribute(const string& tag, const string& attribute, const string& valueString, int which){ + vector<string> tokens = tokenize(tag,":"); + TiXmlHandle tagHandle = storedHandle; + for (int x = 0; x < (int)tokens.size(); x++) { + if (x == 0) + tagHandle = tagHandle.ChildElement(tokens.at(x), which); + else + tagHandle = tagHandle.FirstChildElement(tokens.at(x)); + } + + int ret = 0; + if (tagHandle.ToElement()) { + TiXmlElement* elem = tagHandle.ToElement(); + elem->SetAttribute(attribute, valueString); + + // Do we really need this? We could just ignore this and remove the 'addAttribute' functions... + // Now, just get the ID. + int numSameTags; + TiXmlElement* child = ( storedHandle.FirstChildElement( tokens.at(0) ) ).ToElement(); + for (numSameTags = 0; child; child = child->NextSiblingElement( tokens.at(0) ), ++numSameTags) { + // nothing + } + ret = numSameTags; + } + return ret; +} + +//--------------------------------------------------------- +bool xmlIO::loadFromBuffer( string buffer ) +{ + + int size = buffer.size(); + + bool loadOkay = doc.ReadFromMemory( buffer.c_str(), size);//, TiXmlEncoding encoding = TIXML_DEFAULT_ENCODING); + + return loadOkay; + +} +//--------------------------------------------------------- +void xmlIO::copyXmlToString(string & str) +{ + TiXmlPrinter printer; + doc.Accept(&printer); + + str = printer.CStr(); +} + diff --git a/rotord/src/xmlIO.h b/rotord/src/xmlIO.h new file mode 100755 index 0000000..84db7ca --- /dev/null +++ b/rotord/src/xmlIO.h @@ -0,0 +1,169 @@ +#ifndef __xmlIO_ +#define __xmlIO_ + +//#include "ofMain.h" +//based on xmlSettings from openframeworks, manythanks! +#include <string.h> +#include <vector> +#include "tinyxml.h" +#include "ofUtils.h" + +using namespace std; + +/* + Q: what is the which = 0 argument? + + A: Glad you asked - most of the time you can ignore this and treat it as if it weren't there + But if specified it selects the nth tag with the same tag name at the current root of the document + Normally this just means the top level tags in the document - but if you use the pushTag and popTag + you can temporarily set the root of the document to be that specified tag. + The main idea is to allow you to have multiple tags with the same name. + + So here is an example without pushTag + + <time>102229</time> <-- which = 0 + <time>298292</time> <-- which = 1 + <time>393393</time> <-- which = 2 + <time>447373</time> <-- which = 3 + + But if we wanted to group these into multiple <recording> tags and have multiple time values inside + we can use push and pop to move into the recording tags as if they were the document root + + <recording> <-- we temporarily push into here with pushTag("recording", 0); + <time>19222</time> <-- to set this we call setValue("time", 19222, 0); ( which = 0 ) + <time>23232</time> <-- to set this we call setValue("time", 23232, 1); ( which = 1 ) + </recording> <-- we pop back out here with popTag(); + + <recording> <-- we temporarily push into here with pushTag("recording", 1); <-- now we use 1 to select the 2nd recording tag + <time>33342</time> <-- setValue("time", 33342, 0); ( which = 0 ) + <time>22722</time> <-- setValue("time", 22722, 0); ( which = 1 ) + </recording> + +*/ + + +#define MAX_TAG_VALUE_LENGTH_IN_CHARS 1024 + +class xmlIO{ + + public: + xmlIO(); + xmlIO(const string& xmlFile); + + ~xmlIO(); + + void setVerbose(bool _verbose); + + bool loadFile(const string& xmlFile); + bool saveFile(const string& xmlFile); + bool saveFile(); + + void clearTagContents(const string& tag, int which = 0); + void removeTag(const string& tag, int which = 0); + + bool tagExists(const string& tag, int which = 0); + + // removes all tags from within either the whole document + // or the tag you are currently at using pushTag + void clear(); + + int getValue(const string& tag, int defaultValue, int which = 0); + double getValue(const string& tag, double defaultValue, int which = 0); + string getValue(const string& tag, const string& defaultValue, int which = 0); + + int setValue(const string& tag, int value, int which = 0); + int setValue(const string& tag, double value, int which = 0); + int setValue(const string& tag, const string& value, int which = 0); + + //advanced + + //-- pushTag/popTag + //pushing a tag moves you inside it which has the effect of + //temporarily treating the tag you are in as the document root + //all setValue, readValue and getValue commands are then be relative to the tag you pushed. + //this can be used with addValue to create multiple tags of the same name within + //the pushed tag - normally addValue only lets you create multiple tags of the same + //at the top most level. + + bool pushTag(const string& tag, int which = 0); + int popTag(); + int getPushLevel(); + + //-- numTags + //this only works for tags at the current root level + //use pushTag and popTag to get number of tags whithin other tags + // both getNumTags("PT"); and getNumTags("PT:X"); will just return the + //number of <PT> tags at the current root level. + int getNumTags(const string& tag); + + //-- addValue/addTag + //adds a tag to the document even if a tag with the same name + //already exists - returns an index which can then be used to + //modify the tag by passing it as the last argument to setValue + + //-- important - this only works for top level tags + // to put multiple tags inside other tags - use pushTag() and popTag() + + int addValue(const string& tag, int value); + int addValue(const string& tag, double value); + int addValue(const string& tag, const string& value); + + int addTag(const string& tag); //adds an empty tag at the current level + + + // Attribute-related methods + int addAttribute(const string& tag, const string& attribute, int value, int which = 0); + int addAttribute(const string& tag, const string& attribute, double value, int which = 0); + int addAttribute(const string& tag, const string& attribute, const string& value, int which = 0); + + int addAttribute(const string& tag, const string& attribute, int value); + int addAttribute(const string& tag, const string& attribute, double value); + int addAttribute(const string& tag, const string& attribute, const string& value); + + void removeAttribute(const string& tag, const string& attribute, int which = 0); + void clearTagAttributes(const string& tag, int which = 0); + + int getNumAttributes(const string& tag, int which = 0); + + bool attributeExists(const string& tag, const string& attribute, int which = 0); + + bool getAttributeNames(const string& tag, vector<string>& outNames, int which = 0); + + int getAttribute(const string& tag, const string& attribute, int defaultValue, int which = 0); + double getAttribute(const string& tag, const string& attribute, double defaultValue, int which = 0); + string getAttribute(const string& tag, const string& attribute, const string& defaultValue, int which = 0); + + int setAttribute(const string& tag, const string& attribute, int value, int which = 0); + int setAttribute(const string& tag, const string& attribute, double value, int which = 0); + int setAttribute(const string& tag, const string& attribute, const string& value, int which = 0); + + int setAttribute(const string& tag, const string& attribute, int value); + int setAttribute(const string& tag, const string& attribute, double value); + int setAttribute(const string& tag, const string& attribute, const string& value); + + bool loadFromBuffer( string buffer ); + void copyXmlToString(string & str); + + TiXmlDocument doc; + bool bDocLoaded; + + protected: + + TiXmlHandle storedHandle; + int level; + + + int writeTag(const string& tag, const string& valueString, int which = 0); + bool readTag(const string& tag, TiXmlHandle& valHandle, int which = 0); // max 1024 chars... + + + int writeAttribute(const string& tag, const string& attribute, const string& valueString, int which = 0); + + TiXmlElement* getElementForAttribute(const string& tag, int which); + bool readIntAttribute(const string& tag, const string& attribute, int& valueString, int which); + bool readDoubleAttribute(const string& tag, const string& attribute, double& outValue, int which); + bool readStringAttribute(const string& tag, const string& attribute, string& outValue, int which); +}; + +#endif + |
