pythonでGmailの最新、または未読の本文を取得する

最新のものだけ取得するようにする

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# 最新のメール本文を取得する関数
def gmail_get_latest_message_body(service, labelIdsValue):
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue, maxResults=1).execute()

    if not msg_list['messages']:
        return "No messages found."

    msg = msg_list['messages'][0]
    date = gmail_get_messages_body_date(messages, msg)
    topid = msg['id']
    msg = messages.get(userId='me', id=topid).execute()

    # メールの本文を取得
    if msg["payload"]["body"]["size"] != 0:
        return date + "<br>" + decode(msg["payload"]["body"]["data"])
    elif 'parts' in msg["payload"]:
        # メール本文が parts 属性にある場合
        for part in msg["payload"]["parts"]:
            if part["body"]["size"] != 0:
                return date + "<br>" + decode(part["body"]["data"])
    return date + "<br> No body content"

# メールの受信日時を取得する関数(変更なし)
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# ラベルの表示関数(変更なし)
def gmail_display_label(service):
    results = service.users().labels().list(userId='me').execute()
    labels = results.get('labels', [])

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()
gmail_display_label(service)

# ラベル ID を指定して最新のメール本文を取得
latest_message_body = gmail_get_latest_message_body(service, "Label_4")
print(latest_message_body)

これで最新の1件のメールのみ取得できるようになる

次に未読のものだけ取得するようにする

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# 未読メールの本文を取得する関数
def gmail_get_unread_messages_body(service, labelIdsValue):
    mailBody = []
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue, q="is:unread").execute()

    if 'messages' not in msg_list:
        return ["No unread messages found."]

    for msg in msg_list['messages']:
        date = gmail_get_messages_body_date(messages, msg)
        topid = msg['id']
        msg = messages.get(userId='me', id=topid).execute()

        # メールの本文を取得
        if msg["payload"]["body"]["size"] != 0:
            mailBody.append(date + "<br>" + decode(msg["payload"]["body"]["data"]))
        elif 'parts' in msg["payload"]:
            # メール本文が parts 属性にある場合
            for part in msg["payload"]["parts"]:
                if part["body"]["size"] != 0:
                    mailBody.append(date + "<br>" + decode(part["body"]["data"]))
                    break
        else:
            mailBody.append(date + "<br> No body content")

    return mailBody

# メールの受信日時を取得する関数(変更なし)
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# ラベルの表示関数(変更なし)
def gmail_display_label(service):
    results = service.users().labels().list(userId='me').execute()
    labels = results.get('labels', [])

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()
gmail_display_label(service)

# ラベル ID を指定して未読メールの本文を取得
unread_messages_body = gmail_get_unread_messages_body(service, "Label_4")
for body in unread_messages_body:
    print(body)

未読のメールのみを表示するようにコードを修正するには
Gmail APIのクエリパラメータに q=”is:unread” を追加する

q=”is:unread” パラメータを messages.list
API呼び出しに追加することで、未読のメールのみがフィルタリングされる

取得したメールの中で
decode 関数を使用してメールの本文をデコードし
必要に応じて parts 属性を確認

未読メールがない場合
“No unread messages found.”
というメッセージが返され
未読メールがある場合は
それぞれのメールについて受信日時と本文が表示される

Label_4
に属する未読メールのみを処理しているので
他のラベルや追加のフィルタリング条件を使用したい場合は
labelIdsValue 引数や q パラメータを適宜変更する

この場合
過去全てからになってしまうので期間を設定する

未読で最新のものを取得するようにする
そしてURLを本文から削除

from __future__ import print_function
import os.path
import re
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# URLを削除する関数
def remove_urls(text):
    # URLにマッチする正規表現パターン
    url_pattern = r'https?://\S+|www\.\S+'
    return re.sub(url_pattern, '', text)

