Skip to content

Commit 5e5e09c

Browse files
authored
fix(oiiotool): fixes to --missingfile behavior (AcademySoftwareFoundation#4803)
Fix some `oiiotool --missingfile` behavioral corner cases: When substituting, warn rather than making a true error. This didn't seem like a big deal for a single command line, but when doing a loop over frames, it would prevent correct behavior from pressing on to the next iteration. How big is the substitute image? It uses the rez and channels of the first image successfully read (if available). But it when in a frame loop, it resets this on every iteration, so any missing frame looks like it doesn't have a good size guess -- but it does, as long as it's not the first iteration of the loop. This involves safely gettingthe first image dimensions from the *parent* (each iteration is a child Oiiotool object). Signed-off-by: Larry Gritz <lg@larrygritz.com>
1 parent b7e1862 commit 5e5e09c

2 files changed

Lines changed: 74 additions & 30 deletions

File tree

src/oiiotool/oiiotool.cpp

Lines changed: 48 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,8 @@ Oiiotool::clear_options()
174174
printinfo_nometamatch.clear();
175175
printinfo_verbose = false;
176176
clear_input_config();
177-
first_input_dimensions = ImageSpec();
178-
output_dataformat = TypeDesc::UNKNOWN;
177+
m_first_input_dimensions = ImageSpec();
178+
output_dataformat = TypeDesc::UNKNOWN;
179179
output_channelformats.clear();
180180
output_bitspersample = 0;
181181
output_scanline = false;
@@ -5239,12 +5239,14 @@ input_file(Oiiotool& ot, cspan<const char*> argv)
52395239
if (!ot.imagecache->get_image_info(ustring(filename), 0, 0,
52405240
ustring("exists"), TypeInt, &exists))
52415241
exists = 0;
5242-
// If the image doesn't appear t exist, but it's a procedural image
5243-
// generator, then that's ok.
5242+
// N.B. ImageCache "exists" really means "can be opened and read."
52445243
if (!exists) {
52455244
auto input = ImageInput::create(filename);
5246-
if (input && input->supports("procedural"))
5245+
if (input && input->supports("procedural")) {
5246+
// If the image doesn't appear to exist, but it's a procedural
5247+
// image generator, then that's ok.
52475248
exists = 1;
5249+
}
52485250
if (!input) {
52495251
// If the create call failed, eat any stray global errors it
52505252
// may have issued.
@@ -5253,36 +5255,49 @@ input_file(Oiiotool& ot, cspan<const char*> argv)
52535255
}
52545256
ImageBufRef substitute; // possible substitute for missing image
52555257
if (!exists) {
5256-
// Try to get a more precise error message to report
5257-
if (!Filesystem::exists(filename))
5258-
ot.errorfmt("read", "File does not exist: \"{}\"", filename);
5259-
else {
5258+
// It neither can be opened nor is it a procedural.
5259+
std::string errmsg;
5260+
if (!Filesystem::exists(filename)) {
5261+
// It literally is a nonexistant file.
5262+
errmsg = Strutil::format("File does not exist: \"{}\"",
5263+
filename);
5264+
} else {
5265+
// The file exists, but it can't be opened as an image.
5266+
// Try to get a more precise error message to report.
52605267
auto in = ImageInput::open(filename);
52615268
std::string err = in ? in->geterror() : OIIO::geterror();
5262-
ot.error("read", ot.format_read_error(filename, err));
5269+
errmsg = Strutil::format(ot.format_read_error(filename, err));
52635270
}
52645271
// Second chances: do we have a substitute image policy?
5272+
ImageSpec substitute_spec;
5273+
if (ot.first_input_dimensions_is_set())
5274+
ot.get_first_input_dimensions(substitute_spec);
5275+
else if (ot.parent_oiiotool
5276+
&& ot.parent_oiiotool->first_input_dimensions_is_set())
5277+
ot.parent_oiiotool->get_first_input_dimensions(substitute_spec);
5278+
else {
5279+
// How big to make the substitute image if we haven't yet read
5280+
// any image successfully? Punt and guess HD res RGBA.
5281+
substitute_spec = ImageSpec(1920, 1080, 4);
5282+
}
52655283
if (ot.missingfile_policy == "black") {
5266-
ImageSpec substitute_spec = ot.first_input_dimensions;
5267-
if (substitute_spec.format == TypeUnknown
5268-
|| !substitute_spec.width || !substitute_spec.height
5269-
|| !substitute_spec.nchannels)
5270-
substitute_spec = ImageSpec(1920, 1080, 4);
52715284
substitute.reset(
52725285
new ImageBuf(substitute_spec, InitializePixels::Yes));
52735286
} else if (ot.missingfile_policy == "checker") {
5274-
ImageSpec substitute_spec = ot.first_input_dimensions;
5275-
if (substitute_spec.format == TypeUnknown
5276-
|| !substitute_spec.width || !substitute_spec.height
5277-
|| !substitute_spec.nchannels)
5278-
substitute_spec = ImageSpec(1920, 1080, 4);
52795287
substitute.reset(new ImageBuf(substitute_spec));
52805288
ImageBufAlgo::checker(*substitute, 64, 64, 1,
52815289
{ 0.0f, 0.0f, 0.0f, 1.0f },
52825290
{ 1.0f, 1.0f, 1.0f, 1.0f });
52835291
}
5284-
if (!substitute)
5285-
break;
5292+
if (!exists) {
5293+
if (substitute) {
5294+
ot.warningfmt("read", "{}, Substituting {}", errmsg,
5295+
ot.missingfile_policy);
5296+
} else {
5297+
ot.error("read", errmsg);
5298+
break;
5299+
}
5300+
}
52865301
}
52875302
if (channel_set.size()) {
52885303
ot.input_channel_set = channel_set;
@@ -5306,11 +5321,15 @@ input_file(Oiiotool& ot, cspan<const char*> argv)
53065321
ot.read(policy, channel_set);
53075322
} else
53085323
ot.read_nativespec();
5309-
if (ot.first_input_dimensions.format == TypeUnknown) {
5310-
ot.first_input_dimensions.copy_dimensions(
5311-
*ot.curimg->nativespec());
5312-
ot.first_input_dimensions.channelnames
5324+
if (!ot.first_input_dimensions_is_set()) {
5325+
ImageSpec new_first_dims;
5326+
new_first_dims.copy_dimensions(*ot.curimg->nativespec());
5327+
new_first_dims.channelnames
53135328
= ot.curimg->nativespec()->channelnames;
5329+
ot.set_first_input_dimensions(new_first_dims);
5330+
if (ot.parent_oiiotool)
5331+
ot.parent_oiiotool->set_first_input_dimensions(
5332+
new_first_dims);
53145333
}
53155334
}
53165335
if ((printinfo || ot.printstats || ot.dumpdata || ot.hash)
@@ -7208,8 +7227,9 @@ one_sequence_iteration(Oiiotool& otmain, size_t i, int frame_number,
72087227
}
72097228

72107229
Oiiotool otit; // Oiiotool for this iteration
7211-
otit.imagecache = otmain.imagecache;
7212-
otit.frame_number = frame_number;
7230+
otit.imagecache = otmain.imagecache;
7231+
otit.frame_number = frame_number;
7232+
otit.parent_oiiotool = &otmain;
72137233
otit.getargs((int)seq_argv.size(), (char**)&seq_argv[0]);
72147234

72157235
if (otit.ap.aborted()) {

src/oiiotool/oiiotool.h

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,13 @@ class Oiiotool {
8989
std::string printinfo_nometamatch;
9090
std::string printinfo_format;
9191
std::string missingfile_policy;
92-
ImageSpec input_config; // configuration options for reading
93-
ImageSpec first_input_dimensions;
92+
ImageSpec input_config; // configuration options for reading
9493
std::string input_channel_set; // Optional input channel set
9594
ParamValueList uservars; // User-defined variables (with --set)
9695
ArgParse ap; // Command-line argument parser
9796
ErrorHandler eh;
97+
// If this is a frame iteration child, retain a pointer to the parent
98+
Oiiotool* parent_oiiotool = nullptr;
9899

99100
struct ControlRec {
100101
std::string command; // control command: "if", "while", etc.
@@ -162,6 +163,8 @@ class Oiiotool {
162163
TypeDesc input_dataformat;
163164
int input_bitspersample = 0;
164165
std::map<std::string, std::string> input_channelformats;
166+
ImageSpec m_first_input_dimensions;
167+
mutable spin_mutex m_first_input_dimensions_mutex;
165168

166169
// stat_mutex guards when we are merging another ot's stats into this one
167170
std::mutex m_stat_mutex;
@@ -373,6 +376,27 @@ class Oiiotool {
373376

374377
ColorConfig& colorconfig();
375378

379+
// Copy the internal 'm_first_input_dimensions` to `dims`.
380+
void get_first_input_dimensions(ImageSpec& dims) const
381+
{
382+
std::lock_guard lock(m_first_input_dimensions_mutex);
383+
dims.copy_dimensions(m_first_input_dimensions);
384+
}
385+
386+
bool first_input_dimensions_is_set() const
387+
{
388+
return m_first_input_dimensions.format.basetype != TypeDesc::UNKNOWN;
389+
}
390+
391+
// If the internal 'm_first_input_dimensions` is not yet set, set it to
392+
// the values in `dims`.
393+
void set_first_input_dimensions(const ImageSpec& dims)
394+
{
395+
std::lock_guard lock(m_first_input_dimensions_mutex);
396+
if (!first_input_dimensions_is_set())
397+
m_first_input_dimensions.copy_dimensions(dims);
398+
}
399+
376400
private:
377401
CallbackFunction m_pending_callback;
378402
std::vector<const char*> m_pending_argv;

0 commit comments

Comments
 (0)