diff --git a/automation/v4-docs/get-frame.txt b/automation/v4-docs/get-frame.txt new file mode 100644 index 000000000..5d75e5055 --- /dev/null +++ b/automation/v4-docs/get-frame.txt @@ -0,0 +1,69 @@ +Video Frame functions in Automation 4 + +This file describes the interface used for reading frames from loaded videos. + +--- + +Get a specific frame from the currently loaded video on which multiple other +functions are defined. + +function aegisub.get_frame(frame_number, withSubtitles) + +@frame_number (number) + Number of frame to retrieve. + +@withSubtitles (boolean) + Optional. Whether to load with subtitles drawn on to the frame. + +Returns: frame (userdata) + The frame object defines multiple other functions. See below. + +--- + +Get width of frame object. + +function frame:width() + +Returns: number + Width in pixels. + +--- + +Get height of frame object. + +function frame:height() + +Returns: number + Height in pixels. + +--- + +Get RGB pixel value at a certain position of frame object. + +function frame:frame:getPixel(x, y) + +@x (number) + Pixel to retrieve on the x-axis + +@y (number) + Pixel to retrieve on the y-axis + +Returns: number + Integer value representing the RGB pixel value. + +--- + +Get ASS formated pixel value at a certain position of frame object. + +function frame:getPixelFormatted(x, y) + +@x (number) + Pixel to retrieve on the x-axis + +@y (number) + Pixel to retrieve on the y-axis + +Returns: string + String in ASS format representing the pixel value. e.g. "&H0073FF&" + +--- diff --git a/automation/v4-docs/gui.txt b/automation/v4-docs/gui.txt index 1ec617805..f913e7040 100644 --- a/automation/v4-docs/gui.txt +++ b/automation/v4-docs/gui.txt @@ -55,3 +55,12 @@ Returns: 0 values --- +Determining whether there are unsaved changes + +function aegisub.gui.is_modified() + +Returns: 1 boolean + 1. Whether the current file has unsaved changes. + +--- + diff --git a/src/auto4_lua.cpp b/src/auto4_lua.cpp index bba6e6aeb..36156288b 100644 --- a/src/auto4_lua.cpp +++ b/src/auto4_lua.cpp @@ -50,8 +50,9 @@ #include "project.h" #include "selection_controller.h" #include "subs_controller.h" -#include "video_controller.h" #include "text_selection_controller.h" +#include "video_controller.h" +#include "video_frame.h" #include "utils.h" #include @@ -200,6 +201,116 @@ namespace { } } + std::shared_ptr check_VideoFrame(lua_State *L) { + auto framePtr = static_cast*>(luaL_checkudata(L, 1, "VideoFrame")); + return *framePtr; + } + + int FrameWidth(lua_State *L) { + std::shared_ptr frame = check_VideoFrame(L); + push_value(L, frame->width); + return 1; + } + + int FrameHeight(lua_State *L) { + std::shared_ptr frame = check_VideoFrame(L); + push_value(L, frame->height); + return 1; + } + + int FramePixel(lua_State *L) { + std::shared_ptr frame = check_VideoFrame(L); + size_t x = lua_tointeger(L, -2); + size_t y = lua_tointeger(L, -1); + lua_pop(L, 2); + + if (x < frame->width && y < frame->height) { + if (frame->flipped) + y = frame->height - y; + + size_t pos = y * frame->pitch + x * 4; + // VideoFrame is stored as BGRA, but we want to return RGB + int pixelValue = frame->data[pos+2] * 65536 + frame->data[pos+1] * 256 + frame->data[pos]; + push_value(L, pixelValue); + } else { + lua_pushnil(L); + } + return 1; + } + + int FramePixelFormatted(lua_State *L) { + std::shared_ptr frame = check_VideoFrame(L); + size_t x = lua_tointeger(L, -2); + size_t y = lua_tointeger(L, -1); + lua_pop(L, 2); + + if (x < frame->width && y < frame->height) { + if (frame->flipped) + y = frame->height - y; + + size_t pos = y * frame->pitch + x * 4; + // VideoFrame is stored as BGRA, Color expects RGBA + agi::Color* color = new agi::Color(frame->data[pos+2], frame->data[pos+1], frame->data[pos], frame->data[pos+3]); + push_value(L, color->GetAssOverrideFormatted()); + } else { + lua_pushnil(L); + } + return 1; + } + + int FrameDestory(lua_State *L) { + std::shared_ptr frame = check_VideoFrame(L); + frame.~shared_ptr(); + return 0; + } + + int get_frame(lua_State *L) + { + // get frame number from stack + const agi::Context *c = get_context(L); + int frameNumber = lua_tointeger(L, 1); + + bool withSubtitles = false; + if (lua_gettop(L) >= 2) { + withSubtitles = lua_toboolean(L, 2); + lua_pop(L, 1); + } + lua_pop(L, 1); + + static const struct luaL_Reg FrameTableDefinition [] = { + {"width", FrameWidth}, + {"height", FrameHeight}, + {"getPixel", FramePixel}, + {"getPixelFormatted", FramePixelFormatted}, + {"__gc", FrameDestory}, + {NULL, NULL} + }; + + // create and register metatable if not already done + if (luaL_newmetatable(L, "VideoFrame")) { + // metatable.__index = metatable + lua_pushstring(L, "__index"); + lua_pushvalue(L, -2); + lua_settable(L, -3); + + luaL_register(L, NULL, FrameTableDefinition); + } + + if (c && c->project->Timecodes().IsLoaded()) { + std::shared_ptr frame = c->videoController->GetFrame(frameNumber, !withSubtitles); + + void *userData = lua_newuserdata(L, sizeof(std::shared_ptr)); + + new(userData) std::shared_ptr(frame); + + luaL_getmetatable(L, "VideoFrame"); + lua_setmetatable(L, -2); + } else { + lua_pushnil(L); + } + return 1; + } + int get_keyframes(lua_State *L) { if (const agi::Context *c = get_context(L)) @@ -319,6 +430,12 @@ namespace { return 0; } + int lua_is_modified(lua_State *L) + { + push_value(L, get_context(L)->subsController->IsModified()); + return 1; + } + int project_properties(lua_State *L) { const agi::Context *c = get_context(L); @@ -523,11 +640,13 @@ namespace { set_field(L, "project_properties"); set_field(L, "get_audio_selection"); set_field(L, "set_status_text"); - lua_createtable(L, 0, 4); + set_field(L, "get_frame"); + lua_createtable(L, 0, 5); set_field(L, "get_cursor"); set_field(L, "set_cursor"); set_field(L, "get_selection"); set_field(L, "set_selection"); + set_field(L, "is_modified"); lua_setfield(L, -2, "gui"); // store aegisub table to globals diff --git a/src/video_controller.cpp b/src/video_controller.cpp index 6c4427eaf..3b0647459 100644 --- a/src/video_controller.cpp +++ b/src/video_controller.cpp @@ -40,6 +40,7 @@ #include "time_range.h" #include "async_video_provider.h" #include "utils.h" +#include "video_frame.h" #include @@ -222,6 +223,11 @@ int VideoController::FrameAtTime(int time, agi::vfr::Time type) const { return context->project->Timecodes().FrameAtTime(time, type); } +std::shared_ptr VideoController::GetFrame(int frame, bool raw) const { + double timestamp = TimeAtFrame(frame, agi::vfr::EXACT); + return provider->GetFrame(frame, timestamp, raw); +} + void VideoController::OnVideoError(VideoProviderErrorEvent const& err) { wxLogError( "Failed seeking video. The video file may be corrupt or incomplete.\n" diff --git a/src/video_controller.h b/src/video_controller.h index c15c5c8f1..3bb083ac7 100644 --- a/src/video_controller.h +++ b/src/video_controller.h @@ -39,6 +39,7 @@ class AssDialogue; class AsyncVideoProvider; struct SubtitlesProviderErrorEvent; struct VideoProviderErrorEvent; +struct VideoFrame; namespace agi { struct Context; @@ -159,4 +160,5 @@ public: int TimeAtFrame(int frame, agi::vfr::Time type = agi::vfr::EXACT) const; int FrameAtTime(int time, agi::vfr::Time type = agi::vfr::EXACT) const; + std::shared_ptr GetFrame(int frame, bool raw) const; };