ひゃまだのblog

ひゃまだ(id:hymd3a)の趣味のブログ

Vimのスクリプトをpythonで作る

(2021-06-18 初稿 - 2021-06-19 追記)

最近、pythonに触る機会が多い。頭の容量が少ない筆者は、Vimスクリプトpythonで書けたら、覚えることが少なくていいかなと思った。

調べてみると、Debian Busterのvimは、python3が使えるようだ。

vimのコマンド領域で、以下のとおり入力して"1"が返ってくれば、python3が使える。

:echo has('python3')    # 1

ちなみに、Debian BusterのVimでは、python2系は使えない。

vim --version で調べて、"+"が付いていると使えて、"-"だと使えないとのこと。

このページは、以下のサイトを参考に記述した。とても感謝。m(__)m

なお、ネットを検索して嫌なページを見てしまった。自分が同じことをrubyで考えて作ったページ。進歩がなくて、ほんとに情けない。(T_T)

さて、気を取り直して、まずはワンライナー

:python3 print("Hello Python3")    # Hello Python3
:py3 print("Hello Python3")        # Hello Python3

 python3はpy3と省略できる。

ワンライナーでは、できることが限られるので、pythonを使ったVim関数を作る。ここでは、簡単に日付と時間をvimのbufferに追加する関数。

なお、VimPythonの橋渡しとして、vimモジュールを読み込むと便利らしい。

function! GetDateTime()
py3 <<< EOC
import vim
import datetime

dt_now = datetime.datetime.now()
print(dt_now)
vim.current.buffer.append(str(dt_now))
EOC
endfunction

Vimの関数の中に、「py3 <<< EOC」と「EOC」との間にpythonのコードを書く。

実行方法は以下のとおり。

:source %                   # スクリプトファイルの読み込み
:tabe                       # 新たなバッファを準備
:call GetDateTime()         # GetDateTime()を実行
2021-06-18 20:36:22.859609  # 実行したBufferの最後に追加

なお、vimモジュールを読み込むことによって利用できるコマンドは、以下のサイトを参照してね。

