| @@ -8,16 +8,19 @@ import wave | |||||
| from datetime import datetime, timedelta | from datetime import datetime, timedelta | ||||
| from enum import Enum, auto | from enum import Enum, auto | ||||
| from typing import Callable, TypedDict | from typing import Callable, TypedDict | ||||
| from io import BytesIO | |||||
| import cv2 | import cv2 | ||||
| import emoji | import emoji | ||||
| import ephem | import ephem | ||||
| import numpy as np | |||||
| import pygame | import pygame | ||||
| import pygame.gfxdraw | import pygame.gfxdraw | ||||
| import pytchat | import pytchat | ||||
| import requests | import requests | ||||
| from cv2 import VideoCapture | from cv2 import VideoCapture | ||||
| from ephem import Moon, Observer, Sun | from ephem import Moon, Observer, Sun | ||||
| from pydub import AudioSegment | |||||
| from pygame import Rect, Surface | from pygame import Rect, Surface | ||||
| from pygame.font import Font | from pygame.font import Font | ||||
| from pygame.mixer import Sound | from pygame.mixer import Sound | ||||
| @@ -50,6 +53,7 @@ def main ( | |||||
| x = CWindow.WIDTH * 3 / 4, | x = CWindow.WIDTH * 3 / 4, | ||||
| y = CWindow.HEIGHT - 120, | y = CWindow.HEIGHT - 120, | ||||
| balloon = balloon) | balloon = balloon) | ||||
| Video (game, 'snack_time.mp4').play () | |||||
| CurrentTime (game, SYSTEM_FONT) | CurrentTime (game, SYSTEM_FONT) | ||||
| try: | try: | ||||
| broadcast = Broadcast (os.environ['BROADCAST_CODE']) | broadcast = Broadcast (os.environ['BROADCAST_CODE']) | ||||
| @@ -639,16 +643,6 @@ class Jojoko (GameObject): | |||||
| def redraw ( | def redraw ( | ||||
| self, | self, | ||||
| ) -> None: | |||||
| if self.frame % (FPS * 3600) == 0: | |||||
| self.surface = self._get_surface () | |||||
| surface = pygame.transform.rotate (self.surface, -(90 + math.degrees (self.arg))) | |||||
| surface.set_colorkey ((0, 255, 0)) | |||||
| self.game.screen.blit (surface, surface.get_rect (center = (self.x, self.y))) | |||||
| super ().redraw () | |||||
| def update ( | |||||
| self, | |||||
| ) -> None: | ) -> None: | ||||
| self.moon.compute (self.game.sky.observer) | self.moon.compute (self.game.sky.observer) | ||||
| self.alt = self.moon.alt | self.alt = self.moon.alt | ||||
| @@ -657,7 +651,12 @@ class Jojoko (GameObject): | |||||
| self.arg = self.new_arg | self.arg = self.new_arg | ||||
| self.x = self.new_x | self.x = self.new_x | ||||
| self.y = self.new_y | self.y = self.new_y | ||||
| super ().update () | |||||
| if self.frame % (FPS * 3600) == 0: | |||||
| self.surface = self._get_surface () | |||||
| surface = pygame.transform.rotate (self.surface, -(90 + math.degrees (self.arg))) | |||||
| surface.set_colorkey ((0, 255, 0)) | |||||
| self.game.screen.blit (surface, surface.get_rect (center = (self.x, self.y))) | |||||
| super ().redraw () | |||||
| @property | @property | ||||
| def phase ( | def phase ( | ||||
| @@ -732,30 +731,6 @@ class CWindow: | |||||
| HEIGHT = 768 | HEIGHT = 768 | ||||
| def get_surfaces_from_video ( | |||||
| video_path: str, | |||||
| ) -> list[Surface]: | |||||
| cap = VideoCapture (video_path) | |||||
| if not cap.isOpened (): | |||||
| return [] | |||||
| fps = cap.get (cv2.CAP_PROP_FPS) | |||||
| surfaces: list[Surface] = [] | |||||
| while cap.isOpened (): | |||||
| (ret, frame) = cap.read () | |||||
| if not ret: | |||||
| break | |||||
| frame = cv2.cvtColor (frame, cv2.COLOR_BGR2RGB) | |||||
| frame_surface = pygame.surfarray.make_surface (frame) | |||||
| frame_surface = pygame.transform.rotate (frame_surface, -90) | |||||
| surfaces.append (pygame.transform.flip (frame_surface, True, False)) | |||||
| cap.release () | |||||
| return surfaces | |||||
| class Broadcast: | class Broadcast: | ||||
| chat: PytchatCore | chat: PytchatCore | ||||
| @@ -777,6 +752,126 @@ class Broadcast: | |||||
| return random.choice (chats) | return random.choice (chats) | ||||
| class NicoVideo (Video): | |||||
| ... | |||||
| class Video (GameObject): | |||||
| fps: int | |||||
| pausing: bool = False | |||||
| sound: Sound | |||||
| surfaces: list[Surface] | |||||
| def __init__ ( | |||||
| self, | |||||
| game: Game, | |||||
| path: str, | |||||
| ): | |||||
| super ().__init__ (game) | |||||
| self.pausing = False | |||||
| (self.surfaces, self.fps) = self._create_surfaces (path) | |||||
| self.sound = self._create_sound (path) | |||||
| self.stop () | |||||
| def _create_sound ( | |||||
| self, | |||||
| path: str, | |||||
| ) -> Sound: | |||||
| bytes_io = BytesIO () | |||||
| audio = AudioSegment.from_file (path, format = path.split ('.')[-1]) | |||||
| audio.export (bytes_io, format = 'wav') | |||||
| bytes_io.seek (0) | |||||
| return pygame.mixer.Sound (bytes_io) | |||||
| def _create_surfaces ( | |||||
| self, | |||||
| path: str, | |||||
| ) -> tuple[list[Surface], int]: | |||||
| cap = self._load (path) | |||||
| surfaces: list[Surface] = [] | |||||
| if cap is None: | |||||
| return ([], FPS) | |||||
| fps = int (cap.get (cv2.CAP_PROP_FPS)) | |||||
| while cap.isOpened (): | |||||
| frame = self._read_frame (cap) | |||||
| if frame is None: | |||||
| break | |||||
| surfaces.append (self._convert_to_surface (frame)) | |||||
| new_surfaces: list[Surface] = [] | |||||
| for i in range (len (surfaces) * FPS // fps): | |||||
| new_surfaces.append (surfaces[i * fps // FPS]) | |||||
| return (new_surfaces, fps) | |||||
| def _load ( | |||||
| self, | |||||
| path: str, | |||||
| ) -> VideoCapture | None: | |||||
| """ | |||||
| OpenCV で動画を読込む. | |||||
| """ | |||||
| cap = VideoCapture (path) | |||||
| if cap.isOpened (): | |||||
| return cap | |||||
| return None | |||||
| def _read_frame ( | |||||
| self, | |||||
| cap: VideoCapture, | |||||
| ) -> np.ndarray | None: | |||||
| """ | |||||
| 動画のフレームを読込む. | |||||
| """ | |||||
| ret: bool | |||||
| frame: np.ndarray | |||||
| (ret, frame) = cap.read () | |||||
| if ret: | |||||
| return frame | |||||
| return None | |||||
| def _convert_to_surface ( | |||||
| self, | |||||
| frame: np.ndarray, | |||||
| ) -> Surface: | |||||
| frame = cv2.cvtColor (frame, cv2.COLOR_BGR2RGB) | |||||
| frame_surface = pygame.surfarray.make_surface (frame) | |||||
| frame_surface = pygame.transform.rotate (frame_surface, -90) | |||||
| frame_surface = pygame.transform.flip (frame_surface, True, False) | |||||
| return frame_surface | |||||
| def play ( | |||||
| self, | |||||
| ) -> None: | |||||
| self.enabled = True | |||||
| self.pausing = False | |||||
| self.sound.play () | |||||
| def stop ( | |||||
| self, | |||||
| ) -> None: | |||||
| self.enabled = False | |||||
| self.frame = 0 | |||||
| def pause ( | |||||
| self, | |||||
| ) -> None: | |||||
| self.pausing = True | |||||
| def redraw ( | |||||
| self, | |||||
| ) -> None: | |||||
| self.game.screen.blit (self.surfaces[self.frame], (self.x, self.y)) | |||||
| super ().redraw () | |||||
| def update ( | |||||
| self, | |||||
| ) -> None: | |||||
| if self.frame >= len (self.surfaces) - 1: | |||||
| self.pause () | |||||
| if self.pausing: | |||||
| self.frame -= 1 | |||||
| super ().update () | |||||
| def fetch_bytes_from_url ( | def fetch_bytes_from_url ( | ||||
| url: str, | url: str, | ||||
| ) -> bytes | None: | ) -> bytes | None: | ||||