カテゴリー別アーカイブ: Python

Python kivyで日本語かつListViewを含むkvファイルの使用時にUnicodeDecodeError

最近PythonのGUIフレームワークであるkivyを知ったので、昔作ったCLIスクリプトをデスクトップアプリ化してみています。

その過程で少し特殊なエラーが起きたので、その状況と解決策を書いておきます。

状況

  • kvファイル中にListViewを定義してコードから呼び出し
  • kvファイルはshift-jisで保存(Windowsで動かすため)

その状況で以下のエラーが発生しました。

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x81 in position 448: invalid start byte

ListViewを取り除くと普通に動くため、ListView中で何か問題が起きているようです。

(むしろなぜListViewがある時だけエラーが起きるのか・・・)

解決策

日本語を含むアプリを作成するため、kvファイルの文字コードがshift-jisで保存されていました。

エラー内容と合わせて考えると、ListViewが文字のデコードに失敗しているようです。

そこでkvファイルのエンコーディングを明示的に指定してあげます。

kvファイルの先頭に、

# -*- coding:Shift_JIS -*-

と記述することで解決できました。

ぐぐっても解決法がなかなか見当たらないあたり、まだ日本語でのkivy利用例は少ないのかもしれませんね・・・

Pythonでニコニコ動画ダウンロードするスクリプトを作った

実は以前からPythonでニコニコ動画をダウンロードするスクリプトは作っていたのですが、Python2系でしか動かなかったためリファクタリングついでにPython3に移行してしまいました。

実際に動くものは https://github.com/Sakaki/pyniconico に置いてあります。

githubにも同じような説明を置きましたが、以下のような感じでサクッとダウンロードできます。

$ python download.py -u someone@mail.com -p password sm31606995
ハチ MV「砂の惑星 feat.初音ミク」 ハチ
Downloading: 100%|#######################################################################################|Time: 0:00:09
Saved as ./ハチ_MV「砂の惑星_feat.初音ミク」.mp4

けっこうたくさんのモジュールが必要なので、Virtualenvなどで動かしてあげるといいかもしれません。

また、マイリスト一括ダウンロードもできます。

$ python ../repo/pyniconico/download.py -m ボカロ
【波音リツキレ音源】心做し 【UTAUカバー】 cillia
Downloading: 100%|#######################################################################################|Time: 0:00:03
Saved as ./ボカロ5/【波音リツキレ音源】心做し_【UTAUカバー】.mp4
【初音ミク】 声 【オリジナルPV】 はりー
Downloading: 100%|#######################################################################################|Time: 0:00:16
Saved as ./ボカロ5/【初音ミク】_声_【オリジナルPV】.mp4
【初音ミク】 Initial Song 【オリジナルMV】 40mP
Downloading: 100%|#######################################################################################|Time: 0:00:14
Saved as ./ボカロ5/【初音ミク】_Initial_Song_【オリジナルMV】.mp4

この手のスクリプトはサーバーに負荷をかけるので、利用する量や時間帯などを考えたうえで使ってください。

Twitterの「いいね」から画像だけを抽出するWebサービス

AzureでのDjangoの練習も兼ねて作りました。

https://favorite-images.azurewebsites.net

機能としては、Twitterのいいねから画像が添付されているツイートだけを抽出してリスティングするだけです。

画像をまとめて見たい時、自分のプロフィールからツイートをたどるより便利ではないかと思います。

よければご利用ください・・・

GoでJSONをPOSTしてDjangoで受信してみる

最近Goglandのプレビュー版でコーディングを行っていますが、やっぱりJetBrainsのIDEはいいですね。

おかげで乏しかったGoプロ力が少しずつ付いてきている気がします。


さて、表題の通りですがGoでJSONをPOSTしたい時がありまして、調査も兼ねてテストしてみました。

受信するのはDjangoで、テストのためCSRFは検証しない設定にしています。

参考

コード

Go

package main

import (
    "net/http"
    "fmt"
    "bytes"
)

