--- a +++ b/Tools/ModelUtilities/Video/VideoLookAtCamera.any @@ -0,0 +1,604 @@ +#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