Skip to content

Commit

Permalink
Adds support for reading OTIOZ (#85)
Browse files Browse the repository at this point in the history
* Reads content.otio from OTIOZ files
* Added minizip-ng submodule

Signed-off-by: Joshua Minor <joshm@pixar.com>
Co-authored-by: Thomas Wilshaw <thomaswilshaw@gmail.com>
Co-authored-by: Darby Johnston <darbyjohnston@yahoo.com>
  • Loading branch information
3 people authored Nov 15, 2024
1 parent 1e57ae1 commit 2039019
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 26 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@
[submodule "libs/ImGuiColorTextEdit"]
path = libs/ImGuiColorTextEdit
url = https://github.com/jminor/ImGuiColorTextEdit.git
[submodule "libs/minizip-ng"]
path = libs/minizip-ng
url = https://github.com/zlib-ng/minizip-ng.git
19 changes: 19 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ if(NOT EMSCRIPTEN AND NOT WIN32)
add_subdirectory("libs/glfw")
endif()

# minizip-ng
set(BUILD_SHARED_LIBS OFF)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(MZ_FETCH_LIBS ON)
set(MZ_ZLIB ON)
# OTIOZ doesn't need any of these
set(MZ_BZIP2 OFF)
set(MZ_LZMA OFF)
set(MZ_ZSTD OFF)
set(MZ_LIBCOMP OFF)
set(MZ_PKCRYPT OFF)
set(MZ_WZAES OFF)
set(MZ_OPENSSL OFF)
set(MZ_BCRYPT OFF)
set(MZ_LIBBSD OFF)
set(MZ_ICONV OFF)
add_subdirectory("libs/minizip-ng")

if(NOT EMSCRIPTEN)
add_custom_command(
OUTPUT "${PROJECT_SOURCE_DIR}/fonts/embedded_font.inc"
Expand All @@ -81,6 +99,7 @@ target_compile_definitions(raven
target_link_libraries(raven PUBLIC
OTIO::opentimelineio
IMGUI
MINIZIP::minizip
)

if (APPLE)
Expand Down
176 changes: 150 additions & 26 deletions app.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Raven NLE

#include <cstddef>
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

Expand All @@ -15,10 +17,15 @@
#include "nfd.h"
#endif

#include "mz.h"
#include "mz_zip.h"
#include "mz_strm.h"
#include "mz_zip_rw.h"

#include "fonts/embedded_font.inc"

#include <chrono>
#include <iostream>
#include <filesystem>

void DrawMenu();
void DrawToolbar(ImVec2 buttonSize);
Expand Down Expand Up @@ -271,9 +278,7 @@ void LoadString(std::string json) {
elapsed_seconds);
}

void LoadFile(std::string path) {
auto start = std::chrono::high_resolution_clock::now();

otio::Timeline* LoadOTIOFile(std::string path) {
otio::ErrorStatus error_status;
auto timeline = dynamic_cast<otio::Timeline*>(
otio::Timeline::from_json_file(path, &error_status));
Expand All @@ -282,9 +287,141 @@ void LoadFile(std::string path) {
"Error loading \"%s\": %s",
path.c_str(),
otio_error_string(error_status).c_str());
return nullptr;
}
return timeline;
}

otio::Timeline* LoadOTIOZFile(std::string path) {
otio::Timeline* timeline = nullptr;

void *zip_reader = mz_zip_reader_create();

auto status = mz_zip_reader_open_file(zip_reader, path.c_str());
if (status != MZ_OK) {
Message(
"Error opening \"%s\": %d",
path.c_str(),
status);
} else {
status = mz_zip_reader_locate_entry(zip_reader, "content.otio", 1);
if (status != MZ_OK) {
Message(
"Invalid OTIOZ: \"%s\": \"content.otio\" not found in archive.",
path.c_str());
} else {
mz_zip_file *file_info = NULL;
status = mz_zip_reader_entry_get_info(zip_reader, &file_info);
if (status != MZ_OK) {
Message(
"Invalid OTIOZ: \"%s\": Error getting entry info: %d",
path.c_str(),
status);
} else {
status = mz_zip_reader_entry_open(zip_reader);
if (status != MZ_OK) {
Message(
"Invalid OTIOZ: \"%s\": Unable to open entry: %d",
path.c_str(),
status);
} else {
char* buf = (char*)malloc(file_info->uncompressed_size + 1);
char* buf_cursor = buf;
int64_t bytes_remaining = file_info->uncompressed_size;
int32_t bytes_read = 0;
while (bytes_remaining > 0) {
int32_t chunk_size = bytes_remaining < INT32_MAX ? bytes_remaining : INT32_MAX;
bytes_read = mz_zip_reader_entry_read(zip_reader, buf_cursor, chunk_size);
if (bytes_read > 0) {
bytes_remaining -= bytes_read;
buf_cursor += bytes_read;
} else {
break;
}
}
if (bytes_remaining != 0) {
Message(
"Invalid OTIOZ: \"%s\": Error reading entry: %ld",
path.c_str(),
bytes_remaining);
} else {
// Add a null terminator
buf[file_info->uncompressed_size] = '\0';
std::string json(buf);
timeline = dynamic_cast<otio::Timeline*>(
otio::Timeline::from_json_string(json));
}
free(buf);
mz_zip_reader_entry_close(zip_reader);
}
}
}

mz_zip_reader_close(zip_reader);
}

mz_zip_reader_delete(&zip_reader);

return timeline;
}

std::string FileExtension(std::string path) {
return path.substr(path.find_last_of(".") + 1);
}

std::string LowerCase(std::string str) {
std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); }); // 🙄
return str;
}

