#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 300713 trying to use opencv video loader for seeking not so good - conflicts with included libav - seems to be incorrectly loaded would maybe need to do something like http://stackoverflow.com/questions/12427928/configure-and-build-opencv-to-custom-ffmpeg-install */ #include #include #include #include #include #include #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 #include #include #include #include #include #include #include #include //#include stops the compiler error but causes a linker error. does libavcodec need to be statically linked? #include #include //#include } */ #define AUDIO_INBUF_SIZE 20480 #define AUDIO_REFILL_THRESH 4096 #include //for opencv video IO #include "xmlIO.h" #include "utils.h" //fequal #include "cvimage.h" #include "libavwrapper.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); } int frame(){ return (int)((time*framerate)+0.5); //rounded to the nearest frame } }; 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); //} }; 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)); } float Rfloat(){ return ((float)r)/255.0f; } float Gfloat(){ return ((float)g)/255.0f; } float Bfloat(){ return ((float)b)/255.0f; } 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 &_settings)=0; virtual ~Node(){}; UUID uid; //every usable node has a UUID int id; vector inputs; //simple node can have signal inputs, output depends on node type vector 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 &settings,string key,string def=""){ if (settings.find(key)!=settings.end()) return settings[key]; else return def;}; float find_setting(map &settings,string key,float def){ if (settings.find(key)!=settings.end()) return ofToFloat(settings[key]); else return def;}; int find_setting(map &settings,string key,int def){ if (settings.find(key)!=settings.end()) return ofToInt(settings[key]); else return def;}; void base_settings(map &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_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 &settings) { base_settings(settings); }; Time* clone(map &_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 &settings) { base_settings(settings); }; Track_time* clone(map &_settings) { return new Track_time(_settings);}; const float output(const Time_spec &time) { return time.time/time.duration; } }; class Signal_output: public Signal_node { public: Signal_output(){}; Signal_output(map &settings) { base_settings(settings); }; Signal_output* clone(map &_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 &settings) { base_settings(settings); image=new Image(); }; ~Testcard(){ if (image) delete image;}; Testcard* clone(map &_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;iRGBdata[(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 &settings) { base_settings(settings); image=new Image(); }; ~Invert(){ if (image) delete image;}; Invert* clone(map &_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;iw*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_cycler: public Image_node { //cycles through video inputs in order public: Video_cycler(){}; Video_cycler(map &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 &_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 &settings) { base_settings(settings); string colours=find_setting(settings,"palette",""); for (int i=0;iconnection){ 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 &_settings) { return new Signal_colour(_settings);}; private: vector palette; Image image; int prevcol; }; class Signal_greyscale: public Image_node { //Draws signal bars in greyscale public: Signal_greyscale(){}; Signal_greyscale(map &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 &_settings) { return new Signal_greyscale(_settings);}; private: Image image; uint8_t prevcol; }; #define ARITHMETIC_plus 1 #define ARITHMETIC_minus 2 #define ARITHMETIC_multiply 3 #define ARITHMETIC_divide 4 #define ARITHMETIC_modulo 5 class Image_arithmetic: public Image_node { //Draws signal bars in greyscale public: Image_arithmetic(){image=nullptr;}; Image_arithmetic(map &settings) { base_settings(settings); value=find_setting(settings,"value",0.0f); string _op=find_setting(settings,"operator","+"); if (_op=="+"||_op=="plus"||_op=="add") op=ARITHMETIC_plus; if (_op=="-"||_op=="minus"||_op=="subtract") op=ARITHMETIC_minus; if (_op=="*"||_op=="x"||_op=="multiply") op=ARITHMETIC_multiply; if (_op=="/"||_op=="divide") op=ARITHMETIC_divide; //if (_op=="%"||_op=="mod"||_op=="modulo"||_op=="modulus") op=ARITHMETIC_modulo; ??what would this even mean? image=nullptr; } 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 &_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 &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 &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;iconnection){ if (LUT) { apply_LUT(*(((Image_node*)image_inputs[0]->connection)->get_output(frame))); return image; } } } return nullptr; } Luma_levels* clone(map &_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 &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-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 ("<add_wrap(*temp); } delete temp; } else { if (mode<0.5) (*image)+=*(images[absframe]); else (*image)=image->add_wrap(*(images[absframe])); } } } //for (int i=0;iRGBdata[i]=LUT[in->RGBdata[i]]; //} lastframe=thisframe; return image; } } } } return nullptr; } Echo_trails* clone(map &_settings) { return new Echo_trails(_settings);}; protected: float duration,fadeto; int number; int interval,total,lastframe; //number of frames between displayed echoes unordered_map 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 &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 &_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 &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 &_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;iw/2;i++){ for (int j=0;jh;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;iw;i++){ for (int j=0;jh/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;iw/2;i++){ for (int j=0;jh;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;iw;i++){ for (int j=0;jh/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 &settings) { base_settings(settings); }; ~Monochrome(){ }; Monochrome* clone(map &_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;iRGBdata[(((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 &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 &_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 &settings) { base_settings(settings); image=nullptr; }; ~Alpha_merge(){ if (image) delete image;}; Alpha_merge* clone(map &_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 }; #define VIDEOFRAMES_still 1 #define VIDEOFRAMES_blend 2 class Video_loader: public Image_node { public: Video_loader(){}; Video_loader(map &settings) { base_settings(settings); isLoaded=false; filename=find_setting(settings,"filename",""); speed=find_setting(settings,"speed",1.0f); framerate=find_setting(settings,"framerate",0.0f); //0.0f signifies to use the internal framerate if (filename!="") { load(find_setting(settings,"media_path","")+filename); } string frame_op=find_setting(settings,"mode","still"); if (frame_op=="still") mode=VIDEOFRAMES_still; if (frame_op=="blend") mode=VIDEOFRAMES_blend; lastframe=0; }; ~Video_loader(){}; bool load(const string &filename); Image *output(const Frame_spec &frame); Video_loader* clone(map &_settings) { return new Video_loader(_settings);}; bool isLoaded; private: libav::decoder player; Image image; string filename; float speed; float framerate; int mode; int lastframe; }; /* class CVideo_loader: public Image_node { // attempt // /usr/bin/ld: warning: libavcodec.so.53, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavcodec.so.55 // /usr/bin/ld: warning: libavformat.so.53, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavformat.so.55 // /usr/bin/ld: warning: libavutil.so.51, needed by /usr/lib/gcc/i686-linux-gnu/4.7/../../../../lib/libopencv_highgui.so, may conflict with libavutil.so.52 // No URL Protocols are registered. Missing call to av_register_all()? // libav::Error: file:///mnt/rotor/media/newsins1_360.mp4 Protocol not found // Rotor::Video_loader: failed to load /mnt/rotor/media/newsins1_360.mp4 // 30-07-2013 09:35:31 Rotor: ERROR: could not load newsins1_360.mp4 into video node 03 public: CVideo_loader(){}; CVideo_loader(map &settings) { base_settings(settings); isLoaded=false; }; ~CVideo_loader(){}; bool load(const string &filename); Image *output(const Frame_spec &frame); CVideo_loader* clone(map &_settings) { return new CVideo_loader(_settings);}; bool isLoaded; private: cv::VideoCapture player; Image image; }; */ class Video_output: public Image_node { public: Video_output(){}; Video_output(map &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 &_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 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 &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 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 nodes; vector 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,string media_path); bool loadFile(string &filename,string media_path); bool parseXml(string media_path); 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 &_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"<& command); void session_command(const std::vector& 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 processors); bool _load_audio(const string &filename,vector 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 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