Add automation function to get frame
This commit is contained in:
parent
bde149fd30
commit
8e1cc6228e
4 changed files with 190 additions and 1 deletions
69
automation/v4-docs/get-frame.txt
Normal file
69
automation/v4-docs/get-frame.txt
Normal file
|
@ -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&"
|
||||||
|
|
||||||
|
---
|
|
@ -50,8 +50,9 @@
|
||||||
#include "project.h"
|
#include "project.h"
|
||||||
#include "selection_controller.h"
|
#include "selection_controller.h"
|
||||||
#include "subs_controller.h"
|
#include "subs_controller.h"
|
||||||
#include "video_controller.h"
|
|
||||||
#include "text_selection_controller.h"
|
#include "text_selection_controller.h"
|
||||||
|
#include "video_controller.h"
|
||||||
|
#include "video_frame.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <libaegisub/dispatch.h>
|
#include <libaegisub/dispatch.h>
|
||||||
|
@ -200,6 +201,116 @@ namespace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<VideoFrame> check_VideoFrame(lua_State *L) {
|
||||||
|
auto framePtr = static_cast<std::shared_ptr<VideoFrame>*>(luaL_checkudata(L, 1, "VideoFrame"));
|
||||||
|
return *framePtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FrameWidth(lua_State *L) {
|
||||||
|
std::shared_ptr<VideoFrame> frame = check_VideoFrame(L);
|
||||||
|
push_value(L, frame->width);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FrameHeight(lua_State *L) {
|
||||||
|
std::shared_ptr<VideoFrame> frame = check_VideoFrame(L);
|
||||||
|
push_value(L, frame->height);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int FramePixel(lua_State *L) {
|
||||||
|
std::shared_ptr<VideoFrame> 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<VideoFrame> 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<VideoFrame> frame = check_VideoFrame(L);
|
||||||
|
frame.~shared_ptr<VideoFrame>();
|
||||||
|
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<VideoFrame> frame = c->videoController->GetFrame(frameNumber, !withSubtitles);
|
||||||
|
|
||||||
|
void *userData = lua_newuserdata(L, sizeof(std::shared_ptr<VideoFrame>));
|
||||||
|
|
||||||
|
new(userData) std::shared_ptr<VideoFrame>(frame);
|
||||||
|
|
||||||
|
luaL_getmetatable(L, "VideoFrame");
|
||||||
|
lua_setmetatable(L, -2);
|
||||||
|
} else {
|
||||||
|
lua_pushnil(L);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
int get_keyframes(lua_State *L)
|
int get_keyframes(lua_State *L)
|
||||||
{
|
{
|
||||||
if (const agi::Context *c = get_context(L))
|
if (const agi::Context *c = get_context(L))
|
||||||
|
@ -529,6 +640,7 @@ namespace {
|
||||||
set_field<project_properties>(L, "project_properties");
|
set_field<project_properties>(L, "project_properties");
|
||||||
set_field<lua_get_audio_selection>(L, "get_audio_selection");
|
set_field<lua_get_audio_selection>(L, "get_audio_selection");
|
||||||
set_field<lua_set_status_text>(L, "set_status_text");
|
set_field<lua_set_status_text>(L, "set_status_text");
|
||||||
|
set_field<get_frame>(L, "get_frame");
|
||||||
lua_createtable(L, 0, 5);
|
lua_createtable(L, 0, 5);
|
||||||
set_field<lua_get_text_cursor>(L, "get_cursor");
|
set_field<lua_get_text_cursor>(L, "get_cursor");
|
||||||
set_field<lua_set_text_cursor>(L, "set_cursor");
|
set_field<lua_set_text_cursor>(L, "set_cursor");
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
#include "time_range.h"
|
#include "time_range.h"
|
||||||
#include "async_video_provider.h"
|
#include "async_video_provider.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
#include "video_frame.h"
|
||||||
|
|
||||||
#include <libaegisub/ass/time.h>
|
#include <libaegisub/ass/time.h>
|
||||||
|
|
||||||
|
@ -222,6 +223,11 @@ int VideoController::FrameAtTime(int time, agi::vfr::Time type) const {
|
||||||
return context->project->Timecodes().FrameAtTime(time, type);
|
return context->project->Timecodes().FrameAtTime(time, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<VideoFrame> 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) {
|
void VideoController::OnVideoError(VideoProviderErrorEvent const& err) {
|
||||||
wxLogError(
|
wxLogError(
|
||||||
"Failed seeking video. The video file may be corrupt or incomplete.\n"
|
"Failed seeking video. The video file may be corrupt or incomplete.\n"
|
||||||
|
|
|
@ -39,6 +39,7 @@ class AssDialogue;
|
||||||
class AsyncVideoProvider;
|
class AsyncVideoProvider;
|
||||||
struct SubtitlesProviderErrorEvent;
|
struct SubtitlesProviderErrorEvent;
|
||||||
struct VideoProviderErrorEvent;
|
struct VideoProviderErrorEvent;
|
||||||
|
struct VideoFrame;
|
||||||
|
|
||||||
namespace agi {
|
namespace agi {
|
||||||
struct Context;
|
struct Context;
|
||||||
|
@ -159,4 +160,5 @@ public:
|
||||||
|
|
||||||
int TimeAtFrame(int frame, agi::vfr::Time type = agi::vfr::EXACT) const;
|
int TimeAtFrame(int frame, agi::vfr::Time type = agi::vfr::EXACT) const;
|
||||||
int FrameAtTime(int time, agi::vfr::Time type = agi::vfr::EXACT) const;
|
int FrameAtTime(int time, agi::vfr::Time type = agi::vfr::EXACT) const;
|
||||||
|
std::shared_ptr<VideoFrame> GetFrame(int frame, bool raw) const;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue