顔認識して天気を答える

顔認識して天気を答える

Weather map api で現在地の天気を取得し
Voicevox へ curl で送って音声を作成し
Pygame でしゃべるまではできたので

次にこれを関数にしてkao.pyで実行するようにする

まずはubuntuへ転送して実験する

pip install pygame
pip install --upgrade deepl

で足りないものを入れる

そして

scp weather_* snowpool@192.168.1.69:/home/snowpool/aw10s/

でコピーして

python weather_voice.py

を実行するとubuntu でもできた

次に、このweather_voide.pyのコードを関数にする

現在の天気を取得するコードは

import requests
import json
from datetime import datetime
import deepl

def get_weather_forecast(latitude, longitude, API_key, deepl_auth_key):
    # DeepL Translatorのインスタンスを生成
    translator = deepl.Translator(deepl_auth_key)

    # OpenWeather APIのURL
    url = "https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude=hourly,minutely&units=metric&lang=ja&appid={API_key}"
    url = url.format(lat=latitude, lon=longitude, API_key=API_key)

    # APIリクエスト
    response = requests.get(url)
    jsondata = response.json()

    # 今日の日付を取得
    today = datetime.now().date()

    # 今日の天気予報を探す
    for daily_forecast in jsondata["daily"]:
        date = datetime.fromtimestamp(daily_forecast["dt"]).date()
        if date == today:
            min_temp = daily_forecast["temp"]["min"]
            max_temp = daily_forecast["temp"]["max"]
            weather = daily_forecast["weather"][0]["main"]
            description = daily_forecast["weather"][0]["description"]
            break

    # 天気をdeeplで日本語に翻訳
    weather_japanese = translator.translate_text(weather, target_lang="JA").text

    # 今日の天気予報をまとめる
    today_weather_repo = f"今日の天気は{weather_japanese}、予想最高気温は{max_temp}度、予想最低気温は{min_temp}度です"
    return today_weather_repo

# 関数を使用して天気予報を取得
latitude = "緯度"
longitude = "経度"
API_key = "open weather map APIキー"
deepl_auth_key = "deepLのAPIキー"

weather_report = get_weather_forecast(latitude, longitude, API_key, deepl_auth_key)

# 天気予報をテキストファイルに保存
with open('weather.txt', 'w') as file:
    file.write(weather_report)

これは
weather_forecast.py
にまとめてある

次に
weather_voice.py
をほかで呼べるように関数にする

現状は

import subprocess
import pygame
import time
from weather_forecast import get_weather_forecast

# 天気予報を取得してファイルに保存
latitude = "緯度"
longitude = "経度"
API_key = "open weather map API キー"
deepl_auth_key = "deepLのAPIキー"

weather_report = get_weather_forecast(latitude, longitude, API_key, deepl_auth_key)

with open('weather.txt', 'w') as file:
    file.write(weather_report)

# JSONファイルを作成するためのcurlコマンド
command_json = [
    "curl", "-s", "-X", "POST",
    "192.168.1.69:50021/audio_query?speaker=1",
    "--get", "--data-urlencode", "text@weather.txt"
]

# 音声ファイルを作成するためのcurlコマンド
command_audio = [
    "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
    "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
]

# 最初のコマンドを実行してJSONファイルを作成
with open('query.json', 'w') as file:
    subprocess.run(command_json, stdout=file)

# 第二のコマンドを実行して音声ファイルを作成
with open('test_audio.wav', 'wb') as file:
    subprocess.run(command_audio, stdout=file)

# Pygameの初期化
pygame.init()
pygame.mixer.init()

# WAVファイルを読み込む
sound = pygame.mixer.Sound("test_audio.wav")

# 再生
sound.play()

# 再生が終了するまで待機
while pygame.mixer.get_busy():
    time.sleep(0.1)

これを関数にする

Chatgpt の答えは

import subprocess
import pygame
import time
from weather_forecast import get_weather_forecast

def generate_and_play_weather_report(latitude, longitude, API_key, deepl_auth_key):
    # 天気予報を取得してファイルに保存
    weather_report = get_weather_forecast(latitude, longitude, API_key, deepl_auth_key)
    with open('weather.txt', 'w') as file:
        file.write(weather_report)

    # JSONファイルを作成するためのcurlコマンド
    command_json = [
        "curl", "-s", "-X", "POST",
        "192.168.1.69:50021/audio_query?speaker=1",
        "--get", "--data-urlencode", "text@weather.txt"
    ]

    # 音声ファイルを作成するためのcurlコマンド
    command_audio = [
        "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
        "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
    ]

    # JSONファイルと音声ファイルを作成
    with open('query.json', 'w') as file:
        subprocess.run(command_json, stdout=file)
    with open('test_audio.wav', 'wb') as file:
        subprocess.run(command_audio, stdout=file)

    # Pygameで音声ファイルを再生
    pygame.init()
    pygame.mixer.init()
    sound = pygame.mixer.Sound("test_audio.wav")
    sound.play()
    while pygame.mixer.get_busy():
        time.sleep(0.1)

変更点を確認しておく

インポート部分は変化なし

import subprocess
import pygame
import time
from weather_forecast import get_weather_forecast


# 天気予報を取得してファイルに保存
latitude = "34.745755"
longitude = "137.91146"
API_key = "open weather map のAPIキー"
deepl_auth_key = "deepLのAPIキー"

の部分を削除しkao.pyへ移動させる

また

import weather_voice

をkao,oyへ追加

そしてprintぶんの下へ

weather_voice.generate_and_play_weather_report(latitude, longitude, API_key, deepl_auth_key)

を追記して保存

実験のため
VNC接続して実行

ubuntuで

tigervncserver -xstartup /usr/bin/gnome-session -geometry 800x600 -localhost no :1 

を実行後

Mac の場合
Finder から
移動 > サーバーに接続で

vnc://192.168.1.69:5901

で接続

これで実験すると顔認識した時に今日の天気を教えてくれるようになった

次は画面表示せずに処理するようにコードを書き換える

これは単純にishowの画面表示部分と
認識した部分を枠で囲む部分を削除するだけでOK

import cv2
import time

import weather_voice

# 天気予報を取得してファイルに保存
latitude = "緯度"
longitude = "経度"
API_key = "open weather map APIキー"
deepl_auth_key = "deepLのAPIキー"

# Haar Cascade分類器の読み込み
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

# Webカメラの設定
cap = cv2.VideoCapture(0)  # 0番目のカメラを使用する場合

