// AddTimeCode.C // Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. static const char* const CLASS = "AddTimeCode"; static const char* const HELP = "AddTimeCode:\n" "Adds a timecode to the metadata passed through."; static const double MAX_FPS_VAL = 1000; #include #include #include "DDImage/Iop.h" #include "DDImage/Knobs.h" #include "DDImage/Row.h" #include "DDImage/MetaData.h" #include "DDImage/DDMath.h" #include #include using namespace DD::Image; static const float DEFAULT_FPS = 24.0f; const char* kDefaultPrefix = "input/"; class AddTimeCode : public Iop { MetaData::Bundle _meta; float _fps; bool _fpsFromMeta; const char* _startCode; int _startFrame; bool _startSpecify; const char* _prefix; bool _customPrefix; public: void _validate(bool); void _request(int, int, int, int, ChannelMask, int); void engine(int y, int x, int r, ChannelMask, Row &); int split_input(int) const { return 1; } int maximum_inputs() const { return 1; } int minimum_inputs() const { return 1; } const char* Class() const { return CLASS; } static const Iop::Description d; const char* node_help() const { return HELP; } void knobs(Knob_Callback); AddTimeCode(Node* node) : Iop(node) { _startCode = "01:00:00:00"; _fps = DEFAULT_FPS; _fpsFromMeta = true; _startSpecify = false; _startFrame = 1; _prefix = kDefaultPrefix; _customPrefix = false; } Iop* in() const { return input(0); } void append(Hash& hash) { hash.append(outputContext().frame()); } int knob_changed(DD::Image::Knob* k) { if (k->is("useFrame")) { knob("frame")->enable(k->get_value() != 0.0); return true; } if ( k->is("customPrefix")) { knob("prefix")->enable(k->get_value() != 0.0); return true; } if (k->is("metafps")) { knob("fps")->enable(!k->get_value()); return true; } // set up the enabled/disabled knobs here, at least the first time through // this gets called the first time the ui is updated, which presumably means that the knobs have been set properly at least once if (k == &Knob::showPanel){ knob("frame")->enable(_startSpecify); knob("fps")->enable(!_fpsFromMeta); knob("prefix")->enable( _customPrefix ); } return Iop::knob_changed(k); } const MetaData::Bundle& _fetchMetaData(const char* key) { _meta = Iop::_fetchMetaData(key); int startFrame = info_.first_frame(); if (_startSpecify) { startFrame = _startFrame; } float fps = DEFAULT_FPS; if (_fpsFromMeta) { double fpsAsFl = _meta.getDouble(MetaData::FRAME_RATE); if ((!isfinite(fpsAsFl)) || (fpsAsFl > MAX_FPS_VAL)) { fps = DEFAULT_FPS; } else { fps = float(fpsAsFl); } } else fps = _fps; if (fps == 0.0f) fps = DEFAULT_FPS; int frame = int(outputContext().frame()) - startFrame; Foundry::Base::TimeBase timebase(fps); bool supportsDropFrames = timebase.supportsDropFrames(); bool dropFrames = supportsDropFrames && (Foundry::Base::Timecode::displayTypeFromString(_startCode) == Foundry::Base::Timecode::kDisplayDropFrameTimecode); // set the frame rate for downstream stuff std::string frameRateKey = std::string( MetaData::FRAME_RATE ); if ( _customPrefix ) { size_t index = frameRateKey.find_first_of( kDefaultPrefix ); if ( index != std::string::npos ) { frameRateKey = _prefix + frameRateKey.substr( strlen(kDefaultPrefix) ); } } _meta.setData(frameRateKey, fps); Foundry::Base::Time startFrameOffset = Foundry::Base::Timecode::stringToTime(_startCode, timebase, dropFrames ? Foundry::Base::Timecode::kDisplayDropFrameTimecode : Foundry::Base::Timecode::kDisplayTimecode); if (startFrameOffset == Foundry::Base::kInvalidTime) startFrameOffset = 0; Foundry::Base::Time currentFrame = startFrameOffset + frame; std::string timeCodeString = Foundry::Base::Timecode::timeToString(currentFrame, timebase, dropFrames ? Foundry::Base::Timecode::kDisplayDropFrameTimecode : Foundry::Base::Timecode::kDisplayTimecode); std::string timeCodeKey = std::string( MetaData::TIMECODE ); if ( _customPrefix ) { size_t index = timeCodeKey.find_first_of( kDefaultPrefix ); if ( index != std::string::npos ) { timeCodeKey = _prefix + timeCodeKey.substr( strlen(kDefaultPrefix) ); } } _meta.setData(timeCodeKey, timeCodeString); return _meta; } // Allow transform concatenatation between this Node's input and outputs. Transform* getTransform() { // Preserve transform concatenation - call getDefaultTransform, since this will pass input0's transform return getDefaultTransform(); } }; void AddTimeCode::_validate(bool) { copy_info(); // check if the parameters work float fps = DEFAULT_FPS; if (_fpsFromMeta) { MetaData::Bundle metaData = Iop::_fetchMetaData(MetaData::FRAME_RATE); double fpsAsFl = _meta.getDouble(MetaData::FRAME_RATE); if ((!isfinite(fpsAsFl)) || (fpsAsFl > MAX_FPS_VAL)) { fps = DEFAULT_FPS; } else { fps = float(fpsAsFl); } } else fps = _fps; if (fps == 0.0f) fps = DEFAULT_FPS; Foundry::Base::TimeBase timebase(fps); bool supportsDropFrames = timebase.supportsDropFrames(); bool dropFrames = (Foundry::Base::Timecode::displayTypeFromString(_startCode) == Foundry::Base::Timecode::kDisplayDropFrameTimecode); if (!supportsDropFrames && dropFrames) error("start code indicates drop frames (';') but the fps does not."); Foundry::Base::Time startFrameOffset = Foundry::Base::Timecode::stringToTime(_startCode, timebase, dropFrames ? Foundry::Base::Timecode::kDisplayDropFrameTimecode : Foundry::Base::Timecode::kDisplayTimecode); if (startFrameOffset == Foundry::Base::kInvalidTime) error("Invalid start time code"); } void AddTimeCode::_request(int x, int y, int r, int t, ChannelMask channels, int count) { in()->request(x, y, r, t, channels, count); } void AddTimeCode::engine(int y, int x, int r, ChannelMask channels, Row& out) { out.get(*in(), y, x, r, channels); } void AddTimeCode::knobs(Knob_Callback f) { Eval_String_knob(f, &_startCode, "startcode", "start code"); Tooltip(f, "The start of the time code. Must be in the format of either HH:MM:SS:FF or HH;MM;SS;FF (for drop frames). Supports expressions, e.g. \"[frame]:00:00:00\" will set a different hour for each frame. Drop frames are assumed when the ; is used to delimit the time code, if the frame rate is 29.97 or 59.94."); Float_knob(f, &_fps, "fps"); Tooltip(f, "The frame rate to use to generate the time codes per frame of output. To write the time codes in drop frame format, the start code must have semi-colons (;) in it, and the frame rate must be one of the frame rates that support drop frames (29.97 or 59.94)."); ClearFlags(f, DD::Image::Knob::SLIDER); SetFlags(f, DD::Image::Knob::DISABLED); Bool_knob(f, &_fpsFromMeta, "metafps", "get FPS from metadata"); Int_knob(f, &_startFrame, "frame", "start frame"); SetFlags(f, DD::Image::Knob::DISABLED); Bool_knob(f, &_startSpecify, "useFrame", "use start frame?"); String_knob(f, &_prefix, "prefix" ); SetFlags(f, DD::Image::Knob::DISABLED); Tooltip(f, "The prefix to use for the metadata keys. The default is 'input/'. For example input/fps input/timecode."); SetFlags(f, DD::Image::Knob::DISABLED); Bool_knob(f, &_customPrefix, "customPrefix", "use custom prefix"); } static Iop* build(Node* node) { return new AddTimeCode(node); } const Iop::Description AddTimeCode::d(CLASS, "MetaData/Modify", build);