Go言語でVOICEROID2を操作してみたかった(Windows7)
Go言語を初めてWindowsで使ったよ、初めてWindowsのAPIを使ったよメモ。(Windows7です)
VOICEROID2(結月ゆかり)を買ったので、なんとかして色々連携させてみたかった。
- 出版社/メーカー: AHS
- 発売日: 2017/06/09
- メディア: エレクトロニクス
- この商品を含むブログを見る
画面スクショ
うまくいった方法
喋らせたい文字をクリップボードに入れておいて、Ctrl+Vで貼り付けて、F5キーショートカットで再生して喋らせるやり方。
なんとかできたけど、いろいろ制約ができてしまった。
逆にうまくいなかった方法
SendMessage()やPostMessage()で文字を送ったり制御コードを送るやり方。
たぶんこちらの方が正攻法だと思うのだけど…。
なんでうまくいかなかったかはちょっとよくわからなかったです…。
VOICEROID2にCtrl+Vするコード
package main import ( "fmt" "syscall" "github.com/AllenDang/w32" ) const voiceroid2WindowTitle = "VOICEROID2" var mainHwnd w32.HWND func init() { // ウィンドウタイトルからVOICEROID2のウィンドウハンドルを取得する mainHwnd = findWindow(voiceroid2WindowTitle) if mainHwnd == 0 { mainHwnd = findWindow(voiceroid2WindowTitle + "*") if mainHwnd == 0 { panic("cannot find window of VOICEROID2") } } } func findWindow(wName string) w32.HWND { return w32.FindWindowW(nil, syscall.StringToUTF16Ptr(wName)) } func SayYukari() { w32.SetForegroundWindow(mainHwnd) ctlDown, ctlUp := inputKeys(w32.VK_CONTROL, 0x001D) bsDown, bsUp := inputKeys(w32.VK_BACK, 0x000E) aDown, aUp := inputKeys(0x0041, 0x001E) vDown, vUp := inputKeys(0x0056, 0x002F) f5Down, f5Up := inputKeys(w32.VK_F5, 0x003F) ipts := []w32.INPUT{ // Ctrl + A ctlDown, aDown, aUp, ctlUp, // BackSpace bsDown, bsUp, // Ctrl + V ctlDown, vDown, vUp, ctlUp, // F5 f5Down, f5Up, } ret := w32.SendInput(ipts) fmt.Println("w32.SendInput result:", ret) } func inputKeys(vk, scan uint16) (w32.INPUT, w32.INPUT) { down := w32.INPUT{ Type: w32.INPUT_KEYBOARD, Ki: w32.KEYBDINPUT{ WVk: vk, WScan: scan, }, } up := w32.INPUT{ Type: w32.INPUT_KEYBOARD, Ki: w32.KEYBDINPUT{ WVk: vk, WScan: scan, DwFlags: 0x0002, }, } return down, up }
だめだったパターン…
// Aを送る w32.SendMessage(mainHwnd, w32.WM_KEYDOWN, 0x0041, 1) w32.SendMessage(mainHwnd, w32.WM_KEYUP, 0x0041, 0x6000001)
やってみたメモ
WindowsのAPIを使ったといっても、 AllenDang/w32 を使わせてもらっただけである。
幸いこの中にあるものだけでうまくできた。
KEYBDINPUT構造体を押したいキーの分だけ作って、SendInput()で送る。
バーチャルキーコード(wvk)は、msdnのページを見たり、よくあるやつ(Aは41とか)ですぐわかったけど、スキャンコード(wscan)はよくわからなかったので、spy++で実際に送られてる中身を覗き見てそれをそのまま使った。
SendInput()はSendMessage()などと違って、ウィンドウハンドルを指定できない。
つまりその時点でアクティブになっているウィンドウに対してキーが送られる。
なので、邪道な感じだけど、喋らせるときにVOICEROID2ウィンドウをSetForegroundWindow()でアクティブにしている。
更に邪道な感じで、Ctrl+Vがうまく動くためには、テキスト入力枠にフォーカスが当たってCtrl+Vが押せる状態でなければならない。
(これどうしたらいいんだろうか?)
VOICEROID2は中身が一個のウィンドウっぽくて、メモ帳で試したときとは違い、入れ子のウィンドウになっていなかった。メモ帳は"Edit"というクラス名の子ウィンドウにキーをSendMessage()すれば動いた。VOICEROID2には唯一の親ウィンドウに送ればいいということかと思ったが、そういう感じでもないっぽい? よくわからなかった。
参考URL
- GitHub - AllenDang/w32: A wrapper of windows apis for the Go Programming Language.
- FindWindow 関数
- SendInput 関数
- GetLastError 関数
- PostMessage 関数
- https://msdn.microsoft.com/ja-jp/library/cc411022.aspx;title
- KEYBDINPUT structure (Windows)
- Virtual-Key Codes (Windows)
- Simulating a Ctrl-V keystroke in Win32 (C or C++) using SendInput | ad hocumentation • n. fast, instinctive documentation of ideas and solutions.
- C言語でwin32apiを使ってnotepadにpostmessageでcontrol aを送りたいが方法が分からない。
ZoomのAPIを使ってミーティングを作成する
Zoomメモ。
会社ではリモートワークしてるみんな(自分含む)とミーティングするのに、Zoomが使われている。
ZoomにはAPIがあるので、ミーティング用のURL作成処理をGo言語で作ってみた。
今回は60日トライアルでデベロッパー登録をした。
https://developer.zoom.us/
https://zoom.github.io/api/#create-a-meeting
API KEYとAPI Secret(とAccess Token)を作って控えておく。
ちなみにバージョンはv2を使う。
JWTを使う
import jwt "github.com/dgrijalva/jwt-go" const ZOOM_API_KEY = "xxx" const ZOOM_API_SECRET = "xxx" token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "iss": ZOOM_API_KEY, "exp": fmt.Sprintf("%d", time.Now().Add(1*time.Minute).Unix()), }) tokenString, err := token.SignedString([]byte(ZOOM_API_SECRET)) fmt.Println(tokenString)
1分間だけ有効なJWTができた。
自分のuser_idを知る
$ curl -H 'Authorization: Bearer xxxxxx' https://api.zoom.us/v2/users | jq . { "page_count": 1, "page_number": 1, "page_size": 30, "total_records": 1, "users": [ { "id": "xxx", "first_name": "太郎", "last_name": "山田", "email": "taro.yamada@example.com", "type": 1, "pmi": 000, "timezone": "Asia/Tokyo", "dept": "", "created_at": "xxx", "last_login_time": "xxx", "last_client_version": "xxx" } ] }
自分のuser_idが分かった。
ミーティングを作る
package main import ( "encoding/json" "log" "net/http" "strings" ) const USER_ID = "xxx" const CREATE_MEETING_URL = "https://api.zoom.us/v2/users/" + USER_ID + "/meetings" const JWT = "xxx" type Meeting struct { UUID string `json:"uuid"` ID int `json:"id"` HostID string `json:"host_id"` Topic string `json:"topic"` Type int `json:"type"` Duration int `json:"duration"` Timezone string `json:"timezone"` CreatedAt time.Time `json:"created_at"` StartURL string `json:"start_url"` JoinURL string `json:"join_url"` Settings struct { HostVideo bool `json:"host_video"` ParticipantVideo bool `json:"participant_video"` CnMeeting bool `json:"cn_meeting"` InMeeting bool `json:"in_meeting"` JoinBeforeHost bool `json:"join_before_host"` MuteUponEntry bool `json:"mute_upon_entry"` Watermark bool `json:"watermark"` UsePmi bool `json:"use_pmi"` ApprovalType int `json:"approval_type"` Audio string `json:"audio"` AutoRecording string `json:"auto_recording"` EnforceLogin bool `json:"enforce_login"` EnforceLoginDomains string `json:"enforce_login_domains"` AlternativeHosts string `json:"alternative_hosts"` } `json:"settings"` } func main() { payload := strings.NewReader(`{"type":1}`) // 1は通常の簡易ミーティング req, err := http.NewRequest("POST", CREATE_MEETING_URL, payload) if err != nil { panic(err) } req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer "+JWT) // ヘッダに設定 client := http.DefaultClient res, err := client.Do(req) if err != nil { panic(err) } if res.StatusCode != 201 { log.Fatal("status code is not 201!", res.Status) } defer res.Body.Close() var meeting Meeting err = json.NewDecoder(res.Body).Decode(&meeting) if err != nil { log.Fatal("json parse error!", err) } log.Println(meeting.JoinURL) }
start_urlとjoin_urlの違いがいまひとつよくわからなかった(ドキュメントにも記載なし?)が、join_urlにアクセスしてmacのzoom.usアプリが立ち上がると、普通にミーティングができて開始時刻もそこからだったので、これで問題ないのかな、という感じ。
AWS S3から全部ではないけど大量に何個か取ってきたい
困ったのでメモ
背景
2つの解決策
aws s3 cp s3://my-bucket/mydir/foo/001.png . aws s3 cp s3://my-bucket/mydir/foo/002.png . aws s3 cp s3://my-bucket/mydir/foo/003.png . ...
- --includeオプションを複数連ねて大量に作って、1つのコマンドとして実行する
aws s3 cp s3://my-bucket/mydir/foo/ . --recursive \ --exclude '*' \ --include 001.png \ --include 002.png \ --include 003.png \ ...