# 最後の顔検出時刻
lastTime = None

# メインループ
while True:


    # カメラからのフレームの取得
    ret, frame = cap.read()
    
    # フレームのグレースケール化
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 顔の検出
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
    
    # 検出された顔に対する処理
    for (x, y, w, h) in faces:
        # 検出自の処理(検出から1分たったら再度イベント動かす
        if lastTime is None or time.perf_counter() - lastTime > 60:
            # 検出時刻更新
            lastTime = time.perf_counter()
            print("人間発見、警戒せよw")
            weather_voice.generate_and_play_weather_report(latitude, longitude, API_key, deepl_auth_key)
        
        #画像を表示する場合
        #cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 255, 0), 2)
        #roi_gray = gray[y:y+h, x:x+w]
        #roi_color = frame[y:y+h, x:x+w]
        # 以下は目もマークする場合
        # eyes = eye_cascade.detectMultiScale(roi_gray)
        # for (ex, ey, ew, eh) in eyes:
        #     cv2.rectangle(roi_color, (ex, ey), (ex+ew, ey+eh), (255, 0, 0), 2)

    
    # 結果の表示
    #cv2.imshow('Facial Feature Detection', frame)
    
    # 終了のキー入力
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# 後処理
cap.release()
cv2.destroyAllWindows()

とすればOK

あとは終了する方法が
Ctrl +c 以外にないので
他に終了させる方法があるか考える

それとスペック不足のためか
顔認識してから音声が出るまでに時間がかかる

とりあえず不要部分を削除したのが

import cv2
import time

import weather_voice

# 天気予報を取得してファイルに保存
latitude = "34.745755"
longitude = "137.91146"
API_key = "1082c12d65462d76f7dd1b7ef93c7849"
deepl_auth_key = "5f169e4d-3701-9eff-08fc-bf6065b64c8f:fx"

# Haar Cascade分類器の読み込み
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

# Webカメラの設定
cap = cv2.VideoCapture(0)  # 0番目のカメラを使用する場合

# 最後の顔検出時刻
lastTime = None

# メインループ
while True:


    # カメラからのフレームの取得
    ret, frame = cap.read()
    
    # フレームのグレースケール化
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 顔の検出
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
    
    # 検出された顔に対する処理
    for (x, y, w, h) in faces:
        # 検出自の処理(検出から1分たったら再度イベント動かす
        if lastTime is None or time.perf_counter() - lastTime > 60:
            # 検出時刻更新
            lastTime = time.perf_counter()
            print("今日の天気を顔認識したのでお知らせ")
            weather_voice.generate_and_play_weather_report(latitude, longitude, API_key, deepl_auth_key)
        

# 後処理
cap.release()
cv2.destroyAllWindows()

次にセキュリティのため

vim config.ini

でファイルを作り

[API_KEYS]
OPENWEATHER_API_KEY = open weather map API キー
DEEPL_AUTH_KEY = deepLのAPIキー

というようにしてキーを設定ファイルから読み込むようにする

なおキーを”” で囲むとバグるので注意

あとは
Pythonスクリプトでconfigparserモジュールを使用して設定ファイルから情報を読み込む

import configparser
# 設定ファイルを読み込む
config = configparser.ConfigParser()
config.read('config.ini')

# APIキーを取得
API_key = config['API_KEYS']['OPENWEATHER_API_KEY']
deepl_auth_key = config['API_KEYS']['DEEPL_AUTH_KEY']

これを使うようにコードを変更する

import cv2
import time

import configparser
import weather_voice

# 天気予報を取得してファイルに保存
latitude = "緯度"
longitude = "経度"

# 設定ファイルを読み込む
config = configparser.ConfigParser()
config.read('config.ini')


# APIキーを取得
API_key = config['API_KEYS']['OPENWEATHER_API_KEY']
deepl_auth_key = config['API_KEYS']['DEEPL_AUTH_KEY']

# Haar Cascade分類器の読み込み
face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('haarcascade_eye.xml')

# Webカメラの設定
cap = cv2.VideoCapture(0)  # 0番目のカメラを使用する場合

# 最後の顔検出時刻
lastTime = None

# メインループ
while True:


    # カメラからのフレームの取得
    ret, frame = cap.read()
    
    # フレームのグレースケール化
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    
    # 顔の検出
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
    
    # 検出された顔に対する処理
    for (x, y, w, h) in faces:
        # 検出自の処理(検出から1分たったら再度イベント動かす
        if lastTime is None or time.perf_counter() - lastTime > 60:
            # 検出時刻更新
            lastTime = time.perf_counter()
            print("今日の天気を顔認識したのでお知らせ")
            weather_voice.generate_and_play_weather_report(latitude, longitude, API_key, deepl_auth_key)
        

# 後処理
cap.release()
cv2.destroyAllWindows()

とりあえずこれで完成
どのみちバックグランドで動作させるので
停止方法は保留

あとは GitHub へコードをアップする

Pythonでコマンドラインの実行

Pythonでコマンドラインの実行

あとはコマンドラインをpythonで実行するメソッドが必要

curl -s -X POST "192.168.1.69:50021/audio_query?speaker=1" --get --data-urlencode text@weather.txt > query.json

でJSONファイルを作成

curl -s -H "Content-Type: application/json" -X POST -d @query.json "192.168.1.69:50021/synthesis?speaker=1" > test_audio.wav

で音声ファイルを作成

これを
Pythonで行うようにする

なおcurl の動作をrequest で行えるらしい

pip install requests simpleaudio

でインストール

Chatgpt で

curl -s -X POST "192.168.1.69:50021/audio_query?speaker=1" --get --data-urlencode text@weather.txt > query.json
でJSONファイルを作成

curl -s -H "Content-Type: application/json" -X POST -d @query.json "192.168.1.69:50021/synthesis?speaker=1" > test_audio.wav
で音声ファイルを作成

これらを python で行い
作成した音声ファイルを再生したい

で調べる

帰ってきた答えは

requests ライブラリを使用して、テキストファイルからJSONファイルを作成し、その後音声ファイルを作成します。次に、pydub や simpleaudio のようなライブラリを使用して音声ファイルを再生

とのこと

生成されたコードは

import requests
import json
import simpleaudio as sa

# テキストファイルを読み込む
with open('weather.txt', 'r') as file:
    text = file.read()

# 最初のPOSTリクエストを行い、JSONを取得
response = requests.post(
    "http://192.168.1.69:50021/audio_query?speaker=1",
    data={'text': text}
)
json_data = response.json()

# JSONデータをファイルに保存
with open('query.json', 'w') as json_file:
    json.dump(json_data, json_file)

# 第二のPOSTリクエストを行い、音声データを取得
response = requests.post(
    "http://192.168.1.69:50021/synthesis?speaker=1",
    json=json_data
)

# 取得した音声データをWAVファイルとして保存
with open('test_audio.wav', 'wb') as audio_file:
    audio_file.write(response.content)

# WAVファイルを読み込み、再生する
wave_obj = sa.WaveObject.from_wave_file('test_audio.wav')
play_obj = wave_obj.play()
play_obj.wait_done()  # 再生が終わるまで待機

これを実験で実行

しかしエラーとなる
原因を調べたら

ChatGPT
このエラーメッセージは、simpleaudio モジュールがあなたのシステムのアーキテクチャと互換性がないことを示しています。エラーによると、インストールされたsimpleaudioはx86_64アーキテクチャ用ですが、あなたのシステムはarm64アーキテクチャを必要

とあるため
pygame や pydub などを使う必要がある

pip install requests pygame

の後に

import requests
import json
import pygame

# テキストファイルの内容を読み込む
with open('weather.txt', 'r') as file:
    text = file.read()

# POSTリクエストでJSONデータを取得
response = requests.post(
    "http://192.168.1.69:50021/audio_query?speaker=1",
    data={'text': text}
)
json_data = response.json()

# JSONデータをファイルに保存
with open('query.json', 'w') as json_file:
    json.dump(json_data, json_file)

# JSONファイルを読み込む
with open('query.json', 'r') as json_file:
    json_payload = json.load(json_file)

# POSTリクエストで音声ファイルを取得
response = requests.post(
    "http://192.168.1.69:50021/synthesis?speaker=1",
    json=json_payload
)

# 音声データをファイルに保存
with open('test_audio.wav', 'wb') as audio_file:
    audio_file.write(response.content)

# Pygameの初期化
pygame.init()
pygame.mixer.init()

# WAVファイルを読み込む
sound = pygame.mixer.Sound("test_audio.wav")

# 再生
sound.play()

# 再生が終了するまで待機
while pygame.mixer.get_busy():
    pygame.time.Clock().tick(10)

で実行したが

pygame 2.5.2 (SDL 2.28.3, Python 3.10.6)
Hello from the pygame community. https://www.pygame.org/contribute.html
Traceback (most recent call last):
  File "/Users/snowpool/aw10s/weather/weather_audio.py", line 39, in <module>
    sound = pygame.mixer.Sound("test_audio.wav")
pygame.error: Unrecognized audio format

となる

とりあえず問題を分割
音声の再生ができるかテスト

cp ../test_audio.wav .

あとは

import pygame

# Pygameの初期化
pygame.init()
pygame.mixer.init()

# WAVファイルを読み込む
sound = pygame.mixer.Sound("test_audio.wav")

# 再生
sound.play()

# 再生が終了するまで待機
while pygame.mixer.get_busy():
    pygame.time.Clock().tick(10)

で音声が再生されるのを確認

となると問題は音声ファイルの作成プロセス

import requests
import json

# テキストファイルを読み込む
with open('weather.txt', 'r') as file:
    text = file.read()

# 最初のPOSTリクエストを行い、JSONを取得
response = requests.post(
    "http://192.168.1.69:50021/audio_query?speaker=1",
    data={'text': text}
)
json_data = response.json()

# JSONデータをファイルに保存
with open('query.json', 'w') as json_file:
    json.dump(json_data, json_file)

# 第二のPOSTリクエストを行い、音声データを取得
response = requests.post(
    "http://192.168.1.69:50021/synthesis?speaker=1",
    json=json_data
)

# 取得した音声データをWAVファイルとして保存
with open('test_audio.wav', 'wb') as audio_file:
    audio_file.write(response.content)

で作成したファイルは音声が再生されない

作成に必要なJSONファイルを見てみると
失敗の方は

{"detail": [{"loc": ["query", "text"], "msg": "field required", "type": "value_error.missing"}]}

成功は