func main() {
    jsonStr := "{}"

    req, err := http.NewRequest(
        "POST",
        "http://localhost:8000/test",
        bytes.NewBuffer([]byte(jsonStr)),
    )
    if err != nil {
        fmt.Println(err)
    }

    // Content-Type 設定
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println(err)
    }
    defer resp.Body.Close()
}

Django (views.py)

@csrf_exempt
def test(request):
    if request.method == "POST":
        params = json.loads(request.body.decode())
        print(params)
    return HttpResponse("OK")

request.POST[“data”]をクライアント側に設定するみたいな記事も多かったのですが、content-typeを設定しているのですからrequest.bodyから読むのが筋と思いこのような方法を取っています。

あとbyte型はstr()で変換できないという事を忘れてちょっとハマってしまったorz

【Windows】Bluetoothのリンクキーをスクリプトから取得

少し前にBluetoothキーボードをUbuntuとWindowsで共有する話を書きましたが、何度かペアリングしているうちに両方で登録する手順が面倒に感じてきました。

ペアリング作業だけならともかく、レジストリキーを取得するのは面倒すぎます。

そこで、取得作業をなるべく自動化するべくスクリプトを組むことにしました。

環境

  • Ubuntu 16.04 64bit
  • Windows 10 IP 64bit
  • Microsoft Wedge Mobile Keyboard

リンクキーの取り方

以前もお話ししましたが、Bluetoohで使うリンクキーはレジストリエディタから次のキーから取得できます。

(Pythonで組んだのでバックスラッシュが2つになっています・・・)

HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\services\\BTHPORT\\Parameters\\Keys

また、リンクキーを表示する場合は権限の問題でPSExecというツールからコマンドを発行する必要があります。

そこで、PSExecからregeditをCLIモードで呼び出すべく、次のコマンドを発行しました。

管理者権限でPowerShellを起動して、PSExecがあるディレクトリに移動します。

PS ~\PSTools> .\PsExec.exe -s -i REGEDIT /E D:\temp\test.REG "HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\services\BTHPORT\Parameters\Keys
PS ~\PSTools> cat D:\temp\test.REG
Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\services\BTHPORT\Parameters\Keys]

[HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\services\BTHPORT\Parameters\Keys\(BTアダプタのアドレス)]
"(デバイスのMACアドレス)"=hex:(リンクキー)
"(デバイスのMACアドレス)"=hex:(リンクキー)
(略)

のような出力が得られるはずです。

ただ、これだとどのアドレスがどのデバイスに紐づいているかが分からないので、https://macvendors.com/api を使わせてもらってベンダー情報を取得します。

このAPIは、MACアドレスの上位6桁を送信するとベンダーを返してくれるものです。

PS \PSTools> curl "http://api.macvendors.com/7c1e52"


StatusCode        : 200
StatusDescription : OK
Content           : Microsoft
RawContent        : HTTP/1.1 200 OK
                    Access-Control-Allow-Origin: *
                    Content-Length: 9
                    Content-Type: text/plain
                    Date: Tue, 29 Nov 2016 05:41:26 GMT

                    Microsoft