// Validate that a file has the .otio or .otioz extension
bool SupportedFileType(const std::string& filepath) {
size_t last_dot = filepath.find_last_of('.');

// If no dot is found, it's not a valid file
if (last_dot == std::string::npos) {
return false;
}

// Get and check the extension
std::string extension = filepath.substr(last_dot + 1);
return extension == "otio" || extension == "otioz";
}

void LoadFile(std::string path) {
otio::Timeline* timeline = nullptr;

auto start = std::chrono::high_resolution_clock::now();

if (!std::filesystem::exists(path)) {
Message(
"File not found \"%s\"",
path.c_str());
return;
}

if (!SupportedFileType(path)) {
Message(
"Unsuported file type \"%s\"",
path.c_str());
return;
}

auto ext = LowerCase(FileExtension(path));
if (ext == "otio") {
timeline = LoadOTIOFile(path);
} else if (ext == "otioz") {
timeline = LoadOTIOZFile(path);
} else {
Message(
"Unsupported file type \"%s\"",
ext.c_str());
return;
}

if (!timeline)
return;

LoadTimeline(timeline);

appState.file_path = path;
Expand All @@ -294,7 +431,7 @@ void LoadFile(std::string path) {
double elapsed_seconds = elapsed.count();
Message(
"Loaded \"%s\" in %.3f seconds",
timeline->name().c_str(),
path.c_str(),
elapsed_seconds);
}

Expand Down Expand Up @@ -335,30 +472,18 @@ void MainInit(int argc, char** argv, int initial_width, int initial_height) {

LoadFonts();

// Load an empty timeline if no file is provided
// or if loading the file fails for some reason.
auto tl = new otio::Timeline();
LoadTimeline(tl);

if (argc > 1) {
LoadFile(argv[1]);
} else {
auto tl = new otio::Timeline();
LoadTimeline(tl);
}
}

void MainCleanup() { }

// Validate that a file has the .otio extension
bool is_valid_file(const std::string& filepath) {
size_t last_dot = filepath.find_last_of('.');

// If no dot is found, it's not a valid file
if (last_dot == std::string::npos) {
return false;
}

// Get and check the extension
std::string extension = filepath.substr(last_dot + 1);
return extension == "otio";
}

// Accept and open a file path
void FileDropCallback(int count, const char** filepaths) {
if (count > 1){
Expand All @@ -372,14 +497,13 @@ void FileDropCallback(int count, const char** filepaths) {

std::string file_path = filepaths[0];

if (!is_valid_file(file_path)){
Message("Invalid file: %s", file_path.c_str());
if (!SupportedFileType(file_path)){
Message("Unsupported file type: %s", file_path.c_str());
return;
}

// Loading is done in DrawDroppedFilesPrompt()
prompt_dropped_file = file_path;

}

// Make a button using the fancy icon font
Expand Down Expand Up @@ -863,7 +987,7 @@ void DrawDroppedFilesPrompt() {
// Modal window for confirmation
if (ImGui::BeginPopupModal("Open File?", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("Open file \n%s?", prompt_dropped_file.c_str());

if (ImGui::Button("Yes")) {
LoadFile(prompt_dropped_file);
prompt_dropped_file = "";
Expand Down
1 change: 1 addition & 0 deletions libs/minizip-ng
Submodule minizip-ng added at fe5fed

0 comments on commit 2039019

Please sign in to comment.