{"accent_phrases":[{"moras":[{"text":"キョ","consonant":"ky","consonant_length":0.12866447865962982,"vowel":"o","vowel_length":0.0973580852150917,"pitch":5.962612628936768},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.09024209529161453,"pitch":6.068655967712402},{"text":"ノ","consonant":"n","consonant_length":0.05692561715841293,"vowel":"o","vowel_length":0.11087840050458908,"pitch":5.8726630210876465}],"accent":1,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"テ","consonant":"t","consonant_length":0.0718907043337822,"vowel":"e","vowel_length":0.14058615267276764,"pitch":5.855612277984619},{"text":"ン","consonant":null,"consonant_length":null,"vowel":"N","vowel_length":0.08981689810752869,"pitch":5.842429161071777},{"text":"キ","consonant":"k","consonant_length":0.07256065309047699,"vowel":"i","vowel_length":0.07487940043210983,"pitch":5.817480087280273},{"text":"ワ","consonant":"w","consonant_length":0.057370081543922424,"vowel":"a","vowel_length":0.12027011066675186,"pitch":5.624467849731445}],"accent":1,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"ク","consonant":"k","consonant_length":0.07742901891469955,"vowel":"u","vowel_length":0.0734512135386467,"pitch":5.836529731750488},{"text":"モ","consonant":"m","consonant_length":0.060555391013622284,"vowel":"o","vowel_length":0.18545563519001007,"pitch":5.841731071472168}],"accent":1,"pause_mora":{"text":"、","consonant":null,"consonant_length":null,"vowel":"pau","vowel_length":0.33395129442214966,"pitch":0.0},"is_interrogative":false},{"moras":[{"text":"ヨ","consonant":"y","consonant_length":0.10122014582157135,"vowel":"o","vowel_length":0.08855406194925308,"pitch":5.50715970993042},{"text":"ソ","consonant":"s","consonant_length":0.09714090079069138,"vowel":"o","vowel_length":0.09897967427968979,"pitch":5.924524784088135},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.10465160012245178,"pitch":6.014603614807129},{"text":"サ","consonant":"s","consonant_length":0.09610642492771149,"vowel":"a","vowel_length":0.11944571882486343,"pitch":6.102450370788574},{"text":"イ","consonant":null,"consonant_length":null,"vowel":"i","vowel_length":0.08921179920434952,"pitch":6.081024169921875},{"text":"コ","consonant":"k","consonant_length":0.07877751439809799,"vowel":"o","vowel_length":0.08582849055528641,"pitch":6.140597343444824},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.11994349211454391,"pitch":6.1246795654296875},{"text":"キ","consonant":"k","consonant_length":0.08878674358129501,"vowel":"i","vowel_length":0.09810564666986465,"pitch":6.173953533172607},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.16754235327243805,"pitch":6.181836128234863},{"text":"ン","consonant":null,"consonant_length":null,"vowel":"N","vowel_length":0.06746162474155426,"pitch":6.051656723022461},{"text":"ワ","consonant":"w","consonant_length":0.04867963492870331,"vowel":"a","vowel_length":0.1127525344491005,"pitch":5.779387950897217}],"accent":8,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"ジュ","consonant":"j","consonant_length":0.11186956614255905,"vowel":"u","vowel_length":0.10070556402206421,"pitch":5.609426498413086},{"text":"ウ","consonant":null,"consonant_length":null,"vowel":"u","vowel_length":0.08927937597036362,"pitch":5.8536553382873535}],"accent":2,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"ゴ","consonant":"g","consonant_length":0.06749280542135239,"vowel":"o","vowel_length":0.09443604201078415,"pitch":6.002788543701172},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.12283030897378922,"pitch":6.167281150817871},{"text":"テ","consonant":"t","consonant_length":0.07367654889822006,"vowel":"e","vowel_length":0.14059318602085114,"pitch":6.100039958953857},{"text":"ン","consonant":null,"consonant_length":null,"vowel":"N","vowel_length":0.08243037015199661,"pitch":5.868035316467285}],"accent":1,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"サ","consonant":"s","consonant_length":0.08632750064134598,"vowel":"a","vowel_length":0.1564868539571762,"pitch":5.76937198638916},{"text":"ン","consonant":null,"consonant_length":null,"vowel":"N","vowel_length":0.0757487341761589,"pitch":5.765336036682129}],"accent":2,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"ゴ","consonant":"g","consonant_length":0.05643042176961899,"vowel":"o","vowel_length":0.09696970134973526,"pitch":5.819827079772949},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.10339736193418503,"pitch":5.959120273590088},{"text":"ド","consonant":"d","consonant_length":0.06090632826089859,"vowel":"o","vowel_length":0.17854683101177216,"pitch":5.801456451416016}],"accent":2,"pause_mora":{"text":"、","consonant":null,"consonant_length":null,"vowel":"pau","vowel_length":0.3034582734107971,"pitch":0.0},"is_interrogative":false},{"moras":[{"text":"ヨ","consonant":"y","consonant_length":0.09282273054122925,"vowel":"o","vowel_length":0.08764959871768951,"pitch":5.5444231033325195},{"text":"ソ","consonant":"s","consonant_length":0.09865055978298187,"vowel":"o","vowel_length":0.09965776652097702,"pitch":5.934866905212402},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.10340947657823563,"pitch":6.015321731567383},{"text":"サ","consonant":"s","consonant_length":0.09463881701231003,"vowel":"a","vowel_length":0.11840283870697021,"pitch":6.101940155029297},{"text":"イ","consonant":null,"consonant_length":null,"vowel":"i","vowel_length":0.08852870017290115,"pitch":6.076019287109375},{"text":"テ","consonant":"t","consonant_length":0.0689489021897316,"vowel":"e","vowel_length":0.09846750646829605,"pitch":6.127346038818359},{"text":"エ","consonant":null,"consonant_length":null,"vowel":"e","vowel_length":0.1237698420882225,"pitch":6.114901542663574},{"text":"キ","consonant":"k","consonant_length":0.08733474463224411,"vowel":"i","vowel_length":0.0968116894364357,"pitch":6.151494026184082},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.1778039187192917,"pitch":6.152987480163574},{"text":"ン","consonant":null,"consonant_length":null,"vowel":"N","vowel_length":0.064981609582901,"pitch":6.005949974060059},{"text":"ワ","consonant":"w","consonant_length":0.04751142859458923,"vowel":"a","vowel_length":0.09467842429876328,"pitch":5.725627899169922}],"accent":8,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"ロ","consonant":"r","consonant_length":0.0632673129439354,"vowel":"o","vowel_length":0.10540910810232162,"pitch":5.67587947845459},{"text":"ク","consonant":"k","consonant_length":0.06531907618045807,"vowel":"U","vowel_length":0.05817136913537979,"pitch":0.0},{"text":"テ","consonant":"t","consonant_length":0.07730791717767715,"vowel":"e","vowel_length":0.16715875267982483,"pitch":6.10499382019043},{"text":"ン","consonant":null,"consonant_length":null,"vowel":"N","vowel_length":0.06785128265619278,"pitch":5.860898494720459}],"accent":1,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"ロ","consonant":"r","consonant_length":0.03526639938354492,"vowel":"o","vowel_length":0.11113513261079788,"pitch":5.707563877105713},{"text":"ク","consonant":"k","consonant_length":0.07907760888338089,"vowel":"u","vowel_length":0.07053706794977188,"pitch":5.830077648162842}],"accent":2,"pause_mora":null,"is_interrogative":false},{"moras":[{"text":"ゴ","consonant":"g","consonant_length":0.072600357234478,"vowel":"o","vowel_length":0.09719936549663544,"pitch":5.857402324676514},{"text":"オ","consonant":null,"consonant_length":null,"vowel":"o","vowel_length":0.09454251080751419,"pitch":6.060022354125977},{"text":"ド","consonant":"d","consonant_length":0.05659743398427963,"vowel":"o","vowel_length":0.08684452623128891,"pitch":6.083237171173096},{"text":"デ","consonant":"d","consonant_length":0.05277804285287857,"vowel":"e","vowel_length":0.14346836507320404,"pitch":5.951061248779297},{"text":"ス","consonant":"s","consonant_length":0.07407647371292114,"vowel":"U","vowel_length":0.12432015687227249,"pitch":0.0}],"accent":2,"pause_mora":null,"is_interrogative":false}],"speedScale":1.0,"pitchScale":0.0,"intonationScale":1.0,"volumeScale":1.0,"prePhonemeLength":0.1,"postPhonemeLength":0.1,"outputSamplingRate":24000,"outputStereo":false,"kana":"キョ'オノ/テ'ンキワ/ク'モ、ヨソオサイコオキ'オンワ/ジュウ'/ゴ'オテン/サン'/ゴオ'ド、ヨソオサイテエキ'オンワ/ロ'_クテン/ロク'/ゴオ'ドデ_ス"}

