A hint: This file contains one or more very long lines, so maybe it is better readable using the pure text view mode that shows the contents as wrapped lines within the browser window.
1 /** 2 * @brief Read RAW Yuv files, commonly used for video compression 3 * 4 * This file is a part of PFSTOOLS package. 5 * ---------------------------------------------------------------------- 6 * Copyright (C) 2017 Rafal Mantiuk 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 * ---------------------------------------------------------------------- 22 * 23 * @author George Ash 24 * 25 */ 26 27 #include <config.h> 28 #include <string.h> 29 30 #include <cstdlib> 31 32 #include <algorithm> 33 #include <iostream> 34 #include <vector> 35 //#include <arpa/inet.h> 36 37 #include <cmath> 38 #include <getopt.h> 39 #include <pfs.h> 40 #include <pfsutils.cpp> 41 #include <climits> 42 43 #define PROG_NAME "pfsinyuv" 44 45 char charsToRemove[7] = {'b', 'i', 't', 's', 'f', 'p', 's'}; 46 47 //PL means a planar format per frame. 48 enum ChromaFormat {PL420, PL444, V210}; 49 50 template<typename T> 51 inline T clamp( T val, T min, T max ) 52 { 53 if( val < min ) 54 return min; 55 if( val > max ) 56 return max; 57 return val; 58 } 59 60 61 template<typename T> 62 bool read_yuv_channel( FILE *fh, int width, int height, int stride, pfs::Array2D *dest, float gain, float offset, float min, float max ) 63 { 64 T *line_buf = new T[width]; 65 66 for( int y = 0; y < height; y++ ) { size_t read = fread( line_buf, sizeof( T ), width, fh ); 67 if( y == 0 && read == 0 ) { // End of file reached 68 delete [] line_buf; 69 return false; 70 } 71 72 if( read != width ) { 73 delete [] line_buf; 74 throw pfs::Exception( "Error when reading Yuv file" ); 75 } 76 for( int x = 0; x < width; x++ ) { 77 float v_float = (float)line_buf[x]*gain - offset; 78 (*dest)(x<<stride, y<<stride) = clamp( v_float, min, max ); 79 } 80 81 } 82 delete [] line_buf; 83 return true; 84 } 85 86 void upscale_420to444( pfs::Array2D *ch ) 87 { 88 const int height = ch->getRows(); 89 const int width = ch->getCols(); 90 91 // For even rows 92 for( int y = 0; y < height; y += 2 ) { 93 float v_l = (*ch)(0,y); 94 float v_r; 95 int x; 96 for( x = 1; x < (width-1); x += 2 ) { 97 v_r = (*ch)(x+1,y); 98 // Interpolate between pixels on the left and right 99 (*ch)(x,y) = (v_l + v_r)/2.f; 100 v_l = v_r; 101 } 102 if( x < width ) 103 (*ch)(x,y) = v_r; 104 } 105 106 // For odd rows 107 int y; 108 for( y = 1; y < (height-1); y += 2 ) { 109 for( int x = 0; x < width; x++ ) { 110 (*ch)(x,y) = ((*ch)(x,y-1) + (*ch)(x,y+1))/2.f; 111 } 112 } 113 // Last row 114 if( y < height ) { 115 for( int x = 0; x < width; x++ ) { 116 (*ch)(x,y) = (*ch)(x,y-1); 117 } 118 } 119 } 120 121 void upscale_422to444( pfs::Array2D *ch ) 122 { 123 const int height = ch->getRows(); 124 const int width = ch->getCols(); 125 // For odd columns 126 for (int y = 0; y < height; y++){ 127 for( int x = 1; x < width-1; x+=2 ) { 128 (*ch)(x,y) = ((*ch)(x-1,y) + (*ch)(x+1,y))/2.f; 129 } 130 } 131 } 132 133 void parse210Block(unsigned int word, pfs::Array2D *thirdPart, pfs::Array2D *secondPart, pfs::Array2D *firstPart, unsigned int thirdPartX, unsigned int secondPartx, unsigned int firstPartX, unsigned int y, bool ycy){ //last bool denotes the ordering of luma and chroma within a word. not ycy -? cyc 134 135 const unsigned int bitmask = 0x03FF; 136 137 const float luma_offset = 16.f/219.f; 138 const float luma_gain = 1.f/( (float)(1<<(2))*219.f); 139 140 const float chroma_offset = 128.f/224.f; 141 const float chroma_gain = 1.f/( (float)(1<<(2))*224.f); 142 143 //now because of the somewhat arbitrary ordering, we don't know beforehand what offset and gain to apply. I'm just trying to make this less messy, but there's definitely a better way. TODO this. 144 float first_last_gain = ycy ? luma_gain : chroma_gain; 145 float first_last_offset = ycy ? luma_offset : chroma_offset; 146 147 float second_gain = ycy ? chroma_gain : luma_gain; 148 float second_offset = ycy ? chroma_offset : luma_offset; 149 150 (*thirdPart)(thirdPartX,y) = (float)((word&(bitmask<<20))>>20)*first_last_gain - first_last_offset; 151 (*secondPart)(secondPartx,y) = (float)((word&(bitmask<<10))>>10)*second_gain - second_offset; 152 (*firstPart)(firstPartX,y) = (float)(word&(bitmask))*first_last_gain - first_last_offset; 153 } 154 155 bool read_v210_yuv( FILE *fh, int width, int height, pfs::Array2D *Ydest,pfs::Array2D *Cbdest, pfs::Array2D *Crdest){ 156 assert(sizeof(unsigned int) == 4); 157 158 //2*width needed because width is needed to store luma, and width is also needed to store both chroma 159 160 unsigned int paddedLineWidth = (unsigned int) ceil(((float)width)/6.)*4; //in 32 bit words 161 unsigned int * line_buf = new unsigned int[paddedLineWidth]; 162 163 164 for( int y = 0; y<height; y++){ 165 size_t read = fread( line_buf, sizeof( unsigned int ), paddedLineWidth, fh ); 166 167 if( y == 0 && read == 0 ) { // End of file reached 168 delete [] line_buf; 169 return false; 170 } 171 else if (read != paddedLineWidth){ 172 delete [] line_buf; 173 throw pfs::Exception("Error when reading YUV file"); 174 } 175 else{ 176 //who came up with this insane format 177 //we read 4 words at a time :'( 178 //loop over image pixels, therefore actual data lies in 2* our iteration variable 179 //+= 6 because we write to 6 pixels below 180 unsigned int bufIndex; 181 for (int x = 0; x<width; x+=6){ 182 unsigned int word; 183 bufIndex = (x/6)*4; 184 185 //we store chroma in even cells, will interpolate later 186 187 //first block 188 word = line_buf[bufIndex]; 189 190 parse210Block(word, Crdest, Ydest, Cbdest, x, x, x, y, false); 191 192 //second block 193 word = line_buf[bufIndex + 1]; 194 195 parse210Block(word, Ydest, Cbdest, Ydest, x+2, x+2, x+1, y, true); 196 197 //third block 198 word = line_buf[bufIndex + 2]; 199 200 parse210Block(word, Cbdest, Ydest, Crdest, x+4, x+3, x+2, y, false); 201 202 //forth block 203 word = line_buf[bufIndex + 3]; 204 205 parse210Block(word, Ydest, Crdest, Ydest, x+5, x+4, x+4, y, true); 206 207 } 208 } 209 } 210 delete[] line_buf; 211 upscale_422to444(Cbdest); 212 upscale_422to444(Crdest); 213 return true; 214 } 215 216 void removeCharsFromString(std::string &str, char* charsToRemove ) { 217 for ( unsigned int i = 0; i < strlen(charsToRemove); ++i ) { 218 str.erase( remove(str.begin(), str.end(), charsToRemove[i]), str.end() ); 219 } 220 } 221 222 int parseIntField(std::string i){ 223 //parses an integer field. Removes pesky characters like the b in 10b or the p in 1080p 224 removeCharsFromString(i, charsToRemove); 225 return std::atoi(i.data()); 226 } 227 228 //more readable helper method for strings, case insensitive 229 bool contains(std::string s1, std::string s2){ 230 231 std::transform(s1.begin(), s1.end(), s1.begin(), ::tolower); 232 std::transform(s2.begin(), s2.end(), s2.begin(), ::tolower); 233 234 return s1.find(s2) != std::string::npos || s1 == s2; 235 } 236 237 238 //returns a vector of strings tokenized 239 std::vector<std::string> split(std::string s, std::string delim){ 240 return std::vector<std::string>(); 241 } 242 243 bool parseFileName(const char * fileNameIn, int * width, int * height, int * bitdepth, pfs::ColorSpace * colorspace, ChromaFormat * chromaFormat, int * fps){ 244 try{ 245 /* Parses the filename, puts the result into fields pointed to by the arguments 246 returns true if all went well, false if there's a malformed filename 247 string should be <prefix>_<width>x<height(p)>_<fps>_<bitdepth(b)>_<colorspace>_<chroma_format> 248 Regex would be nice*/ 249 std::string fileName(fileNameIn); 250 const std::string delimiter = "_"; 251 size_t pos = 0; 252 std::string token; 253 std::vector<std::string> tokens; 254 255 while ((pos = fileName.find(delimiter)) != std::string::npos) { 256 token = fileName.substr(0, pos); 257 tokens.push_back(token); 258 fileName.erase(0, pos + delimiter.length()); 259 } 260 tokens.push_back(fileName); 261 262 for (std::vector<std::string>::iterator tok = tokens.begin() + 1; tok != tokens.end(); ++tok) { 263 264 if (contains(*tok, "x")){ 265 sscanf(tok->c_str(), "%dx%d", width, height); 266 } 267 else if(*tok == "10" || *tok == "8" || *tok == "12" || tok->at(tok->length() - 1) == 'b' || contains(*tok, "bit")){ //last character is a b 268 *bitdepth = parseIntField(*tok); 269 } 270 else if(*tok == "24" || *tok == "25" || *tok == "50" || *tok == "60" || contains(*tok, "fps")){ 271 *fps = parseIntField(*tok); 272 } 273 else if (contains(*tok, "pq") || (contains(*tok, "2020") && !contains(*tok, "hlg"))){ 274 *colorspace = pfs::CS_PQYCbCr2020; 275 } 276 else if (contains(*tok, "bt") || contains(*tok, "709")){ 277 *colorspace = pfs::CS_YCbCr709; 278 } 279 else if (contains(*tok, "hlg")){ 280 *colorspace = pfs::CS_HLGYCbCr2020; 281 } 282 else if(contains(*tok, "444")) *chromaFormat = PL444; 283 else if(contains(*tok, "420")) *chromaFormat = PL420; 284 else if(contains(*tok, "V210")) *chromaFormat = V210; 285 } 286 return true; 287 } 288 catch(...){ 289 return false; 290 } 291 } 292 293 class YUVReader 294 { 295 FILE *fh; 296 int width, height; 297 int bit_depth; 298 ChromaFormat chromaFormat; 299 unsigned long int fileSize; 300 301 enum ColorSpace { REC709, REC2020 }; 302 ColorSpace color_space; 303 304 public: 305 YUVReader( FILE *fh, int width, int height, int bit_depth, ChromaFormat chromaFormat) : fh(fh), width( width ), height( height ), bit_depth( bit_depth ), chromaFormat( chromaFormat ) 306 { 307 //initialize filesize 308 fseek(fh, 0L, SEEK_END); 309 fileSize = ftell(fh); 310 //go back from where we came 311 fseek(fh, 0L, SEEK_SET); 312 } 313 314 int getWidth() const 315 { 316 return width; 317 } 318 319 int getHeight() const 320 { 321 return height; 322 } 323 324 unsigned long int getFileSize() const 325 { 326 return fileSize; 327 } 328 329 /* 330 Advances a number of frames through the file without reading them */ 331 // bool advanceFrames(int frameNo){ 332 // int divisor = subsampling_420 ? 4 : 1; //this is the integer divisor of the green and blue channels (they are 4 times smaller if 4:2:0 subsampling is enabled) 333 // long int offset = frameNo * (/*Red:*/ width * height * sizeof(STORE_FORMAT) + /*Green, Blue:*/ 2*width*height*sizeof(STORE_FORMAT)/divisor); 334 // return fseek(fh, offset, SEEK_CUR); 335 // } 336 337 /* Seek to a given frame */ 338 bool seekToFrame(int frameNo){ 339 unsigned long int offset = getFileOffsetFromFrame(frameNo); 340 if(offset > fileSize){ 341 throw pfs::Exception("Seeking past EOF, is your given frame range within the range of the input?"); 342 } 343 else{ 344 return fseek(fh, offset, SEEK_SET); 345 } 346 } 347 348 //gets the position in the file of the specified frame 349 unsigned long int getFileOffsetFromFrame(int frameNo){ 350 if(chromaFormat == V210){ 351 /*TODO This*/ 352 return (unsigned long int) ceil(((float) width)/6)*16*height*frameNo; 353 } 354 else{ 355 unsigned long int divisor = chromaFormat == PL420 ? 4 : 1; //this is the integer divisor of the green and blue channels (they are 4 times smaller if 4:2:0 subsampling is enabled) 356 357 if( frameNo <= 0 ) 358 throw pfs::Exception( "Invalid frame index. Frame are indexed from 1" ); 359 360 unsigned long int storeFormatSize = bit_depth <= 8 ? sizeof(unsigned char) : sizeof(unsigned short); 361 unsigned long int offset = (frameNo-1) * (/*Luma:*/ width * height * storeFormatSize + /*CrCb:*/ 2*width*height*storeFormatSize/divisor); 362 return offset; 363 } 364 } 365 366 /** 367 * Read a single frame from Yuv video file and store in 3 RGB channels of type pfs::Array. 368 * Integer values are converted into floating point values. 369 * 370 * @return TRUE if the frame has been loaded successfully, FALSE if end-of-file. 371 * Exception is thrown if there is an issue reading a full frame. 372 */ 373 374 bool readImage( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B ){ 375 if(chromaFormat == V210){ 376 return read_v210_yuv(fh, width, height, R, G, B); 377 } 378 else{ 379 if(bit_depth <= 8){ 380 return readImageTyped<unsigned char>(R, G, B); 381 } 382 else{ 383 return readImageTyped<unsigned short>(R, G, B); 384 } 385 } 386 } 387 388 private: 389 template<typename T> 390 bool readImageTyped( pfs::Array2D *R, pfs::Array2D *G, pfs::Array2D *B) 391 { 392 393 { // Read Y 394 const float offset = 16.f/219.f; 395 const float gain = 1.f/( (float)(1<<(bit_depth-8))*219.f); 396 if( !read_yuv_channel<T>( fh, width, height, 0, R, gain, offset, 0.f, 1.f ) ) { 397 return false; 398 } 399 } 400 401 { // Read Cb, Cr 402 const float offset = 128.f/224.f; 403 const float gain = 1.f/( (float)(1<<(bit_depth-8))*224.f); 404 unsigned int chroma_width = width; 405 unsigned int chroma_height = height; 406 unsigned int chroma_stride = 0; 407 if (chromaFormat == PL420){ 408 chroma_width = width/2; 409 chroma_height = height/2; 410 chroma_stride = 1; 411 } 412 413 if( !read_yuv_channel<T>( fh, chroma_width, chroma_height, chroma_stride, G, gain, offset, -0.5f, 0.5f ) ) 414 throw pfs::Exception( "EOF reached when reading the Cb portion of a frame" ); 415 416 if( !read_yuv_channel<T>( fh, chroma_width, chroma_height, chroma_stride, B, gain, offset, -0.5f, 0.5f ) ) 417 throw pfs::Exception( "EOF reached when reading the Cr portion of a frame" ); 418 if(chromaFormat == PL420){ 419 upscale_420to444( B ); 420 upscale_420to444( G ); 421 } 422 } 423 return true; 424 } 425 }; 426 427 428 class QuietException 429 { 430 }; 431 432 void printHelp() 433 { 434 std::cerr << PROG_NAME " [--verbose] [--quiet] [--width] [--height] [--colorspace] [--no-guess] [--chroma-format] [--bit-depth] [--frames] [--help]" << std::endl 435 << "See man page for more information." << std::endl; 436 } 437 438 439 void readFrames( int argc, char* argv[] ) 440 { 441 pfs::DOMIO pfsio; 442 443 bool verbose = false; 444 bool quiet = false; 445 bool opt_noguess = false; 446 int opt_width = -1; 447 int opt_height = -1; 448 int opt_bitdepth = -1; 449 const char * opt_chroma_subsampling = NULL; 450 int opt_frame_min = -1; 451 int opt_frame_max = -1; 452 int opt_frame_stride = 0; 453 int opt_fps = -1; 454 char * filename; 455 pfs::ColorSpace opt_colorspace = pfs::CS_INVALID; 456 457 // Parse command line parameters 458 static struct option cmdLineOptions[] = { 459 { "help", no_argument, NULL, 'p' }, 460 { "verbose", no_argument, NULL, 'v' }, 461 { "width", required_argument, NULL, 'w' }, 462 { "height", required_argument, NULL, 'h' }, 463 { "bit-depth", required_argument, NULL, 'b' }, 464 { "colorspace", required_argument, NULL, 'c' }, 465 { "quiet", no_argument, NULL, 'q' }, 466 { "no-guess", no_argument, NULL, 'n'}, 467 { "fps", required_argument, NULL, 'f'}, 468 { "chroma-format", required_argument, NULL, 's'}, 469 { "frames", required_argument, NULL, 'r'}, 470 { NULL, 0, NULL, 0 } 471 }; 472 static const char optstring[] = "-pw:h:vqb:c:f:nr:s:"; 473 474 int optionIndex = 0; 475 while( 1 ) { 476 int c = getopt_long (argc, argv, optstring, cmdLineOptions, &optionIndex); 477 if(c == -1){ 478 break; 479 } 480 switch( c ) { 481 case 'p': 482 printHelp(); 483 throw QuietException(); 484 case 'v': 485 verbose = true; 486 break; 487 case 'w': 488 opt_width = strtol( optarg, NULL, 10 ); 489 break; 490 case 'h': 491 opt_height = strtol( optarg, NULL, 10 ); 492 break; 493 case 'b': 494 opt_bitdepth = strtol( optarg, NULL, 10 ); 495 break; 496 case 's': 497 opt_chroma_subsampling = optarg; 498 break; 499 case 'n': 500 opt_noguess = true; 501 break; 502 case 'f': 503 opt_fps = strtol(optarg, NULL, 10); 504 break; 505 case 'r': { 506 pfs::parseFrameRange(optarg, opt_frame_min, opt_frame_max, opt_frame_stride); 507 VERBOSE_STR << "Reading frames " << opt_frame_min << ":" << opt_frame_stride << ":" 508 << opt_frame_max << std::endl; 509 break; 510 } 511 case 'c': 512 if( !strcasecmp( optarg, "pq2020" ) ) { 513 opt_colorspace = pfs::CS_PQYCbCr2020; 514 } 515 else if( !strcasecmp( optarg, "bt709" )) { 516 opt_colorspace = pfs::CS_YCbCr709; 517 } 518 else if( !strcasecmp( optarg, "hlg2020")){ 519 opt_colorspace = pfs::CS_HLGYCbCr2020; 520 } 521 else { 522 throw pfs::Exception( "Unrecognized colorspace name" ); 523 } 524 break; 525 case 'q': 526 quiet = true; 527 break; 528 case '?': 529 throw QuietException(); 530 case ':': 531 throw QuietException(); 532 case '\1': 533 filename = optarg; 534 } 535 } 536 537 FILE * fh; 538 if(filename != NULL){ 539 fh = fopen (filename, "rb"); 540 } 541 542 if( fh == NULL ) { 543 throw pfs::Exception("Couldn't find a filename to open, did you provide one"); 544 }; // No more frames 545 546 int width = -1, height = -1, bitdepth = -1, frame_min = 1, frame_max = INT_MAX, frame_stride = 1, fps = -1; 547 ChromaFormat chromaFormat = PL444; 548 pfs::ColorSpace colorspace = pfs::CS_INVALID; 549 550 VERBOSE_STR << "reading file '" << filename << "'" << std::endl; 551 //we infer the metadata from the filename unless noguess is specified 552 if(!opt_noguess){ 553 std::string rawName(filename); //these three lines extract the filename proper, without the containing directory prefix 554 long unsigned int substring = rawName.find_last_of("\\/"); 555 rawName = rawName.substr(substring == std::string::npos ? 0 : substring); 556 bool goodFileName = parseFileName(rawName.c_str(), &width, &height, &bitdepth, &colorspace, &chromaFormat, &fps); 557 if(!goodFileName){ 558 VERBOSE_STR << "Unable to parse filename: " << filename << "\n"; 559 } 560 } 561 // Over-ride the auto-recognized params with the ones specified as arguments 562 if( opt_width > -1 ) 563 width = opt_width; 564 if( opt_height > -1 ) 565 height = opt_height; 566 if( opt_bitdepth > -1 ) 567 bitdepth = opt_bitdepth; 568 if( opt_frame_min > -1 ) 569 frame_min = opt_frame_min; 570 if( opt_frame_max > -1 ) 571 frame_max = opt_frame_max; 572 if( opt_frame_stride != 0) 573 frame_stride = opt_frame_stride; 574 if( opt_colorspace != pfs::CS_INVALID ) 575 colorspace = opt_colorspace; 576 if( opt_chroma_subsampling != NULL ){ 577 //set up our chromat supsampling, defaults to planar 444 578 chromaFormat = 579 strcmp(opt_chroma_subsampling, "420") == 0 ? PL420 : 580 strcmp(opt_chroma_subsampling, "V210") == 0 ? V210 : 581 PL444; 582 583 } 584 if( opt_fps > 0 ){ 585 fps = opt_fps; 586 } 587 588 VERBOSE_STR << "Yuv file " << width << "x" << height << " " << bitdepth << "bits" << std::endl; 589 switch( colorspace ) { 590 case pfs::CS_PQYCbCr2020: 591 VERBOSE_STR << "colorspace: HDR PQ BT2020" << std::endl; 592 break; 593 case pfs::CS_YCbCr709: 594 VERBOSE_STR << "colorspace: SDR BT709" << std::endl; 595 break; 596 case pfs::CS_HLGYCbCr2020: 597 VERBOSE_STR << "colorspace: HDR HLG BT2020" << std::endl; 598 break; 599 } 600 601 if( width <= 0 || height <= 0 ) 602 throw pfs::Exception( "Unspecified or incorrect resolution of the Yuv file" ); 603 604 if( bitdepth < 8 || bitdepth > 16 ) 605 throw pfs::Exception( "Unspecified or incorrect bit-depth of the Yuv file" ); 606 607 if( frame_min < 0 || frame_max < 0){ 608 throw pfs::Exception( "Frame range is invalid "); 609 } 610 if( colorspace == pfs::CS_INVALID ){ 611 throw pfs::Exception( "Unspecified colorspace of the Yuv file" ); 612 } 613 if( fps > 0 ){ 614 VERBOSE_STR << "FPS: " << fps << std::endl; 615 } 616 617 YUVReader reader( fh, width, height, bitdepth, chromaFormat); 618 619 620 int currentFrame = frame_min; 621 622 while( (currentFrame <= frame_max && frame_stride > 0) 623 || (currentFrame >= frame_max && frame_stride < 0) ) { //inclusive 624 625 pfs::Frame *frame = pfsio.createFrame( reader.getWidth(), 626 reader.getHeight() ); 627 pfs::Channel *X, *Y, *Z; 628 frame->createXYZChannels( X, Y, Z ); 629 630 if(reader.seekToFrame(currentFrame)) { 631 //some error occured in reading :( 632 pfsio.freeFrame( frame ); 633 break; 634 } 635 636 //Store RGB data temporarily in XYZ channels 637 bool success = reader.readImage( X, Y, Z ); 638 if( !success ) { // EOF reached 639 pfsio.freeFrame( frame ); 640 break; 641 } 642 643 VERBOSE_STR << "Reading frame " << currentFrame << std::endl; 644 645 if( colorspace == pfs::CS_YCbCr709 ) { 646 // The trick to get LDR data in the pfs stream 647 pfs::transformColorSpace( colorspace, X, Y, Z, pfs::CS_SRGB, X, Y, Z ); 648 pfs::transformColorSpace( pfs::CS_RGB, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); 649 } else { 650 pfs::transformColorSpace( colorspace, X, Y, Z, pfs::CS_XYZ, X, Y, Z ); 651 } 652 653 654 switch( colorspace ) { 655 case pfs::CS_PQYCbCr2020: 656 frame->getTags()->setString("LUMINANCE", "ABSOLUTE"); 657 break; 658 case pfs::CS_YCbCr709: 659 frame->getTags()->setString("LUMINANCE", "DISPLAY"); 660 break; 661 case pfs::CS_HLGYCbCr2020: 662 frame->getTags()->setString("LUMINANCE", "ABSOLUTE"); 663 break; 664 } 665 666 if( fps > 0 ){ 667 frame->getTags()->setString("FPS", pfs::intToString(fps).c_str()); 668 } 669 670 const char *fileNameTag = strcmp( "-", filename )==0 ? "stdin" : filename; 671 frame->getTags()->setString( "FILE_NAME", fileNameTag ); 672 673 pfsio.writeFrame( frame, stdout ); 674 pfsio.freeFrame( frame ); 675 676 currentFrame += frame_stride; 677 } 678 fclose(fh); 679 } 680 681 682 int main( int argc, char* argv[] ) 683 { 684 try { 685 readFrames( argc, argv ); 686 } 687 catch( pfs::Exception ex ) { 688 fprintf( stderr, PROG_NAME " error: %s\n", ex.getMessage() ); 689 return EXIT_FAILURE; 690 } 691 catch( QuietException ) { 692 return EXIT_FAILURE; 693 } 694 return EXIT_SUCCESS; 695 }