プログラミングの最近のブログ記事

例えば、子供向けに、一問一問別ファイルになったなぞなぞ音声ファイルのうちから 3問だけランダムに抜き出して、ひとつの音声ファイルにしてダウンロードさせるとか、まあ、そういう感じのデモ要件があるので、プログラムを作ってみた。

まあ、ffmpeg 使えば一発なんだけどね。

プログラム的には、結合する 3つのファイルをランダムにもとめて、そのファイル名を i オプションと一緒に連結していくだけ。(ひとつのファイルごとに i オプションは必要なので注意)

下のように編集したオプションで ffmpeg を実行するだけだ。
(出力ファイル名を毎回同じにするなら、上書き許可の y オプションもお忘れなく。プログラム内から呼ぶのであれば、ffmpeg は大量のログを標準出力に出すので、loglevel オプションでの抑制もお忘れなく)

/usr/bin/ffmpeg -y  -i /data/11.mp3 -i /deta/7.mp3 -i /data/5.mp3  -loglevel quiet -filter_complex "concat=n=3:v=0:a=1" /data/nazonazo.mp3

赤いところがプログラムの編集が必要なところ。
n=3 は入力ファイルの数で、最後の /data/nazonazo.mp3 が出力ファイル名だ。

連結対象のファイルの個数が変わらないのなら n=3 のところは変更不要だし、毎回同じファイルに書くのなら /data/nazonazo.mp3 も編集不要。

結合したファイルを聞いてみたが、つなぎ目に変なノイズが入ることもなく、なかなかいい感じであった。
まあ、DateTime 型のオブジェクトには AddMonths メソッドがあるので、

Dim hoge_today As DateTime = "2020/03/23"
Dim hoge_addmon As DateTime = hoge_today.AddMonths(1)

とすれば、hoge_addmon には 2020/04/23 がセットされる。

一応、自動で月の最終日の判断はするので、

Dim hoge_today As DateTime = "2020/03/31"
Dim hoge_addmon As DateTime = hoge_today.AddMonths(1)

であれば、hoge_addmon には 2020/04/30 がセットされる。
決して、2020/05/01 であったり、ましてや 2020/04/31 になってしまうわけではない。

