Skip to content

Commit d25cd13

Browse files
Cxx examples (#918)
* 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
1 parent c66ff11 commit d25cd13

File tree

9 files changed

+780
-10
lines changed

9 files changed

+780
-10
lines changed

CMakeLists.txt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ set(OTIO_PYTHON_INSTALL_DIR "" CACHE STRING "Python installation dir (such as th
3434
# Build options
3535
option(OTIO_SHARED_LIBS "Build shared if ON, static if OFF" ON)
3636
option(OTIO_CXX_COVERAGE "Invoke code coverage if lcov/gcov is available" OFF)
37+
option(OTIO_CXX_EXAMPLES "Build CXX examples (also requires OTIO_PYTHON_INSTALL=ON)" OFF)
3738
option(OTIO_AUTOMATIC_SUBMODULES "Fetch submodules automatically" ON)
3839

3940
#------------------------------------------------------------------------------
@@ -129,7 +130,6 @@ endif()
129130
#------------------------------------------------------------------------------
130131
# Global language settings
131132

132-
133133
if (NOT CMAKE_CXX_STANDARD)
134134
set(CMAKE_CXX_STANDARD 11)
135135
endif()
@@ -153,6 +153,8 @@ if(WIN32)
153153
set(OTIO_DEBUG_POSTFIX "d")
154154
endif()
155155

156+
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
157+
156158
#------------------------------------------------------------------------------
157159
# Fetch or refresh submodules if requested
158160
#
@@ -207,3 +209,8 @@ add_subdirectory(src/opentimelineio)
207209
if(OTIO_PYTHON_INSTALL)
208210
add_subdirectory(src/py-opentimelineio)
209211
endif()
212+
213+
if(OTIO_CXX_EXAMPLES)
214+
add_subdirectory(examples)
215+
endif()
216+

examples/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
find_package(PythonLibs REQUIRED)
2+
3+
include_directories(${PROJECT_SOURCE_DIR}/src
4+
${PROJECT_SOURCE_DIR}/src/deps
5+
${PROJECT_SOURCE_DIR}/src/deps/optional-lite/include
6+
${PYTHON_INCLUDE_DIRS})
7+
8+
list(APPEND examples flatten_video_tracks)
9+
list(APPEND examples summarize_timing)
10+
if(OTIO_PYTHON_INSTALL)
11+
list(APPEND examples python_adapters_child_process)
12+
list(APPEND examples python_adapters_embed)
13+
endif()
14+
foreach(example ${examples})
15+
add_executable(${example} ${example}.cpp util.h util.cpp)
16+
target_link_libraries(${example} OTIO::opentimelineio ${PYTHON_LIBRARIES})
17+
set_target_properties(${example} PROPERTIES FOLDER examples)
18+
endforeach()

examples/flatten_video_tracks.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
#include "util.h"
2+
3+
#include <opentimelineio/stackAlgorithm.h>
4+
#include <opentimelineio/timeline.h>
5+
6+
#include <iostream>
7+
#include <sstream>
8+
9+
namespace otio = opentimelineio::OPENTIMELINEIO_VERSION;
10+
11+
int main(int argc, char** argv)
12+
{
13+
if (argc != 3)
14+
{
15+
std::cout << "Usage: flatten_video_tracks (inputpath) (outputpath)" << std::endl;
16+
return 1;
17+
}
18+
19+
// Read the file
20+
otio::ErrorStatus error_status;
21+
otio::SerializableObject::Retainer<otio::Timeline> timeline(dynamic_cast<otio::Timeline*>(otio::Timeline::from_json_file(argv[1], &error_status)));
22+
if (!timeline)
23+
{
24+
print_error(error_status);
25+
return 1;
26+
}
27+
auto video_tracks = timeline.value->video_tracks();
28+
auto audio_tracks = timeline.value->audio_tracks();
29+
30+
std::cout << "Read " << video_tracks.size() << " video tracks and " <<
31+
audio_tracks.size() << " audio tracks." << std::endl;
32+
33+
// Take just the video tracks - and flatten them into one.
34+
// This will trim away any overlapping segments, collapsing everything
35+
// into a single track.
36+
std::cout << "Flattening " << video_tracks.size() << " video tracks into one..." << std::endl;
37+
auto onetrack = otio::flatten_stack(video_tracks, &error_status);
38+
if (!onetrack)
39+
{
40+
print_error(error_status);
41+
return 1;
42+
}
43+
44+
// Now make a new empty Timeline and put that one Track into it
45+
std::string name;
46+
std::stringstream ss(name);
47+
ss << timeline.value->name() << " Flattened";
48+
auto newtimeline = otio::SerializableObject::Retainer<otio::Timeline>(new otio::Timeline(ss.str()));
49+
auto stack = otio::SerializableObject::Retainer<otio::Stack>(new otio::Stack());
50+
newtimeline.value->set_tracks(stack);
51+
if (!stack.value->append_child(onetrack, &error_status))
52+
{
53+
print_error(error_status);
54+
return 1;
55+
}
56+
57+
// keep the audio track(s) as-is
58+
for (const auto& audio_track : audio_tracks)
59+
{
60+
auto clone = dynamic_cast<otio::Track*>(audio_track->clone(&error_status));
61+
if (!clone)
62+
{
63+
print_error(error_status);
64+
return 1;
65+
}
66+
if (!stack.value->append_child(clone, &error_status))
67+
{
68+
print_error(error_status);
69+
return 1;
70+
}
71+
}
72+
73+
// ...and save it to disk.
74+
std::cout << "Saving " << newtimeline.value->video_tracks().size() << " video tracks and " <<
75+
newtimeline.value->audio_tracks().size() << " audio tracks." << std::endl;
76+
if (!timeline.value->to_json_file(argv[2], &error_status))
77+
{
78+
print_error(error_status);
79+
return 1;
80+
}
81+
82+
return 0;
83+
}
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
// Example OTIO C++ code for reading and writing files supported by the OTIO
2+
// Python adapters.
3+
//
4+
// This example uses the "otioconvert" utility in a child process to convert
5+
// between input/output files and JSON that can be used from C++ code.
6+
//
7+
// To run this example make sure that the "otioconvert" utility is in your
8+
// search path and the environment variable PYTHONPATH is set correctly.
9+
10+
#include "util.h"
11+
12+
#include <opentimelineio/timeline.h>
13+
14+
#include <iostream>
15+
#include <sstream>
16+
17+
#if defined(_WINDOWS)
18+
#ifndef WIN32_LEAN_AND_MEAN
19+
#define WIN32_LEAN_AND_MEAN
20+
#endif // WIN32_LEAN_AND_MEAN
21+
#include <cctype>
22+
#include <codecvt>
23+
#include <locale>
24+
#include <windows.h>
25+
#include <combaseapi.h>
26+
#else // _WINDOWS
27+
#include <stdio.h>
28+
#endif // _WINDOWS
29+
30+
namespace otio = opentimelineio::OPENTIMELINEIO_VERSION;
31+
32+
class PythonAdapters
33+
{
34+
public:
35+
static otio::SerializableObject::Retainer<otio::Timeline> read_from_file(
36+
std::string const&,
37+
otio::ErrorStatus*);
38+
39+
static bool write_to_file(
40+
otio::SerializableObject::Retainer<otio::Timeline> const&,
41+
std::string const&,
42+
otio::ErrorStatus*);
43+
44+
private:
45+
static bool _run_process(std::string const& cmd_line, otio::ErrorStatus*);
46+
};
47+
48+
otio::SerializableObject::Retainer<otio::Timeline> PythonAdapters::read_from_file(
49+
std::string const& file_name,
50+
otio::ErrorStatus* error_status)
51+
{
52+
// Convert the input file to a temporary JSON file.
53+
const std::string temp_file_name = create_temp_dir() + "/temp.otio";
54+
std::stringstream ss;
55+
ss << "otioconvert" << " -i " << normalize_path(file_name) << " -o " << temp_file_name;
56+
_run_process(ss.str(), error_status);
57+
58+
// Read the temporary JSON file.
59+
return dynamic_cast<otio::Timeline*>(otio::Timeline::from_json_file(temp_file_name, error_status));
60+
}
61+
62+
bool PythonAdapters::write_to_file(
63+
otio::SerializableObject::Retainer<otio::Timeline> const& timeline,
64+
std::string const& file_name,
65+
otio::ErrorStatus* error_status)
66+
{
67+
// Write the temporary JSON file.
68+
const std::string temp_file_name = create_temp_dir() + "/temp.otio";
69+
if (!timeline.value->to_json_file(temp_file_name, error_status))
70+
{
71+
return false;
72+
}
73+
74+
// Convert the temporary JSON file to the output file.
75+
std::stringstream ss;
76+
ss << "otioconvert" << " -i " << temp_file_name << " -o " << normalize_path(file_name);
77+
_run_process(ss.str(), error_status);
78+
79+
return true;
80+
}
81+
82+
#if defined(_WINDOWS)
83+
84+
class WCharBuffer
85+
{
86+
public:
87+
WCharBuffer(const WCHAR* data, size_t size)
88+
{
89+
p = new WCHAR[(size + 1) * sizeof(WCHAR)];
90+
memcpy(p, data, size * sizeof(WCHAR));
91+
p[size] = 0;
92+
}
93+
94+
~WCharBuffer()
95+
{
96+
delete[] p;
97+
}
98+
99+
WCHAR* p = nullptr;
100+
};
101+
102+
bool PythonAdapters::_run_process(const std::string& cmd_line, otio::ErrorStatus* error_status)
103+
{
104+
// Convert the command-line to UTF16.
105+
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> utf16;
106+
std::wstring w_cmd_line = utf16.from_bytes("/c " + cmd_line);
107+
WCharBuffer w_cmd_line_buf(w_cmd_line.c_str(), w_cmd_line.size());
108+
109+
// Create the process and wait for it to complete.
110+
STARTUPINFOW si;
111+
ZeroMemory(&si, sizeof(si));
112+
PROCESS_INFORMATION pi;
113+
si.cb = sizeof(si);
114+
ZeroMemory(&pi, sizeof(pi));
115+
if (0 == CreateProcessW(
116+
// TODO: MSDN documentation says to use "cmd.exe" for the "lpApplicationName"
117+
// argument, but that gives the error: "The system cannot find the file specified."
118+
// https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw
119+
//L"cmd.exe",
120+
L"C:\\windows\\system32\\cmd.exe",
121+
w_cmd_line_buf.p,
122+
NULL,
123+
NULL,
124+
FALSE,
125+
0,
126+
NULL,
127+
NULL,
128+
&si,
129+
&pi))
130+
{
131+
const DWORD error = GetLastError();
132+
TCHAR error_buf[4096];
133+
FormatMessage(
134+
FORMAT_MESSAGE_FROM_SYSTEM |
135+
FORMAT_MESSAGE_IGNORE_INSERTS,
136+
NULL,
137+
error,
138+
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
139+
error_buf,
140+
4096,
141+
NULL);
142+
error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED;
143+
error_status->details = "cannot create process: " + std::string(error_buf, lstrlen(error_buf));
144+
return false;
145+
}
146+
WaitForSingleObject(pi.hProcess, INFINITE);
147+
CloseHandle(pi.hProcess);
148+
CloseHandle(pi.hThread);
149+
150+
return true;
151+
}
152+
153+
#else // _WINDOWS
154+
155+
bool PythonAdapters::_run_process(const std::string& cmd_line, otio::ErrorStatus* error_status)
156+
{
157+
FILE* f = popen(cmd_line.c_str(), "r");
158+
if (!f)
159+
{
160+
error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED;
161+
error_status->details = "cannot create process";
162+
return false;
163+
}
164+
if (-1 == pclose(f))
165+
{
166+
error_status->outcome = otio::ErrorStatus::Outcome::FILE_OPEN_FAILED;
167+
error_status->details = "cannot execute process";
168+
return false;
169+
}
170+
return true;
171+
}
172+
173+
#endif // _WINDOWS
174+
175+
int main(int argc, char** argv)
176+
{
177+
if (argc != 3)
178+
{
179+
std::cout << "Usage: python_adapters_child_process (inputpath) (outputpath)" << std::endl;
180+
return 1;
181+
}
182+
183+
otio::ErrorStatus error_status;
184+
auto timeline = PythonAdapters::read_from_file(argv[1], &error_status);
185+
if (!timeline)
186+
{
187+
print_error(error_status);
188+
return 1;
189+
}
190+
191+
std::cout << "Video tracks: " << timeline.value->video_tracks().size() << std::endl;
192+
std::cout << "Audio tracks: " << timeline.value->audio_tracks().size() << std::endl;
193+
194+
if (!PythonAdapters::write_to_file(timeline, argv[2], &error_status))
195+
{
196+
print_error(error_status);
197+
return 1;
198+
}
199+
200+
return 0;
201+
}

0 commit comments

Comments
 (0)