605 lines (484 with data), 20.8 kB
#ifndef TOOLS_MODELUTILITIES_VIDEO_VIDEOLOOKATCAMERA
#define TOOLS_MODELUTILITIES_VIDEO_VIDEOLOOKATCAMERA
/*
---
group: Video tools
topic: Video
descr: Creates a video object for saving mp4 videos from model simulations.
---
Add `#include "<AMMR_TOOLS>/ModelUtilities/Video/VideoLookAtCamera.any"`
To use the class template.
*/
#define _CAMERA_MAIN_FILE_DIRECTORY_ ANYBODY_PATH_MAINFILEDIR
#ifndef _CAMERA_FFMPEG_DEFAULT_PATH_
#define _CAMERA_FFMPEG_DEFAULT_PATH_ ANYBODY_PATH_INSTALLDIR + "\Assist\ffmpeg.exe"
#endif
#ifndef _ANYBODY_OVERLAY_LOGO_
#define _ANYBODY_OVERLAY_LOGO_ ANYBODY_PATH_INSTALLDIR + "\Assist\video_overlay.png"
#endif
// Class creating a video camera for video recording of AnyBody simulations.
//
// :::{rubric} Usage
// :::
//
// Once the model is loaded run `Create_Video` operation to create the
// Video. The video will be saved in the same folder as the main file.
//
//
// ```
// #include "<ANYBODY_PATH_MODELUTILS>/Video/VideoLookAtCamera.any"
//
// // This is a simple example of using the camera class to create videos.
// VideoLookAtCamera MyCam (UP_DIRECTION = y) =
// {
// // The point the camera focus on
// CameraLookAtPoint = Main.MyModel.Femur.Knee.r;
//
// // The vertical field of view in meters at the LookAtPoint
// CameraFieldOfView = 1;
//
// // The direction which the camera is placed
// // (In global coordinates with respect to the LookAtPoint)
// CameraDirection = {1, 1, -1};
//
// // The operations which should be included in the video.
// Analysis = {
// AnyOperation &ref = Main.MyStudy.InverseDynamics;
// };
// };
// ```
//
//
#class_template VideoLookAtCamera (
UP_DIRECTION = y,
CREATE_GIF = 0,
TRANSPARENT_BACKGROUND=0,
_AUTO_PLAY_VIDEOS = 1,
_OVER_WRITE = 1,
_DEBUG = 0,
_CLEAN_UP_IMAGES = 1,
_AUTO_OPEN_PREVIEW_ = 1,
ENABLE_SAVE_SETTINGS=0,
ENABLE_OVERLAY= 1,
VIDEO_ENCODER="libvpx-vp9"
)
{
// Arguments:
// ---------------
//VideoLookAtCamera#UP_DIRECTION
// The direction of the up vector of the camera.
//
//VideoLookAtCamera#TRANSPARENT_BACKGROUND
// Create images and videos with transparent background.
//
//VideoLookAtCamera#CREATE_GIF
// If set to 1, a gif will be created in addition to the video.
//
//VideoLookAtCamera#ENABLE_OVERLAY
// If set to 1, the settings used to create the video will be saved in a file.
//
//VideoLookAtCamera#VIDEO_ENCODER
// Sets the codec used for encoding the video in FFMPEG. The default "libvpx-vp9" creates
// good quality videos and works with the FFMPEG converter shipped with AnyBody.
// Setting this to other codecs like for example "libx264", requires that you specify
// a version of FFMPEG you installed your self. Point the VideoPathFFMPEG member to
// where FFMPEG is installed on the machine. You may also need to to set
// Operations.ConvertVideo.video_extension
//VideoLookAtCamera
/// File name of the video that will be created
#var AnyString VideoName = ANYBODY_NAME_MAINFILE;
//VideoLookAtCamera
/// Video resolution in pixels: Defaults to FullHD
/// Width of the video in pixels
#var AnyIntArray VideoResolution = {1920,1080};
//VideoLookAtCamera
/// From FFMPEG: The range of the quantizer scale is 0-51: where 0 is lossless,
/// and 51 is worst possible. A lower value is a higher quality.
#var AnyIntVar ConstantRateFactor = 30;
//VideoLookAtCamera
/// The ratio between video resolution and input images saved
/// from anybody. Default is to save images in same resolution as
/// the output video. It is an advantage to set this to 2 or 4 when
/// making videos with a low resolution
#var AnyFloat VideoInputScale = 1;
//VideoLookAtCamera
/// Deprecated. Use the VIDEO_ENCODER argument instead.
#var AnyString VideoCodec = "";
//VideoLookAtCamera
/// This is the start frame used when creating Videos. This can be used to
/// for skipping some of the initial frames.
#var AnyInt VideoStartFrame = 0;
//VideoLookAtCamera
/// Image used to overlay (e.g. logo) on the final video
#var AnyString OverlayImage = _ANYBODY_OVERLAY_LOGO_;
//VideoLookAtCamera
/// Height of the overlay image in percent of the video
#var AnyVar OverlayImageScale = 0.08;
//VideoLookAtCamera
/// The path to the ffmpeg binary.
#var AnyStringVar VideoPathFFMPEG = _CAMERA_FFMPEG_DEFAULT_PATH_;
//VideoLookAtCamera
/// Determines the speed of the video. Setting it to
/// nStep/(tEnd-tStart) make the video run in real time.
#var AnyIntVar VideoInputFrameRate = 24;
//VideoLookAtCamera
/// Video output framerate.
#var AnyIntVar VideoOutputFrameRate = 24;
//VideoLookAtCamera
/// The output path where the video is saved
#var AnyStringVar VideoOutputPath = _CAMERA_MAIN_FILE_DIRECTORY_;
//VideoLookAtCamera
/// The point the camera focus on
#var AnyVec3 CameraLookAtPoint = DesignVar({0,1,0});
//VideoLookAtCamera
/// The distance from the camera to the scene. This does NOT determine the
/// size of scene, since Perspective is off by default. To zoom in/out, use
/// CameraFeildOfView.
#var AnyVar CameraDistance = DesignVar(6);
//VideoLookAtCamera
/// The vertical field of view in meters at the LookAtPoint
#var AnyVar CameraFieldOfView = DesignVar(2);
/// The direction which the camera is placed
/// (In global coordinate with respect to the LookAtPoint)
#var AnyVec3 CameraDirection = DesignVar({1, 0, 0});
// The following is AnyScript Magic ;)
// Covert the x/y/z Enum from UP_DIRECTION to a {1,0,0}/{0,1,0}/{0,0,1} vector
/// The up direction of the camera as a vector. This is set by the UP_DIRECTION
/// can be overwritten.
#var AnyVec3 CameraUpDirection= DesignVar({{1,0,0},{0,1,0},{0,0,1}}[round(sum({-0.5,0,0.5}*RotMat(pi, UP_DIRECTION ))+1)]);
//VideoLookAtCamera
/// The background color used for the video
#var AnyVec3 BackgroundColor = DesignVar({1,1,1});
//VideoLookAtCamera
/// Counter for numbering the saved images. This defaults to the
/// camera class builtin counter.
#var AnyInt Counter = Camera.Recorder.Counter;
//VideoLookAtCamera
/// The resolution used when converting the video to a gif file.
/// By default it will follow video resolution up until 600x600.
#var AnyIntArray GifResolution = {min({VideoResolution[0],600}),min({VideoResolution[1],600})} ;
//VideoLookAtCamera
/// Video output framerate.
#var AnyIntVar GifOutputFrameRate = VideoOutputFrameRate;
//VideoLookAtCamera
/// Extension of the output video. Defaults to .webm for VP9 encoded videos, otherwise .mp4
#var AnyStringVar VideoExtension =
#if VIDEO_ENCODER == "libvpx-vp9"
".webm";
#else
".mp4";
#endif
/// Switch to view camera eye point.
#var AnySwitchVar viewCamera = Off;
AnyFolder CheckInputs = {
AnyInt VideoInputFrameRate = assert(gtfun(.VideoInputFrameRate,0), "Video Input frame rate should larger than 0");
AnyInt deprecated_codec_member = warn(orfun(eqfun(.VideoCodec, "libvpx-vp9"), eqfun(.VideoCodec, "")), strformat("\n") +"Deprecated: The VideoLookAtCamera.VideoCodec member is deprecated. Use class argument VIDEO_ENCODER instead");
#if VIDEO_ENCODER != "libvpx-vp9" & TRANSPARENT_BACKGROUND
AnyInt warn_transparent = warn(0, strformat("\nOnly few video codecs support transparent vides. Consider setting VIDEO_ENCODER=" + strquote("libvpx-vp9") + " to create videos that support transparency."));
#endif
#if VIDEO_ENCODER != "libvpx-vp9"
AnyInt unsupported_codec = expect(neqfun(.VideoPathFFMPEG, _CAMERA_FFMPEG_DEFAULT_PATH_),
strformat("\n\n"+
" The FFMPEG version shipped with AnyBody only supports the 'libvpx-vp9' codec.\n"+
" To use other codecs point the 'VideoPathFFMPEG' member to a different FFMPEG version." +
"\n"));
#endif
};
/// Operation to show a preview of what the camera sees.
AnyOperationSequence Preview =
{
Settings = {
#var DisplayPriority = PriorityNormal;
};
#var AnyStringVar FileName = .VideoName + "_Preview.png";
AnyFileVar preview_file = .VideoOutputPath +"/" + FileName;
AnyOperation& Reset = .Operations.Reset;
AnyOperationSetValue SetFilename=
{
Source = {&.preview_file};
Target = {&..Camera.Recorder.FileName};
};
AnyOperation &StartTrigger = .Operations.StartTrigger;
AnyOperation &StopTrigger = .Operations.StopTrigger;
#if _AUTO_OPEN_PREVIEW_
AnyOperation& OpenPreview = .Operations.OpenPreview;
#endif
AnyOperationMacro ResetFilename =
{
MacroStr = {"classoperation "+CompleteNameOf(..Camera.Recorder.F) + "ileName " + strquote("Reset Value")};
};
};
AnyOperationSequence Create_Video =
{
Settings = {
#var DisplayPriority = PriorityHigh;
};
AnyOperationSequence &PreAnalysis = .PreAnalysis;
AnyOperation& Reset = .Operations.Reset;
AnyOperation &StartTrigger = .Operations.StartTrigger;
AnyOperation &Operation = .Analysis;
AnyOperation &StopTrigger = .Operations.StopTrigger;
AnyOperation& ConvertVideo = .Operations.ConvertVideo;
#if _CLEAN_UP_IMAGES == 1
AnyOperation& RemoveImageFiles = .Operations.RemoveImageFiles;
#endif
#if CREATE_GIF == 1
AnyOperation& CreateAnimatedGif = .Operations.Convert_video_to_animated_gif;
#endif
#if _AUTO_PLAY_VIDEOS
AnyOperation& PlayVideo = .Operations.PlayVideo;
#if CREATE_GIF == 1
AnyOperation& PlayGif = .Operations.Open_gif;
#endif
#endif
};
AnyCameraLookAt Camera = {
AnyDrawCamera drawcam = {Visible = ..viewCamera;};
#var Perspective = DesignVar(Off);
#var EyePoint = .CameraLookAtPoint + .CameraDistance *.CameraDirection;
#var LookAtPoint = .CameraLookAtPoint;
#var UpPoint = DesignVar(EyePoint + 100*.CameraUpDirection);
#var FocalDist = .CameraDistance;
#var FocalHeight = .CameraFieldOfView;
#var RenderUserInterfaceViewState = On;
AnyScene Scene =
{
BackgroundColor = ..BackgroundColor;
};
AnyCamRecorder Recorder = {
AnyStringVar F = ""; //< Dummy variable used to get the full name of Recorder.FileName. Do not delete it.
#var AnyString CounterFormat = "%04d";
#var pxWidth = round(..VideoResolution[0] * ..VideoInputScale*1.0);
#var pxHeight = round(..VideoResolution[1] * ..VideoInputScale*1.0);
#var Trig = DesignVar(Off);
#var ResetTrig = DesignVar(Off);
#var AntiAlias = 16;
#if TRANSPARENT_BACKGROUND
#var TransparentBackgroundOnOff = On;
#endif
FileName = ..VideoOutputPath+ "/"+ ..VideoName + "_" + strval(..Counter , CounterFormat)+ ".png";
AnySwitchVar Offsetting = Off;
AnySwitchVar OnSetting = On;
AnyOperationSetValue TriggerOff =
{
Source = { &.Offsetting};
Target = {&.Trig};
};
AnyOperationSetValue TriggerOn =
{
Source = {&.OnSetting};
Target = {&.Trig};
};
AnyOperationSetValue ResetTriggerOff =
{
Source = { &.Offsetting};
Target = {&.ResetTrig};
};
AnyOperationSetValue ResetTriggerOn =
{
Source = {&.OnSetting};
Target = {&.ResetTrig};
};
};
#if ENABLE_SAVE_SETTINGS
AnyOperationSequence Save_Settings= {
AnyOperationSetValue TouchSettings =
{
Source =
{
&...CameraLookAtPoint,
&...CameraDistance,
&...CameraFieldOfView,
&...CameraDirection,
&...CameraUpDirection,
&...BackgroundColor,
&...CameraUpDirection
};
Target = Source;
};
AnyOperationMacro SaveValues =
{
MacroStr = {"classoperation Main "+strquote("Save Values")+ " --file=" + strquote(...VideoName+"_Camera_Settings.anyset") };
};
};
AnyOperationMacro Load_Settings =
{
MacroStr = {"classoperation Main "+strquote("Load Values")+ " --file=" + strquote(..VideoName+"_Camera_Settings.anyset") };
};
#endif
};
AnyFolder Operations =
{
#if _DEBUG
AnyStringVar PostCmd = " && set /p=DEBUG: Hit ENTER to continue...";
#else
AnyStringVar PostCmd = "";
#endif
AnyOperationSequence Convert_video_to_animated_gif =
{
AnyFileVar video_file = ..VideoOutputPath+ "/"+ ..VideoName + ".mp4";
AnyFileVar gif_file = ..VideoOutputPath+ "/"+ ..VideoName + ".gif";
AnyFileVar palette_file = ..VideoOutputPath+ "/"+ ..VideoName + "_pallete.png";
AnyStringVar filters = "fps="+strval(..GifOutputFrameRate)+",scale=w=" + strval(..GifResolution[0]) +
":h=" + strval(..GifResolution[1]) +
":force_original_aspect_ratio=decrease:flags=lanczos";
/// Run through the video to calculate a color pallete to use when
/// generating the gif file. This improves quality a lot.
AnyOperationShellExec GeneratePallete =
{
FileName = "cmd.exe";
Arguments = "/C call "+strquote(...VideoPathFFMPEG) +
" -v warning" +
" -i " + strquote(FilePathCompleteOf(.video_file)) +
" -vf " + strquote(.filters + ",palettegen=stats_mode=diff") +
" -y " +
strquote(FilePathCompleteOf(.palette_file))
#if _DEBUG
+ " && set /p=DEBUG: Hit ENTER to continue..";
#else
+ " || set /p=Pallete generation failed: Hit ENTER to continue...";
#endif
#var WorkDir = ...VideoOutputPath;
#var Show = 1;
Wait = 1;
};
/// Convert video to a gif file using the color palette.
AnyOperationShellExec EncodeGif =
{
FileName = "cmd.exe";
//
Arguments = "/C call "+strquote(...VideoPathFFMPEG) +
" -v warning" +
" -i " + strquote(FilePathCompleteOf(.video_file)) +
" -i " + strquote(FilePathCompleteOf(.palette_file)) +
" -lavfi " + strquote(.filters + " [x]; [x][1:v] paletteuse=dither=floyd_steinberg:diff_mode=rectangle") +
iffun(_OVER_WRITE, " -y "," ") +
strquote(FilePathCompleteOf(.gif_file))
#if _DEBUG
+ " && set /p=DEBUG: Hit ENTER to continue..";
#else
+" || set /p=Encode GIF failed: Hit ENTER to continue...";
#endif
#var WorkDir = ...VideoOutputPath;
#var Show = 1;
Wait = 1;
};
AnyOperationShellExec DeletePallete =
{
FileName = "cmd.exe";
Arguments = "/C del " + strquote(FilePathCompleteOf(.palette_file))+..PostCmd;
#var WorkDir = ...VideoOutputPath;
#var Show = _DEBUG;
Wait = _DEBUG;
};
};
AnyOperationShellExec Open_gif =
{
FileName = "cmd.exe";
#var Arguments = "/C call "+ strquote(FilePathCompleteOf(.Convert_video_to_animated_gif.gif_file))+.PostCmd;
#var Show = _DEBUG;
Wait = _DEBUG;
};
AnyOperationShellExec OpenPreview =
{
FileName = "cmd.exe";
#var Arguments = "/C TITLE Open Preview&&" +"call "+ strquote(FilePathCompleteOf(..Preview.preview_file))+.PostCmd;
#var Show = _DEBUG;
Wait = _DEBUG;
};
/// Resets the Camera trigger.
AnyOperationSequence Reset =
{
AnyOperation &reset_on = ..Camera.Recorder.ResetTriggerOn;
AnyOperation &reset_off = ..Camera.Recorder.ResetTriggerOff;
};
/// Starts the recorder
AnyOperation& StartTrigger = .Camera.Recorder.TriggerOn;
/// Stops the recorder
AnyOperation &StopTrigger = .Camera.Recorder.TriggerOff;
/// Convert video using FFMPEG
AnyOperationShellExec ConvertVideo =
{
#var FileName = "cmd.exe";
#var AnyStringVar inputfile = ..VideoName + "_" + ..Camera.Recorder.CounterFormat +".png";
#var AnyFileVar out_file = ..VideoOutputPath+ "/"+ ..VideoName + ..VideoExtension;
#if ENABLE_OVERLAY
#var AnyString OverLayFilter = "[bg];[1:v]scale=-2:2*trunc("+strval(..VideoResolution[1])+"*"+strval(..OverlayImageScale)+"/2)[overlay];[bg][overlay]overlay=W-w:H-h";
//Option to fade out overlay: ":enable='between(t,0,2)'"
//Option to make transparent overlay: "format=argb,geq=r='r(X,Y)':a='1.0*alpha(X,Y)'"
#else
#var AnyString OverLayFilter = "";
#endif
AnyFloat InputOutputScale = ..VideoInputScale;
#var AnyStringVar ffmpegBaseArg =
"call "+strquote(..VideoPathFFMPEG) +
iffun(_OVER_WRITE, " -y","") +
" -hide_banner -loglevel warning " +
" -r " + strval(..VideoInputFrameRate) +
" -start_number " + strval(..VideoStartFrame,..Camera.Recorder.CounterFormat) +
" -i "+strquote(inputfile) +
#if ENABLE_OVERLAY
" -i " + strquote(..OverlayImage) +
#endif
" -filter_complex " + strquote("[0:v]fps="+strval(..VideoOutputFrameRate)+ ",scale=trunc(iw/(2*"+strval(InputOutputScale,"%g" )+"))*2:trunc(ih/(2*"+strval(InputOutputScale,"%g" )+"))*2" + OverLayFilter + "[outv]") +
" -map [outv]" +
" -an" +
" -metadata comment=" + strquote("Created with the AnyBody Modeling System.")
;
#var AnyStringVar CodecArgs_default = " -c:v "+ VIDEO_ENCODER +" -crf " + strval(..ConstantRateFactor)+" -level 3.0 -pix_fmt yuv420p -strict experimental -movflags +faststart ";
#var AnyStringVar CodecArgs_vp9 = " -c:v libvpx-vp9 -an -crf " + strval(..ConstantRateFactor)+" -pix_fmt yuva420p -row-mt 1 "; //-metadata:s:v:0 alpha_mode=" + strquote("1")
// #var AnyStringVar CodecArgs_VP9 = " -c:v apng" + " -crf " + strval(..ConstantRateFactor)+" -pix_fmt rgba -row-mt 1 ";
#var AnyStringVar OutputArg = strquote(FilePathCompleteOf(out_file));
#var Arguments = "/C "
+ " TITLE AnyBody - Encoding video && "
+ "echo Creating Video: "+ OutputArg +" && "
+ "echo ^> ffmpeg: "+ strquote(..VideoPathFFMPEG) + " && "
#if VIDEO_ENCODER == "libvpx-vp9"
+ "echo ^> Finding optimal bitrate and settings. Please wait. && "
+ ffmpegBaseArg + CodecArgs_vp9 +"-v error -pass 1 -f null NUL && "
+ "echo Done && "
+ "echo ^> Encoding video: && "
+ ffmpegBaseArg + " -stats " + CodecArgs_vp9 + " -pass 2 -auto-alt-ref 0 "
#else
+ "echo ^> Encoding video: && "
+ ffmpegBaseArg + " -stats " + CodecArgs_default
#endif
+ OutputArg
#if _DEBUG
+ " && set /p=DEBUG: Hit ENTER to continue..";
#else
+ " || set /p=Video Encoding failed: Hit ENTER to continue..";
#endif
#var WorkDir = ..VideoOutputPath;
#var Show = DesignVar(1);
};
///
AnyOperationShellExec RemoveImageFiles =
{
#var FileName = "cmd.exe";
#var Arguments = "/C TITLE Removing Images && del ffmpeg2pass-0.log && set fn=" + strquote(..VideoName+ "_" + "*.png") + " && call set fn=%fn:/=\% && call del %fn% " + .PostCmd;
#var WorkDir = ..VideoOutputPath;
#var Show = _DEBUG;
Wait = _DEBUG;
};
AnyOperationShellExec PlayVideo =
{
FileName = "cmd.exe";
#var Arguments = "/C TITLE Play Video&& call "+ strquote(FilePathCompleteOf(..Create_Video.ConvertVideo.out_file))+.PostCmd;
#var Show = _DEBUG;
Wait = _DEBUG;
};
};
/// Operations which will be executed before recording starts
AnyOperationSequence PreAnalysis =
{
Settings.DisplayPriority = PriorityLow;
AnyOperationDummy no_op = {};
};
/// Operations which will be recorded
AnyOperationSequence Analysis =
{
AnyOperationDummy no_op = {};
};
};
#endif