Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds support for reading OTIOZ #85

Merged
merged 10 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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