CampBuddy/Camp.Buddy v2.2.1/Camp_Buddy-2.2.1-pc/renpy/display/video.py
2025-03-03 23:00:33 +01:00

557 lines
16 KiB
Python

# Copyright 2004-2019 Tom Rothamel <pytom@bishoujo.us>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
from __future__ import print_function
import renpy.display
import renpy.audio
import collections
# The movie displayable that's currently being shown on the screen.
current_movie = None
# True if the movie that is currently displaying is in fullscreen mode,
# False if it's a smaller size.
fullscreen = False
# The size of a Movie object that hasn't had an explicit size set.
default_size = (400, 300)
# The file we allocated the surface for.
surface_file = None
# The surface to display the movie on, if not fullscreen.
surface = None
def movie_stop(clear=True, only_fullscreen=False):
"""
Stops the currently playing movie.
"""
if (not fullscreen) and only_fullscreen:
return
renpy.audio.music.stop(channel='movie')
def movie_start(filename, size=None, loops=0):
"""
This starts a movie playing.
"""
if renpy.game.less_updates:
return
global default_size
if size is not None:
default_size = size
filename = [ filename ]
if loops == -1:
loop = True
else:
loop = False
filename = filename * (loops + 1)
renpy.audio.music.play(filename, channel='movie', loop=loop)
movie_start_fullscreen = movie_start
movie_start_displayable = movie_start
# A map from a channel name to the movie texture that is being displayed
# on that channel.
texture = { }
# The set of channels that are being displayed in Movie objects.
displayable_channels = collections.defaultdict(list)
# A map from a channel to the topmost Movie being displayed on
# that channel. (Or None if no such movie exists.)
channel_movie = { }
# Is there a video being displayed fullscreen?
fullscreen = False
# Movie channels that had a hide operation since the last interaction took
# place.
reset_channels = set()
def early_interact():
"""
Called early in the interact process, to clear out the fullscreen
flag.
"""
displayable_channels.clear()
channel_movie.clear()
def interact():
"""
This is called each time the screen is drawn, and should return True
if the movie should display fullscreen.
"""
global fullscreen
for i in list(texture.keys()):
if not renpy.audio.music.get_playing(i):
del texture[i]
if renpy.audio.music.get_playing("movie"):
for i in displayable_channels.keys():
if i[0] == "movie":
fullscreen = False
break
else:
fullscreen = True
else:
fullscreen = False
return fullscreen
def get_movie_texture(channel, mask_channel=None, side_mask=False):
if not renpy.audio.music.get_playing(channel):
return None, False
c = renpy.audio.music.get_channel(channel)
surf = c.read_video()
if side_mask:
if surf is not None:
w, h = surf.get_size()
w //= 2
mask_surf = surf.subsurface((w, 0, w, h))
surf = surf.subsurface((0, 0, w, h))
else:
mask_surf = None
elif mask_channel:
mc = renpy.audio.music.get_channel(mask_channel)
mask_surf = mc.read_video()
else:
mask_surf = None
if mask_surf is not None:
# Something went wrong with the mask video.
if surf:
renpy.display.module.alpha_munge(mask_surf, surf, renpy.display.im.identity)
else:
surf = None
if surf is not None:
renpy.display.render.mutated_surface(surf)
tex = renpy.display.draw.load_texture(surf, True)
texture[channel] = tex
new = True
else:
tex = texture.get(channel, None)
new = False
return tex, new
def render_movie(channel, width, height):
tex, _new = get_movie_texture(channel)
if tex is None:
return None
sw, sh = tex.get_size()
scale = min(1.0 * width / sw, 1.0 * height / sh)
dw = scale * sw
dh = scale * sh
rv = renpy.display.render.Render(width, height)
rv.forward = renpy.display.render.Matrix2D(1.0 / scale, 0.0, 0.0, 1.0 / scale)
rv.reverse = renpy.display.render.Matrix2D(scale, 0.0, 0.0, scale)
rv.blit(tex, (int((width - dw) / 2), int((height - dh) / 2)))
return rv
def default_play_callback(old, new): # @UnusedVariable
renpy.audio.music.play(new._play, channel=new.channel, loop=new.loop, synchro_start=True)
if new.mask:
renpy.audio.music.play(new.mask, channel=new.mask_channel, loop=new.loop, synchro_start=True)
class Movie(renpy.display.core.Displayable):
"""
:doc: movie
This is a displayable that shows the current movie.
`fps`
The framerate that the movie should be shown at. (This is currently
ignored, but the parameter is kept for backwards compatibility.
The framerate is auto-detected.)
`size`
This should be specified as either a tuple giving the width and
height of the movie, or None to automatically adjust to the size
of the playing movie. (If None, the displayable will be (0, 0)
when the movie is not playing.)
`channel`
The audio channel associated with this movie. When a movie file
is played on that channel, it will be displayed in this Movie
displayable. If this is not given, and the `play` is provided,
a channel name is automatically selected.
`play`
If given, this should be the path to a movie file. The movie
file will be automatically played on `channel` when the Movie is
shown, and automatically stopped when the movie is hidden.
`side_mask`
If true, this tells Ren'Py to use the side-by-side mask mode for
the Movie. In this case, the movie is divided in half. The left
half is used for color information, while the right half is used
for alpha information. The width of the displayable is half the
width of the movie file.
Where possible, `side_mask` should be used over `mask` as it has
no chance of frames going out of sync.
`mask`
If given, this should be the path to a movie file that is used as
the alpha channel of this displayable. The movie file will be
automatically played on `movie_channel` when the Movie is shown,
and automatically stopped when the movie is hidden.
`mask_channel`
The channel the alpha mask video is played on. If not given,
defaults to `channel`\ _mask. (For example, if `channel` is "sprite",
`mask_channel` defaults to "sprite_mask".)
`start_image`
An image that is displayed when playback has started, but the
first frame has not yet been decoded.
`image`
An image that is displayed when `play` has been given, but the
file it refers to does not exist. (For example, this can be used
to create a slimmed-down mobile version that does not use movie
sprites.) Users can also choose to fall back to this image as a
preference if video is too taxing for their system. The image will
also be used if the video plays, and then the movie ends.
``play_callback``
If not None, a function that's used to start the movies playing.
(This may do things like queue a transition between sprites, if
desired.) It's called with the following arguments:
`old`
The old Movie object, or None if the movie is not playing.
`new`
The new Movie object.
A movie object has the `play` parameter available as ``_play``,
while the ``channel``, ``loop``, ``mask``, and ``mask_channel`` fields
correspond to the given parameters.
Generally, this will want to use :func:`renpy.music.play` to start
the movie playing on the given channel, with synchro_start=True.
A minimal implementation is::
def play_callback(old, new):
renpy.music.play(new._play, channel=new.channel, loop=new.loop, synchro_start=True)
if new.mask:
renpy.music.play(new.mask, channel=new.mask_channel, loop=new.loop, synchro_start=True)
`loop`
If False, the movie will not loop. If `image` is defined, the image
will be displayed when the movie ends. Otherwise, the movie will
become transparent.
This displayable will be transparent when the movie is not playing.
"""
fullscreen = False
channel = "movie"
_play = None
mask = None
mask_channel = None
side_mask = False
image = None
start_image = None
play_callback = None
loop = True
def ensure_channel(self, name):
if name is None:
return
if renpy.audio.music.channel_defined(name):
return
if self.mask:
framedrop = True
else:
framedrop = False
renpy.audio.music.register_channel(name, renpy.config.movie_mixer, loop=True, stop_on_mute=False, movie=True, framedrop=framedrop)
def __init__(self, fps=24, size=None, channel="movie", play=None, mask=None, mask_channel=None, image=None, play_callback=None, side_mask=False, loop=True, start_image=None, **properties):
super(Movie, self).__init__(**properties)
global auto_channel_serial
if channel == "movie" and play and renpy.config.auto_movie_channel:
channel = "movie_{}_{}".format(play, mask)
self.size = size
self.channel = channel
self._play = play
self.loop = loop
if side_mask:
mask = None
self.mask = mask
if mask is None:
self.mask_channel = None
elif mask_channel is None:
self.mask_channel = channel + "_mask"
else:
self.mask_channel = mask_channel
self.side_mask = side_mask
self.ensure_channel(self.channel)
self.ensure_channel(self.mask_channel)
self.image = renpy.easy.displayable_or_none(image)
self.start_image = renpy.easy.displayable_or_none(start_image)
self.play_callback = play_callback
if (self.channel == "movie") and (renpy.config.hw_video) and renpy.mobile:
raise Exception("Movie(channel='movie') doesn't work on mobile when config.hw_video is true. (Use a different channel argument.)")
def render(self, width, height, st, at):
if self._play and not (renpy.game.preferences.video_image_fallback is True):
channel_movie[self.channel] = self
if st == 0:
reset_channels.add(self.channel)
playing = renpy.audio.music.get_playing(self.channel)
not_playing = not playing
if self.channel in reset_channels:
not_playing = False
if (self.image is not None) and not_playing:
surf = renpy.display.render.render(self.image, width, height, st, at)
w, h = surf.get_size()
rv = renpy.display.render.Render(w, h)
rv.blit(surf, (0, 0))
return rv
if self.size is None:
tex, _ = get_movie_texture(self.channel, self.mask_channel, self.side_mask)
if (not not_playing) and (tex is not None):
width, height = tex.get_size()
rv = renpy.display.render.Render(width, height)
rv.blit(tex, (0, 0))
elif (not not_playing) and (self.start_image is not None):
surf = renpy.display.render.render(self.start_image, width, height, st, at)
w, h = surf.get_size()
rv = renpy.display.render.Render(w, h)
rv.blit(surf, (0, 0))
else:
rv = renpy.display.render.Render(0, 0)
else:
w, h = self.size
if not playing:
rv = None
else:
rv = render_movie(self.channel, w, h)
if rv is None:
rv = renpy.display.render.Render(w, h)
# Usually we get redrawn when the frame is ready - but we want
# the movie to disappear if it's ended, or if it hasn't started
# yet.
renpy.display.render.redraw(self, 0.1)
return rv
def play(self, old):
if old is None:
old_play = None
else:
old_play = old._play
if (self._play != old_play) or renpy.config.replay_movie_sprites:
if self._play:
if self.play_callback is not None:
self.play_callback(old, self)
else:
default_play_callback(old, self)
else:
renpy.audio.music.stop(channel=self.channel)
if self.mask:
renpy.audio.music.stop(channel=self.mask_channel)
def stop(self):
if self._play:
renpy.audio.music.stop(channel=self.channel)
if self.mask:
renpy.audio.music.stop(channel=self.mask_channel)
def per_interact(self):
displayable_channels[(self.channel, self.mask_channel)].append(self)
renpy.display.render.redraw(self, 0)
def visit(self):
return [ self.image, self.start_image ]
def playing():
if renpy.audio.music.get_playing("movie"):
return True
for i in displayable_channels:
channel, _mask_channel = i
if renpy.audio.music.get_playing(channel):
return True
return
def update_playing():
"""
Calls play/stop on Movie displayables.
"""
old_channel_movie = renpy.game.context().movie
for c, m in channel_movie.items():
old = old_channel_movie.get(c, None)
if (c in reset_channels) and renpy.config.replay_movie_sprites:
m.play(old)
elif old is not m:
m.play(old)
for c, m in old_channel_movie.items():
if c not in channel_movie:
m.stop()
renpy.game.context().movie = dict(channel_movie)
reset_channels.clear()
def frequent():
"""
Called to update the video playback. Returns true if a video refresh is
needed, false otherwise.
"""
update_playing()
renpy.audio.audio.advance_time()
if displayable_channels:
update = True
for i in displayable_channels:
channel, mask_channel = i
c = renpy.audio.audio.get_channel(channel)
if not c.video_ready():
update = False
break
if mask_channel:
c = renpy.audio.audio.get_channel(mask_channel)
if not c.video_ready():
update = False
break
if update:
for v in displayable_channels.values():
for j in v:
renpy.display.render.redraw(j, 0.0)
return False
elif fullscreen and not ((renpy.android or renpy.ios) and renpy.config.hw_video):
c = renpy.audio.audio.get_channel("movie")
if c.video_ready():
return True
else:
return False
return False