となる

つまりJSON作成が失敗している

Request による生成を断念
代わりに subprocess によるLinux コマンドを実行する方法に買える

import subprocess

# curl コマンドを定義
command = [
    "curl", "-s", "-X", "POST",
    "192.168.1.69:50021/audio_query?speaker=1",
    "--get", "--data-urlencode", "text@weather.txt"
]

# コマンドを実行し、出力を query.json にリダイレクト
with open('query.json', 'w') as file:
    subprocess.run(command, stdout=file)

でサブプロセスを使う方法にすると
JSONファイルの作成が成功

これを

curl -s -H "Content-Type: application/json" -X POST -d @query.json "192.168.1.69:50021/synthesis?speaker=1" > test_audio.wav

とすると音声ファイルができているのを確認

次に一緒に音声ファイル作成までをpython でやるようにする

import subprocess

# 最初のcurlコマンド(JSONファイルの作成)
command_json = [
    "curl", "-s", "-X", "POST",
    "192.168.1.69:50021/audio_query?speaker=1",
    "--get", "--data-urlencode", "text@weather.txt"
]

# 第二のcurlコマンド(音声ファイルの作成)
command_audio = [
    "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
    "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
]

# 最初のコマンドを実行してJSONファイルを作成
with open('query.json', 'w') as file:
    subprocess.run(command_json, stdout=file)

# 第二のコマンドを実行して音声ファイルを作成
with open('test_audio.wav', 'wb') as file:
    subprocess.run(command_audio, stdout=file)

これで音声ファイルができているのを確認できたので
次に
これをpygame. で再生する

import subprocess
import pygame
import time

# JSONファイルを作成するためのcurlコマンド
command_json = [
    "curl", "-s", "-X", "POST",
    "192.168.1.69:50021/audio_query?speaker=1",
    "--get", "--data-urlencode", "text@weather.txt"
]

# 音声ファイルを作成するためのcurlコマンド
command_audio = [
    "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
    "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
]

# 最初のコマンドを実行してJSONファイルを作成
with open('query.json', 'w') as file:
    subprocess.run(command_json, stdout=file)

# 第二のコマンドを実行して音声ファイルを作成
with open('test_audio.wav', 'wb') as file:
    subprocess.run(command_audio, stdout=file)

# Pygameの初期化
pygame.init()
pygame.mixer.init()

# WAVファイルを読み込む
sound = pygame.mixer.Sound("test_audio.wav")

# 再生
sound.play()

# 再生が終了するまで待機
while pygame.mixer.get_busy():
    time.sleep(0.1)

これでようやくcurl でファイルを作成し
Pygame で音声再生ができるようになった

次はテキストファイルではなく
Weather map api へアクセスし
その結果をテキストファイルへ保存
それを変換する
もしくはそのまま処理するようにする

pygame で読み上げ

一度に全て処理しpygame で読み上げ

wavファイルの読み上げを gygame にすることでエラー対処

import requests
import json
from datetime import datetime
import deepl

def get_weather_forecast(latitude, longitude, API_key, deepl_auth_key):
    # DeepL Translatorのインスタンスを生成
    translator = deepl.Translator(deepl_auth_key)

    # OpenWeather APIのURL
    url = "https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude=hourly,minutely&units=metric&lang=ja&appid={API_key}"
    url = url.format(lat=latitude, lon=longitude, API_key=API_key)

    # APIリクエスト
    response = requests.get(url)
    jsondata = response.json()

    # 今日の日付を取得
    today = datetime.now().date()

    # 今日の天気予報を探す
    for daily_forecast in jsondata["daily"]:
        date = datetime.fromtimestamp(daily_forecast["dt"]).date()
        if date == today:
            min_temp = daily_forecast["temp"]["min"]
            max_temp = daily_forecast["temp"]["max"]
            weather = daily_forecast["weather"][0]["main"]
            description = daily_forecast["weather"][0]["description"]
            break

    # 天気をdeeplで日本語に翻訳
    weather_japanese = translator.translate_text(weather, target_lang="JA").text

    # 今日の天気予報をまとめる
    today_weather_repo = f"今日の天気は{weather_japanese}、予想最高気温は{max_temp}度、予想最低気温は{min_temp}度です"
    return today_weather_repo

# 関数を使用して天気予報を取得
latitude = "緯度"
longitude = "経度"
API_key = "weather map apiキー"
deepl_auth_key = "deeplのAPIキー"

weather_report = get_weather_forecast(latitude, longitude, API_key, deepl_auth_key)

# 天気予報をテキストファイルに保存
with open('weather.txt', 'w') as file:
    file.write(weather_report)

として関数にして
weather_forecast.py
として保存

次に

import subprocess
import pygame
import time
from weather_forecast import get_weather_forecast

# 天気予報を取得してファイルに保存
latitude = "緯度"
longitude = "経度"
API_key = "weather map apiキー"
deepl_auth_key = "DeepL APIキー"

weather_report = get_weather_forecast(latitude, longitude, API_key, deepl_auth_key)

with open('weather.txt', 'w') as file:
    file.write(weather_report)

# JSONファイルを作成するためのcurlコマンド
command_json = [
    "curl", "-s", "-X", "POST",
    "192.168.1.69:50021/audio_query?speaker=1",
    "--get", "--data-urlencode", "text@weather.txt"
]

# 音声ファイルを作成するためのcurlコマンド
command_audio = [
    "curl", "-s", "-H", "Content-Type: application/json", "-X", "POST",
    "-d", "@query.json", "192.168.1.69:50021/synthesis?speaker=1"
]

# 最初のコマンドを実行してJSONファイルを作成
with open('query.json', 'w') as file:
    subprocess.run(command_json, stdout=file)

# 第二のコマンドを実行して音声ファイルを作成
with open('test_audio.wav', 'wb') as file:
    subprocess.run(command_audio, stdout=file)

# Pygameの初期化
pygame.init()
pygame.mixer.init()

# WAVファイルを読み込む
sound = pygame.mixer.Sound("test_audio.wav")

# 再生
sound.play()

# 再生が終了するまで待機
while pygame.mixer.get_busy():
    time.sleep(0.1)

