Skip to content

Commit

Permalink
Cxx examples (#918)
Browse files Browse the repository at this point in the history
* Initial work on C++ examples

* Start conversion of example flatten_video_tracks.py

* Add option to build CXX examples

* Use Retainers instead of raw pointers

* Implement Windows functionality

* Separate Python adapter examples

* Add POSIX code

* Clone the audio tracks

* Avoid temp files by using Python directly

* Separate CMake logic for pure C++ examples and C++/Python examples
  • Loading branch information
darbyjohnston authored Mar 24, 2021
1 parent c66ff11 commit d25cd13
Show file tree
Hide file tree
Showing 9 changed files with 780 additions and 10 deletions.
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ set(OTIO_PYTHON_INSTALL_DIR "" CACHE STRING "Python installation dir (such as th
# Build options
option(OTIO_SHARED_LIBS "Build shared if ON, static if OFF" ON)
option(OTIO_CXX_COVERAGE "Invoke code coverage if lcov/gcov is available" OFF)
option(OTIO_CXX_EXAMPLES "Build CXX examples (also requires OTIO_PYTHON_INSTALL=ON)" OFF)
option(OTIO_AUTOMATIC_SUBMODULES "Fetch submodules automatically" ON)

#------------------------------------------------------------------------------
Expand Down Expand Up @@ -129,7 +130,6 @@ endif()
#------------------------------------------------------------------------------
# Global language settings


if (NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 11)
endif()
Expand All @@ -153,6 +153,8 @@ if(WIN32)
set(OTIO_DEBUG_POSTFIX "d")
endif()

set_property(GLOBAL PROPERTY USE_FOLDERS ON)

#------------------------------------------------------------------------------
# Fetch or refresh submodules if requested
#
Expand Down Expand Up @@ -207,3 +209,8 @@ add_subdirectory(src/opentimelineio)
if(OTIO_PYTHON_INSTALL)
add_subdirectory(src/py-opentimelineio)
endif()

if(OTIO_CXX_EXAMPLES)
add_subdirectory(examples)
endif()

18 changes: 18 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
find_package(PythonLibs REQUIRED)

include_directories(${PROJECT_SOURCE_DIR}/src
${PROJECT_SOURCE_DIR}/src/deps
${PROJECT_SOURCE_DIR}/src/deps/optional-lite/include
${PYTHON_INCLUDE_DIRS})

list(APPEND examples flatten_video_tracks)
list(APPEND examples summarize_timing)
if(OTIO_PYTHON_INSTALL)
list(APPEND examples python_adapters_child_process)
list(APPEND examples python_adapters_embed)
endif()
foreach(example ${examples})
add_executable(${example} ${example}.cpp util.h util.cpp)
target_link_libraries(${example} OTIO::opentimelineio ${PYTHON_LIBRARIES})
set_target_properties(${example} PROPERTIES FOLDER examples)
endforeach()
83 changes: 83 additions & 0 deletions examples/flatten_video_tracks.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#include "util.h"

#include <opentimelineio/stackAlgorithm.h>
#include <opentimelineio/timeline.h>

#include <iostream>
#include <sstream>

namespace otio = opentimelineio::OPENTIMELINEIO_VERSION;

int main(int argc, char** argv)
{
if (argc != 3)
{
std::cout << "Usage: flatten_video_tracks (inputpath) (outputpath)" << std::endl;
return 1;
}

// Read the file
otio::ErrorStatus error_status;
otio::SerializableObject::Retainer<otio::Timeline> timeline(dynamic_cast<otio::Timeline*>(otio::Timeline::from_json_file(argv[1], &error_status)));
if (!timeline)
{
print_error(error_status);
return 1;
}
auto video_tracks = timeline.value->video_tracks();
auto audio_tracks = timeline.value->audio_tracks();

std::cout << "Read " << video_tracks.size() << " video tracks and " <<
audio_tracks.size() << " audio tracks." << std::endl;

// Take just the video tracks - and flatten them into one.
// This will trim away any overlapping segments, collapsing everything
// into a single track.
std::cout << "Flattening " << video_tracks.size() << " video tracks into one..." << std::endl;
auto onetrack = otio::flatten_stack(video_tracks, &error_status);
if (!onetrack)
{
print_error(error_status);
return 1;
}

// Now make a new empty Timeline and put that one Track into it
std::string name;
std::stringstream ss(name);
ss << timeline.value->name() << " Flattened";
auto newtimeline = otio::SerializableObject::Retainer<otio::Timeline>(new otio::Timeline(ss.str()));
auto stack = otio::SerializableObject::Retainer<otio::Stack>(new otio::Stack());
newtimeline.value->set_tracks(stack);
if (!stack.value->append_child(onetrack, &error_status))
{
print_error(error_status);
return 1;
}

// keep the audio track(s) as-is
for (const auto& audio_track : audio_tracks)
{
auto clone = dynamic_cast<otio::Track*>(audio_track->clone(&error_status));
if (!clone)
{
print_error(error_status);
return 1;
}
if (!stack.value->append_child(clone, &error_status))
{
print_error(error_status);
return 1;
}
}

// ...and save it to disk.
std::cout << "Saving " << newtimeline.value->video_tracks().size() << " video tracks and " <<
newtimeline.value->audio_tracks().size() << " audio tracks." << std::endl;
if (!timeline.value->to_json_file(argv[2], &error_status))
{
print_error(error_status);
return 1;
}

return 0;
}
201 changes: 201 additions & 0 deletions examples/python_adapters_child_process.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Example OTIO C++ code for reading and writing files supported by the OTIO
// Python adapters.
//
// This example uses the "otioconvert" utility in a child process to convert
// between input/output files and JSON that can be used from C++ code.
//
// To run this example make sure that the "otioconvert" utility is in your
// search path and the environment variable PYTHONPATH is set correctly.

#include "util.h"

#include <opentimelineio/timeline.h>

#include <iostream>
#include <sstream>

#if defined(_WINDOWS)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif // WIN32_LEAN_AND_MEAN
#include <cctype>
#include <codecvt>
#include <locale>
#include <windows.h>
#include <combaseapi.h>
#else // _WINDOWS
#include <stdio.h>
#endif // _WINDOWS

namespace otio = opentimelineio::OPENTIMELINEIO_VERSION;

class PythonAdapters
{
public:
static otio::SerializableObject::Retainer<otio::Timeline> read_from_file(
std::string const&,
otio::ErrorStatus*);

static bool write_to_file(
otio::SerializableObject::Retainer<otio::Timeline> const&,
std::string const&,
otio::ErrorStatus*);

private:
static bool _run_process(std::string const& cmd_line, otio::ErrorStatus*);
};

otio::SerializableObject::Retainer<otio::Timeline> PythonAdapters::read_from_file(
std::string const& file_name,
otio::ErrorStatus* error_status)
{
// Convert the input file to a temporary JSON file.
const std::string temp_file_name = create_temp_dir() + "/temp.otio";
std::stringstream ss;
ss << "otioconvert" << " -i " << normalize_path(file_name) << " -o " << temp_file_name;
_run_process(ss.str(), error_status);

// Read the temporary JSON file.
return dynamic_cast<otio::Timeline*>(otio::Timeline::from_json_file(temp_file_name, error_status));
}

bool PythonAdapters::write_to_file(
otio::SerializableObject::Retainer<otio::Timeline> const& timeline,
std::string const& file_name,
otio::ErrorStatus* error_status)
{
// Write the temporary JSON file.
const std::string temp_file_name = create_temp_dir() + "/temp.otio";
if (!timeline.value->to_json_file(temp_file_name, error_status))
{
return false;
}

// Convert the temporary JSON file to the output file.
std::stringstream ss;
ss << "otioconvert" << " -i " << temp_file_name << " -o " << normalize_path(file_name);
_run_process(ss.str(), error_status);

return true;
}

#if defined(_WINDOWS)

class WCharBuffer
{
public:
WCharBuffer(const WCHAR* data, size_t size)
{
p = new WCHAR[(size + 1) * sizeof(WCHAR)];
memcpy(p, data, size * sizeof(WCHAR));
p[size] = 0;
}

~WCharBuffer()
{
delete[] p;
}

WCHAR* p = nullptr;
};

bool PythonAdapters::_run_process(const std::string& cmd_line, otio::ErrorStatus* error_status)
{
// Convert the command-line to UTF16.
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> utf16;
std::wstring w_cmd_line = utf16.from_bytes("/c " + cmd_line);
WCharBuffer w_cmd_line_buf(w_cmd_line.c_str(), w_cmd_line.size());

// Create the process and wait for it to complete.
STARTUPINFOW si;
ZeroMemory(&si, sizeof(si));
PROCESS_INFORMATION pi;
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (0 == CreateProcessW(
// TODO: MSDN documentation says to use "cmd.exe" for the "lpApplicationName"
// argument, but that gives the error: "The system cannot find the file specified."
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
//L"cmd.exe",
L"C:\\windows\\system32\\cmd.exe",
w_cmd_line_buf.p,
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi))
{
const DWORD error = GetLastError();
TCHAR error_buf[4096];
FormatMessage(
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
error_buf,
4096,
NULL);
error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED;
error_status->details = "cannot create process: " + std::string(error_buf, lstrlen(error_buf));
return false;
}
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);

return true;
}

#else // _WINDOWS

bool PythonAdapters::_run_process(const std::string& cmd_line, otio::ErrorStatus* error_status)
{
FILE* f = popen(cmd_line.c_str(), "r");
if (!f)
{
error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED;
error_status->details = "cannot create process";
return false;
}
if (-1 == pclose(f))
{
error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED;
error_status->details = "cannot execute process";
return false;
}
return true;
}

#endif // _WINDOWS

int main(int argc, char** argv)
{
if (argc != 3)
{
std::cout << "Usage: python_adapters_child_process (inputpath) (outputpath)" << std::endl;
return 1;
}

otio::ErrorStatus error_status;
auto timeline = PythonAdapters::read_from_file(argv[1], &error_status);
if (!timeline)
{
print_error(error_status);
return 1;
}

std::cout << "Video tracks: " << timeline.value->video_tracks().size() << std::endl;
std::cout << "Audio tracks: " << timeline.value->audio_tracks().size() << std::endl;

if (!PythonAdapters::write_to_file(timeline, argv[2], &error_status))
{
print_error(error_status);
return 1;
}

return 0;
}
Loading

0 comments on commit d25cd13

Please sign in to comment.