Skip to content

Commit

Permalink
Merge pull request #31 from darbyjohnston/refactor6
Browse files Browse the repository at this point in the history
Refactoring
  • Loading branch information
darbyjohnston authored Dec 6, 2024
2 parents 5b09cbd + 329f939 commit 42247cb
Show file tree
Hide file tree
Showing 76 changed files with 2,545 additions and 1,138 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
.vscode
42 changes: 24 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,31 @@

<img src="images/toucan.svg" alt="toucan" width="100">


Toucan
======
Toucan is a software renderer for OpenTimelineIO files. Toucan can render an
OpenTimelineIO file with multiple tracks, clips, transitions, and effects
into an image sequence or movie file.

The project currently consists of:
The project consists of:
* C++ library for rendering timelines
* Collection of OpenFX image effect plugins
* Command line renderer
* Interactive viewer
* Example .otio files

Current limitations:
* Audio is not yet supported
* Nested timelines are not yet supported
* Exporting movie files currently relies on the FFmpeg command line program
(see below: FFmpeg Encoding)
OpenFX Plugins:
* Generators: Checkers, Fill, Gradient, Noise
* Drawing: Box, Line, Text
* Filters: Blur, Color Map, Invert, Power, Saturate, Unsharp Mask
* Transforms: Crop, Flip, Flop, Resize, Rotate
* Transitions: Dissolve, Horizontal Wipe, Vertical Wipe
* Color space: Color Convert, Premultiply Alpha, Un-Premultiply Alpha

TODO:
* Audio support
* Nested timeline support