として作成したファイルを元に
Subprocess で
Curl で音声ファイルを作成

それをpygame で読み上げるようにした

とりあえず動作確認はできたので
次に顔認識と合わせてみる

現在地の天気を音声にする

現在地の天気を音声にする

1日の天気を取得するには座標取得が必要
Google で現在地の緯度経度を調べ

for daily_forecast in jsondata["daily"]:
    date = datetime.fromtimestamp(daily_forecast["dt"]).date()
    if date == today:
        min_temp = daily_forecast["temp"]["min"]
        max_temp = daily_forecast["temp"]["max"]
        weather = daily_forecast["weather"][0]["main"]
        description = daily_forecast["weather"][0]["description"]
        break

というように
OpenWeather APIの「Daily Forecast」エンドポイントを使用

ただしそのままだと天気は英語になる

OpenWeather APIで日本語の天気情報を取得するには、APIリクエストにlangパラメータを追加してjaを追加

リクエストURLを

url = "https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude=hourly,minutely&units=metric&lang=ja&appid={API_key}"

とする

これで詳細な天気は日本語になったけど
天気そのものは英語のまま

辞書を作成してもいいけど
おそらく今後天気を追加していくことになるので
面倒なので
DeepLを使って翻訳する

import deepl

auth_key = "取得したDeepLのAPIキー"  # Replace with your key
translator = deepl.Translator(auth_key)

あとは

#天気をdeeplで英語から日本語にする

weather_japanese = translator.translate_text(weather,target_lang="JA")
# 結果の表示
print(f"今日の最高気温: {max_temp}°C")
print(f"今日の最低気温: {min_temp}°C")
print(f"天気: {weather_japanese} ({description})")

とすれば

python current_weather.py
今日の最高気温: 11.26°C
今日の最低気温: 3.72°C
天気: 雲 (薄い雲)

となる

あとはこれをテキストにする

# 今日の天気予報をまとめる
today_weather_repo = f"今日の天気は{weather_japanese}、予想最高気温は{max_temp}°C、予想最低気温は{min_temp}°C です"

# 結果の表示
print(today_weather_repo)

pythonの場合
テキスト内に変数を入れるなら{変数名}で組み込むことができる

これで文章はできたので
次にdockerで voicevox をバックグランドで起動し
ここへテキストを送り音声を作成する

とりあえず

docker run --rm -p '192.168.1.69:50021:50021' voicevox/voicevox_engine:cpu-ubuntu20.04-latest

で前回起動しているが

docker run –rm -p
はDockerコンテナを実行する際に使用されるオプション

これらのオプションは以下のような意味を持ち
–rm:
このオプションは、コンテナの実行が終了した後にコンテナを自動的に削除するようDockerに指示
通常、コンテナは停止してもファイルシステムが保持され、
後でdocker startコマンドで再開することができるが、
–rmオプションがあると、コンテナが停止したときにそのファイルシステムも一緒に削除される
一時的なテストや一回きりの実行に便利

-p:
-pオプションはポートマッピングを設定するために使用
このオプションはホストのポートとコンテナのポートを紐
形式は -p ホストのポート:コンテナのポート
例えば、-p 8080:80 と指定すると、ホストの8080ポートがコンテナの80ポートにマッピングされる

これにより、ホストの8080ポートにアクセスすると、そのリクエストがコンテナの80ポートに転送される

これらのオプションを組み合わせることで、特定のポートを公開しながら
コンテナの使用が終了した後には自動的に削除するように設定することができる

例えば、docker run –rm -p 8080:80 nginx というコマンドは
nginxサーバーを実行するコンテナを作成し
ホストの8080ポートをコンテナの80ポートにマッピングする
そして、コンテナの実行が終了すると、そのコンテナは自動的に削除される

これだとコンテナが消える

やりたいのはバックグランドでvoicevoxの起動なので

docker pull voicevox/voicevox_engine:cpu-ubuntu20.04-latest

でコンテナ取得

バックグランドでの起動は
-d オプションをつければいいので

docker run -d  -p '192.168.1.69:50021:50021' voicevox/voicevox_engine:cpu-ubuntu20.04-latest

でバックグランドで起動

次にMac で

 echo -h "今日の天気は雲、予想最高気温は11.26°C、予想最低気温は3.72°C です" > weather.txt

でテキストを作成

一度停電になり停止したため
再度ubuntu の起動と

docker run -d  -p '192.168.1.69:50021:50021' voicevox/voicevox_engine:cpu-ubuntu20.04-latest


バックグランドでdocker の起動

再度

cat weather.txt 
-h 今日の天気は雲、予想最高気温は11.26°C、予想最低気温は3.72°C です

で調べたら
余計なものが入っていた

過去ログを見たら
-n オプションの間違いだった

とりあえずファイルを編集し

curl -s -X POST "192.168.1.69:50021/audio_query?speaker=1" --get --data-urlencode text@weather.txt > query.json

でJSONファイルを作成

curl -s -H "Content-Type: application/json" -X POST -d @query.json "192.168.1.69:50021/synthesis?speaker=1" > test_audio.wav

で音声ファイルを作成

Mac でFinder から再生すると音声ができていて音声の再生も問題なく実行できたが
Cを度ではなくシーとなったり
曇はくも
と発音される

次にファイルをubuntu にコピーして
Ubuntu から docker で実行できるかテスト

scp weather.txt snowpool@192.168.1.69:/home/snowpool/aw10s/

でコピー

Ubuntu へログインし

curl -s -X POST "192.168.1.69:50021/audio_query?speaker=1" --get --data-urlencode text@weather.txt > query.json

curl -s -H "Content-Type: application/json" -X POST -d @query.json "192.168.1.69:50021/synthesis?speaker=1" > test_audio.wav

次にリモートで音声ファイルを再生
Aplayを使うことで音声再生が可能

aplay test_audio.wav

しかし
これだとパソコン本体にログインしていないと再生されない

目的はリモート接続での音声再生またはcronなどでのログインしていない除隊でも音声を再生させること

ラズパイで過去にやったような気がするので検索

https://www.sato-susumu.com/entry/2023/01/06/203541#pygamemixerSoundでwav再生
を参考に
mpg123
で再生

sudo apt install mpg123

でインストール

 mpg123 test_audio.wav

を実行したが

High Performance MPEG 1.0/2.0/2.5 Audio Player for Layers 1, 2 and 3
	version 1.29.3; written and copyright by Michael Hipp and others
	free software (LGPL) without any warranty but with best wishes


Terminal control enabled, press 'h' for listing of keys and functions.

Playing MPEG stream 1 of 1: test_audio.wav ...

MPEG 1.0 L I cbr865 44100 stereo
[src/libmpg123/layer1.c:check_balloc():30] error: Illegal bit allocation value.
[src/libmpg123/layer1.c:INT123_do_layer1():228] error: Aborting layer I decoding after step one.
Note: Illegal Audio-MPEG-Header 0xf6fff3ff at offset 5272.
Note: Trying to resync...
Note: Skipped 96 bytes in input.
[src/libmpg123/layer1.c:check_balloc():30] error: Illegal bit allocation value.
[src/libmpg123/layer1.c:INT123_do_layer1():228] error: Aborting layer I decoding after step one.
> 002+612  00:00.01+00:05.32 --- 100=100   0 kb/s  942 B acc  128 clip p+0.000  
MPEG 1.0 L I cbr865 44100 stereo
Note: Illegal Audio-MPEG-Header 0xf6fff7ff at offset 6310.
Note: Trying to resync...
Note: Skipped 922 bytes in input.
[src/libmpg123/layer1.c:check_balloc():30] error: Illegal bit allocation value.
[src/libmpg123/layer1.c:INT123_do_layer1():228] error: Aborting layer I decoding after step one.
> 003+611  00:00.02+00:05.31 --- 100=100   0 kb/s  942 B acc    0 clip p+0.000  
MPEG 1.0 L I cbr865 44100 stereo
Note: Illegal Audio-MPEG-Header 0x6cffcb00 at offset 8174.
Note: Trying to resync...
Note: Skipped 809 bytes in input.

Warning: Big change from first (MPEG version, layer, rate). Frankenstein stream?
> 004+610  00:00.20+00:31.85 --- 100=100   0 kb/s  942 B acc    0 clip p+0.000  
MPEG 2.5 L III cbr144 11025 stereo

Warning: Big change (MPEG version, layer, rate). Frankenstein stream?

というようにバグった

現在地の天気情報の取得その2

現在地の天気情報の取得その2

import requests
import json
from pprint import pprint
url = "https://api.openweathermap.org/data/2.5/weather?zip={zip_place}&units=metric&appid={API_key}"
# xxxxx
url = url.format(zip_place = "郵便番号,JP", API_key = "APIキー")

jsondata = requests.get(url).json()
pprint(jsondata)

print("天気:",jsondata["weather"][0]["main"])
print("天気詳細:",jsondata["weather"][0]["description"])

print("都市名:",jsondata["name"])
print("気温:",jsondata["main"]["temp"])
print("体感気温:",jsondata["main"]["feels_like"])
print("最低気温:",jsondata["main"]["temp_min"])
print("最高気温:",jsondata["main"]["temp_max"])
print("気圧:",jsondata["main"]["pressure"])
print("湿度:",jsondata["main"]["humidity"])

print("風速:",jsondata["wind"]["speed"])
print("風の方角:",jsondata["wind"]["deg"])

で実行すると

{'base': 'stations',
 'clouds': {'all': 0},
 'cod': 200,
 'coord': {'lat': 緯度, 'lon': 経度},
 'dt': 1701631236,
 'id': 0,
 'main': {'feels_like': 4.12,
          'humidity': 60,
          'pressure': 1021,
          'temp': 6.65,
          'temp_max': 6.65,
          'temp_min': 6.65},
 'name': 'Kawai',
 'sys': {'country': 'JP',
         'id': 2008260,
         'sunrise': 1701639567,
         'sunset': 1701675443,
         'type': 2},
 'timezone': 32400,
 'visibility': 10000,
 'weather': [{'description': 'clear sky',
              'icon': '01n',
              'id': 800,
              'main': 'Clear'}],
 'wind': {'deg': 288, 'gust': 5.36, 'speed': 3.58}}
天気: Clear
天気詳細: clear sky
都市名: 番地
気温: 6.65
体感気温: 4.12
最低気温: 6.65
最高気温: 6.65
気圧: 1021
湿度: 60
風速: 3.58
風の方角: 288

となる

デフォルトがだと気温が華氏で取得されます。摂氏を基準としたい場合はクエリに units=metric を含めます

これだと英語なので日本語で天気を取得したい

https://zenn.dev/masaru21/articles/d26519ec888f01
を元に

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>日本語のタイトル</title>
    <!--
    <link rel="stylesheet" href="/css/style.css" type="text/css">
    <script type="text/javascript" src="/js/main.js"></script>

    -->

  </head>
  <body>
  <script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script>
  <script>

  $(function() {
    var API_KEY = 'APIキー'
    var item_id = 'lat=35.2932718&lon=139.97671'
    
    var url = 'https://api.openweathermap.org/data/2.5/forecast?' + item_id + '&lang=ja&units=metric&appid=' + API_KEY

    $.ajax({
      url: url,
      dataType: "json",
      type: 'GET',
    })
    .done(function(data) {
      var insertHTML = "";
      var cityName = '<h2>' + data.city.name + '</h2>';
      $('#city-name').html(cityName);
      for (var i = 0; i <= 39; i = i + 8) {
        insertHTML += buildHTML(data, i);
      }
      $('#weather').html(insertHTML);
    })
    .fail(function(data) {
      console.log("失敗しました");
    });
  });

  function buildHTML(data, i) {
    var Week = new Array("(日)","(月)","(火)","(水)","(木)","(金)","(土)");
    var date = new Date (data.list[i].dt_txt);
    date.setHours(date.getHours() + 9);
    var month = date.getMonth()+1;
    var day = month + "月" + date.getDate() + "日" + Week[date.getDay()] + date.getHours() + ":00";
    var icon = data.list[i].weather[0].icon;
    var html =
    '<div class="weather-report">' +
      '<div id="cityname">' + data.city.name + '</div>' +
      '<img src="https://openweathermap.org/img/w/' + icon + '.png">' +
      '<div class="weather-date">' + day + '</div>' +
      '<div class="weather-main">'+ data.list[i].weather[0].description + '</div>' +
      '<div class="weather-temp">' + Math.round(data.list[i].main.temp) + '℃</div>' +
    '</div>';
    return html
  }

  </script>
  <style>
    .content {
        text-align: center;
        margin-left: auto;
        margin-right: auto;
    }

    .weather-report {
        margin-right: 20px;
        margin-left: 20px;
        float: left;
    }

  </style>   
    <div class="content">
      <div id="weather"></div>

    </div>
 

  </body>
