[38ba34]: / Tools / ModelUtilities / Video / VideoLookAtCamera.any

Download this file

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