# 未読で最新のメール本文を取得する関数
def gmail_get_latest_unread_message_body(service, labelIdsValue):
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue, q="is:unread").execute()

    if 'messages' not in msg_list:
        return "No unread messages found."

    # 未読メッセージのリストを取得し、最初のメッセージ(最新)を選択
    msg = msg_list['messages'][0]
    date = gmail_get_messages_body_date(messages, msg)
    topid = msg['id']
    msg = messages.get(userId='me', id=topid).execute()


    # メールの本文を取得
    body = ""
    if msg["payload"]["body"]["size"] != 0:
        body = decode(msg["payload"]["body"]["data"])
    elif 'parts' in msg["payload"]:
        # メール本文が parts 属性にある場合
        for part in msg["payload"]["parts"]:
            if part["body"]["size"] != 0:
                body = decode(part["body"]["data"])
                break

    if not body:
        return date + "<br> No body content"

    # URLを削除
    body_no_urls = remove_urls(body)
    return date + "<br" + body_no_urls

# メールの受信日時を取得する関数(変更なし)
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# ラベルの表示関数(変更なし)
def gmail_display_label(service):
    results = service.users().labels().list(userId='me').execute()
    labels = results.get('labels', [])

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()
gmail_display_label(service)

# ラベル ID を指定して未読メールの本文を取得
latest_unread_message_body  = gmail_get_latest_unread_message_body(service, "Label_4")
print(latest_unread_message_body)


しかしこれだと本文の中のURLのみ削除しているため
発信元とかについては削除されていないので
これも対処する必要がある

Gmail 本文取得

pythonでGmail 本文取得

まずラベルを指定して取得するには
ラベルIDが必要になる

vim base.py

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials

# If modifying these scopes, delete the file token.json.
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

import base64, email #デコード用
import dateutil.parser

#token.jsonを設定
tokenPath = "token.json"

#credentials.jsonを設定
credentialsPath = "credentials.json"


#メール本文のデコード
def decode(encoded):
   decoded = base64.urlsafe_b64decode(encoded).decode()
   return decoded


#メール本文の内容を配列で取得する関数
def gmail_get_messages_body(service, labelIdsValue):
   mailBody = []
   
   # メッセージの一覧を取得
   messages = service.users().messages()
   msg_list = messages.list(userId='me', labelIds=labelIdsValue).execute() 
   # msg_list = messages.list(userId='me', labelIds=labelIdsValue ,maxResults=456).execute() #最大値指定
   
   # 取得したメッセージの一覧を配列に格納
   for msg in msg_list['messages']:
       
       #メールの受信日時を取得
       date = gmail_get_messages_body_date(messages,msg)
       
       topid = msg['id']     
       msg = messages.get(userId='me', id=topid).execute()
       
       if(msg["payload"]["body"]["size"]!=0):
           mailBody.append(date+"<br>"+decode(msg["payload"]["body"]["data"])) 
       else:
           #メールによっては"parts"属性の中に本文がある場合もある
           mailBody.append(date+"<br>"+decode(msg["payload"]["parts"][0]["body"]["data"])) 

   return mailBody
   

#gmail_get_messages_body関数内で受信日時を取得する関数
def gmail_get_messages_body_date(messages,msg):
   msg_id = msg['id']
   m = messages.get(userId='me', id=msg_id, format='raw').execute()
   raw = base64.urlsafe_b64decode(m['raw'])
   
   # Emailを解析する
   eml = email.message_from_bytes(raw)
   
   date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
   return date

#ラベルのIDやnameを表示する関数
def gmail_display_label(service):
   results = service.users().labels().list(userId='me').execute()
   labels = results.get('labels', [])

   if not labels:
       print('No labels found.')
   else:
       print('Labels:')
       for label in labels:
           print(label)


#初期化(from quickstart.py)
def gmail_init():
   creds = None
   # The file token.json stores the user's access and refresh tokens, and is
   # created automatically when the authorization flow completes for the first
   # time.
   if os.path.exists(tokenPath):
       creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
   # If there are no (valid) credentials available, let the user log in.
   if not creds or not creds.valid:
       if creds and creds.expired and creds.refresh_token:
           creds.refresh(Request())
       else:
           flow = InstalledAppFlow.from_client_secrets_file(
               credentialsPath, SCOPES)
           creds = flow.run_local_server(port=0)
       # Save the credentials for the next run
       with open(tokenPath, 'w') as token:
           token.write(creds.to_json())
           
   service = build('gmail', 'v1', credentials=creds)
   return service


#ここからやりたい処理を書く
service = gmail_init()

#quickstart.pyと同じ処理にしてみた
results = service.users().labels().list(userId='me').execute()
labels = results.get('labels', [])

if not labels:
   print('No labels found.')
else:
   print('Labels:')
   for label in labels:
       print(label['name'])
       
gmail_display_label(service)

として

python base.py

を実行すると
ラベルとID一覧が表示される

これでラベルIDがわかったので本文を取得するようにコード変更

from __future__ import print_function
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
from google.oauth2.credentials import Credentials
import base64
import email
import dateutil.parser

# スコープの設定
SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']

# トークンとクレデンシャルのパス
tokenPath = "token.json"
credentialsPath = "credentials.json"

# メール本文のデコード関数
def decode(encoded):
    decoded = base64.urlsafe_b64decode(encoded).decode()
    return decoded

# メール本文を取得する関数
def gmail_get_messages_body(service, labelIdsValue):
    mailBody = []
    messages = service.users().messages()
    msg_list = messages.list(userId='me', labelIds=labelIdsValue).execute()

    for msg in msg_list['messages']:
        date = gmail_get_messages_body_date(messages, msg)
        topid = msg['id']
        msg = messages.get(userId='me', id=topid).execute()

        # メールの本文を取得
        if msg["payload"]["body"]["size"] != 0:
            mailBody.append(date + "<br>" + decode(msg["payload"]["body"]["data"]))
        elif 'parts' in msg["payload"]:
            # メール本文が parts 属性にある場合
            for part in msg["payload"]["parts"]:
                if part["body"]["size"] != 0:
                    mailBody.append(date + "<br>" + decode(part["body"]["data"]))
                    break
        else:
            mailBody.append(date + "<br> No body content")

    return mailBody

# メールの受信日時を取得する関数
def gmail_get_messages_body_date(messages, msg):
    msg_id = msg['id']
    m = messages.get(userId='me', id=msg_id, format='raw').execute()
    raw = base64.urlsafe_b64decode(m['raw'])
    eml = email.message_from_bytes(raw)
    date = dateutil.parser.parse(eml.get('Date')).strftime("%Y-%m-%d_%H-%M-%S")
    return date

# ラベルの表示関数(変更なし)
def gmail_display_label(service):
    results = service.users().labels().list(userId='me').execute()
    labels = results.get('labels', [])

# Gmail API 初期化関数(変更なし)
def gmail_init():
    creds = None
    if os.path.exists(tokenPath):
        creds = Credentials.from_authorized_user_file(tokenPath, SCOPES)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                credentialsPath, SCOPES)
            creds = flow.run_local_server(port=0)
        with open(tokenPath, 'w') as token:
            token.write(creds.to_json())
    service = build('gmail', 'v1', credentials=creds)
    return service

# メイン処理
service = gmail_init()
gmail_display_label(service)

# ラベル ID を指定してメール本文を取得
mail_bodies = gmail_get_messages_body(service, "Label_4")
for body in mail_bodies:
    print(body)

これで

python base.py

とすると
Lavel_4のIDのメールの本文を表示することができる

顔認識して天気を答える

顔認識して天気を答える

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

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

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

Ubuntu 22.04 OpenCVをVNCで表示

Ubuntu 22.04 OpenCVをVNCで表示

 ssh snowpool@192.168.1.69

でログイン

Opencvの処理をSSHで表示するには
ポートフォワーディングが必要

sudo vim /etc/ssh/sshd_config

X11Forwarding yes

となっているのを確認

一度ログアウト

ssh -X snowpool@192.168.1.69

とオプションに -X をつければOK

OpenCV については
既にソースからインストール済み

wget https://github.com/opencv/opencv/blob/master/data/haarcascades/haarcascade_frontalface_default.xml


Haar Cascadeファイルの取得

次にpixcel8で自分の写真を撮影し
GoogleDrive へアップロード

これをubuntuに転送する

scp PXL_20231208_210913098.jpg-20231208T211055Z-001.zip snowpool@192.168.1.69:/home/snowpool/aw10s/

ファイル名が長いので変更

圧縮されているので

unzip PXL_20231208_210913098.jpg-20231208T211055Z-001.zip 

で解凍後

mv PXL_20231208_210913098.jpg image.jpg

でファイル名変更

あとは

vim face_recognition.py

でファイルを作成

import cv2

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

# 画像の読み込み
img = cv2.imread('image.jpg')

# グレースケール変換
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 顔の検出
faces = face_cascade.detectMultiScale(gray, 1.1, 4)

# 顔の周囲に枠を描画
for (x, y, w, h) in faces:
    cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)

# 結果の表示
cv2.imshow('img', img)
cv2.waitKey()
cv2.destroyAllWindows()

を保存し
実行したら

    face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
SystemError: <class 'cv2.CascadeClassifier'> returned a result with an exception set

となる

原因は
https://qiita.com/hatorijobs/items/df2c8793509430f8d543
にあるように
githubのhaarcascade_frontalface_default.xmlをダウンロードして、読み込むとエラーになる。
そのため、公式サイトのhaarcascade_frontalface_default.xmlファイルをダウンロードして、
読み込んだら、成功
とのこと

結構chatgptだけだとヒントにはなるけどエラーが多い

普通に
ubuntu22.04 OpenCV 顔認識で検索し

https://techlog.mydns.jp/?p=417
を参考に
https://github.com/opencv/opencv/tree/master/data/haarcascades
から
haarcascade_eye.xml
haarcascade_frontalface_default.xml
をダウンロード

これをubuntu に転送する

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

そして

pip install opencv-python

でライブラリインストール

vim kao.py

でファイルを作成

import cv2
import time

# 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")
            
        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()

これで実行したけど

[ WARN:0@0.046] global cap_v4l.cpp:982 open VIDEOIO(V4L2:/dev/video0): can't open camera by index
[ WARN:0@0.046] global obsensor_stream_channel_v4l2.cpp:82 xioctl ioctl: fd=-1, req=-2140645888
[ WARN:0@0.046] global obsensor_stream_channel_v4l2.cpp:138 queryUvcDeviceInfoList ioctl error return: 9
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:82 xioctl ioctl: fd=-1, req=-2140645888
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:138 queryUvcDeviceInfoList ioctl error return: 9
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:82 xioctl ioctl: fd=-1, req=-2140645888
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:138 queryUvcDeviceInfoList ioctl error return: 9
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:82 xioctl ioctl: fd=-1, req=-2140645888
[ WARN:0@0.047] global obsensor_stream_channel_v4l2.cpp:138 queryUvcDeviceInfoList ioctl error return: 9
[ERROR:0@0.047] global obsensor_uvc_stream_channel.cpp:156 getStreamChannelGroup Camera index out of range
Traceback (most recent call last):
  File "/home/snowpool/aw10s/kao.py", line 22, in <module>
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.error: OpenCV(4.8.0) /io/opencv/modules/imgproc/src/color.cpp:182: error: (-215:Assertion failed) !_src.empty() in function 'cvtColor'

となる

とりあえずリモートでopencvからやる

import cv2
bgr = cv2.imread(‘image.jpg')
cv2.imshow("", bgr)
cv2.waitKey(0)
cv2.destroyAllWindows()

vim test_opencv.py

として保存

https://www.kkaneko.jp/tools/ubuntu/opencv.html
を参考に実行

しかし

 bgr = cv2.imread('image,jpg')
[ WARN:0@168.990] global loadsave.cpp:248 findDecoder imread_('image,jpg'): can't open/read file: check file path/integrity
>>> 

となる

pyenvのPythonと組み合わせるOpenCVのビルド: Ubuntu-22.04編
を参考に

#!/usr/bin/env python

import os
import sys
sys.path.insert(0, f"{os.environ['HOME']}/dev-root/opencv4/lib/python3.11/site-packages")
import cv2

print(cv2.__version__)

を実行したら

4.8.0

となるので
これは問題ないみたい

https://www.kkaneko.jp/tools/ubuntu/opencv.html
を参考に

import cv2
bgr = cv2.imread('fruits.jpg')
cv2.imshow("", bgr)

まで実行すると

cv2.error: OpenCV(4.8.0) /io/opencv/modules/highgui/src/window.cpp:1272: error: (-2:Unspecified error) The function is not implemented. Rebuild the library with Windows, GTK+ 2.x or Cocoa support. If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config, then re-run cmake or configure script in function 'cvShowImage'

となる

これを検索すると
https://qiita.com/tik26/items/a75e03e523926cd2f059
にそれらしいものがあったので
一度OpenCV をアンインストール

sudo apt update
sudo apt install -y libgtk2.0-dev pkg-config

pip3 install opencv-python

今度は

qt.qpa.xcb: could not connect to display 
qt.qpa.plugin: Could not load the Qt platform plugin "xcb" in "/home/snowpool/.local/lib/python3.10/site-packages/cv2/qt/plugins" even though it was found.
This application failed to start because no Qt platform plugin could be initialized. Reinstalling the application may fix this problem.

Available platform plugins are: xcb.

中止 (コアダンプ)

となる

エラーメッセージを検索すると
QtとOpenCVの両方をインストールするとエラーが発生する[Python]
https://blog.nplpl.com/421
によれば
どうやら双方にGUI機能が含まれているから競合しているらしいので
opencv-python をアンインストール
GUI機能を含まない、OpenCVのヘッドレス版をインストール
でOKらしい

pip uninstall -y opencv-python
pip3 install opencv-python-headless

今度は

cv2.error: OpenCV(4.8.0) /io/opencv/modules/highgui/src/window.cpp:1272: error: (-2:Unspecified error) The function is not implemented. Rebuild the library with Windows, GTK+ 2.x or Cocoa support. If you are on Ubuntu or Debian, install libgtk2.0-dev and pkg-config, then re-run cmake or configure script in function 'cvShowImage'

となる

https://keep-loving-python.hatenablog.com/entry/2023/02/05/110149
によれば

python -m pip install opencv-python==4.6.0.66 --force-reinstall

というように

--force-reinstall 

すればOKらしいが
その前に一度cmake をもう一度やってからにしてみる

とりあえずパスを調べる
ChatGPT によれば

import cv2
print(cv2.__file__)


Pythonでインストールされている場合のパスが出る
/usr/local/lib/python3.10/dist-packages/cv2/__init__.py

次にapt などの場合

sudo dpkg -L libopencv-dev

で表示

/.
/usr
/usr/bin
/usr/bin/opencv_annotation
/usr/bin/opencv_interactive-calibration
/usr/bin/opencv_model_diagnostics
/usr/bin/opencv_version
/usr/bin/opencv_visualisation
/usr/bin/opencv_waldboost_detector
/usr/lib
/usr/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu/cmake
/usr/lib/x86_64-linux-gnu/cmake/opencv4
/usr/lib/x86_64-linux-gnu/cmake/opencv4/OpenCVConfig-version.cmake
/usr/lib/x86_64-linux-gnu/cmake/opencv4/OpenCVConfig.cmake
/usr/lib/x86_64-linux-gnu/cmake/opencv4/OpenCVModules-release.cmake
/usr/lib/x86_64-linux-gnu/cmake/opencv4/OpenCVModules.cmake
/usr/lib/x86_64-linux-gnu/pkgconfig
/usr/lib/x86_64-linux-gnu/pkgconfig/opencv4.pc
/usr/share
/usr/share/doc
/usr/share/doc/libopencv-dev
/usr/share/doc/libopencv-dev/copyright
/usr/share/licenses
/usr/share/licenses/opencv4
/usr/share/licenses/opencv4/SoftFloat-COPYING.txt
/usr/share/man
/usr/share/man/man1
/usr/share/man/man1/opencv_createsamples.1.gz
/usr/share/man/man1/opencv_haartraining.1.gz
/usr/share/man/man1/opencv_performance.1.gz
/usr/share/man/man1/opencv_traincascade.1.gz
/usr/share/doc/libopencv-dev/changelog.Debian.gz

確かソースビルドのはず


メモを見たが
https://www.kkaneko.jp/tools/ubuntu/ubuntu_opencv.html#S1
にあるようなログ
つまり
Make のオプションがhistoryコマンドで出ない
つまりpythonでは入っているが
Make して入っていないようだ

次にVNC接続し実験

ubuntuで

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

を実行後

Mac の場合
Finder から
移動 > サーバーに接続で
vnc://192.168.1.69:5901
で接続

すると

import cv2
bgr = cv2.imread('fruits.jpg')
cv2.imshow("", bgr)

まで実行すると

Failed to load module "canberra-gtk-module"

とエラーが変わる

これを検索

OpenCVを実行するとでてくるFailed to load module “canberra-gtk-module”というエラーの対処法
によれば

sudo apt-get install libcanberra-gtk*

でOK

これで
再度

import cv2
bgr = cv2.imread('fruits.jpg')
cv2.imshow("", bgr)
waitKey(0)

でフルーツ画像が表示される

消すには

cv2.destroyAllWindows()

を実行

とりあえずVNCではできるけど
Ssh Xポートフォワーディングでやるのは今は無理っぽいのがわかった

現在地の明日の天気を取得

現在地の明日の天気を取得

毎回アレクサで聴くのは面倒なので
部屋に入った時、顔認識したら明日の天気を知らせるようにする

夜なら明日の天気
朝なら今日の天気とする

なので音声合成と画像認識、顔認識ができればOK

とりあえず明日の天気の取得をする

とりあえずChatGPT で調べる

Weather APIを使うようなので

無料で使える天気API「Free Weather API」の利用登録とキー発行手順
を参考に

https://www.weatherapi.com/signup.aspx
から登録しようとしたら
502 Bad Gateway
となるので

OpenWeatherMapAPI
を使うことにする

openweathermap.orgのAPIを使って天気表示

天候・気温予測サービス “OpenWeather” のAPIを活用する
を参考に

まずはAPI key の取得

https://home.openweathermap.org/users/sign_up
からできる

規約には
We will use information you provided for management and administration purposes, and for keeping you informed by mail, telephone, email and SMS of other products and services from us and our partners. You can proactively manage your preferences or opt-out of communications with us at any time using Privacy Centre. You have the right to access your data held by us or to request your data to be deleted. For full details please see the OpenWeather Privacy Policy.

I am 16 years old and over
I agree with Privacy Policy, Terms and conditions of sale and Websites terms and conditions of use

I consent to receive communications from OpenWeather Group of Companies and their partners:

System news (API usage alert, system update, temporary system shutdown, etc)
Product news (change to price, new product features, etc)
Corporate news (our life, the launch of a new service, etc)

日本語にすると
当社は、お客様から提供された情報を管理および管理の目的で使用し、また当社および当社のパートナーからの他の製品やサービスについて郵便、電話、電子メール、SMS でお客様に通知し続けるために使用します。お客様は、プライバシー センターを使用して、いつでも自分の設定を積極的に管理したり、当社とのコミュニケーションをオプトアウトしたりできます。あなたには、当社が保有する自分のデータにアクセスする権利、または自分のデータの削除を要求する権利があります。詳細については、OpenWeather プライバシー ポリシーをご覧ください。

私は16歳以上です
プライバシー ポリシー、販売条件、およびウェブサイトの利用条件に同意します
私は、OpenWeather Group of Companies およびそのパートナーからの連絡を受け取ることに同意します。

システムニュース(API使用状況のアラート、システムアップデート、システムの一時シャットダウンなど)
製品ニュース(価格変更、新製品特長など)
企業ニュース(私たちの生活、新サービスの開始など)

登録しようとしたら
既に去年登録していた….

キーは
Gmailに送られていてた

これを元に
https://qiita.com/K_Nemoto/items/51e124b3628106c6ef0a#apiを活用する
を参考に

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
都市名: Kawai
気温: 6.65
体感気温: 4.12
最低気温: 6.65
最高気温: 6.65
気圧: 1021
湿度: 60
風速: 3.58
風の方角: 288

となり入力した郵便番号の今日の天気が出る

とりあえず目的は今日、明日の天気の取得

他にも三時間ごとの天気とかもあるけど
今は不要
後々サーバー作って手持ちのスマホとかのGPSとリンクして
現在地の三時間ごとの天気で雨とか霧が出るなら注意とか
移動予定のところで滞在時間内に雨になりそうなら表示する感じか

パラメータとしては
https://hibi-update.org/other/openweathermap-api/
がわかりやすい

次に現在地のものを調べてみた

https://qiita.com/iwasan06/items/94db02186b17bf2d09fc
にあるけど
.erb なのでこれはrailsコード

位置情報取得にはGCPを使ってるが高いので別のものにする

https://zenn.dev/amuro/articles/96f61aff90e9da
にはスマホアプリへの現在地の実装方法が載ってるけど
今じゃない

https://note.com/ai_frontline/n/na22bd0ed7870
だとCHAT GPTと組み合わせだけど
それじゃない

むしろ
https://3pysci.com/openweathermap-5/
によれば

https://api.openweathermap.org/data/2.5/onecall?lat=
{lat}&lon={lon}&exclude={part}&appid={API key}

 となっているので

https://api.openweathermap.org/data/2.5/onecall?lat=33.44&lon=-94.04&appid={API key}

というように
緯度経度とAPI key があれば可能らしい
これなら現在地で取得可能

ということで
Pythonで現在地の緯度経度を取得するコードを作る

しかし考えた結果、現在地取得は不要
自宅の今日あすの天気と移動さきは移動さきの地名を入れるので
緯度経度で出すことはほぼない

次はopencv関連をやる

YouTube live カメラ動画をopencvで表示

YouTube live カメラ動画をopencvで表示

静岡県の薩埵峠のyoutubeライブ画像をopencv で表示する

当初は

YouTube動画をOpenCVでキャプチャするスクリプト
を参考に実行したがエラーとなる

YouTubeのライブ配信をOpenCVで再生する
も同様にエラーとなる

ChatGpt で
opencv でYouTubeライブカメラの画像を表示するPythonコード
を表示し実行したが
これもエラー

ERROR: Unable to extract uploader id; please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; see  https://yt-dl.org/update  on how to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.
Traceback (most recent call last):
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/YoutubeDL.py", line 815, in wrapper
    return func(self, *args, **kwargs)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/YoutubeDL.py", line 836, in __extract_info
    ie_result = ie.extract(url)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/extractor/common.py", line 534, in extract
    ie_result = self._real_extract(url)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/extractor/youtube.py", line 1794, in _real_extract
    'uploader_id': self._search_regex(r'/(?:channel|user)/([^/?&#]+)', owner_profile_url, 'uploader id') if owner_profile_url else None,
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/extractor/common.py", line 1012, in _search_regex
    raise RegexNotFoundError('Unable to extract %s' % _name)
youtube_dl.utils.RegexNotFoundError: Unable to extract uploader id; please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; see  https://yt-dl.org/update  on how to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/youtuvelive.py", line 36, in <module>
    display_youtube_stream(youtube_url)
  File "/Users/snowpool/aw10s/youtuvelive.py", line 11, in display_youtube_stream
    info_dict = ydl.extract_info(url, download=False)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/YoutubeDL.py", line 808, in extract_info
    return self.__extract_info(url, ie, download, extra_info, process)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/YoutubeDL.py", line 824, in wrapper
    self.report_error(compat_str(e), e.format_traceback())
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/YoutubeDL.py", line 628, in report_error
    self.trouble(error_message, tb)
  File "/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/youtube_dl/YoutubeDL.py", line 598, in trouble
    raise DownloadError(message, exc_info)
youtube_dl.utils.DownloadError: ERROR: Unable to extract uploader id; please report this issue on https://yt-dl.org/bug . Make sure you are using the latest version; see  https://yt-dl.org/update  on how to update. Be sure to call youtube-dl with the --verbose flag and include its complete output.

となる

youtube_dlライブラリがYouTubeのビデオからアップローダーIDを抽出することができない
で検索

Pythonのパッケージyoutube_dlで、DownloadErrorが発生する。

を参考に

pip install yt-dlp

でインストールし

import youtube_dl

の代わりに

from yt_dlp import YoutubeDL

でインポート

import cv2
import youtube_dl
from yt_dlp import YoutubeDL


def display_youtube_stream(url):
    ydl_opts = {
        'format': 'best[ext=mp4]',  # mp4 format, you can change this to other formats
        'quiet': True,
    }

    with YoutubeDL() as ydl:
        info_dict = ydl.extract_info(url, download=False)
        video_url = info_dict['url']

    cap = cv2.VideoCapture(video_url)

    if not cap.isOpened():
        print("Error: Could not open stream.")
        exit()

    while True:
        ret, frame = cap.read()
        if not ret:
            print("Failed to grab frame.")
            break

        cv2.imshow('YouTube Live Stream', frame)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cap.release()
    cv2.destroyAllWindows()

# Replace with your YouTube live stream URL
youtube_url = 'https://www.youtube.com/watch?v=6S4qvf97cbQ'
display_youtube_stream(youtube_url)

として保存

これで実行すると
YouTube live画像が表示されます

なお表示しているのは
LIVE】静岡市さった峠 交通の要衝