なんか、ネット上の情報を見ていると、「.NET って、ちゃんと最終日の判断してるやん!!」「3/31の一ヶ月後が 4/30 になってて感動!」みたいな記事が多い。
いや、それは当たり前なんちゃう?(^^; そんな感動することか!?と思うのだが、VB.NET なんかは、何もプログラミングのことを知らずに入社したプログラマが最初に使う言語ということも多いだろうから、こういう感動をする人も多いのかなあ・・・とか思ったり。

ただこの「自動で最終日をセットしてくれて素敵」は、基準となる年月日が「31日まである月」のみの話である。

Dim hoge_today As DateTime = "2020/02/29"
Dim hoge_addmon As DateTime = hoge_today.AddMonths(1)

の場合、hoge_addmon には 2020/03/29 がセットされる。最終日でもなんでもない。

Dim hoge_today As DateTime = "2020/02/29"
Dim hoge_addmon As DateTime = hoge_today.AddMonths(-1)

なら、2020/01/29 がセットされる。やはり 1月の最終日ではない。

結局、翌月の最終日を求めようと思えば、単純に AddMonths を使うのではなく、

Dim old_mm As String = hoge_today.ToString("MM")
If hoge_today.AddDays(1).ToString("MM") <> old_mm Then
    saisyu_flg = 1
End If

みたいに最終日かどうかをチェックして、最終日だったときは、

Dim days As Integer = DateTime.DaysInMonth(hoge_today.AddMonths(1).Year, theDay.AddMonths(1).Month)
Dim hoge_addmon As DateTime = New DateTime(hoge_today.AddMonths(1).Year, hoge_today.AddMonths(1).Month, days)

みたいにしないとダメ?

すんまへん。VB.NET とかそんなに詳しいわけじゃないんで、もっとこんな簡単な方法が・・・みたいなものがあればご教示ください。>識者の方
そうか(笑)

VB.NET で DBNull を String 変数に代入するときは、ToString() でよかったのか(^^;;;
(あ、もともと DB からも文字型として読み込んだデータの話ね。もちろん数値型のデータは ToString してたよ)

例えば、dt As DataTable に DB から取得した値が入っているとして、それを a As String な変数に代入する際、例外発生を回避するために、

Dim a As String = if (IsDBNull(dt(cnt)("namae")), String.Empty, dt(cnt)("namae"))

とかやりよったわぁ。

Dim  a As String =  dt(cnt)("namae").ToString()

でよかったのね(笑)

ToString メソッドは、DBNull だったら空文字に変換してくれるのか。なぜか、Nothing を渡すものだと思ってた。

ということで、わざわざ If 文で置換してやらなくても、とりあえず(元の変数が String 型だろうが関係なく)ToString() して代入というのがコーディングも簡単でいいね。

早く教えてくれなきゃ(笑)
VisualBasic は文字列補間(変数補間、変数置換)の機能がないので、ある文字リテラルの中に別の変数の値を埋め込もうとすると、

Dim day  As String = "13"
Dim text As String = "死の数字は" & day & "です"

のように、&(+ でもいいのか)で変数を連結してやる必要があったのだが、今のバージョンだと、

Import System.Text
Dim day  As String = "13"
Dim text As String = $"死の数字は{day}です"

のように、文字列内に変数を埋め込むことができる。
文字列を囲むダブルクオーテーションの前に、$(ダラーマーク)を置くことで変数を挿入できるようになる。(System.Text のインポートを忘れずに(笑))

他の言語(C, Perl, PHP, Python...)だと文字列補間はできて当たり前なので、どうにも VisualBasic が使いづらいと考える理由のひとつだったのだが、いつの間にか解決してたのね。

ちなみに Perl なら、

my $day  = "13";
my $text = "死の数字は$dayです";

である。

VB.NET で $ をつけ忘れたら文字列補間は使えないが、これは Perl で文字列をシングルクオーテーションで囲んでしまった場合と同じだ。

[VB.NET]
a = $"これは{b}です"
[Perl]
$a = "これは$bです";

上の二つは同じ意味(文字列補間可能)だし、

[VB.NET]
a = "これは{b}です"
[Perl]
$a = 'これは$bです';

は同じ意味だ。文字列補間はできない。

下の例だと、VB.NET では「これは{b}です」とそのまま出力されるし、Perlでも「これは$bです」とそのまま出力される。

「VB.NET 使いの人」から見たら、「何をいまさら」なんだろうけど、俺のように色々な開発言語の現場を流れ歩き、ひとつの言語を突き詰めて勉強する機会がないと、例えば & で連結するなど(不格好だが)他の手段を知っていればそれを使うし、なかなか知識のアップデートができないのよね。これもそのひとつ。

コーディング基準・規約などできちんと「可読性向上のため、補間文字列を積極的に使用する」などと明記されていれば気づくけど。
以前からこのブログにもたまに書いていたが、LibreOffice Calc でサーバが吐き出したテキストファイルを読み込み集計、最後にその結果を元に印刷処理を行うマクロを Python で書いていたのだが、OpenOffice.org 系オフィススイーツの定番マクロ言語である Basic で書き直すことにした。

理由は色々あるので、別のエントリーにまとめようかな。

とりあえず、今日の日付をセルにセットするだけの簡単な処理を Python と Basic で書いてみたので貼っておく。どちらもまったく同じ動きをする。

def get_today(*args):

    doc = XSCRIPTCONTEXT.getDocument()

    # シートを選択
    sheet = doc.getSheets().getByName('Main')

    # 今日の日付の取得
    dt_now = (datetime.datetime.now()).strftime('%Y/%m/%d')

    # すでにセルに値が入っている時は更新するか確認する
    kaisai_nengetsu = sheet.getCellRangeByName('C3')

    if kaisai_nengetsu.String:
        msg_box = Bridge()
        res = msg_box.run_querydialog(title='開催年月日セット', message='すでに値がセットされています。\n上書きでセットしますか?')

        # res=2 はい res=3 いいえ
        if res != 2:    # 「はい」でなければ終了
            return

    kaisai_nengetsu.String = dt_now

次に Basic で書かれたマクロ。

Option Compatible

Sub get_now_date

Dim objDoc As Object
Dim objSheet As Object
Dim objCell As Object

objDoc = StarDesktop.CurrentComponent
objSheet = objDoc.Sheets.getByName("Main")

' 既にセルに値が入っていたら、上書きするかを聞く       
objCell = objSheet.getCellRangeByName("C3")

If objCell.String <> "" Then
If MsgBox ("すでに値がセットされています。" & vbCrLf & "上書きでセットしますか?", 292, "開催年月日セット") = 7 Then
        ' いいえのボタンが押された場合
return
End If
End If 

' 現在時刻の取得
objCell.String = Format(Now(), "yyyy/mm/dd")

End Sub

以上。まあ、これだけ簡単な処理では特にどっちの言語で書いてもなんの問題もない。
見比べてもあまりおもしろいもんじゃないな(笑)

<追記>
コメントで「文字列リテラル中の \n は効かない」ということを教えていただきましたので、Option Compatible を指定して VBA compatibility モードをオンにし、vbCrLf 定数を連結する形に修正しました。16進文字"\x0D\x0A"を直打ちでもよさげですが、ついでに VBA compatibility モードを試してみたかったので。
VisualBasic から ADO.NET を使用して SQL Server に接続・使用する簡単な例。

SQL Server のインストールや、DB作成、TABLE作成は済んでいること。

(1) データソースの選択と、DB接続設定を行う

    • [表示]→[サーバーエクスプローラー] でサーバーエクスプローラーを表示する。
    • サーバーエクスプローラー上にある DB アイコン(データベースへの接続)をクリック。
    • 「データソースの選択」ダイヤログが開くので、データソースで「Microsoft SQL Server」を選択。データプロバイダーは「.NET Framework SQL Server用データプロバイダー」を選択する。[続行]ボタン押下。
    • 「接続の追加」画面が開くので、「サーバー名」を設定。サーバ名がわからなければ SQL Server Management Studio 等で調べる。うちの場合は「XVIDEOS\SQLEXPRESS」。XVIDEOS は PC名。(一覧から選択するのかと思ったが、フルに手入力しないといけなかった)
    • 「サーバにログオンする」の「認証」を「SQL Server 認証」に変更する。ユーザ名とパスワードが入力可能になるので入力。(あらかじめ SQL Server Management Studio で作成したもの)
    • 「データベースへの接続」の「データベース名の選択または入力」を選択。DB名を入力する。
    • 画面下の「テスト接続」ボタンを押してみて正常に接続されれば「OK」ボタン押下。(うまくいかなければ、DB名やユーザアカウント、パスワードが間違っていないか?)

(2) データソースの登録

    • [表示]→[その他のウィンドウ]→[データソース]でデータソースウィンドウを表示。
    • データソースウィンドウの上の一番左の DB アイコン(データソースの追加)をクリック。
    • 「データソースの種類の選択」画面で「データベース」を選択し「次へ」ボタン押下。
    • 「データベースモデルの選択」画面で「データセット」を選択して「次へ」ボタン押下。
    • 「データ接続の選択」では、対象の「データ接続」を選択し、「はい。重要情報を接続文字列に含めます」を選択し「次へ」ボタン押下。
    • 「接続文字列をアプリケーション構成ファイルに保存する」画面では、そのまま「次へ」ボタン押下。
    • 「データベースオブジェクトの選択」画面では、プログラムから参照する TABLE などをすべて選択する。「完了」ボタン押下。

(3) プログラム開発

サンプル用に、超簡単なプログラムを作る。
ID と NAME という項目を持つ m_user という TABLE を参照し、NAME を画面に出すというプログラム。
例えば、ID='admin' の NAMEは'偉い人'である。

20200701_VB1.jpg

開発する画面は「IDを入力し『参考』ボタン(・・・参照と打ったつもりなのに(^^;)を押すと、下に『名前は○○です』」と表示する」という単純なもの。

「参考」ボタンの Click イベントのソースを下記のとおり書いた。

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Dim sql As String

        'SQLステートメント
        sql = "SELECT name FROM m_user WHERE id='" & TextBox1.Text & "'"

        Label1.Text = ""

        'コネクション指定
        Using connection As New SqlClient.SqlConnection(My.Settings.nandfConnectionString)

            'データコマンドを定義する
            Using command As New SqlClient.SqlCommand(sql, connection)

                'コネクションを開く
                connection.Open()

                'データリーダの生成
                Dim dr As SqlClient.SqlDataReader = command.ExecuteReader()

                'データが存在する場合、label1にセット
                '検索結果は1件だけ
                Do While dr.Read
                    Label1.Text = "名前は " & dr("name") & " です"
                Loop

                'データリーダを閉じる
                dr.Close()

                'コネクションを閉じる
                connection.Close()

            End Using

        End Using

    End Sub

いやあ、ADO.NET はデータソースの選択までのアレコレが煩わしいね。まあ、おかげでソースを書くのは楽なんだけど(笑)

それより、うーむ・・・まだ 5年前にやった VB.NET 案件で DB 利用をどのようにコーディングしたか思い出せない。なんとなく、インタフェースは ADO.NET だったような気がするが・・・
LibreOffice のマクロを Python で書いた場合の、「OK」ボタンが1つだけのダイアログボックスと、「はい」「いいえ」の選択肢のあるダイヤログボックスの使い方。
情報をググってみても、なんか「表示させるまで」の情報や、「OK」ボタン一個だけの場合の情報ばかりが多かったので、具体的な例をあげとこう。

a.セルに値をセットする時、すでに値がセットされていたら上書きするか確認表示

 20200613_dialog1.jpg

b.読み込むファイルが無い時、その旨をアラート表示

 20200613_dialog2.jpg

ソースは以下のとおり。(@ty21kyさんの Qiita の記事を参考に)

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import uno
import datetime
import re
import os

CARD_DATA = '/data/number_cardid.csv'

class Bridge(object):

    def __init__(self):

        self._context = XSCRIPTCONTEXT.getComponentContext()
        self._desktop = XSCRIPTCONTEXT.getDesktop()
        self._frame = self._desktop.CurrentFrame
        self._window = self._frame.ContainerWindow
        self._toolkit = self._window.Toolkit

    def run_infodialog(self, title='', message=''):

        msgbox = self._toolkit.createMessageBox(self._window,
                                                'infobox',    # ヒントアイコン
                                                1,            # 「OK」ボタン
                                                title,
                                                message)
        msgbox.execute()
        msgbox.dispose()

    def run_querydialog(self, title='', message=''):

        msgbox = self._toolkit.createMessageBox(self._window,
                                                'querybox',   # 疑問符アイコン
                                                3,            # 「はい」「いいえ」ボタン
                                                title,
                                                message)
        return msgbox.execute()

def get_today(*args):

    doc = XSCRIPTCONTEXT.getDocument()
    sheet = doc.getSheets().getByName('Main')
    dt_now = (datetime.datetime.now()).strftime('%Y/%m/%d')
    kaisai_nengetsu = sheet.getCellRangeByName('C3')

    if kaisai_nengetsu.String:
        msg_box = Bridge()
        res = msg_box.run_querydialog(title='開催年月日セット', message='すでに値がセットされています。\n上書きでセットしますか?')

        if res != 2:    # 「はい」でなければ終了(res=2 はい res=3 いいえ)
            return

    kaisai_nengetsu.String = dt_now

def load_card_all(*args):

    doc = XSCRIPTCONTEXT.getDocument()
    sheet = doc.getSheets().getByName('Card')

    if not os.path.isfile(CARD_DATA):
        msg_box = Bridge()
        msg_box.run_infodialog(title='ファイルエラー', message='ファイルが存在しません\n' + CARD_DATA)
        return

    f = open(CARD_DATA)
    lines = f.readlines()
    f.close()
    i = 0;
    for line in lines:
        list = re.split("\|\|", line.rstrip('\n'))
        card = sheet.getCellByPosition(0, i)
        id = sheet.getCellByPosition(1, i)
        card.Value = list[0]
        id.String = list[1]
        i += 1

窓の大きさとかは他の方のブログを見ると、設定はできるのに無視されるようだ。

つーか、LibreOffice(OpenOffice.org も)の Python マクロについて書かれた本って無いのね。
まあ、マニアックなネタだからな(笑)

<追記>
エラーダイアログは「infobox」ではなく「errorbox」だよね。
ダイアログ表示関数にメッセージタイプも渡すように修正してもいいが、エラーはエラーで別関数を用意した方がソース的にはわかりやすいかな?

 20200614_windows3.jpg

LibreOffice の Python マクロを久しぶりに作っているので、「知っていたのに忘れてしまったこと」で小一時間ほど無駄にしてしまった(^^;

例えば以下のようなマクロを実行する時。

<略>
def get_today():
    '''
    今日の日付を取得して開催年月日にセット
    '''

    doc = XSCRIPTCONTEXT.getDocument()

    # シートを選択
    sheet = doc.getSheets().getByName('Main')

    # 今日の日付の取得(YYYY/MM/DD)
    dt_now = (datetime.datetime.now()).strftime('%Y/%m/%d')

    # 開催年月日のセルにセット
    # すでに日付が入っている時は更新するか確認する
    kaisai_nengetsu = sheet.getCellRangeByName('C3')

    if kaisai_nengetsu.String:
        msg_box = Bridge()
        res = msg_box.run_querydialog(title='開催年月日セット', message='すでに値がセットされています。\n上書きでセットしますか?')

        if res != 2:    # 「はい」でなければ終了
            return

    kaisai_nengetsu.String = dt_now
<略>

これを、「ツール」→「マクロ」→「マクロの管理」→「Python」でマクロを実行したときは問題ない。

20200612_libre1.jpg

しかし、これをフォームコントロールの「プッシュボタン」のイベントとして実行すると、

com.sun.star.uno.RuntimeException: Error during invoking function get_today in module file:///C:/Users/hoge/AppData/Roaming/LibreOffice/4/user/Scripts/python/m_main.py (<class 'TypeError'>: get_today() takes 0 positional arguments but 1 was given
  File "C:\Program Files\LibreOffice\program\pythonscript.py", line 921, in invoke
    ret = self.func( *args )
)

というエラーになってしまうのである。

20200612_libre2.jpg.jpg

get_today 関数の引数は無しなのに、ボタンのマクロ設定には m_main.py$get_today (user, Python) と何やら渡されてるから?
0 positional arguments(引数はゼロ個(無し))なのに、but 1 was given(しかし、引数がひとつ渡された)ってことよね?

そう、すっかり忘れていたのである。
ボタンで呼ばれるマクロの関数は、*args という任意の数の引数を受け取れる可変長引数を指定しないといけないことを・・・
でも、思い出したでぇ~(笑)

というわけで、関数宣言部を、

def get_today(*args):

と直したらエラーは消えたのでありました。めでたし、めでたし。

以前このブログでも何度かエントリーを書いた SONY RC-S380 で FeliCa カードを読み込んだ情報を Python で利用する話。

Windows 7 Professional で開発をしてたんだけど、今回、環境を Windows 10 Pro に移した。

その際、ちょっと Windows 7 の時何をやったかすっかり忘れてて、うろ覚えでセットアップして「プチはまり」をしたので環境構築についてメモっておく(^^;

ちなみに、Python 3.8.3 はすでにインストールされている。

1.WinUSB ドライバのインストール

https://zadig.akeo.ie/ からダウンロードした zadig-2.5.exe を使って WinUSB のインストールを行う。

RC-S380 を Windows 10 に接続したときに自動でインストールされた sonynfcport100c(v1.5.7.2)を WinUSB(v6.1.7600.16385)に Replace する。

・・・が、これ、本当に必要だったのか?Windows 7 のときには必要だったが、Windows 10 だと sonynfcport100c ドライバがインストールされているので、WinUSB ドライバを入れる必要があるのかどうなのか?

もし、このページを見てセットアップをしようと思っている人は、一度、この作業無しで RC-S380 が読めるか試してみてほしい。俺は面倒臭いので試さないけど(笑)

2.pip のバージョンアップ

必須ではないが、モジュールのインストールの度にワーニングが出るのでバージョンアップしておく。

コマンドプロンプトで、
python -m pip install --upgrade pip

3.nfcpy モジュールのインストール

コマンドプロンプトで、
pip3 install nfcpy

libusb1  1.8
ndeflib  0.3.3
nfcpy    1.0.3
pip      20.1.1
pyDes    2.0.1
pyserial 3.4
などがインストールされる。

4.libusb-1.0.dllのインストール

から最新版ダウンロード。
今日時点の最新版は、

ダウンロード後、展開したファイルを、Windows システムフォルダにコピー。

・64bit版
展開場所\MS64\dll\libusb-1.0.dll → C:\Windows\System32 にコピー

・32bit版
展開場所\MS32\dll\libusb-1.0.dll → C:\Windows\SysWOW64 にコピー

余談だが、64bit プログラムを格納する場所が System32 だとか、このわかりづらい構成はどうにかならんものか?(^^;>Windows

以上で、Windows 10 でも、Python プログラムの中から FeliCa カードが読めるはずだ。

ちなみに、作成したプログラムの中で HTTP プロトコルを使ってサーバにカード情報を飛ばしているので、requests モジュールも pip でインストールしないといけないのだが、これは、まあ、俺独自の話なので(笑)

複数の Raspberry Pi などの端末につながったカードリーダーで FeliCa カードを読んで、http プロトコルでカード情報をサーバに送る仕組みを作ってる。

データを送ってリターンコードを受け取るだけの簡単な API の仕組みなので(データ種はカード新規登録、読み込み時間報告、カード登録キャンセルなど色々あるけど)、サーバ側も Apache とかインストールはせず Python で書こうかと思った。クライアント側のプログラムを Python で書いてるんでね。

ところが、ネット(Qiita「pythonでローカルwebサーバを立ち上げる」など)で拾った下のようなスクリプトを走らせて、そこに Python で作ったクライアントソフトでアクセスしてみると・・・

import http.server
import socketserver

PORT = 3000
Handler = http.server.SimplehttpRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

これが、501 エラーになるのだ。

127.0.0.1 - - [06/Feb/2020 22:41:16] code 501, message Unsupported method ('POST')

ってエラーログ吐いてる。

POST メソッドに対応してないようだ。ええ???

確かに、ブラウザからアクセス(つまり GET メソッドのアクセス)してみたらエラーにならず、サーバスクリプトを置いているディレクトリのファイルリストが表示された。GET メソッドは OK のようだ。

20200206_pyserver.jpg

うーん・・・POST メソッドを使うには、http.server 以外にも import せんといかんのかいな?面倒くさっ。

というわけで、サーバ側は Node.js で組むことにした。(そして今、ほぼ完成してちゃんと動いている)

ただ、Python も JavaScript(しかも Node.js 拡張版)も日頃使ってる言語じゃないので、いちいちググりながら組んでいくのが面倒だけど、ま、これも勉強だからな。
仕事をしつつ勉強できるというのは良いことだ(笑)。そう思っておこう。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはパソコンです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。


月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら