// yuvReader.C // Copyright (c) 2009 The Foundry Visionmongers Ltd. All Rights Reserved. // Permission is granted to reuse portions or all of this code for the // purpose of implementing Nuke plugins, or to demonstrate or document // the methods needed to implemente Nuke plugins. // Reads those video files known as yuv, abekas, accom, quatel, d1, // 4:2:2, etc. Also reads interlaced versions of these files, // recognized by the name "sdl" // This is also an example of a file-reading plugin. #include "DDImage/FileReader.h" #include "DDImage/Row.h" #include // needed to measure file size using namespace DD::Image; class yuvReader : public FileReader { bool interlaced; MetaData::Bundle _meta; const MetaData::Bundle& fetchMetaData(const char* key) override { return _meta; } public: yuvReader(Read*, bool interlaced, int fd, const unsigned char*, int, int bits = 16, bool bug = false); ~yuvReader() override; void engine(int y, int x, int r, ChannelMask, Row &) override; void open() override; static const Reader::Description d; static const Reader::Description sdld; }; // You must write a test function that tells Nuke if a file really is of // the type that you can read. Usually this will work by examining data // near the start of the file, which is passed by the block pointer and // with the integer length. You can also read data from the file descriptor, // but be sure to seek it back to length. // // If the user types "yuv:name" it will only try the yuv test and will fail // if the file does not pass. // // In the more common case where the user types "name.yuv", // although the filename will be used to select which test to try first, // Nuke will go through all known file types and call the test to try to // figure out what format a file is. This allows a file to be read even // if it is misnamed. // // Yuv is unusual in that there is no data, but the files can be identified // by a very specific length, which I determine from the fd: static unsigned cachesize; // remembered static bool test(int fd, const unsigned char* block, int length) { struct stat buf; fstat(fd, &buf); cachesize = buf.st_size; if (cachesize % 1440) return false; // must be an even multiple of 720 pixels if (cachesize < 100 * 1440) return false; // also make sure it has some height return true; } // This is the function called to from the description to create the reader: static Reader* build(Read* iop, int fd, const unsigned char* b, int n) { return new yuvReader(iop, false, fd, b, n); } // The description has a null-separated list of possilble names to call // this from, and pointers to the test and build procedures: const Reader::Description yuvReader::d("yuv\0", build, test); // I also provide "sdl" files, which are the same format except the lines // are in the file in interlaced order. In order for Nuke to see this, a // plugin named "sdlReader" must be created that loads this one, usually // by just putting "load yuvReader" into a .tcl plugin: static Reader* buildsdl(Read* iop, int fd, const unsigned char* b, int n) { return new yuvReader(iop, true, fd, b, n); } const Reader::Description yuvReader::sdld("sdl\0", buildsdl, test); // The constructor should read any interesting information out of the // file and set the info_ structure by calling set_info() and then // perhaps modifying it further. // In this case I use the size measured by the test() function to set // the number of rows. yuvReader::yuvReader(Read* iop, bool interlaced, int fd, const unsigned char* block, int len, int b, bool bug) : FileReader(iop, fd, block, len) { this->interlaced = interlaced; set_info(720, cachesize / 1440, 3); // set which direction is most efficient to read. 1 for up, -1 for down: info_.ydirection(-1); _meta.setData(MetaData::DEPTH, MetaData::DEPTH_8); } // frame() is called before the first call to engine(). This can be // used to read in information that is not needed to fill in the info_ // in the constructor. For some formats this will read in all the // pixels. Because Nuke sets a mutex around the calls to this code // you don't have to worry about multithreading issues in here. // If this is a movie format, you should implement the animated(double,double) // function and return true, and this will be called when the frame // number changes. You can call frame() to get the current frame number. // For YUV files nothing needs to be done. void yuvReader::open() {} // If you allocate tables in the constructor or in frame() you will need // to make the destructor get rid of them, so memory does not leak: yuvReader::~yuvReader() { // nothing } // The engine reads individual rows out of the input. void yuvReader::engine(int y, int x, int xr, ChannelMask channels, Row& row) { // Usually it is easier to deal with the whole width of the file. This // modifies the line to the full width if a smaller amount was requested: row.range(0, 720); // Figure out the line number of the input file we want: int inputy = height() - y - 1; if (interlaced) { // this is the normal state if (inputy % 2) inputy = (height() + inputy) / 2; else inputy = (inputy / 2); } // set up pointers to the output: float* R = row.writable(Chan_Red); float* G = row.writable(Chan_Green); float* B = row.writable(Chan_Blue); // Read in the part of the file we need. This block of the file will // remain in memory until we call unlock(). This locking mechanism // allows multiple threads to read the file at once. // The other 2 arguments are the minimum and maximum length that // must be locked, it will try to lock the maximum but won't produce // an error unless less than the minimum is available. FILE_OFFSET offset = inputy * 1440; FILE_OFFSET end = offset + 1440; lock(offset, 1440, 1440); // The YUV matrix multiplication is done in file space, not linear space. // So first we convert the read bytes to floating point values in the // 0-255 range (actually values outside that range work as well) while (offset < end) { int y, u, v; u = byte(offset++) - 128; y = byte(offset++) - 16; // if (y<0) y = 0; v = byte(offset++) - 128; float Y = (1.1644f / 255.0f) * y; *R++ = Y + (1.5966f / 255.0f) * v; *G++ = Y - (0.391998f * u + .813202f * v) / 255.0f; *B++ = Y + (2.0184f / 255.0f) * u; y = byte(offset++) - 16; // if (y<0) y = 0; Y = (1.1644f / 255.0f) * y; *R++ = Y + (1.5966f / 255.0f) * v; *G++ = Y - (0.391998f * u + .813202f * v) / 255.0f; *B++ = Y + (2.0184f / 255.0f) * u; } // do this as soon as possible so other threads that are blocked // can start reading data from the file: unlock(); // Now convert the floating-point values into linear 0-1 values: for (Channel z = Chan_Red; z <= Chan_Blue; incr(z)) { float* P = row.writable(z); from_float(z, P, P, nullptr, 720); } }