|
@@ -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: |
|
|