@@ -1,5 +1,6 @@ | |||
import subprocess | |||
from ctypes import * | |||
from ctypes import ARRAY # type: ignore | |||
from ctypes import POINTER, byref, c_int, c_ubyte, cast, cdll | |||
class Aques: | |||
@@ -5,4 +5,3 @@ OPENAI_ORGANISATION: str = 'org-XXXXXXXXXXXXXXXXXXXXXXXX' | |||
# API Key | |||
OPENAI_API_KEY: str = 'sk-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' | |||
@@ -1,82 +0,0 @@ | |||
Traceback (most recent call last): | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_exceptions.py", line 10, in map_exceptions | |||
yield | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_backends/sync.py", line 126, in read | |||
return self._sock.recv(max_bytes) | |||
File "/usr/lib/python3.10/ssl.py", line 1288, in recv | |||
return self.read(buflen) | |||
File "/usr/lib/python3.10/ssl.py", line 1161, in read | |||
return self._sslobj.read(len) | |||
TimeoutError: The read operation timed out | |||
The above exception was the direct cause of the following exception: | |||
Traceback (most recent call last): | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_transports/default.py", line 66, in map_httpcore_exceptions | |||
yield | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_transports/default.py", line 228, in handle_request | |||
resp = self._pool.handle_request(req) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/connection_pool.py", line 268, in handle_request | |||
raise exc | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/connection_pool.py", line 251, in handle_request | |||
response = connection.handle_request(request) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/connection.py", line 103, in handle_request | |||
return self._connection.handle_request(request) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/http2.py", line 185, in handle_request | |||
raise exc | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/http2.py", line 148, in handle_request | |||
status, headers = self._receive_response( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/http2.py", line 292, in _receive_response | |||
event = self._receive_stream_event(request, stream_id) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/http2.py", line 333, in _receive_stream_event | |||
self._receive_events(request, stream_id) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/http2.py", line 361, in _receive_events | |||
events = self._read_incoming_data(request) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/http2.py", line 452, in _read_incoming_data | |||
raise exc | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_sync/http2.py", line 438, in _read_incoming_data | |||
data = self._network_stream.read(self.READ_NUM_BYTES, timeout) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_backends/sync.py", line 124, in read | |||
with map_exceptions(exc_map): | |||
File "/usr/lib/python3.10/contextlib.py", line 153, in __exit__ | |||
self.gen.throw(typ, value, traceback) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpcore/_exceptions.py", line 14, in map_exceptions | |||
raise to_exc(exc) from exc | |||
httpcore.ReadTimeout: The read operation timed out | |||
The above exception was the direct cause of the following exception: | |||
Traceback (most recent call last): | |||
File "/home/miteruzo/Downloads/nizika_broadcast/main.py", line 117, in <module> | |||
Main.main () | |||
File "/home/miteruzo/Downloads/nizika_broadcast/main.py", line 106, in main | |||
live_chat = pytchat.create (video_id = YOUTUBE_ID) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/pytchat/core/__init__.py", line 7, in create | |||
return PytchatCore(_vid, **kwargs) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/pytchat/core/pytchat.py", line 96, in __init__ | |||
self._setup() | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/pytchat/core/pytchat.py", line 106, in _setup | |||
channel_id=util.get_channelid(self._client, self._video_id), | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/pytchat/util/__init__.py", line 103, in get_channelid | |||
resp = client.get("https://www.youtube.com/embed/{}".format(quote(video_id)), headers=config.headers) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_client.py", line 1041, in get | |||
return self.request( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_client.py", line 814, in request | |||
return self.send(request, auth=auth, follow_redirects=follow_redirects) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_client.py", line 901, in send | |||
response = self._send_handling_auth( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_client.py", line 929, in _send_handling_auth | |||
response = self._send_handling_redirects( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_client.py", line 966, in _send_handling_redirects | |||
response = self._send_single_request(request) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_client.py", line 1002, in _send_single_request | |||
response = transport.handle_request(request) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_transports/default.py", line 227, in handle_request | |||
with map_httpcore_exceptions(): | |||
File "/usr/lib/python3.10/contextlib.py", line 153, in __exit__ | |||
self.gen.throw(typ, value, traceback) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/httpx/_transports/default.py", line 83, in map_httpcore_exceptions | |||
raise mapped_exc(message) from exc | |||
httpx.ReadTimeout: The read operation timed out | |||
@@ -1,30 +0,0 @@ | |||
Traceback (most recent call last): | |||
File "/home/miteruzo/Downloads/nizika_broadcast/main.py", line 136, in <module> | |||
Main.main ((len (sys.argv) > 1) and (sys.argv[1] == '-g')) | |||
File "/home/miteruzo/Downloads/nizika_broadcast/main.py", line 60, in main | |||
answer: str = Talk.main (message, chat_item.author['name'], histories, goatoh_mode).replace ('\n', ' ') | |||
File "/home/miteruzo/Downloads/nizika_broadcast/talk.py", line 23, in main | |||
= cls.__get_message (message, name, histories, goatoh_mode) | |||
File "/home/miteruzo/Downloads/nizika_broadcast/talk.py", line 196, in __get_message | |||
return openai.chat.completions.create ( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/_utils/_utils.py", line 301, in wrapper | |||
return func(*args, **kwargs) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/resources/chat/completions.py", line 598, in create | |||
return self._post( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/_base_client.py", line 1096, in post | |||
return cast(ResponseT, self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)) | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/_base_client.py", line 856, in request | |||
return self._request( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/_base_client.py", line 894, in _request | |||
return self._retry_request( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/_base_client.py", line 966, in _retry_request | |||
return self._request( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/_base_client.py", line 894, in _request | |||
return self._retry_request( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/_base_client.py", line 966, in _retry_request | |||
return self._request( | |||
File "/home/miteruzo/.local/lib/python3.10/site-packages/openai/_base_client.py", line 908, in _request | |||
raise self._make_status_error_from_response(err.response) from None | |||
openai.InternalServerError: Error code: 500 - {'error': {'message': 'The server had an error processing your request. Sorry about that! You can retry your request, or contact us through our help center at help.openai.com if you keep seeing this error. (Please include the request ID req_ca732fde8fe201933c96c403d44db7e5 in your email.)', 'type': 'server_error', 'param': None, 'code': None}} | |||
@@ -9,19 +9,20 @@ import time | |||
from datetime import datetime, timedelta | |||
import emoji | |||
import ephem | |||
import ephem # type: ignore | |||
import pygame | |||
import pygame.gfxdraw | |||
import pytchat | |||
import pytchat # type: ignore | |||
from playsound import playsound | |||
from pygame.locals import * | |||
from youtube import * | |||
import play_movie | |||
from aques import Aques | |||
from common_const import * | |||
from common_module import CommonModule | |||
from mode import Mode | |||
from talk import Talk | |||
from youtube import * | |||
class Main: | |||
@@ -39,7 +40,7 @@ class Main: | |||
argv: list, | |||
argc: int) \ | |||
-> None: | |||
mode = Mode.NIZIKA | |||
mode: Mode = Mode.NIZIKA | |||
match (argc > 1) and argv[1]: | |||
case '-g': | |||
mode = Mode.GOATOH | |||
@@ -108,6 +109,12 @@ class Main: | |||
# ニジカの “ぬ゛ぅ゛ぅ゛ぅ゛ん゛” | |||
noon = pygame.mixer.Sound ('noon.wav') | |||
# “あっ!” | |||
deerjika_oh = pygame.mixer.Sound ('oh.wav') | |||
# おやつタイムのテーマ | |||
snack_time_sound = pygame.mixer.Sound ('snack_time.wav') | |||
# ゴートうの “ムムムム” | |||
mumumumu = pygame.mixer.Sound ('mumumumu.wav') | |||
@@ -132,9 +139,12 @@ class Main: | |||
# 会話の履歴 | |||
histories: list = [] | |||
# おやつ記録 | |||
has_snack = False | |||
while True: | |||
# 観測地の日づけ更新 | |||
observer.date: datetime = datetime.now ().date () | |||
observer.date = datetime.now ().date () | |||
# 日の出開始 | |||
sunrise_start: datetime = ( | |||
@@ -189,7 +199,7 @@ class Main: | |||
# Chat オブジェクトが有効 | |||
# Chat 取得 | |||
chat_items: list = live_chat.get ().items | |||
chat_items = live_chat.get ().items | |||
if chat_items: | |||
# 溜まってゐる Chat からランダムに 1 つ抽出 | |||
@@ -208,7 +218,7 @@ class Main: | |||
if goatoh_mode: | |||
goatoh_talking = True | |||
if double_mode: | |||
goatoh_talking: bool = random.random () < .5 | |||
goatoh_talking = random.random () < .5 | |||
while True: | |||
# ChatGPT API を呼出し,返答を取得 | |||
@@ -225,54 +235,9 @@ class Main: | |||
+ f'{json.dumps (chat_item.__dict__)}\t' | |||
+ f'{answer}\n') | |||
# 吹出し描画(ニジカは上,ゴートうは下) | |||
if nizika_mode: | |||
screen.blit (balloon, (0, 0)) | |||
if goatoh_mode: | |||
screen.blit (balloon, (0, 384)) | |||
if double_mode: | |||
screen.blit (pygame.transform.flip ( | |||
balloon, | |||
not goatoh_talking, | |||
False), | |||
(0, 0)) | |||
# 視聴者コメント描画 | |||
screen.blit ( | |||
user_font.render ( | |||
'> ' + (message | |||
if (CommonModule.len_by_full ( | |||
message) | |||
<= 21) | |||
else (CommonModule.mid_by_full ( | |||
message, 0, 19.5) | |||
+ '...')), | |||
True, | |||
(0, 0, 0)), | |||
(120, 70 + 384) if goatoh_mode else (120 + (64 if (double_mode and not goatoh_talking) else 0), 70)) | |||
# ニジカの返答描画 | |||
screen.blit ( | |||
nizika_font.render ( | |||
(answer | |||
if CommonModule.len_by_full (answer) <= 16 | |||
else CommonModule.mid_by_full (answer, 0, 16)), | |||
True, | |||
(192, 0, 0)), | |||
(100, 150 + 384) if goatoh_mode else (100 + (64 if (double_mode and not goatoh_talking) else 0), 150)) | |||
if CommonModule.len_by_full (answer) > 16: | |||
screen.blit ( | |||
nizika_font.render ( | |||
(CommonModule.mid_by_full (answer, 16, 16) | |||
if CommonModule.len_by_full (answer) <= 32 | |||
else (CommonModule.mid_by_full ( | |||
answer, 16, 14.5) | |||
+ '...')), | |||
True, | |||
(192, 0, 0)), | |||
(100, 200 + 384) if goatoh_mode else (100 + (64 if (double_mode and not goatoh_talking) else 0), 200)) | |||
pygame.display.update () | |||
cls.draw_talking (screen, balloon, user_font, nizika_font, | |||
message, answer, mode, | |||
mode == 3 and goatoh_talking) | |||
# 鳴く. | |||
if goatoh_talking: | |||
@@ -286,10 +251,11 @@ class Main: | |||
time.sleep (1.5) | |||
# 返答の読上げを WAV ディタとして生成,取得 | |||
wav: bytearray | None | |||
try: | |||
wav: bytearray | None = Aques.main (answer, goatoh_talking) | |||
wav = Aques.main (answer, goatoh_talking) | |||
except: | |||
wav: None = None | |||
wav = None | |||
# 読上げを再生 | |||
if wav is not None: | |||
@@ -324,13 +290,93 @@ class Main: | |||
# 再生成 | |||
live_chat = pytchat.create (video_id = YOUTUBE_ID) | |||
if has_snack and datetime.now ().hour == 14: | |||
has_snack = False | |||
pygame.display.update () | |||
if (not has_snack) and datetime.now ().hour == 15: | |||
has_snack = True | |||
deerjika_oh.play () | |||
time.sleep (0.6) | |||
snack_time_sound.play () | |||
play_movie.main (screen, 'snack_time.mp4') | |||
query = 'おやつタイムだ!!!!' | |||
cls.draw_bg (screen, bg_day, bg_evening, bg_night, bg_grass, | |||
kita, jojoko, | |||
sunrise_start, sunrise_end, sunset_start, sunset_end, | |||
sun_alt, sun_az, moon_alt, moon_az, moon_days_old) | |||
cls.draw_talking (screen, balloon, user_font, nizika_font, | |||
query, Talk.main (query).replace ('\n', ' ')) | |||
noon.play () | |||
time.sleep (1.5) | |||
for event in pygame.event.get (): | |||
if event.type == QUIT: | |||
pygame.quit () | |||
sys.exit () | |||
@staticmethod | |||
def draw_talking ( | |||
screen: pygame.Surface, | |||
balloon: pygame.Surface, | |||
user_font: pygame.font.Font, | |||
nizika_font: pygame.font.Font, | |||
query: str, | |||
answer: str, | |||
mode: Mode = Mode.NIZIKA, | |||
flip: bool = False, | |||
) -> None: | |||
# 吹出し描画(ニジカは上,ゴートうは下) | |||
nizika_mode = False | |||
goatoh_mode = False | |||
double_mode = False | |||
match mode: | |||
case Mode.NIZIKA: | |||
screen.blit (balloon, (0, 0)) | |||
nizika_mode = True | |||
case Mode.GOATOH: | |||
screen.blit (balloon, (0, 384)) | |||
goatoh_mode = True | |||
case Mode.DOUBLE: | |||
screen.blit (pygame.transform.flip (balloon, flip, False), (0, 0)) | |||
double_mode = True | |||
# 視聴者コメント描画 | |||
screen.blit ( | |||
user_font.render ( | |||
('> ' + (query | |||
if (CommonModule.len_by_full (query) <= 21) | |||
else (CommonModule.mid_by_full (query, 0, 19.5) + '...'))), | |||
True, (0, 0, 0)), | |||
((120, 70 + 384) | |||
if goatoh_mode | |||
else (120 + (64 if (double_mode and flip) else 0), 70))) | |||
# ニジカの返答描画 | |||
screen.blit ( | |||
nizika_font.render ( | |||
(answer | |||
if CommonModule.len_by_full (answer) <= 16 | |||
else CommonModule.mid_by_full (answer, 0, 16)), | |||
True, | |||
(192, 0, 0)), | |||
(100, 150 + 384) if goatoh_mode else (100 + (64 if (double_mode and flip) else 0), 150)) | |||
if CommonModule.len_by_full (answer) > 16: | |||
screen.blit ( | |||
nizika_font.render ( | |||
(CommonModule.mid_by_full (answer, 16, 16) | |||
if CommonModule.len_by_full (answer) <= 32 | |||
else (CommonModule.mid_by_full ( | |||
answer, 16, 14.5) | |||
+ '...')), | |||
True, | |||
(192, 0, 0)), | |||
(100, 200 + 384) if goatoh_mode else (100 + (64 if (double_mode and flip) else 0), 200)) | |||
pygame.display.update () | |||
@classmethod | |||
def draw_bg ( | |||
cls, | |||
@@ -349,8 +395,8 @@ class Main: | |||
sun_az: float, | |||
moon_alt: float, | |||
moon_az: float, | |||
moon_days_old: float) \ | |||
-> None: | |||
moon_days_old: float, | |||
) -> None: | |||
sunrise_centre: datetime = ( | |||
sunrise_start + (sunrise_end - sunrise_start) / 2) | |||
sunset_centre: datetime = ( | |||
@@ -379,16 +425,16 @@ class Main: | |||
screen.blit (bg_night, (0, 0)) | |||
if sunrise_start <= dt < sunrise_end: | |||
bg_evening.set_alpha (255 - ((abs (dt - sunrise_centre) * 510) | |||
/ (sunrise_end - sunrise_centre))) | |||
bg_evening.set_alpha (255 - int ((abs (dt - sunrise_centre) * 510) | |||
/ (sunrise_end - sunrise_centre))) | |||
elif sunset_start <= dt < sunset_end: | |||
bg_evening.set_alpha (255 - ((abs (dt - sunset_centre) * 510) | |||
/ (sunset_end - sunset_centre))) | |||
bg_evening.set_alpha (255 - int ((abs (dt - sunset_centre) * 510) | |||
/ (sunset_end - sunset_centre))) | |||
else: | |||
bg_evening.set_alpha (0) | |||
if sunrise_start <= dt < sunset_end: | |||
jojoko.set_alpha (255 - 255 / 15 * abs (moon_days_old - 15)) | |||
jojoko.set_alpha (255 - int (255 / 15 * abs (moon_days_old - 15))) | |||
else: | |||
jojoko.set_alpha (255) | |||
@@ -0,0 +1,44 @@ | |||
import cv2 | |||
import pygame | |||
import sys | |||
def main ( | |||
screen: pygame.Surface, | |||
video_path: str, | |||
) -> None: | |||
# OpenCV で動画を読み込む | |||
cap = cv2.VideoCapture (video_path) | |||
if not cap.isOpened (): | |||
return | |||
# screen の幅、高さ | |||
(width, height) = screen.get_size () | |||
fps = cap.get (cv2.CAP_PROP_FPS) | |||
clock = pygame.time.Clock () | |||
while cap.isOpened (): | |||
# 動画のフレームを読み込む | |||
(ret, frame) = cap.read () | |||
if not ret: | |||
break | |||
# OpenCV の BGR フォーマットを RGB に変換 | |||
frame = cv2.cvtColor (frame, cv2.COLOR_BGR2RGB) | |||
# Numpy 配列を Pygame サーフェスに変換 | |||
frame_surface = pygame.surfarray.make_surface (frame) | |||
frame_surface = pygame.transform.rotate (frame_surface, -90) | |||
frame_surface = pygame.transform.flip (frame_surface, True, False) | |||
frame_surface = pygame.transform.scale (frame_surface, (width, height)) | |||
# フレームを描画 | |||
screen.blit (frame_surface, (0, 0)) | |||
pygame.display.update () | |||
# FPS に応じて待機 | |||
clock.tick (fps) | |||
cap.release () |
@@ -15,7 +15,7 @@ from openai.types.chat import (ChatCompletionAssistantMessageParam, | |||
ChatCompletionUserMessageParam) | |||
from openai.types.chat.chat_completion_message import ChatCompletionMessage | |||
from .connection import OPENAI_API_KEY, OPENAI_ORGANISATION # type: ignore | |||
from connection import OPENAI_API_KEY, OPENAI_ORGANISATION # type: ignore | |||
class Talk: | |||