TomWhitwell / SlowMovie

MIT License
342 stars 67 forks source link

Enhancement: collaborate to create a SlowMovie plugin for PaperPi #113

Open txoof opened 2 years ago

txoof commented 2 years ago

Describe the problem Not so much a problem as an idea. I maintain PaperPi and received a few suggestions to add a "slow movie player" to the display loop.

Describe the solution you'd like I'd like to collaborate and see if we can roll SlowMovie into a plugin for PaperPi.

Describe alternatives you've considered I can, of course, pull your code and try to reverse engineer it into a plugin, though it might be more fun to do this as a collaboration and save me reinventing your wheel.

Thanks for making cool stuff and sharing it.

robweber commented 2 years ago

I took a quick look through the documentation of your demo plugin. Seems like each plugin provides it's internal logic to get whatever data it needs and then returns a series of key:value pairs that are fed into the layout file. The layout file determines how these should be displayed. Does that sound about right? For SlowMovie all that would really need to be returned is a path to the image file you want to display. The logic would get the next frame, render it, etc and then return it for your underlying program to handle the drawing.

I think pulling apart the code to do this is certainly doable, although I have a few follow up questions.

  1. The image would be updated as part of the normal PaperPi rotation correct? For example you'd see the weather, clock, etc and then the SlowMovie image?
  2. How does the data update process work? I didn't read through your main library but is this threaded with plugin as a "worker" or does it data get updated for each plugin as it is requested to show data? I guess specifically I'm thinking of SlowMovie and if each time the plugin is called is it going to run the full "grab frame" process or will that process run in the background and PaperPi is simply asking for the result? Each of these might make a difference in terms of how the plugin is written. If a background worker isn't used it might be better to include some logic around "should I update" and return the location to the last image if the answer is no. If a worker is used it could simply chug along and render images based on the timer method we currently use.
  3. How do you handle dependencies for plugins? ffmpeg specifically is a major dependency. Does each plugin include a list of requirements or do you roll them all up into the main PaperPi project dependency list?
txoof commented 2 years ago

@robweber Thanks for the quick reply and all the research you did. I hope you found the documentation readable.

You've got the basics correct with regards to how plugins generate content for the layouts and are eventually displayed. SlowMovie would just need to generate the frames and deliver them to the PaperPi plugin on demand.

To answer your questions:

Question 1. Yes, The SlowMovie image would display in the rotation a. one screen of weather b. one screen of clock c. one screen of SlowMovie

Question 2: I'm going to struggle to answer this precisely because this is very much my hacked out solution and I'm only vaguely familiar with the terms "worker" and "background worker."

Here's the basic logic:

Some plugins, such as the Spotify and Logitech Media Server plugins can "take over" when music is playing. None of the other plugins get to display, but are still updated in the background so they have data when the music stops.

I'm not sure which approach would be best within the existing structure of SlowMovie. My initial thought would be that the plugin would connect with SlowMovie and ask for a frame to be rendered on demand. Letting SlowMovie chug along in the background churning out frames, would be pretty hard to sync with the actual display rate because that is user-configurable and can be changed. SlowMovie might also get badly out of sync and end up just churning out too many or too few frames.

Question 3: Dependencies are a bit of a nightmare right now. I wanted to make this a "simple" install for beginner users so I distribute PaperPi as a PyInstaller frozen blob that's executable. Everything is included in the blob and it's getting a bit enormous. To deal with this I've been trying to figure out how to split out the plugins and come up with a better method for installing them and their dependencies. I'm afraid that I'm running into the edge of my skill set on this front.

As I write this, I think the best way to build this would be to require a SlowMovie to be installed separately and then connect to it from the plugin and request frame N or something similar. Users that want SlowMovie can follow your excellent guides and then install the plugin independently.

robweber commented 2 years ago

Thanks for the detailed answers.

It sounds like there would be two different "timers" going on with a PaperPi plugin integration. You'd have the normal SlowMovie timer (which could be configured via your PaperPi settings) that would tell it to update X number of frames every X number of seconds. Then there would be the PaperPi timer that controls how long it would display each individual plugin through it's rotation. Based on your explanation above that would allow PaperPi to ask for the next image as it rotates through the plugins and SlowMovie would pull new frames based on it's settings. (example: each plugins displays for 30 seconds but SlowMovie only updates the video image every 10 minutes - the same image might get displayed 4-5 times depending on how many plugins PaperPi is running). Does that kind of sound like how things would work from your end?

I agree it might be easiest to keep the SlowMovie install as it's own thing and then enabling the PaperPi plugin would just call some SlowMovie methods. In order for this to work properly I think we'd have to refactor a few things on this side. Namely we'd want to break up the frame grabbing from the EPD code into some definitive classes. Right now the slowmovie.py file has a lot of the code for this intermingled. If we could break this out into a class that basically takes the video file and parameters it could be called from other classes via an import.

What type of object to you expect for an image in PaperPi? In the layout.py file for the demo project it makes reference to an ImageBlock, which appears to be a custom class. If we could return either a Pillow Image or a path to generated image is that something you could use? Trying to think of a way to marry the two without having code here depend too much on pieces on your end and vice versa. I'm imagining something like the following on the PaperPi side:

(pseudo-code here)

import slowmovie

need_update = False

# use some logic to determine if we need an update, if X number of seconds have passed
need_update = check_update()

if(need_update):
  s = SlowMovie(filename, num_frames, other_options......)
  # returns Image object or path to one
  image = s.get_frame()
else:
  image = last_used_image  # assume the last used image is saved in memory or on disk

return image

Would something like that work? I could start a fork to refactor some things on our side. Basically just moving some code blocks around to get them in their own classes. That way we could keep all of our EPD and timer code separate.

txoof commented 2 years ago

Your proposed approach sounds pretty reasonable. Ideally PaperPi just asks for the next active frame based on its internal logic and SlowMovie generates either a path or a PIL object. PaperPi can work with either.

I think there are two interesting ways to approach this:

  1. SlowMovie runs as a daemon and generates images bases on its config and then makes the "current" frame available either in a specific path or via a method call. SlowMovie doesn't display anything on its own, but leaves that up to PaperPi. PaperPi plugin asks for the "current" image and displays it.
    • This means PaperaPi could show the same image multiple times for 5-10 minutes (or whatever). This feels in the original spirit of the SlowMovie project
    • It's also likely that some frames will be skipped all together in some circumstances such as if you are listening to music and the Spotify plugin has taken over the display.
  2. SlowMovie runs only when called from the plugin and pulls frame N as specified by the plugin. The plugin keeps track of the current frame internally.
    • The plugin could be configured to pull frames at a specific rate, say once every refresh, every minute, 10, etc.
    • I'm not sure how it will keep track of the frame number between restarts, but that's tomorrow Aaron's problem.

Which one sounds most logical or in the spirit of the project to you?

I'm a little concerned with how to integrate SlowMivie as an import. I just started to learn about appending to the module search paths, but I don't quite have a handle on that yet.

robweber commented 2 years ago

If you want SlowMovie to just sort of chug away on it's own a simple solution might just be to run it with the omni_epd.mock device driver. This is a virtual driver that sends nothing to the display, but instead writes an image to a file instead. You could setup SlowMovie to run as normal, but using that fake device for output. The PaperPi plugin could just have an option for specifying the path to that file.

The only downside to this approach would be that control of SlowMovie functions (video selection, frame updates, etc) would all have to happen outside of your PaperPi install. You wouldn't be able to pass anything on from your plugin to SlowMovie itself. Since SlowMovie does allow configuration via a .conf type file though it would be simple for users to setup. It would involve basically no changes on our end though, you could get this going immediately.

Here is a link to the driver specific options for the EPD control. The options for the mock device are listed.

txoof commented 2 years ago

That's a pretty great place to start. Let me hack out proof of concept using that option first and see how it turns out. This option would obviously be the lowest overhead for you all. Using this solution, some frames will likely get skipped, but I don't think users will "feel" the missed frames unless the rate is unreasonably fast for the movie.

robweber commented 2 years ago

Great, glad this will get you started.

I definitely agree this could result in some frames not displaying but the end user will have to play with that. If they set SlowMovie to update every 30 seconds but PaperPi is switching plugins every 2 min, as an example, they're going to miss something. On the other hand most people probably miss many frames as it's meant to show the video slowly over time, you're probably not looking every time it updates.