現在のバッファの最後に追加するのは、上記スクリプトのとおりだが、カーソルがある行に追加するのはどうするのだろう… (・・?

で、調べてみると、以下のサイトに答えがあった。多謝。m(__)m

line('.')というVimのコマンドを実行して、evalで評価するようだ。

line = int(vim.eval("line('.')"))

なるほど、vimモジュールに無いコマンドは、vimで実行してからvimモジュールで評価すれば良いのね。目からウロコ。なんか、無限にコマンドが広がった感じ。

それで、現在のカーソル行に日付を挿入するスクリプトは以下のとおり。

なお、line('.')で得られる行番号は1からで、appendは0からなので、-1することと、strftimeで日付の出力を整形したこととに注意。

function! GetDateTime()
py3 <<< EOC
import vim
import datetime

dt_now = datetime.datetime.now()
line = int(vim.eval("line('.')")) - 1 
vim.current.buffer.append(dt_now.strftime("[%Y-%m-%d %H:%M:%S]"), line)
EOC
endfunction

実行すると、以下のように実行時の日付と時刻がカーソル行に挿入されればOK。

[2021-06-18 21:45:04]

この他、変数の受け渡し等、記述することはいっぱいあるのだが、最初に紹介したサイトに例があるので参考に。(超手抜き ^^;)

ちなみに、このページで作成したスクリプトは以下のコマンドと同じ。(^^ゞ

ちなみに、%はvimでは編集中ファイルの意味だから、エスケープしてるよ。

r! date +"[\%Y-\%M-\%D \%H-\%m-\%s]"

dateコマンドのように独立しても使えると、やはり便利だよね。だから、編集中のbufferの行や単語単位の細かい修正等、よ〜く考えてスクリプト作った方がいいかもね。 > 自分。^^;

関連ページ

pythonでtwitterの投稿に「いいね」してくれたユーザの一覧(API Ver2)を出力する

(2021-06-10 初稿 - )

はじめに

TwitterAPI Ver2が公開されて、投稿に「いいね」(like)してくれた人の一覧を得ることができるようになった。ただし、1 tweetあたり100個までとのこと。筆者の場合は、超余裕の仕様。(笑)

せっかくなので、API Ver2で自分の投稿に「いいね」してくれた奇特な方の一覧を取得してみた。

ちなみに、筆者の環境は以下のとおり。

検索の仕方が悪く、良いサイトが見つからなかったので、変なスクリプトを自作。^^;

スクリプト自体は、上記サイトの主にcurlの部分を参考に作成した。

f:id:hymd3a:20210610175911p:plain

Twitter画面

準備

スクリプトの作成よりも、準備の方が数倍大変。(-_-;)

アプリケーションの登録と各種tokenキーなどの取得

以下のdeveloperのサイトにログインして、なんとかしてアプケーションを登録。

その際、V1.1 ACCESSとV2 ACCESSができるようにプロジェクトを作成し、スタンドアロンにはしないこと。なお、登録等の詳細は他のサイトを検索してね(手抜き)

必要なキーは、consumer_key、consumer_secret、access_token_key、access_token_secretとbearer_tokenで、全てメモをしておくこと。

ただし、このページのスクリプトでは、bearer_tokenしか使わない

ユーザーIDの確認

スクリプトでは、user_idが必要になる。普段見る機会が少ないが、以下のサイト等を参照して、自分のuser_idを確認する。

要約すると、Twitterのホームページをブラウザで開き、ソースコードを表示して、「user_id」を検索する。

スクリプトの作成

Bearer_tokenとuser_idを手元に準備できたら、スクリプトを作成を開始する。

なお、筆者は、pythonも素人なので、いろいろ指摘してね。

自分の直近のtweet idを取得する

「いいね」してくれたユーザ一覧を取得するためには、まずtweet idを取得する必要がある。

tweet idも普段見ないが、自分のtweet一つだけをブラウザで開いてみると、アドレスバーに並んでいる数字列がtweet id。

さて、tweet id取得の参考になるのは、上記サイトの以下のcurlスクリプト

curl -H "Authorization: Bearer $BEARER_TOKEN" "https://api.twitter.com/2/users/{use_id}/tweets?max_results=5"

ヘッダーにbearer_tokenを指定、user_idを記入して、twitterapiにアクセスすれば良いよう…

curlが使える環境ならば、自分のtweetが5件取得できるか実際に試してみて。

この部分を、pythonにすると以下のよう。

url = "https://api.twitter.com/2/users/{}/tweets?max_results={}".format(user_id,MAX_COUNT)
head = {"Authorization": "Bearer {}".format(bearer_token)}
req = urllib.request.Request(url, headers=head)
res = urllib.request.urlopen(req).read()

ちなみに、MAX_COUNTは最大100個までだそうだが、あまりに多いのは負荷がかかるのでやめようね。

{
  "data": [
    {
      "id": "1338971066773905408",
      "text": "💡 Using Twitter data for academic research? Join our next livestream this Friday @ 9am PT on https://t.co/GrtBOXh5Y1!n n@SuhemParack will show how to get started with recent search &amp; filtered stream endpoints on the #TwitterAPI v2, the new Tweet payload, annotations, &amp; more. https://t.co/IraD2Z7wEg"
    },
    (中略)
    {
      "id": "1334564488884862976",
      "text": "Before we release new #TwitterAPI endpoints, we let developers test drive a prototype of our intended design. @i_am_daniele takes you behind the scenes of an endpoint in the making. https://t.co/NNTDnciwNq"
    }
  ],
  "meta": {
    "oldest_id": "1334564488884862976",
    "newest_id": "1338971066773905408",
    "result_count": 5,
    "next_token": "7140dibdnow9c7btw3w29grvxfcgvpb9n9coehpk7xz5i"
  }
}

取得データは、jsonの形式で、'meta'の'result_count'が5であることに注目。

返ってきたjsonを辞書型にコンバートすると配列のように扱えてとても便利。

td_dic = json.loads(res.decode('utf-8'))

例えば、最初のtweet_idとresult_countを表示するには、以下のとおり。

print(tid_dic['data'][0]['id'])          #  1338971066773905408
print(tid_dic['meta']['result_count'])   # 5

Tweet IDごとの「いいね」(liking_users)の一覧を取得

Tweet idが取得できるようになったので、各tweet idごとのいいねユーザ(liking_users)の一覧を取得する。

curl -H "Authorization: Bearer $BEARER_TOKEN" https://api.twitter.com/2/tweets/{tweet_id}/liking_users

上記をpythonにすると以下のとおり。

url = "https://api.twitter.com/2/tweets/{}/liking_users".format(str(post_id))
head = {"Authorization": "Bearer {}".format(bearer_token)}
req = urllib.request.Request(url, headers=head)
res = urllib.request.urlopen(req).read()

やはり、これも辞書型に変換して、以下のとおりダラダラと出力。

for i in range(uids['meta']['result_count']):
    print(uids['data'][i]['id'], uids['data'][i]['name'], uids['data'][i]['username'])

作成したスクリプトの実行

作成したスクリプトの全体は、以下のとおり(p-tw-likes.py)。

#!/usr/bin/env python3
#coding: UTF-8

import json
import time
import urllib.request
import urllib.parse
import urllib.error

#  Constant
NAME = 'hymd3a'
USERID = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'        # たくさんの数字列
MAX_COUNT = 5

def getMyTweetId(user_id):
    try:
        url = "https://api.twitter.com/2/users/{}/tweets?max_results={}".format(user_id,MAX_COUNT)
        head = {"Authorization": "Bearer {}".format(bearer_token)}
        req = urllib.request.Request(url, headers=head)
        res = urllib.request.urlopen(req).read()
        td = json.loads(res.decode('utf-8'))
        #print(json.dumps(td, indent=4, sort_keys=True, ensure_ascii=False))
        return td
    except urllib.error.HTTPError as err:
        print(err.reason, err.code)
        return False

def getUserIDsPostLikes(post_id):
    time.sleep(1)
    try:
        url = "https://api.twitter.com/2/tweets/{}/liking_users".format(str(post_id))
        head = {"Authorization": "Bearer {}".format(bearer_token)}
        req = urllib.request.Request(url, headers=head)
        res = urllib.request.urlopen(req).read()
        jd = json.loads(res.decode('utf-8'))
        #print(json.dumps(jd, indent=4, sort_keys=True, ensure_ascii=False))
        return jd
    except urllib.error.HTTPError as err:
        print(err.reason, err.code)
        return False

# Bearer Token
bearer_token="xxxxxxTakusanxnoxMojixgaxNarandexIruyoxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

tid_dic = getMyTweetId(USERID)

for i in range(tid_dic['meta']['result_count']):
    uids = getUserIDsPostLikes(tid_dic['data'][i]['id'])
    for i in range(uids['meta']['result_count']):
        print(uids['data'][i]['id'], uids['data'][i]['name'], uids['data'][i]['username'])

実行は、以下のようにすると、たくさんいいねしてくれたユーザがわかり、感謝しないとね。

./p-tw-likes.py | sort | uniq -c | sort -n
      User ID           Name     Username  
  1 917984000000000000 ユーザ1    lemon
  2 000000000000092305 ユーザ2    orange
  2 935800000000000231 ユーザ3    apple
  3 000000000087439106 ユーザ4    pine
  4 891467010000000000 ユーザ5    grape

sort、特にuniq -cがカウントしてくれて、とてもいい働きをしてくれて感謝。

Debianでgpxファイルを見る

(2021-06-07 初稿 - )

先日、yamap に登録して活動を記録したところ、活動記録はgpxファイルでダウンロードできた。

Debianでもgpxファイルが見られるように、以下のとおりgpxviewerをインストール。

(gpxファイルは、テキストファイルなので、エディタでも開くことができる。)

sudo apt install gpxviewer

 使い方は簡単で、gpxviewerを起動後、「ファイル」、「開く」で目的のgpxファイルを選択する。

f:id:hymd3a:20210607184947p:plain

先日行った阿知ケ谷アルプスの軌跡(gpxファイル)を表示

ちなみに、Google Mapでもgpxファイルは少し面倒だけど読み込める。

Google Mapを開いて、「マイプレイス」、「マイマップ」、「地図を作成」、「インポート」で目的のgpxファイルを貼り付ける。

詳しくは、以下のページを参照のこと。多謝

筆者は、山登りもハイキングも初心者だけど、興味があったら覗いてみてね。

関連ページ

動画ファイルからmp3を抜き出す

(初稿 2019-12-29 - 転記・修正 2021-06-01 )

はじめに

動画ファイル(*.webm、*.mkv、*.mp4を想定)から、音声ファイル(mp3)を抜き出す方法。複数のファイルがある場合に、面倒なのでbashスクリプトを作成した。

なお、筆者の環境は、Debian Buster。

動画ファイルからmp3を抜き出す

ffmpegを利用して、動画からmp3を抜き出す。

ffmpegのインストールは以下のとおり。

sudo apt install ffmpeg

実際に音声ファイルを抜き出す方法は、以下のとおり。なお、詳細については、ffmpegのヘルプを参照のこと。

ffmpeg -i input.webm -acodec libmp3lame -aq 4 output.mp3

上記コマンドでできることを確認したら、以下のbashスクリプトを適当なエディタで作成し、フォルダ内の動画ファイルから一括して音声ファイルを取り出す。
ここでは、v2mp3というファイル名にした。

#!/bin/bash
# Written by Hyamada 2019-12-29
# 動画ファイルからmp3を抜き出す

for f in *.mkv *.mp4 *.webm ; do 
  [ -f "$f" ] || continue
  /usr/bin/ffmpeg -i "$f" -acodec libmp3lame -aq 4 "${f%.*}.mp3" ; 
done

エディタで作成したら、以下のとおり実行権を与えてね。

$ chmod +x v2mp3

ここでは、mkv、mp4、webmの拡張子のファイルを動画ファイルとしたが、不足する場合は、追加をしてね。

また、指定する拡張子のファイルがないと、エラーで終了してしまうので、ファイルがない場合はスキップ(continue)するようにした。

おわりに

bashに限らず、for in doが正常に動くとうれしくなるね(自己満足 ^^;)。

関連ページ

Chromecastの設定変更

(2021-05-31 初稿 - )

2021年2月4日に購入したChromecastがわが家に届いた。

f:id:hymd3a:20210531103427j:plain

購入したChromecast

さっそくわが家のテレビに接続し、Android端末にGoogleホームをインストール後、便利に使っていた。設定方法については、特に難しいこともなかったので省略。^^;

で、問題となったのは、Chromecastをつなげる部屋とを変更したため、接続するWifiアクセスポイントも変更になり、うまく接続できなくなってしまった。

いろいろと試行錯誤したが、たどり着いた結論としては、設定をリセット(工場出荷時に)した方が簡単だということ。

リセットは、円筒形の側面に細長いボタンがあるので、長押しで簡単にできる。

結局、ここで言いたいのは、ChromecastでWifi等設定を変更する場合は、リセットした方が早いということだけ。^^;

Chromecastが無くても、Raspberry Piがあれば、モニタにキャストすることができる。

下にリンクを貼っておくので、ご興味のある方はどうぞ。

関連ページ

 

pythonでネストした関数間で変数を共有する

(2021-05-25 初稿 - )

はじめに

 pythonスクリプトで、ネストした関数間で変数が共有できなくて困った。
実際には、tkinterを用いたスクリプトで、bind関数を作ったので、関数の中に関数を作るはめになった。
よほどのことがない限り、普通は関数の外に関数を作るけどね。^^;

以下のサイトに詳しい解説があるので参照のこと。多謝

スクリプト

まずは、簡単な例を。

def fn_parent():
    count = 0
    def fn_child():
        print(count)
    return fn_child()

fn_parent()
$ python3 ./nonlocal01.py 
0

親関数で定義された変数の値参照はOK。

しかし、親関数で定義された変数の値を変更しようとすると。

def fn_parent():
    count = 0
    def fn_child():
        count += 1
        print(count)
    return fn_child()

fn_parent()

1が返ってくることを期待するが…

$ python3 ./nonlocal02.py 
Traceback (most recent call last):
  File "./notlocal01.py", line 9, in 
    fn_parent()
  File "./notlocal01.py", line 7, in fn_parent
    return fn_child()
  File "./notlocal01.py", line 5, in fn_child
    count += 1
UnboundLocalError: local variable 'count' referenced before assignment

上記のように、値を変更しようとすると、定義する前に関数を参照しやがってと怒られる。(-_-;)

そこで、nonlocalを入れることにより、正常に動作し、かつ、値も変えられる。

def fn_parent():
    count = 0
    def fn_child():
        nonlocal count
        count += 1
        print(count)
    return fn_child()

fn_parent()
$ python3 ./nonlocal03.py 
1

ちなみに、筆者が作成していたtkinterスクリプトは以下のとおり。
ディレクトリとファイルの選択をやりたかった。

def ask_dir_file(dirname, filename):
    sub = tk.Tk()
    sub.withdraw()

    sub.title("Input Correct Dirname, Filename")
    sub.geometry("400x120")

    # function
    def deletevalue(event):
      #エントリーの中身を削除
      editbox1.delete(0, tk.END)
      editbox2.delete(0, tk.END)

    def ok_get(event):
        sub.quit() 

    def chg_dir(n):
        nonlocal dirname, filename
        dbase = str_base(dirname)
        dnum  = str_num(dirname)
        dirname = dbase + num_adj(dnum, n)  
        editbox1.delete(0, tk.END)
        editbox1.insert(tk.END, dirname)
        fbase   = str_base(filename.split('.')[0])
        fnum    = str_num(filename.split('.')[0])
        fext    = '.' + filename.split('.')[1]
        filename = fbase + num_adj(fnum, 0) + fext
        editbox2.delete(0, tk.END)
        editbox2.insert(tk.END,filename)

    def up_dir(event):
        chg_dir(1)

    def down_dir(event):
        chg_dir(-1)

    #ラベル
    label1 = tk.Label(sub, text='Directory:')
    label2 = tk.Label(sub, text='Filename:')
    label1.place(x = 20, y = 20)
    label2.place(x = 20, y = 50)

    #エントリー
    editbox1 = tk.Entry(sub, width=40)
    editbox1.insert(tk.END, dirname)
    editbox1.place(x=100, y=20)
    editbox2 = tk.Entry(sub, width=40)
    editbox2.insert(tk.END,filename)
    editbox2.place(x=100, y=50)

    #ボタン
    button1 = tk.Button(sub, text='OK')
    button1.bind("", ok_get)
    button1.place(x=300, y=80)
    button2 = tk.Button(sub, text='Clear')
    button2.bind("", deletevalue)
    button2.place(x=200, y=80)

    # UpキーでDirname変更
    sub.bind('', up_dir)
    sub.bind('', down_dir)

    # Enter でも OK
    sub.bind('', ok_get)
    sub.deiconify()
    sub.mainloop()
    sub.withdraw()
    return editbox1.get(), editbox2.get()

 

今回の例は、nonlocalで逃げましたが、本格的には上記サイトにも記述があるように、クラスやジェネレータを使うのが一般的だとか。

また何かわかったら記述するね。

関連ページ

SambaでラズパイをRegzaの録画サーバにする

(2021-05-22 初稿 - )

 

わが家のテレビは、古い東芝Regza(37Z1 2010年購入)で、この機種はNAS等に録画することが可能である。

最近、Raspberry Pi(以下、ラズパイ)に様々な機能を持たせて24時間のサーバにしているので、このラズパイに大容量のハードディスク(といっても、3TB)を付けて、Sambaを導入してRegzaの録画サーバにした。

Regzaの録画サーバとしてのSambaの設定にやや手こずったので、ここに備忘録を残す。

(古いRegzaを所有しており、ラズパイを使っている等、極めてニッチな… (^_^;))

なお、ここでは大容量のハードディスクを接続する方法については記載しないので、他所を参考に。

Sambaの導入

ラズパイで以下を実施。

sudo apt install samba

Sambaの設定

sambaの設定は、以下のとおり行う。

sudo vi /etc/samba/smb.conf

なお、以下は設定後のtestparmの出力を掲載するので、参考にして設定すること。

設定は、主に[homes]と[RPI4-NAS]のセクションを行う。

hide@rasd4:~ $ testparm
rlimit_max: increasing rlimit_max (1024) to minimum Windows limit (16384)
WARNING: The "null passwords" option is deprecated
Load smb config files from /etc/samba/smb.conf
rlimit_max: increasing rlimit_max (1024) to minimum Windows limit (16384)
WARNING: The "null passwords" option is deprecated
Processing section "[homes]"
Processing section "[printers]"
Processing section "[print$]"
Processing section "[RPI4-NAS]"
Loaded services file OK.
Server role: ROLE_STANDALONE

Press enter to see a dump of your service definitions

# Global parameters
[global]
	client min protocol = NT1
	dos charset = CP932
	log file = /var/log/samba/log.%m
	logging = file
	map to guest = Bad User
	max log size = 1000
	ntlm auth = ntlmv1-permitted
	null passwords = Yes
	obey pam restrictions = Yes
	pam password change = Yes
	panic action = /usr/share/samba/panic-action %d
	passwd chat = *Enter\snew\s*\spassword:* %n\n *Retype\snew\s*\spassword:* %n\n *password\supdated\ssuccessfully* .
	passwd program = /usr/bin/passwd %u
	server max protocol = NT1
	server role = standalone server
	unix extensions = No
	unix password sync = Yes
	usershare allow guests = Yes
	idmap config * : backend = tdb


[homes]
	browseable = No
	comment = Home Directories
	create mask = 0700
	directory mask = 0700
	valid users = %S


[printers]
	browseable = No
	comment = All Printers
	create mask = 0700
	path = /var/spool/samba
	printable = Yes


[print$]
	comment = Printer Drivers
	path = /var/lib/samba/printers


[RPI4-NAS]
	comment = Share folder
	create mask = 0777
	directory mask = 0775
	force user = root
	guest ok = Yes
	inherit acls = Yes
	path = /mnt/nas/share
	read only = No

録画の方法

Regzaでレグザリンクボタンを押し、RPI4-NASの装置を選択するだけ。

f:id:hymd3a:20210522120233j:plain

録画機器の選択

テストで録画した番組

f:id:hymd3a:20210522120323j:plain

テストで録画した番組

Regzaとラズパイで楽しい録画を!!

関連ページ