diff --git a/data/Filter.otio b/data/Filter.otio index c91c600..027a970 100644 --- a/data/Filter.otio +++ b/data/Filter.otio @@ -201,7 +201,7 @@ "OTIO_SCHEMA": "Effect.1", "effect_name": "toucan:Blur", "metadata": { - "radius": 50.0 + "radius": 10.0 } } ], @@ -245,7 +245,7 @@ "effect_name": "toucan:UnsharpMask", "metadata": { "kernel": "gaussian", - "width": 50.0, + "width": 10.0, "contrast": 1.0, "threshold": 0.0 } diff --git a/data/MissingMedia.otio b/data/MissingMedia.otio new file mode 100644 index 0000000..fb30e4a --- /dev/null +++ b/data/MissingMedia.otio @@ -0,0 +1,52 @@ +{ + "OTIO_SCHEMA": "Timeline.1", + "metadata": {}, + "name": "Gap", + "tracks": { + "OTIO_SCHEMA": "Stack.1", + "children": [ + { + "OTIO_SCHEMA": "Track.1", + "children": [ + { + "OTIO_SCHEMA": "Clip.1", + "media_reference": { + "OTIO_SCHEMA": "ExternalReference.1", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "target_url": "Letter_None.png" + }, + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "name": "Letter_None" + } + ], + "kind": "Video", + "name": "Video" + } + ], + "name": "Stack" + } +} diff --git a/data/MissingMedia2.otio b/data/MissingMedia2.otio new file mode 100644 index 0000000..0d0d44c --- /dev/null +++ b/data/MissingMedia2.otio @@ -0,0 +1,86 @@ +{ + "OTIO_SCHEMA": "Timeline.1", + "metadata": {}, + "name": "Gap", + "tracks": { + "OTIO_SCHEMA": "Stack.1", + "children": [ + { + "OTIO_SCHEMA": "Track.1", + "children": [ + { + "OTIO_SCHEMA": "Clip.1", + "media_reference": { + "OTIO_SCHEMA": "ExternalReference.1", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "target_url": "Letter_A.png" + }, + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "name": "Letter_A" + }, + { + "OTIO_SCHEMA": "Clip.1", + "media_reference": { + "OTIO_SCHEMA": "ExternalReference.1", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "target_url": "Letter_None.png" + }, + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 3 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24, + "value": 0 + } + }, + "name": "Letter_None" + } + ], + "kind": "Video", + "name": "Video" + } + ], + "name": "Stack" + } +} diff --git a/lib/toucan/FFmpeg.cpp b/lib/toucan/FFmpeg.cpp index 0fb1498..1f6e5bc 100644 --- a/lib/toucan/FFmpeg.cpp +++ b/lib/toucan/FFmpeg.cpp @@ -25,7 +25,7 @@ namespace toucan std::vector getVideoExtensions() { - return std::vector({ ".mov", ".mp4", ".m4v" }); + return std::vector({ ".mov", ".mp4", ".m4v", ".y4m" }); } bool hasVideoExtension(const std::string& value) diff --git a/lib/toucan/FFmpegWrite.cpp b/lib/toucan/FFmpegWrite.cpp index 2fa9da9..42bacc6 100644 --- a/lib/toucan/FFmpegWrite.cpp +++ b/lib/toucan/FFmpegWrite.cpp @@ -5,6 +5,8 @@ #include "Util.h" +#include + #include #include @@ -173,7 +175,30 @@ namespace toucan void Write::writeImage(const OIIO::ImageBuf& buf, const OTIO_NS::RationalTime& time) { - const auto& spec = buf.spec(); + auto spec = buf.spec(); + const OIIO::ImageBuf* bufP = &buf; + OIIO::ImageBuf tmp; + switch (spec.format.basetype) + { + case OIIO::TypeDesc::INT8: + spec.format.basetype = OIIO::TypeDesc::UINT8; + break; + case OIIO::TypeDesc::INT16: + case OIIO::TypeDesc::UINT32: + case OIIO::TypeDesc::INT32: + case OIIO::TypeDesc::UINT64: + case OIIO::TypeDesc::INT64: + case OIIO::TypeDesc::HALF: + case OIIO::TypeDesc::FLOAT: + spec.format.basetype = OIIO::TypeDesc::UINT16; + break; + } + if (buf.spec().format.basetype != spec.format.basetype) + { + tmp = OIIO::ImageBufAlgo::copy(buf, spec.format); + bufP = &tmp; + } + AVPixelFormat avPixelFormatIn = AV_PIX_FMT_NONE; switch (spec.nchannels) { @@ -251,7 +276,7 @@ namespace toucan av_image_fill_arrays( _avFrame2->data, _avFrame2->linesize, - reinterpret_cast(buf.localpixels()), + reinterpret_cast(bufP->localpixels()), _avPixelFormatIn, spec.width, spec.height, diff --git a/lib/toucan/ImageGraph.cpp b/lib/toucan/ImageGraph.cpp index 23cea0d..44b7b8c 100644 --- a/lib/toucan/ImageGraph.cpp +++ b/lib/toucan/ImageGraph.cpp @@ -53,38 +53,64 @@ namespace toucan { if (auto externalRef = dynamic_cast(clip->media_reference())) { - auto read = std::make_shared( - _timelineWrapper->getMediaPath(externalRef->target_url()), - _timelineWrapper->getMemoryReference(externalRef->target_url())); - const auto& spec = read->getSpec(); - if (spec.width > 0) + try { - _imageSize.x = spec.width; - _imageSize.y = spec.height; - _imageChannels = spec.nchannels; - _imageDataType = toImageDataType(spec.format); - break; + auto read = std::make_shared( + _timelineWrapper->getMediaPath(externalRef->target_url()), + _timelineWrapper->getMemoryReference(externalRef->target_url())); + const auto& spec = read->getSpec(); + if (spec.width > 0) + { + _imageSize.x = spec.width; + _imageSize.y = spec.height; + _imageChannels = spec.nchannels; + _imageDataType = toImageDataType(spec.format); + break; + } + } + catch (const std::exception& e) + { + if (_options.log) + { + _options.log->log( + "ImageGraph", + e.what(), + MessageLogType::Error); + } } } else if (auto sequenceRef = dynamic_cast(clip->media_reference())) { - auto read = std::make_shared( - _timelineWrapper->getMediaPath(sequenceRef->target_url_base()), - sequenceRef->name_prefix(), - sequenceRef->name_suffix(), - sequenceRef->start_frame(), - sequenceRef->frame_step(), - sequenceRef->rate(), - sequenceRef->frame_zero_padding(), - _timelineWrapper->getMemoryReferences()); - const auto& spec = read->getSpec(); - if (spec.width > 0) + try { - _imageSize.x = spec.width; - _imageSize.y = spec.height; - _imageChannels = spec.nchannels; - _imageDataType = toImageDataType(spec.format); - break; + auto read = std::make_shared( + _timelineWrapper->getMediaPath(sequenceRef->target_url_base()), + sequenceRef->name_prefix(), + sequenceRef->name_suffix(), + sequenceRef->start_frame(), + sequenceRef->frame_step(), + sequenceRef->rate(), + sequenceRef->frame_zero_padding(), + _timelineWrapper->getMemoryReferences()); + const auto& spec = read->getSpec(); + if (spec.width > 0) + { + _imageSize.x = spec.width; + _imageSize.y = spec.height; + _imageChannels = spec.nchannels; + _imageDataType = toImageDataType(spec.format); + break; + } + } + catch (const std::exception& e) + { + if (_options.log) + { + _options.log->log( + "ImageGraph", + e.what(), + MessageLogType::Error); + } } } else if (auto generatorRef = dynamic_cast(clip->media_reference())) @@ -143,7 +169,7 @@ namespace toucan // Get the track effects. const auto& effects = track->effects(); - if (!effects.empty()) + if (trackNode && !effects.empty()) { trackNode = _effects(host, effects, trackNode); } @@ -226,7 +252,7 @@ namespace toucan } // Handle transitions. - if (item) + if (item && out) { if (auto prevTransition = OTIO_NS::dynamic_retainer_cast(prev)) { @@ -323,34 +349,65 @@ namespace toucan std::shared_ptr read; if (!_loadCache.get(externalRef, read)) { - read = std::make_shared( - _timelineWrapper->getMediaPath(externalRef->target_url()), - _timelineWrapper->getMemoryReference(externalRef->target_url())); + try + { + read = std::make_shared( + _timelineWrapper->getMediaPath(externalRef->target_url()), + _timelineWrapper->getMemoryReference(externalRef->target_url())); + } + catch (const std::exception& e) + { + if (_options.log) + { + _options.log->log( + "ImageGraph", + e.what(), + MessageLogType::Error); + } + } _loadCache.add(externalRef, read); } - out = read; //! \bug Workaround for when the available range does not match //! the range in the media. - const OTIO_NS::TimeRange& timeRange = read->getTimeRange(); - const auto availableOpt = externalRef->available_range(); - if (availableOpt.has_value() && - !availableOpt.value().start_time().strictly_equal(timeRange.start_time())) + if (read) { - timeOffset += availableOpt.value().start_time() - timeRange.start_time(); + const OTIO_NS::TimeRange& timeRange = read->getTimeRange(); + const auto availableOpt = externalRef->available_range(); + if (availableOpt.has_value() && + !availableOpt.value().start_time().strictly_equal(timeRange.start_time())) + { + timeOffset += availableOpt.value().start_time() - timeRange.start_time(); + } } + + out = read; } else if (auto sequenceRef = dynamic_cast(clip->media_reference())) { - auto read = std::make_shared( - _timelineWrapper->getMediaPath(sequenceRef->target_url_base()), - sequenceRef->name_prefix(), - sequenceRef->name_suffix(), - sequenceRef->start_frame(), - sequenceRef->frame_step(), - sequenceRef->rate(), - sequenceRef->frame_zero_padding(), - _timelineWrapper->getMemoryReferences()); + std::shared_ptr read; + try + { + read = std::make_shared( + _timelineWrapper->getMediaPath(sequenceRef->target_url_base()), + sequenceRef->name_prefix(), + sequenceRef->name_suffix(), + sequenceRef->start_frame(), + sequenceRef->frame_step(), + sequenceRef->rate(), + sequenceRef->frame_zero_padding(), + _timelineWrapper->getMemoryReferences()); + } + catch (const std::exception& e) + { + if (_options.log) + { + _options.log->log( + "ImageGraph", + e.what(), + MessageLogType::Error); + } + } out = read; } else if (auto generatorRef = dynamic_cast(clip->media_reference())) @@ -367,7 +424,7 @@ namespace toucan // Get the effects. const auto& effects = item->effects(); - if (!effects.empty()) + if (out && !effects.empty()) { out = _effects(host, effects, out); } diff --git a/lib/toucan/MessageLog.cpp b/lib/toucan/MessageLog.cpp index 4334138..4995e5f 100644 --- a/lib/toucan/MessageLog.cpp +++ b/lib/toucan/MessageLog.cpp @@ -12,6 +12,7 @@ namespace toucan const std::string& message, MessageLogType type) { + std::unique_lock lock(_mutex); switch (type) { case MessageLogType::Info: diff --git a/lib/toucan/MessageLog.h b/lib/toucan/MessageLog.h index 394f992..3e867f1 100644 --- a/lib/toucan/MessageLog.h +++ b/lib/toucan/MessageLog.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include namespace toucan @@ -24,5 +25,8 @@ namespace toucan const std::string& prefix, const std::string& message, MessageLogType = MessageLogType::Info); + + private: + std::mutex _mutex; }; } diff --git a/lib/toucan/Read.cpp b/lib/toucan/Read.cpp index 440b73b..7df5b71 100644 --- a/lib/toucan/Read.cpp +++ b/lib/toucan/Read.cpp @@ -19,22 +19,23 @@ namespace toucan _path(path), _memoryReader(getMemoryReader(memoryReference)) { - // Open the file ane get information. - try + // Open the file and get information. + if (ffmpeg::hasVideoExtension(path.extension().string())) { _ffRead = std::make_unique(path, memoryReference); _spec = _ffRead->getSpec(); _timeRange = _ffRead->getTimeRange(); } - catch (const std::exception&) - {} - if (!_ffRead) + else { _input = OIIO::ImageInput::open(_path.string(), nullptr, _memoryReader.get()); - if (_input) + if (!_input) { - _spec = _input->spec(); + std::stringstream ss; + ss << "Cannot open file: " << _path.string(); + throw std::runtime_error(ss.str()); } + _spec = _input->spec(); } } diff --git a/lib/toucanView/App.cpp b/lib/toucanView/App.cpp index b37f198..35a5538 100644 --- a/lib/toucanView/App.cpp +++ b/lib/toucanView/App.cpp @@ -68,20 +68,12 @@ namespace toucan "toucan-view", dtk::Size2I(1920, 1080)); addWindow(_window); + _window->show(); if (!_path.empty()) { - try - { - _filesModel->open(_path); - } - catch (const std::exception& e) - { - _context->getSystem()->message("ERROR", e.what(), _window); - } + open(_path); } - - _window->show(); } App::~App() @@ -115,4 +107,19 @@ namespace toucan { return _windowModel; } + + void App::open(const std::filesystem::path& path) + { + try + { + _filesModel->open(path); + } + catch (const std::exception& e) + { + _context->getSystem()->message( + "ERROR", + e.what(), + _window); + } + } } diff --git a/lib/toucanView/App.h b/lib/toucanView/App.h index a73cd84..3cd3655 100644 --- a/lib/toucanView/App.h +++ b/lib/toucanView/App.h @@ -5,6 +5,8 @@ #include +#include + namespace toucan { class FilesModel; @@ -42,6 +44,9 @@ namespace toucan //! Get the window model. const std::shared_ptr& getWindowModel() const; + //! Open a file. + void open(const std::filesystem::path&); + private: std::shared_ptr _messageLog; std::string _path; diff --git a/lib/toucanView/ExportTool.cpp b/lib/toucanView/ExportTool.cpp index ab7fd02..fa75c2a 100644 --- a/lib/toucanView/ExportTool.cpp +++ b/lib/toucanView/ExportTool.cpp @@ -216,43 +216,19 @@ namespace toucan _exportSequenceButton->setClickedCallback( [this] { - _timeRange = _file->getPlaybackModel()->getInOutRange(); - _export(); + _export(ExportType::Sequence); }); _exportStillButton->setClickedCallback( [this] { - _timeRange = OTIO_NS::TimeRange( - _file->getPlaybackModel()->getCurrentTime(), - OTIO_NS::RationalTime(1.0, _file->getPlaybackModel()->getTimeRange().duration().rate())); - _export(); + _export(ExportType::Still); }); _exportMovieButton->setClickedCallback( [this] { - _timeRange = _file->getPlaybackModel()->getInOutRange(); - const std::string baseName = _movieBaseNameEdit->getText(); - const std::string extension = _movieExtensionEdit->getText(); - const std::filesystem::path path = _outputPathEdit->getPath() / (baseName + extension); - const IMATH_NAMESPACE::V2d imageSize = _graph->getImageSize(); - ffmpeg::VideoCodec videoCodec = ffmpeg::VideoCodec::First; - ffmpeg::fromString(_movieCodecs[_movieCodecComboBox->getCurrentIndex()], videoCodec); - try - { - _ffWrite = std::make_shared( - path, - OIIO::ImageSpec(imageSize.x, imageSize.y, 3), - _timeRange, - videoCodec); - _export(); - } - catch (const std::exception& e) - { - auto dialogSystem = getContext()->getSystem(); - dialogSystem->message("ERROR", e.what(), getWindow()); - } + _export(ExportType::Movie); }); _timer = dtk::Timer::create(context); @@ -263,18 +239,6 @@ namespace toucan [this](const std::shared_ptr& file) { _file = file; - if (_file) - { - _graph = std::make_shared( - _file->getPath(), - _file->getTimelineWrapper()); - } - else - { - _timeRange = OTIO_NS::TimeRange(); - _time = OTIO_NS::RationalTime(); - _graph.reset(); - } _exportSequenceButton->setEnabled(_file.get()); _exportStillButton->setEnabled(_file.get()); _exportMovieButton->setEnabled(_file.get()); @@ -320,9 +284,51 @@ namespace toucan _setSizeHint(_layout->getSizeHint()); } - void ExportWidget::_export() + void ExportWidget::_export(ExportType type) { - _time = _timeRange.start_time(); + if (!_file) + return; + + _graph = std::make_shared( + _file->getPath(), + _file->getTimelineWrapper()); + _imageSize = _graph->getImageSize(); + + switch (type) + { + case ExportType::Sequence: + _timeRange = _file->getPlaybackModel()->getInOutRange(); + break; + case ExportType::Still: + _timeRange = OTIO_NS::TimeRange( + _file->getPlaybackModel()->getCurrentTime(), + OTIO_NS::RationalTime(1.0, _file->getPlaybackModel()->getTimeRange().duration().rate())); + break; + case ExportType::Movie: + { + _timeRange = _file->getPlaybackModel()->getInOutRange(); + const std::string baseName = _movieBaseNameEdit->getText(); + const std::string extension = _movieExtensionEdit->getText(); + const std::filesystem::path path = _outputPathEdit->getPath() / (baseName + extension); + ffmpeg::VideoCodec videoCodec = ffmpeg::VideoCodec::First; + ffmpeg::fromString(_movieCodecs[_movieCodecComboBox->getCurrentIndex()], videoCodec); + try + { + _ffWrite = std::make_shared( + path, + OIIO::ImageSpec(_imageSize.x, _imageSize.y, 3), + _timeRange, + videoCodec); + } + catch (const std::exception& e) + { + auto dialogSystem = getContext()->getSystem(); + dialogSystem->message("ERROR", e.what(), getWindow()); + } + break; + } + default: break; + } _dialog = dtk::ProgressDialog::create( getContext(), @@ -333,64 +339,73 @@ namespace toucan [this] { _timer->stop(); + _graph.reset(); _ffWrite.reset(); _dialog.reset(); }); _dialog->show(); + _time = _timeRange.start_time(); _timer->start( std::chrono::microseconds(0), [this] { - if (auto node = _graph->exec(_host, _time)) + _exportFrame(); + }); + } + + void ExportWidget::_exportFrame() + { + if (auto node = _graph->exec(_host, _time)) + { + const auto buf = node->exec(); + try + { + if (_ffWrite) { - const auto buf = node->exec(); - if (_ffWrite) - { - try - { - _ffWrite->writeImage(buf, _time); - } - catch (const std::exception& e) - { - if (_dialog) - { - _dialog->close(); - } - auto dialogSystem = getContext()->getSystem(); - dialogSystem->message("ERROR", e.what(), getWindow()); - } - } - else - { - const std::string fileName = getSequenceFrame( - _outputPathEdit->getPath().string(), - _imageBaseNameEdit->getText(), - _time.to_frames(), - _imagePaddingEdit->getValue(), - _imageExtensionEdit->getText()); - buf.write(fileName); - } + _ffWrite->writeImage(buf, _time); } - + else + { + const std::string fileName = getSequenceFrame( + _outputPathEdit->getPath().string(), + _imageBaseNameEdit->getText(), + _time.to_frames(), + _imagePaddingEdit->getValue(), + _imageExtensionEdit->getText()); + buf.write(fileName); + } + } + catch (const std::exception& e) + { if (_dialog) { - const OTIO_NS::RationalTime end = _timeRange.end_time_inclusive(); - if (_time < end) - { - _time += OTIO_NS::RationalTime(1.0, _timeRange.duration().rate()); - const OTIO_NS::RationalTime duration = _timeRange.duration(); - const double v = duration.value() > 0.0 ? - (_time - _timeRange.start_time()).value() / static_cast(duration.value()) : - 0.0; - _dialog->setValue(v); - } - else - { - _dialog->close(); - } + _dialog->close(); } - }); + getContext()->getSystem()->message( + "ERROR", + e.what(), + getWindow()); + } + } + + if (_dialog) + { + const OTIO_NS::RationalTime end = _timeRange.end_time_inclusive(); + if (_time < end) + { + _time += OTIO_NS::RationalTime(1.0, _timeRange.duration().rate()); + const OTIO_NS::RationalTime duration = _timeRange.duration(); + const double v = duration.value() > 0.0 ? + (_time - _timeRange.start_time()).value() / static_cast(duration.value()) : + 0.0; + _dialog->setValue(v); + } + else + { + _dialog->close(); + } + } } void ExportWidget::_widgetUpdate() diff --git a/lib/toucanView/ExportTool.h b/lib/toucanView/ExportTool.h index 1d5dd08..ac80c80 100644 --- a/lib/toucanView/ExportTool.h +++ b/lib/toucanView/ExportTool.h @@ -47,7 +47,16 @@ namespace toucan void sizeHintEvent(const dtk::SizeHintEvent&) override; private: - void _export(); + enum class ExportType + { + Sequence, + Still, + Movie + }; + + void _export(ExportType); + void _exportFrame(); + void _widgetUpdate(); std::shared_ptr _host; @@ -55,6 +64,7 @@ namespace toucan OTIO_NS::TimeRange _timeRange; OTIO_NS::RationalTime _time; std::shared_ptr _graph; + IMATH_NAMESPACE::V2d _imageSize = IMATH_NAMESPACE::V2d(0, 0); std::vector _movieCodecs; std::shared_ptr _ffWrite; diff --git a/lib/toucanView/FilesModel.cpp b/lib/toucanView/FilesModel.cpp index f815b01..65914e2 100644 --- a/lib/toucanView/FilesModel.cpp +++ b/lib/toucanView/FilesModel.cpp @@ -30,8 +30,8 @@ namespace toucan { if (auto context = _context.lock()) { - auto files = _files->get(); auto file = std::make_shared(context, _host, path); + auto files = _files->get(); files.push_back(file); _files->setIfChanged(files); const int index = files.size() - 1; diff --git a/lib/toucanView/MenuBar.cpp b/lib/toucanView/MenuBar.cpp index 1f2bee4..08dbd78 100644 --- a/lib/toucanView/MenuBar.cpp +++ b/lib/toucanView/MenuBar.cpp @@ -12,9 +12,7 @@ #include #include -#include #include -#include #include #include @@ -94,7 +92,7 @@ namespace toucan file.string(), [this, file] { - _filesModel->open(file); + _app.lock()->open(file); _menus["File"]->close(); }); _menus["RecentFiles"]->addItem(item); @@ -155,17 +153,7 @@ namespace toucan getWindow(), [this](const std::filesystem::path& path) { - if (auto context = getContext()) - { - try - { - _filesModel->open(path); - } - catch (const std::exception& e) - { - context->getSystem()->message("ERROR", e.what(), getWindow()); - } - } + _app.lock()->open(path); }, _filesModel->getRecentFilesModel()); } diff --git a/lib/toucanView/ThumbnailGenerator.cpp b/lib/toucanView/ThumbnailGenerator.cpp index fcaa43d..34cdb26 100644 --- a/lib/toucanView/ThumbnailGenerator.cpp +++ b/lib/toucanView/ThumbnailGenerator.cpp @@ -7,27 +7,40 @@ #include -#include - namespace toucan { ThumbnailGenerator::ThumbnailGenerator( const std::filesystem::path& path, const std::shared_ptr& timelineWrapper, - const std::shared_ptr& host) : + const std::shared_ptr& host, + const std::shared_ptr& log) : _path(path), _timelineWrapper(timelineWrapper), - _host(host) + _host(host), + _log(log) { _thread.running = true; _thread.thread = std::thread( [this] { - _graph = std::make_shared(_path, _timelineWrapper); - const IMATH_NAMESPACE::V2i& imageSize = _graph->getImageSize(); - _aspect = imageSize.y > 0 ? - (imageSize.x / static_cast(imageSize.y)) : - 0.F; + try + { + _graph = std::make_shared(_path, _timelineWrapper); + const IMATH_NAMESPACE::V2i& imageSize = _graph->getImageSize(); + _aspect = imageSize.y > 0 ? + (imageSize.x / static_cast(imageSize.y)) : + 0.F; + } + catch (const std::exception& e) + { + if (_log) + { + _log->log( + "ThumbnailGenerator", + e.what(), + MessageLogType::Error); + } + } while (_thread.running) { @@ -134,72 +147,85 @@ namespace toucan { thumbnail = i->second; } - else + else if (_graph) { - auto graph = _graph->exec(_host, request->time); - const auto sourceBuf = graph->exec(); - const auto& sourceSpec = sourceBuf.spec(); - - const dtk::Size2I thumbnailSize(request->height * _aspect, request->height); - dtk::ImageInfo info; - info.size = thumbnailSize; - switch (sourceSpec.nchannels) + try { - case 1: - switch (sourceSpec.format.basetype) + auto graph = _graph->exec(_host, request->time); + const auto sourceBuf = graph->exec(); + const auto& sourceSpec = sourceBuf.spec(); + + const dtk::Size2I thumbnailSize(request->height * _aspect, request->height); + dtk::ImageInfo info; + info.size = thumbnailSize; + switch (sourceSpec.nchannels) { - case OIIO::TypeDesc::UINT8: info.type = dtk::ImageType::L_U8; break; - case OIIO::TypeDesc::UINT16: info.type = dtk::ImageType::L_U16; break; - case OIIO::TypeDesc::HALF: info.type = dtk::ImageType::L_F16; break; - case OIIO::TypeDesc::FLOAT: info.type = dtk::ImageType::L_F32; break; + case 1: + switch (sourceSpec.format.basetype) + { + case OIIO::TypeDesc::UINT8: info.type = dtk::ImageType::L_U8; break; + case OIIO::TypeDesc::UINT16: info.type = dtk::ImageType::L_U16; break; + case OIIO::TypeDesc::HALF: info.type = dtk::ImageType::L_F16; break; + case OIIO::TypeDesc::FLOAT: info.type = dtk::ImageType::L_F32; break; + } + break; + case 2: + switch (sourceSpec.format.basetype) + { + case OIIO::TypeDesc::UINT8: info.type = dtk::ImageType::LA_U8; break; + case OIIO::TypeDesc::UINT16: info.type = dtk::ImageType::LA_U16; break; + case OIIO::TypeDesc::HALF: info.type = dtk::ImageType::LA_F16; break; + case OIIO::TypeDesc::FLOAT: info.type = dtk::ImageType::LA_F32; break; + } + break; + case 3: + switch (sourceSpec.format.basetype) + { + case OIIO::TypeDesc::UINT8: info.type = dtk::ImageType::RGB_U8; break; + case OIIO::TypeDesc::UINT16: info.type = dtk::ImageType::RGB_U16; break; + case OIIO::TypeDesc::HALF: info.type = dtk::ImageType::RGB_F16; break; + case OIIO::TypeDesc::FLOAT: info.type = dtk::ImageType::RGB_F32; break; + } + break; + default: + switch (sourceSpec.format.basetype) + { + case OIIO::TypeDesc::UINT8: info.type = dtk::ImageType::RGBA_U8; break; + case OIIO::TypeDesc::UINT16: info.type = dtk::ImageType::RGBA_U16; break; + case OIIO::TypeDesc::HALF: info.type = dtk::ImageType::RGBA_F16; break; + case OIIO::TypeDesc::FLOAT: info.type = dtk::ImageType::RGBA_F32; break; + } + break; } - break; - case 2: - switch (sourceSpec.format.basetype) - { - case OIIO::TypeDesc::UINT8: info.type = dtk::ImageType::LA_U8; break; - case OIIO::TypeDesc::UINT16: info.type = dtk::ImageType::LA_U16; break; - case OIIO::TypeDesc::HALF: info.type = dtk::ImageType::LA_F16; break; - case OIIO::TypeDesc::FLOAT: info.type = dtk::ImageType::LA_F32; break; - } - break; - case 3: - switch (sourceSpec.format.basetype) - { - case OIIO::TypeDesc::UINT8: info.type = dtk::ImageType::RGB_U8; break; - case OIIO::TypeDesc::UINT16: info.type = dtk::ImageType::RGB_U16; break; - case OIIO::TypeDesc::HALF: info.type = dtk::ImageType::RGB_F16; break; - case OIIO::TypeDesc::FLOAT: info.type = dtk::ImageType::RGB_F32; break; - } - break; - default: - switch (sourceSpec.format.basetype) + info.layout.mirror.y = true; + + if (info.isValid()) { - case OIIO::TypeDesc::UINT8: info.type = dtk::ImageType::RGBA_U8; break; - case OIIO::TypeDesc::UINT16: info.type = dtk::ImageType::RGBA_U16; break; - case OIIO::TypeDesc::HALF: info.type = dtk::ImageType::RGBA_F16; break; - case OIIO::TypeDesc::FLOAT: info.type = dtk::ImageType::RGBA_F32; break; + thumbnail = dtk::Image::create(info); + auto resizedBuf = OIIO::ImageBufAlgo::resize( + sourceBuf, + "", + 0.F, + OIIO::ROI( + 0, info.size.w, + 0, info.size.h, + 0, 1, + 0, std::min(4, sourceSpec.nchannels))); + memcpy( + thumbnail->getData(), + resizedBuf.localpixels(), + thumbnail->getByteCount()); } - break; } - info.layout.mirror.y = true; - - if (info.isValid()) + catch (const std::exception& e) { - thumbnail = dtk::Image::create(info); - auto resizedBuf = OIIO::ImageBufAlgo::resize( - sourceBuf, - "", - 0.F, - OIIO::ROI( - 0, info.size.w, - 0, info.size.h, - 0, 1, - 0, std::min(4, sourceSpec.nchannels))); - memcpy( - thumbnail->getData(), - resizedBuf.localpixels(), - thumbnail->getByteCount()); + if (_log) + { + _log->log( + "ThumbnailGenerator", + e.what(), + MessageLogType::Error); + } } _thread.cache[std::make_pair(request->time, request->height)] = thumbnail; diff --git a/lib/toucanView/ThumbnailGenerator.h b/lib/toucanView/ThumbnailGenerator.h index 91fb092..1068601 100644 --- a/lib/toucanView/ThumbnailGenerator.h +++ b/lib/toucanView/ThumbnailGenerator.h @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -34,7 +35,8 @@ namespace toucan ThumbnailGenerator( const std::filesystem::path&, const std::shared_ptr&, - const std::shared_ptr&); + const std::shared_ptr&, + const std::shared_ptr& = nullptr); ~ThumbnailGenerator(); @@ -56,6 +58,7 @@ namespace toucan std::filesystem::path _path; std::shared_ptr _timelineWrapper; std::shared_ptr _host; + std::shared_ptr _log; std::shared_ptr _graph; float _aspect = 1.F;