</html>

をvscode の golive で実行してみた

これで5日間の天気が表示される
ただし、場所が

http://api.openweathermap.org/geo/1.0/direct?q=fukuokashi&limit=5&appid={API KEY}

で福岡に指定している

ソースの中で

 var item_id = 'lat=35.2932718&lon=139.97671'

で緯度経度を指定している

とりあえずchat GPT で日本語で天気を取得する方法を調べたら

Pythonの辞書を使って英語の天気の説明を日本語にマッピングする
英語から日本語への基本的な翻訳の辞書を作り
APIから取得した英語の天気説明を日本語に変換

書に含まれていない天気の条件には「不明」と表示されますが、必要に応じて辞書を拡張する

とのこと

# 天気の英語から日本語への翻訳辞書
weather_translation = {
    "Clear": "晴れ",
    "Clouds": "曇り",
    "Rain": "雨",
    "Snow": "雪",
    "Thunderstorm": "雷雨",
    "Drizzle": "霧雨",
    "Mist": "霧",
    "Fog": "濃霧"
}

# APIから取得した天気を翻訳する
weather_english = jsondata["weather"][0]["main"]
weather_japanese = weather_translation.get(weather_english, "不明")  # 辞書にない場合は「不明」と表示

print("天気(日本語):", weather_japanese)

この処理を加え

import requests
import json
from pprint import pprint
url = "https://api.openweathermap.org/data/2.5/weather?zip={zip_place}&units=metric&appid={API_key}"
# xxxxx
url = url.format(zip_place = "郵便番号,JP", API_key = "APIキー")

# 天気の英語から日本語への翻訳辞書
weather_translation = {
    "Clear": "晴れ",
    "Clouds": "曇り",
    "Rain": "雨",
    "Snow": "雪",
    "Thunderstorm": "雷雨",
    "Drizzle": "霧雨",
    "Mist": "霧",
    "Fog": "濃霧"
}


jsondata = requests.get(url).json()

# APIから取得した天気を翻訳する
weather_english = jsondata["weather"][0]["main"]
weather_japanese = weather_translation.get(weather_english, "不明")  # 辞書にない場合は「不明」と表示

pprint(jsondata)

# print("天気:",jsondata["weather"][0]["main"])
print("天気:",weather_japanese)

print("天気詳細:",jsondata["weather"][0]["description"])

print("都市名:",jsondata["name"])
print("気温:",jsondata["main"]["temp"])
print("体感気温:",jsondata["main"]["feels_like"])
print("最低気温:",jsondata["main"]["temp_min"])
print("最高気温:",jsondata["main"]["temp_max"])
print("気圧:",jsondata["main"]["pressure"])
print("湿度:",jsondata["main"]["humidity"])

print("風速:",jsondata["wind"]["speed"])
print("風の方角:",jsondata["wind"]["deg"])

とすることで天気が日本語になった

つまり自分で辞書を作成しないとだめ

とりあえず
* Clear sky(晴天)
* Few clouds(少し曇り)
* Scattered clouds(散らばった雲)
* Broken clouds(雲が多い)
* Overcast clouds(曇天)
* Light rain(小雨)
* Moderate rain(中雨)
* Heavy intensity rain(大雨)
* Very heavy rain(非常に激しい雨)
* Extreme rain(極端な雨)
* Freezing rain(凍雨)
* Light intensity shower rain(弱いにわか雨)
* Shower rain(にわか雨)
* Heavy intensity shower rain(激しいにわか雨)
* Ragged shower rain(不規則なにわか雨)
* Light snow(小雪)
* Snow(雪)
* Heavy snow(大雪)
* Sleet(みぞれ)
* Light shower sleet(軽いにわかみぞれ)
* Shower sleet(にわかみぞれ)
* Light rain and snow(小雨と雪)
* Rain and snow(雨と雪)
* Light shower snow(軽いにわか雪)
* Shower snow(にわか雪)
* Heavy shower snow(激しいにわか雪)
* Mist(靄)
* Smoke(煙)
* Haze(霞)
* Sand/ dust whirls(砂塵旋風)
* Fog(霧)
* Sand(砂)
* Dust(ほこり)
* Volcanic ash(火山灰)
* Squalls(スコール)
* Tornado(竜巻)
* Thunderstorm with light rain(雷雨と小雨)
* Thunderstorm with rain(雷雨)
* Thunderstorm with heavy rain(雷雨と大雨)
* Light thunderstorm(軽い雷雨)
* Thunderstorm(雷雨)
* Heavy thunderstorm(激しい雷雨)
* Ragged thunderstorm(不規則な雷雨)
* Thunderstorm with light drizzle(雷雨と弱い霧雨)
* Thunderstorm with drizzle(雷雨と霧雨)
* Thunderstorm with heavy drizzle(雷雨と激しい霧雨)

となるらしい

面倒なので

url = "https://api.openweathermap.org/data/2.5/weather?zip={zip_place}&units=metric&lang=ja&appid={API_key}"

としたら詳細天気は英語から日本語になったけど
天気は英語のままなのでやっぱり辞書は必要かも

とりあえず一日の最高気温と最低気温を取得したいのなら
もう少し処理が必要

import requests
import json
from datetime import datetime, timedelta

# OpenWeather APIのURLとパラメータを設定
url = "https://api.openweathermap.org/data/2.5/onecall?lat={lat}&lon={lon}&exclude=hourly,minutely&units=metric&appid={API_key}"
latitude = "緯度"  # 例としての緯度
longitude = "経度"  # 例としての経度
API_key = "YOUR_API_KEY"  # あなたのAPIキーをここに入力

# APIリクエスト
url = url.format(lat=latitude, lon=longitude, API_key=API_key)
response = requests.get(url)
jsondata = response.json()

# 今日の日付を取得
today = datetime.now().date()

# 今日の最高気温と最低気温を探す
for daily in jsondata["daily"]:
    date = datetime.fromtimestamp(daily["dt"]).date()
    if date == today:
        min_temp = daily["temp"]["min"]
        max_temp = daily["temp"]["max"]
        print("今日の最高気温:", max_temp, "度")
        print("今日の最低気温:", min_temp, "度")
        break

というように緯度経度で取得する

とりあえず日本語で天気取得できたので
これを音声にする