#34 動画再生可能に
このコミットが含まれているのは:
@@ -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:
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする