WPP
Kivy を Pyinstaller で Windows Exe 化するときにした諸々

久々に Kivy でプログラムをつくったのですが.Exe するときにハマったのでメモ。自分の状況にピッタリマッチする記事がなかなかなかったので誰かの役にたつかも。

Kivy で UI をつくり .mp4 ファイルの生成するアプリを作成しました。 .py から import 部分を抜き出すとこんな感じになっています。

ソースは app.py という名前です。

ざっくりとしたモジュール構成は、
Kivy のいくつかの UI 部品 (ビデオ pane を含む) を配置し、 plyer でポップアップ通知がでるというものです。

pyinstaller のインストール方法や作りたい実行ファイルと同じ OS でやる必要があるとかそういったことは特に解説しません。

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.video import Video
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.popup import Popup
from kivy.uix.filechooser import FileChooserListView

from plyer import notification

# このへんは、 pyinstaller が自動的に "よしな" にししてくれます。
import os
import platform
from typing import Callable, Optional
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Optional
from enum import Enum

pyinstaller 自体はとても簡単に使えるのでおむろに pyinstall --onefile app.py してみます。app.exe と app.spec ファイルが生成されます。

が、 app.exe を実行してみるといろいろファイルが足りないようで追加していききます。

sdl2 と angle を取り込む

詳細な仕組みはよくわかっていませんが、Kivy で GUI を表示するとsdl2 か glew を使うものらしいです。先程つくった .exe は sdl を使っているようなのでそれを明示してやります。

import os
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'

from kivy_deps import sdl2, angle

deps = []
deps += [Tree(p) for p in sdl2.dep_bins]
deps += [Tree(p) for p in angle.dep_bins]

exe = EXE(
  ...
  # [] のあたりに入れる
  *deps,
  ...
)

app.py にも KIVY_GL_BACKEND = ‘angle_sdl2’ を設定してやります。

今度は、編集した app.spec を使って pyinstaller app.spec を実行しします。 誤ってさっきと同じ app.py を指定するとせっかく変更した app.spec が上書きされるので注意してください。

gstreamer の取込み

app.exe を実行してみると、どうやら gstreamer 関係の初期化に失敗するようです。

なので app.spec に追記していきます。

ビデオ再生には、ffpyplayer が使われるという記述もみうけられるのですが、試した環境では gstreamer が使われていたので素直にそれに従います。
ffpyplayer の場合にも同じ様に import と deps に追加してやれば多分うまくいきます。

import os
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'

from kivy_deps import sdl2, angle

deps = []
deps += [Tree(p) for p in sdl2.dep_bins]
deps += [Tree(p) for p in angle.dep_bins]
deps += [Tree(p) for p in gstreamer.dep_bins]

exe = EXE(
  ...
  # [] のあたりに入れる
  *deps,
  ...
)

再度 pyinstaller app.spec を実行する。

今度は、pywin32 のエラーが出た。pywin32 は おそらく app.py で明示的に Kivy のタイマー処理を使っている箇所がありそこで使われているっぽい。

pywin32 は今までと異なり pyinstaller から collect_all というモジュールをインポートしてこないといけないらしい。

from PyInstaller.utils.hooks import collect_all

pywin32_datas, pywin32_binaries, pywin32_hiddenimports = collect_all('pywin32')

a = Analysis(
  ...
  hidenimports = [
    'win32timezone',
    'win32api',
    'win32con'
  ] + pywin32_hiddenimports
  ...
)

でここまでの内容をまとめると app.spec はこうなった。

# -*- mode: python ; coding: utf-8 -*-

import os
os.environ['KIVY_GL_BACKEND'] = 'angle_sdl2'

from kivy_deps import sdl2, angle, gstreamer
from PyInstaller.utils.hooks import collect_all

deps =[]
deps += [Tree(p) for p in sdl2.dep_bins]
deps += [Tree(p) for p in angle.dep_bins]
deps += [Tree(p) for p in gstreamer.dep_bins]

pywin32_datas, pywin32_binaries, pywin32_hiddenimports = collect_all('pywin32')

a = Analysis(
    ['app-v18.py'],
    pathex=[],
    binaries=[],
    datas=[],
    hiddenimports=[
        'win32timezone',
        'pywintypes',
        'win32api',
        'win32con',
        'plyer',
        'plyer.platforms',
        'plyer.platforms.win',
        'plyer.platforms.win.notification'
    ] + pywin32_hiddenimports,
    hookspath=[],
    hooksconfig={},
    runtime_hooks=[],
    excludes=[],
    noarchive=False,
    optimize=0,
)
pyz = PYZ(a.pure)

exe = EXE(
    pyz,
    a.scripts,
    a.binaries,
    a.datas,
    # set deps
    *deps,
    exclude_binaries=False,
    name='app', # exe の名前なので好きに変えていい
    debug=False,
    bootloader_ignore_signals=False,
    strip=False,
    upx=True,
    console=True,
    disable_windowed_traceback=False,
    argv_emulation=False,
    target_arch=None,
    codesign_identity=None,
    entitlements_file=None,
)

Kivy をパッキングするときは割とハマるので、次回はここをベースにしてなんとかしたい。

にしてもインストーラ系のビルドは、動作確認が面倒で、時間かかるのであまり好きにはなれない。

参考

Build with PyInstaller, cannot find proper OpenGL after lock windows with “Win+L” · Issue #7177 · kivy/kivy

IME辞書を自動で Synology Drive にバックアップ

最近こっている事の一つにIME辞書にショートカットを登録することがある。そうすると IME 辞書をバックアップしたくなる。

IME 辞書そのものは普通のファイルなので単にコピーするだけで OK ですが、まあ面倒くさい。Win11 で試した限り辞書の保存場所を変える方法はなさそう。

自分の 場合辞書は
C:\Users\ユーザー名\AppData\Roaming\Microsoft\IME\15.0\IMEJP\UserDict
に保存されている。

これをなんとかして Synology Drive のフォルダにコピーできればバックアップができて、それを定期的に実行すれば自動バックアップの完成という訳。

NAS がなくとも、この自動コピーの仕組みでバックアップは実現できます。

手順

  1. 辞書ファイルのコピー

まずは、辞書をコピーするスクリプトを作成する。コピーには個人的な好みで cwRsync を使用する。

こんな感じになる。/cygdrive/c/ からはじめると c:\ の意味になるようだ。

