Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Embed Video into GUI - Python API

    Cinema 4D SDK
    python windows
    2
    6
    904
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • E
      ezeuz
      last edited by

      Hi, I was wondering if anyone ever tried embedding a video into the GUI via Python?

      One method I was thinking of was to use OpenCV for the video backend, while getting the frames one-by-one and draw it into an UserArea.

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @ezeuz
        last edited by ferdinand

        Hey @ezeuz,

        Thank you for reaching out to us. I do not know if someone tried doing this with a user area (UA), but here are a few thoughts:

        • OpenCV seems a bit overkill for the backend, you could just use c4d.BaseBitmap.InitWith to load your video files. Unless you plan to display video files in formats that Cinema 4D cannot load.
        • While your approach is in general correct, you seem to overlook something that many people tend to overlook: All drawing functions in Cinema 4D, just as in pretty much every other API, are not called for each frame. Instead they act as a buffer into which you put drawing instructions which then are carried out by Cinema 4D in an optimized manner, i.e., stuff is being cached. The applies to everything from drawing into a viewport to drawing into the canvas of a dialog/UA.
        • So, when you do not take care of this, Cinema 4D would draw your UA once and then cache that bitmap. After that it would never call you again until the user resizes the parent of the UA or otherwise makes this bitmap invalid.
        • The normal way of invalidating a UA are user inputs, the user either clicks on the UA or types something while the UA is focused, and in the event handling for that we then force a redraw. For displaying a video this strategy does not work as we do not have any events being fired for each frame by Cinema 4D where we could call c4d.gui.GeUserArea.Redraw() to force a redraw.
        • The solution to this is to use a timer, which is a feature of the c4d.gui.GeUserArea class. You can start a timer with c4d.gui.GeUserArea.SetTimer() and then you will receive a c4d.gui.GeUserArea.Timer() call for each timer event, which you can use to update your video frame.
        • The problem with this is that GeDialog/GeUserArea.Timerdoes not offer arbitrary precision, nor high precision in general. To display 30FPS, you would need a timer event every 33.33ms. When you set the timer that low, you will have quite a bit of error. You can adapt to this, but this might result in a bit of jitter in the video playback.
        • Finally, and this is the most important point, Python is a terrible choice to draw something with 30 FPS or even more to the screen. When you design your code carefully, i.e., your GeUserArea.DrawMsg should be hyper-optimized, most of the code will actually run in the C++ backend and this could work. But this can easily perform very poorly.

        Cheers,
        Ferdinand

        Result

        Timer fired after 0.000 seconds
        Timer fired after 0.070 seconds
        Timer fired after 0.047 seconds
        Timer fired after 0.047 seconds
        Timer fired after 0.048 seconds
        Timer fired after 0.061 seconds
        Timer fired after 0.046 seconds
        Timer fired after 0.047 seconds
        Timer fired after 0.051 seconds
        Timer fired after 0.011 seconds
        Timer fired after 0.055 seconds
        Timer fired after 0.043 seconds
        

        Code

        """Simple example of a GeDialog that uses a timer to update itself.
        """
        
        import c4d
        import time
        
        class MyDialog(c4d.gui.GeDialog):
            """Implementation of a GeDialog that uses a timer to update itself.
            """
            def __init__(self):
                """Constructor.
        
                Initializes the last tick time and the count of timer firings.
                """
                self._lastTick: float | None = None
                self._count: int = 0
        
            def CreateLayout(self) -> bool:
                """Called by Cinema 4D to let the dialog populate its layout.
                """
                self.SetTitle("Timer Example")
                self.AddStaticText(1000, c4d.BFH_SCALEFIT, name="I have no controls, just a timer")
                self.SetTimer(33)  # Set the timer to fire every 33ms
                
                return True
        
            def Timer(self, msg: c4d.BaseContainer) -> None:
                """Called by Cinema 4D when the timer fires.
        
                The point of this example is to demonstrate that the ticks are not as regular as one might 
                expect. When I ran this example, I got the following output:
        
                    Timer fired after 0.000 seconds
                    Timer fired after 0.070 seconds
                    Timer fired after 0.047 seconds
                    Timer fired after 0.047 seconds
                    Timer fired after 0.048 seconds
                    Timer fired after 0.061 seconds
                    Timer fired after 0.046 seconds
                    Timer fired after 0.047 seconds
                    Timer fired after 0.051 seconds
                    Timer fired after 0.011 seconds
                    Timer fired after 0.055 seconds
                    Timer fired after 0.043 seconds
                
                The first 0.000 is okay because it's the first tick. But the rest of the ticks are not
                regular spaced 33ms ticks although that is what we asked for. As rule of thumb, timers below
                100ms are getting increasingly relatively inaccurate, and for all timer events you can expect
                a margin of error of about 50ms. This can get worse when Cinema 4D is under heavy load.
        
                What is mitigating this a bit is that we use here Python's time.perf_counter() function to 
                measure time which despite its name is not a high-resolution timer. So, the actual error is
                a bit lower.
                """
                t: float = time.perf_counter()
                delta: float = t - self._lastTick if self._lastTick else 0
                self._lastTick = t
        
                print(f"Timer fired after {delta:.3f} seconds")
                self._count += 1
                if self._count > 10:
                    self.SetTimer(0)  # Stop the timer
        
        def main():
            dialog = MyDialog()
            dialog.Open(c4d.DLG_TYPE_MODAL)  # Open the dialog
        
        # Execute main()
        if __name__=='__main__':
            main()
        

        MAXON SDK Specialist
        developers.maxon.net

        1 Reply Last reply Reply Quote 1
        • E
          ezeuz
          last edited by

          @ferdinand Thanks a lot on the feedbacks! Yeah I was thinking of using external library coz I was used to Maya, and the media support in that app was horrendous. I guess even Audio support-wise, Cinema 4D is miles better out-of-the-box. Hopefully c4d fits with our usecase.

          Ahhh I see, good point on the Python & GUI limitations. Thinking back, it makes sense that high-performance GUI rarely rendered/looped for each frame. Our use case was more like generating some kind of animation based on a video. The video is there for just for side-by-side preview.

          So all in all, we don't really need highly accurate playback. By using the time delta instead of raw timer and skipping frames here and there, overall I presume it'll be fine (say, even 10 FPS out of a 30 FPS video). But those are something I hadn't thought about, thanks a ton once again!

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @ezeuz
            last edited by

            Hey @ezeuz,

            I guess even Audio support-wise

            I am not quite sure how this is meant, but BaseBitmap is just an interface for pixel content (which despite its a bit misleading name can also be used for video content). All audio information will be ignored by BaseBitmap.

            Cheers,
            Ferdinand

            MAXON SDK Specialist
            developers.maxon.net

            E 1 Reply Last reply Reply Quote 0
            • E
              ezeuz @ferdinand
              last edited by ezeuz

              @ferdinand Oh I was just making a parallel, how Cinema 4D supports more audio nicely by default (compared to Maya), so I can generally expect Cinema 4D supports a lot more media compared to Maya (which I had experience developing a plugin on before moving to C4D).

              1 Reply Last reply Reply Quote 0
              • ferdinandF
                ferdinand
                last edited by

                Hey,

                okay, good to know. I just wanted to make clear that BaseBitmap does not support audio playback or sampling audio information. Except for the SoundEffectorCustomGui, there is in fact no sound support at all in the Python API. The C++ API offers more support with the MEDIASESSION namespace, but also this is mostly geared towards writing such information.

                Cheers,
                Ferdinand

                MAXON SDK Specialist
                developers.maxon.net

                1 Reply Last reply Reply Quote 0
                • First post
                  Last post