Forms             : {}
Headers           : {[Access-Control-Allow-Origin, *], [Content-Length, 9], [Content-Type, text/plain], [Date, Tue, 29 Nov 2016 05:
                    41:26 GMT]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : System.__ComObject
RawContentLength  : 9

製品情報までは取れませんが、今回はMicrosoftが作っているデバイスが分かればいいので良しとします。

これらを組み合わせてスクリプトを書きます。

スクリプト

PSToolsのディレクトリがスクリプトと同じディレクトリにある状態で実行できます。

#!/usr/bin/env python3.5
# -*- coding:utf-8 -*-

import os
import json
import time
import requests
from subprocess import call

current_dir = os.path.dirname(os.path.abspath(__file__)).replace("/", os.path.sep)


def get_keys():
    cmdfmt = current_dir + '\\PSTools\\PsExec.exe -s -i REGEDIT /E {path} ' \
             '"HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\services\\BTHPORT\\Parameters\\Keys"'
    filename = current_dir + "\\temp.REG"
    cmd = cmdfmt.format(path=filename)
    try:
        call(cmd, shell=True)
        with open(filename, "r") as f:
            reginfo_raw = f.read().replace("\x00", "")
    except Exception as e:
        print(e)
        reginfo_raw = ""
    finally:
        if os.path.exists(filename):
            os.remove(filename)
    reginfo = reginfo_raw.split('"')[1:]
    return [{"macaddr": reginfo[i], "key": reginfo[i+1][5:-2]} for i in range(0, len(reginfo), 2)]


def recv_vendor(macaddr):
    time.sleep(0.5)
    return requests.get("http://api.macvendors.com/" + macaddr[:6]).text

if __name__ == "__main__":
    keys = get_keys()
    for key in keys:
        key.update({"vendor": recv_vendor(key["macaddr"])})
    print(json.dumps(keys, indent=4, separators=(',', ': ')))

ポイントはPsExecの出力ファイルがUnicode形式だったので、f.read()する時にreplace(“\x00”, “”)しているあたりですかね。。

もっと行儀のよい方法があったら教えていただけるとありがたいです。

これを実行すると、

PS \regshow> python .\regedit.py

PsExec v2.2 - Execute processes remotely
Copyright (C) 2001-2016 Mark Russinovich
Sysinternals - www.sysinternals.com


REGEDIT exited on SAKAKI-MOBILE with error code 0.
[
(略)
    {
        "macaddr": "(デバイスのMACアドレス)",
        "key": "(リンクキー)",
        "vendor": "Microsoft"
    }
]

いい感じに自動化できました。

これからはじゃんじゃんOS間でペアリングしていきますよ!

【Python】Amazonにスクリプトでログインしようとした話

結果失敗していますが、一応調べたことを残しておきます。


Amazonにログインするには、次のフォームデータが必要らしいです。

['email', 'password', 'openid.return_to', 'prevRID', 'openid.pape.max_auth_age', 'openid.assoc_handle', 'openid.ns', 'openid.ns.pape', 'pageId', 'appActionToken', 'openid.identity', 'create', 'appAction', 'openid.mode', 'openid.claimed_id', 'metadata1']

しかし、最後のmetadata1がなんかいろんなことをやらないと取得できないらしく、このページに取り方が書いてあるっぽいのですが、正直めんどくさくなったので読んでないですw

この値はfwcim.profile();みたいな関数を呼べると生成できるらしいのですが、Amazonさんからはログインするなという強い意志が感じ取れましたしやめておきます・・・

ログインせずに目的を達成する方法がないかやってみようと思います。


以下、書きかけのコードを供養しておきます。

途中でNoneを返しているのはログインクッキーを返す予定だったからです。

#!/usr/bin/env python3.5
# -*- coding:utf-8 -*-

import requests
import re
import json
from bs4 import BeautifulSoup


def exec_login(auth):
    username = auth["username"]
    password = auth["password"]
    session = requests.session()
    # トップページにアクセスしてクッキーとログインページURLを貰ってくる
    res = session.get("https://www.amazon.co.jp/")
    cookies = res.cookies
    urls = re.findall("a href='.*?'", res.text)
    url = None
    for url in urls:
        if "/gp/navigation/redirector.html" in url:
            break
        else:
            url = None
    if url is None:
        return None
    # ログインページにアクセスしてトークンを貰う
    res = session.get("https://www.amazon.co.jp/" + url[9:-1], cookies=cookies)
    cookies = res.cookies
    soup = BeautifulSoup(res.text, "html.parser")
    login_form = soup.find("form", {"name": "signIn"})
    login_url = login_form["action"]
    hidden_elems = login_form.find_all("input", {"type": "hidden"})
    # 送信パラメータに追加
    login_params = {}
    for elem in hidden_elems:
        login_params[elem["name"]] = elem["value"]
    login_params["email"] = username
    login_params["password"] = password
    # ログイン実行
    res = session.post(login_url, data=login_params, cookies=cookies)
    print(res.text)

if __name__ == "__main__":
    with open("./auth.json") as f:
        auth = json.loads(f.read())
    exec_login(auth)

PQI Air Card向けにPythonをクロスコンパイルしてみた

この記事はこのページで解説されている内容を執筆時点で行う方法を書いたものです。

環境はUbuntu 16.04 64bitで、マシンはVenue 11 Pro 7140を使いました。

発端

PQI Air Cardを使ってデジカメで撮影した画像をowncloudに自動同期したいと思い立ち、自動同期に必要なcurlをPQI Air Card向けにクロスコンパイルする方法を調べました。

その課程でPythonのクロスコンパイル方法を解説したサイトにたどり着き、クロスコンパイルの流れを確認するためにPythonも導入することにしました。

上記サイトの解説ではうまく行かない部分もあったので、そのあたりも含め書いていきます。

なお、curlをコンパイルする記事は新しい記事にあります。

ツールチェーンのダウンロード

PQI Air Card向けのバイナリをコンパイルするにはARM向けのツールチェーンが必要ですが、ダウンロード先を探すのに苦労しました。

Arch LinuxのWikiのリンクが生きていましたので、ここからダウンロードしました。

しかし、公式サイトでは公開停止になっているので何故ダウンロードできるか(また、ダウンロードしていいのか)は不明です。

私も探して無かったら諦めようと思っていました。

展開してパスを通す

これは、上のサイトとほぼ同じ作業になります。

$ tar xf arm-2014.05-29-arm-none-linux-gnueabi-i686-pc-linux-gnu.tar.bz2
$ mv arm-2014.05 ~/arm-dev-sc
$ ln -s ~/arm-dev{-sc,}
$ echo 'export PATH=$PATH:~/arm-dev/bin' >> ~/.bashrc
$ source ~/.bashrc

ディレクトリ名がちょっと変わっていますね。なんでだろうか。。

これでビルドするためのツールにパスが通ったので、次に進みます。

Pythonにパッチを当てる

ビルド用のPythonをダウンロードし、パッチを当てます。

$ wget http://www.python.org/ftp/python/2.7.3/Python-2.7.3.tar.bz2
$ wget https://raw.githubusercontent.com/sjkingo/python-arm-xcompile/master/files/Python-2.7.3-xcompile.patch

パッチのリンクが切れていたので、検索してgithubにあった物を使わせていただきました。

普通にビルドしてから、パッチを当てます。

$ tar xf Python-2.7.3.tar.bz2 && cd Python-2.7.3
$ ./configure
$ make python Parser/pgen
$ mv python buildpython
$ mv Parser/pgen buildpgen
$ make distclean
$ patch -p1 < ../Python-2.7.3-xcompile.patch

いよいよ、ARM向けにビルドします。

ARM向けにビルド

まずはconfigureですね。

$ CC=arm-none-linux-gnueabi-gcc CXX=arm-none-linux-gnueabi-g++ AR=arm-none-linux-gnueabi-ar RANLIB=arm-none-linux-gnueabi-ranlib BASECFLAGS=-mcpu=arm926ej-s \
./configure --host=arm-none-linux-gnueabi --build=x86_64-linux-gnu --prefix=/usr

しかし、ここでエラー。

configure.logを見てみると、次の1文が。。

/home/sakaki/arm-dev-sc/bin/arm-none-linux-gnueabi-gcc: No such file or directory

あるぇ・・・ls -laとかすると普通にファイル自体はあるんですけどね。。

しかし、実行するとNo such file or directoryのエラーが出ます。

$ ls /home/sakaki/arm-dev-sc/bin/arm-none-linux-gnueabi-gcc
/home/sakaki/arm-dev-sc/bin/arm-none-linux-gnueabi-gcc
$ /home/sakaki/arm-dev-sc/bin/arm-none-linux-gnueabi-gcc
/home/sakaki/arm-dev-sc/bin/arm-none-linux-gnueabi-gcc: No such file or directory

あっ、そういえばtoolchainのファイル名にi686ってありましたよね。。

これ32bit向けのバイナリじゃないですか。

というわけで、ここを参考に32bit向けのバイナリを実行できるようにします。

sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386

これで./configureしたら無事に通ったので、makeします。

あとは先人のサイトと全く同じ手順で問題ありませんでした。

$ make HOSTPYTHON=./buildpython HOSTPGEN=./buildpgen CROSS_COMPILE=arm-none-linux-gnueabi- CROSS_COMPILE_TARGET=yes HOSTARCH=arm-none-linux-gnueabi BUILDARCH=x86_64-linux-gnu
$ make install HOSTPYTHON=./buildpython CROSS_COMPILE=arm-none-linux-gnueabi- CROSS_COMPILE_TARGET=yes prefix=`pwd`/_install
$ cp -rf _install{,_strip}
$ ls -1 _install_strip/lib/python2.7/lib-dynload/*.so | xargs -n1 arm-none-linux-gnueabi-strip

これでビルドは完了です。

手元にPQI Air Cardが無いのでバイナリの転送は後で行いますが、PQI Air Cardで動作する物をコンパイルできる事が確認できました。

さーて、次はいよいよcURLをコンパイルするぞ!!

Python3で音楽ファイルのタグ付け(mutagen・Pillow)

やりたかったこと


ニコニコ動画からダウンロードしたボカロ曲が大量にあるのですが、Pythonでカバーアートを付けようと思いスクリプトを組んでみました。

開発環境がWindowsだったのでいろいろと苦労させられましたが、無事にそれっぽい流れを実現することができました。

ちなみに、タグ付け後はWindowsのエクスプローラでこんな感じにきれいに表示されます。嬉しい!!

vocatag


流れ


まずはrequestsでニコ動からサムネイルを取得します。

その後、Pillowを使って画像のサイズを取得し、正方形にクロップします。

最後にmutagenを使ってタグ付けをします。

本当はeyeD3を使いたかったのですが、Python3非対応だったので諦めました。


コード


import requests
from mutagen.id3 import ID3, APIC
from mutagen.id3 import ID3NoHeaderError
from PIL import Image
from io import BytesIO

def add_tag(file_path, thumbnail_url):
    try:
        tags = ID3(file_path)
    except ID3NoHeaderError:
        print("Adding ID3 header.")
        tags = ID3()
    retry = 3
    while retry >= 0:
        try:
            coverart_page= requests.get(thumbnail_url + ".L")
            if coverart_page.status_code == 404:
                coverart_page= requests.get(thumbnail_url)
            jpeg_file = BytesIO(coverart_page.content)
            image_processor = Image.open(jpeg_file)
            size = image_processor.size
            smaller, larger = size if size[0] < size[1] else size[::-1]
            box = ((larger-smaller)/2, 0, (larger-smaller)/2+smaller, smaller)
            cropped = image_processor.crop(box)
            temp = BytesIO()
            cropped.save(temp, format="JPEG")
            temp.seek(0)
            coverart = temp.read()
            tags["APIC:"] = APIC(
                encoding=3,
                mime='image/jpeg',
                type=3,
                desc='Cover',
                data=coverart
            )
            break
        except requests.ConnectionError as e:
            print(e)
            retry -= 1
    tags.save(file_path, v1=0, v2_version=3)

Pillowはファイルの読み書きを前提に作られていますので、今回のようにファイルを仲介せずにカバーアートを埋め込む場合、BytesIOなどのファイルオブジェクトを仲介する必要があります。(読み込みに関してはバイト文字列からの関数があった気もしますが)

また、ニコニコ動画のサムネイルは大サイズと小サイズの2種類がありますので、requestsで サムネイルURL + .L のURLを取得して404が帰ってきたらリトライして小サイズを持ってくるようにしています。