rsync -av /cygdrive/c/Users/172369/AppData/Roaming/Microsoft/IME/15.0/IMEJP/UserDict/* /cygdrive/c/synologydriveのフォルダ/

cwrsync (rsync) は実際に試す前に -n を追加して (-avn) で実行する内容を試した方がいい。特にデリートコマンド系は危険。

もし、Windows標準の Robocopoy を使うならこんな感じ。(恥ずかしながらこのコマンド初めて知りました。)

Robocopy.exe C:\Users\172369\AppData\Roaming\Microsoft\IME\15.0\IMEJP\UserDict\ . C:\synologydriveのフォルダ /XO

Robocopy はかなり複雑なのでちょっと難しい印象。それで避けていたのかもしれない。

スクリプトを書く際に注意しなくてはいけないのは後段のタスクスケジューラで実行する時にカレントディレクトリがシステムディレクトリ(確か system32 だったか)になってしまうので .\ とかするとコピーができずに永遠に終わらなくなったりする。

  1. タスクスケジューラに登録

2つの参考リンクをミックスした感じで

[start] -> tasks と入力してタスクスケジューラを起動する。

新しいタスクの作成にすすみ、名前を適当に入れる。
今回は、コピー元、コピー先ともに自分自身がオーナーのユーザーフォルダなので実行するユーザーを変更する必要はない。

[トリガー]タブは、[毎日]、[繰り返し間隔] [1時間] に設定する。

[操作]タブ、
プログラム: C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
引数の追加: 作成したスクリプトのフルパス
自分の環境では ExecutionPolicy を許可ずみなので -ExecutionPolicy Bypass は不要でした。

ちょっと面倒ですがここまですると、 Synology Drive へ定期コピーし Synology Drive が NAS へ同期

することで自動バックアップする一連の流れを設定とすることができました。

参考

cwRsync – Rsync for Windows | itefix.net

Robocopyの基本動作とコマンドオプション解説

Windows10でPowerShellスクリプトをスケジュール実行する – 3テラバイト

タスクスケジューラでPowerShell スクリプトを実行する | Windows 実践ガイド

Windows updateが 0x80240438 で失敗する場合

数週間前から Windows update が失敗する現象が発生していて少し気になっていたが解決したので記録に残す。

現象の発生状況

問題の PC は、普段は Windows ドメイン (アクティブ ディレクトリ)に参加していて色々な設定がよしなにされている(有無を言わせず強制されている)。インターネットには正常に接続できている。

Windows Update を実行すると「更新サービスに接続できませんでした。。。。」というメッセージが表示され、

イベントビューアでは WindowsUpdateClient がエラー 0x80240438 で失敗している。

解決方法

はじめ、不用意にサービスを停止してしまって Windows update が失敗するのだと思い試行錯誤したが原因は他にあった。

Windows update の参照先が WSUS (Windows Server Update Services) に向いていてそこにアクセスできなくなっていることが問題だったようだ。WSUS はざっくりいうと Windows update のダウンロード元を社内サーバーなどに置くことができるソフト。利用すると会社の PC が一斉にアップデートを実施してインターネットのダウンロード帯域を食いつぶすことを防ぐことができる。

HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate キー

デフォルト変更値
DoNotConnectToWindowsUpdateInternetLocations10
WUServer
WUStatusServer

グループポリシーかログイン時のスクリプトで上記レジストリが設定されているっぽいが、それを変更できちゃうのはちょっと解せない。がアップデートできたので OK。

まとめると、何らかの方法で WSUS を使う設定がされており、かつ WSUS にアクセスできない状況で今回のエラーが発生していた模様。エラーコード自身は他の原因とも共有されているらしいので WSUS 関連とは限らないっぽい。

大体、このケースにぶち当たる人は個人ユースでは皆無と思われる。なので検索で見つからないのも無理はない、参照ページがなかったらサービス周りを疑い続けたと思うのでとても助かった。

ついでに依存サービス

Windows update はいくつかのサービスに依存しているようで、サービス表示名のサービス名対応は以下のようになる。sc コマンドで BITS などのサービス名の方を使う。

  • Background Intelligent Transfer service – BITS
  • Windows Update – wuauserv
  • Windows Modules Installer – TrustedInstaller

参照

Windows Update 「更新サービスに接続できませんでした」エラーの解消 – A Memorandum

scoop で adb 34.0.5 をインストールするとエラー “Can’t shim ‘platform-tools\dmtracedump.exe’: File doesn’t exist.”

今日もプチはまりをメモしていきます。

個人的には、choco より scoop のが好きです。思い込みかもしれないが、scoop の方がインストール時にエラーがでることが少ない気がする。まあ、気がするだけかもしれませんが。。。

現象

タイトル通りなのですが、scoop で adb をアップデートしようとすると上記のエラーが発生してコケるっていう問題です。

cant install adb Can’t shim ‘platform-tools\dmtracedump.exe’: File doesn’t exist. [Bug] · Issue #5719 · ScoopInstaller/Scoop

ここで指摘されているように 大本の Platform Tools から dmtracedump.exe が削除されていることが問題のようです。

回避策

@BruceKangCN さんが指摘しているように、adb.json から dmtracedump.exe の行を削除すればいいです。

  1. scoopホーム/bucket/main/bucket/adb.json を開き
  2. 13 行目あたりを削除して保存
  3. $ scoop install (or update) adb

私の場合は /opt 下に scoop のホームをまとめていますが、普通は C:\Users\ユーザー名 下あたりにあるようです、その辺は調べてください。scoop 使う位なので自力で調べられるでしょう。

追記

上記実施すると次回の scoop update に失敗することがわかりました。インストールしたら .json ファイルを元に戻すのが良さそうです。

$ cd [adb.json のあるフォルダ]
$ git restore adb.json

scoop 内部ではどうやら git リポジトリをそのまま使用しているようで、ファイルを変更すると pull できないってエラーが発生します。restore で戻してやると回復します。

余談ですが、scoop とか choco とか一般ユーザーこそ使ったほうが便利だと思うのですが、まわりの非エンジニアの人たちで使っている人を見たことがありません。もったいないと思うんだけどな、まあそれはいいとして。

すでに修正版の PR は作成されているようなので近いうちにマージされるだろからそれまで adb@34.0.4 をインストールしてもいいかもしれません。