Toucan relies on the following libraries:
* [OpenTimelineIO](https://github.com/PixarAnimationStudios/OpenTimelineIO)
Expand All @@ -31,15 +38,6 @@ Toucan relies on the following libraries:

Supported VFX platforms: 2024, 2023, 2022

OpenFX Plugins
==============
The OpenFX image effect plugins include:
* Generators: Checkers, Fill, Gradient, Noise
* Drawing: Box, Line, Text
* Filters: Blur, Color Map, Invert, Power, Saturate, Unsharp Mask
* Transforms: Crop, Flip, Flop, Resize, Rotate
* Transitions: Dissolve, Horizontal Wipe, Vertical Wipe
* Color spaces: Color Convert, Premultiply Alpha, Un-Premultiply Alpha

Example Renders
===============
Expand Down Expand Up @@ -91,11 +89,18 @@ Multiple effects on clips, tracks, and stacks:

![Track Effects](images/MultipleEffects.png)


FFmpeg Encoding
===============
Toucan can send rendered images to the FFmpeg command line program for encoding.
The images can be sent as either the y4m format or raw video. The images are
piped directly to FFmpeg without the overhead of disk I/O.
Toucan can write movies with FFmpeg directly, or send raw images to the FFmpeg
command line program over a pipe.

Example command line writing a movie directly:
```
toucan-render Transition.otio Transition.mov -vcodec MJPEG
```

Raw images can be sent to FFmpeg as either the y4m format or raw video.

Example command line using the y4m format:
```
Expand Down Expand Up @@ -127,6 +132,7 @@ can be found by running `toucan-render` with the `-print_size` option.
* `-i pipe:`: Read from standard input instead of a file.
* `output.mov`: The output movie file.


Building
========

Expand Down
1 change: 1 addition & 0 deletions bin/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
add_subdirectory(toucan-render)
add_subdirectory(toucan-filmstrip)
if(toucan_VIEW)
add_subdirectory(toucan-view)
endif()
195 changes: 195 additions & 0 deletions bin/toucan-filmstrip/App.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Contributors to the toucan project.

#include "App.h"

#include <toucan/Util.h>

#include <OpenImageIO/imagebufalgo.h>

namespace toucan
{
App::App(std::vector<std::string>& argv)
{
_exe = argv.front();
argv.erase(argv.begin());

_args.list.push_back(std::make_shared<CmdLineValueArg<std::string> >(
_args.input,
"input",
"Input .otio file."));
auto outArg = std::make_shared<CmdLineValueArg<std::string> >(
_args.output,
"output",
"Output image file.");
_args.list.push_back(outArg);

_options.list.push_back(std::make_shared<CmdLineFlagOption>(
_options.verbose,
std::vector<std::string>{ "-v" },
"Print verbose output."));
_options.list.push_back(std::make_shared<CmdLineFlagOption>(
_options.help,
std::vector<std::string>{ "-h" },
"Print help."));

if (!argv.empty())
{
for (const auto& option : _options.list)
{
option->parse(argv);
}
if (!_options.help)
{
for (const auto& arg : _args.list)
{
arg->parse(argv);
}
if (argv.size())
{
_options.help = true;
}
}
}
else
{
_options.help = true;
}
}

App::~App()
{}

int App::run()
{
if (_options.help)
{
_printHelp();
return 1;
}

const std::filesystem::path parentPath = std::filesystem::path(_exe).parent_path();
const std::filesystem::path inputPath(_args.input);
const std::filesystem::path outputPath(_args.output);
const auto outputSplit = splitFileNameNumber(outputPath.stem().string());
const int outputStartFrame = atoi(outputSplit.second.c_str());
const size_t outputNumberPadding = getNumberPadding(outputSplit.second);

// Open the timeline.
_timelineWrapper = std::make_shared<TimelineWrapper>(inputPath);

// Get time values.
const OTIO_NS::TimeRange& timeRange = _timelineWrapper->getTimeRange();
const OTIO_NS::RationalTime timeInc(1.0, timeRange.duration().rate());
const int frames = timeRange.duration().value();

// Create the image graph.
std::shared_ptr<MessageLog> log;
if (_options.verbose)
{
log = std::make_shared<MessageLog>();
}
ImageGraphOptions imageGraphOptions;
imageGraphOptions.log = log;
_graph = std::make_shared<ImageGraph>(
inputPath.parent_path(),
_timelineWrapper,
imageGraphOptions);
const IMATH_NAMESPACE::V2d imageSize = _graph->getImageSize();

// Create the image host.
std::vector<std::filesystem::path> searchPath;
searchPath.push_back(parentPath);
#if defined(_WINDOWS)
searchPath.push_back(parentPath / ".." / ".." / "..");
#else // _WINDOWS
searchPath.push_back(parentPath / ".." / "..");
#endif // _WINDOWS
ImageEffectHostOptions imageHostOptions;
imageHostOptions.log = log;
_host = std::make_shared<ImageEffectHost>(
searchPath,
imageHostOptions);

// Initialize the filmstrip.
OIIO::ImageBuf filmstripBuf;
const int thumbnailWidth = 360;
const int thumbnailSpacing = 0;
IMATH_NAMESPACE::V2d thumbnailSize;
if (imageSize.x > 0 && imageSize.y > 0)
{
thumbnailSize = IMATH_NAMESPACE::V2d(
thumbnailWidth,
thumbnailWidth / static_cast<float>(imageSize.x / static_cast<float>(imageSize.y)));
const IMATH_NAMESPACE::V2d filmstripSize(
thumbnailSize.x * frames + thumbnailSpacing * (frames - 1),
thumbnailSize.y);
filmstripBuf = OIIO::ImageBufAlgo::fill(
{ 0.F, 0.F, 0.F, 0.F },
OIIO::ROI(0, filmstripSize.x, 0, filmstripSize.y, 0, 1, 0, 4));
}

// Render the timeline frames.
int filmstripX = 0;
for (OTIO_NS::RationalTime time = timeRange.start_time();
time <= timeRange.end_time_inclusive();
time += timeInc)
{
std::cout << (time - timeRange.start_time()).value() << "/" <<
timeRange.duration().value() << std::endl;

if (auto node = _graph->exec(_host, time))
{
// Execute the graph.
const auto buf = node->exec();

// Append the image.
const auto thumbnailBuf = OIIO::ImageBufAlgo::resize(
buf,
"",
0.0,
OIIO::ROI(0, thumbnailSize.x, 0, thumbnailSize.y, 0, 1, 0, 4));
OIIO::ImageBufAlgo::paste(
filmstripBuf,
filmstripX,
0,
0,
0,
thumbnailBuf);
filmstripX += thumbnailSize.x + thumbnailSpacing;
}
}

// Write the image.
filmstripBuf.write(outputPath.string());

return 0;
}

void App::_printHelp()
{
std::cout << "Usage:" << std::endl;
std::cout << std::endl;
std::cout << " toucan-filmstrip (input) (output) [options...]" << std::endl;
std::cout << std::endl;
std::cout << "Arguments:" << std::endl;
std::cout << std::endl;
for (const auto& arg : _args.list)
{
std::cout << " " << arg->getName() << " - " << arg->getHelp() << std::endl;
std::cout << std::endl;
}
std::cout << std::endl;
std::cout << "Options:" << std::endl;
std::cout << std::endl;
for (const auto& option : _options.list)
{
for (const auto& line : option->getHelp())
{
std::cout << " " << line << std::endl;
}
std::cout << std::endl;
}
}
}

51 changes: 51 additions & 0 deletions bin/toucan-filmstrip/App.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Contributors to the toucan project.

#pragma once


#include <toucan/CmdLine.h>
#include <toucan/ImageEffectHost.h>
#include <toucan/ImageGraph.h>
#include <toucan/TimelineWrapper.h>

#include <OpenImageIO/imagebuf.h>

namespace toucan
{
class App : public std::enable_shared_from_this<App>
{
public:
App(std::vector<std::string>&);

~App();

int run();

private:
void _printHelp();

std::string _exe;

struct Args
{
std::string input;
std::string output;
std::vector<std::shared_ptr<ICmdLineArg> > list;
};
Args _args;

struct Options
{
bool verbose = false;
bool help = false;
std::vector<std::shared_ptr<ICmdLineOption> > list;
};
Options _options;

std::shared_ptr<TimelineWrapper> _timelineWrapper;
std::shared_ptr<ImageGraph> _graph;
std::shared_ptr<ImageEffectHost> _host;
};
}

21 changes: 21 additions & 0 deletions bin/toucan-filmstrip/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
set(HEADERS
App.h)
set(SOURCE
App.cpp
main.cpp)

add_executable(toucan-filmstrip ${HEADERS} ${SOURCE})
target_link_libraries(toucan-filmstrip toucan)
set_target_properties(toucan-filmstrip PROPERTIES FOLDER bin)
add_dependencies(toucan-filmstrip ${TOUCAN_PLUGINS})

install(
TARGETS toucan-filmstrip
RUNTIME DESTINATION bin)

foreach(OTIO CompositeTracks Draw Filter Gap Generator LinearTimeWarp Transition Transition2 Transform)
add_test(
toucan-filmstrip-${OTIO}
${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/toucan-filmstrip${CMAKE_EXECUTABLE_SUFFIX}
${PROJECT_SOURCE_DIR}/data/${OTIO}.otio ${OTIO}.png)
endforeach()
28 changes: 28 additions & 0 deletions bin/toucan-filmstrip/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Contributors to the toucan project.

#include "App.h"

#include <iostream>

using namespace toucan;

int main(int argc, char** argv)
{
int out = 1;
std::vector<std::string> args;
for (int i = 0; i < argc; ++i)
{
args.push_back(argv[i]);
}
try
{
auto app = std::make_shared<App>(args);
out = app->run();
}
catch (const std::exception& e)
{
std::cout << "ERROR: " << e.what() << std::endl;
}
return out;
}
Loading

0 comments on commit 42247cb

Please sign in to comment.