ビリヤード大会エントリー用のカスタムフォームを作ってみた
※2020/8/3 Demoサイトへのリンクを追加
※2020/8/8 btn-group-toggle クラスで作った radio button のインデントが解決
どうも、キヨタです。
しばらく更新が滞ってしまいましたがサボっていた訳ではありませんw
本業の繁忙期だったのと、インプットが多くてなかなか進まなかったのです。
今回はビリヤードの大会のエントリーフォームを実装してみました。
本当はかなり多岐にわたって試行錯誤したのですが真面目に書くと疲れるでこの記事では雰囲気だけ書くので留めておこうと思います。
ビリヤードで公式戦に出られる方はそのうちこのフォームに出会うかも。
検討背景
- 私の趣味がビリヤードで業界的につながりがある
- ビリヤード界隈は業界的にデジタル化が遅れている
- 公式戦のエントリーがメールベースで運営が大変そう
- 入力の抜け漏れないようにフォーム化すればええやん
検討成果
最終的には下図のようなものが出来上がりました。
Demo : Sample大会 エントリーフォーム
- Google Apps Script
- フォーム制御
- SpreadSheetへのデータ格納
- メール自動応答
- レスポンシブデザイン
- Bootstrap (web)
- mjml (mail)
どうしてもできなかった事
- Bootstrapの btn-group-toggle クラスで作った radio button の左にどうしてもインデントが入ってしまう
[追記]
Bootstrapのform-checkクラスにpaddingが設定されていた事が問題だとの指摘があり、 下記の記述をstyleとして追加したら直りました。
.form-check { padding: 0; }
参考文献
ほぼゼロから色々詰め込んだのでちょっと多すぎ
できる限りWindows 10のプリイン環境を使って見やすいコードの説明資料を作る
どうも、キヨタです。
基本このブログは仕事に関係ない趣味のプログラミング関係の話をダラダラと書くことを目的としていますが、 本日は業務上で懇切丁寧なコード説明資料を作りたくなったので環境を整えたという話を書こうと思います。
検討背景
- 自分の職場はOSSのフリーソフトなどの活用にあまり免疫がなく関係者の説得が面倒なので最小限に抑えたい
- コーディングにうとい人を威嚇しないよう白背景のドキュメントを作りたい
(背景が黒いだけで怖がる人がいる) - ダウンロードしてローカルでも見れるようにOfficeドキュメントを使いたい
(はてブロとかのが書くのは楽なんですが機密情報とかDRMとか)
俺得要件
- 最終ドキュメントはExcel (キャンバスがほぼ無限大)
- ドキュメント上でのコード自体の読みやすさ
- シンタックスハイライト
- 可読性の高いプリインフォント
- コーディング時はDark Theme
- ドキュメントはLight Theme
準備したもの
言わずと知れた人気エディター。
Microsoft製なのでリッチテキスト形式でコピーができる!
Excelにシンタックスハイライトしたコードを貼り付けられる!
- Toggle Light/Dark Theme
Visual Studio CodeのExtention。
Ctrl
+ Shift
+ Alt
+ T
でLightテーマとDarkテーマが切り替えられる。
システムのカラースキームに合わせてトグルする設定があるらしいが、今回は不要なのでVisual Studio Codeの settings.json
に下記の1行を追記した。
これを追記しないとVisual Studio Code起動時に謎ポップアップが出てくるので割と鬱陶しかった。
"window.autoDetectColorScheme": false,
その他設定
Web作ってたりするとどうしても日本語書く必要が出てきてしまうのでフォント選定がしたくなった。
Visual Studio Codeのデフォルトだと日本語はMS ゴシックになっているようで不快感がすごかったので、
Windows 10にデフォルトで入っている綺麗な等幅フォントである游ゴシックを採用。
英字フォントのデフォルトになっているConsolasは結構好みなので残した。
Visual Studio Codeのsettings.json
に下記の1行を追記した。
"editor.fontFamily": "Consolas, '游ゴシック Medium', 'Courier New', monospace",
できた!
できたできた!
作業手順めっちゃ簡単ですごく満足感高いです。
- コーディングする (Dark Theme)
Ctrl
+Shift
+Alt
+T
でLightテーマに切り替え- Visual Studio Code上でソースコードをコピー
- Excelに貼り付けるとシンタックスハイライトされた白背景のコードが!
わーい!
若干気になる挙動
Ctrl
+C
でコピーしてExcelに貼り付けても何もペーストされない
何故かわかりませんが Ctrl
+ C
でコピーするとExcelで貼り付けても何もペーストされませんでした。
なんか Visual Studio Code の Vim Extention あたりが悪さしている気がしますが、
とりあえず下記の方法ではシンタックスハイライト含めてちゃんとペーストできました。
まぁ、すごい頻繁にやる作業ではないのでこれぐらいは許容範囲内ですかね。 個人的には十分満足できました。
よかったよかった。
続・カメラからのデータ取り込みコマンドを作ってみた
どうも、キヨタです。
先日作成したカメラからのデータ取り込みコマンドに早速不満が出てきたので書き直してみました。
元記事はこちらになります。
データコピーで色々使ってみると撮映日がファイル作成日と異なるケースがあり、コピー先の仕分けが期待と異なる結果となってしまっていました。
そこで、今回は静止画ファイルに限りExifから撮映日を取得してきて保存先の仕分けをするように実装変更しました。
仕様おさらい
- 取り込み元メディアでは指定ディレクトリ以下のサブディレクトリまで探索し、拡張子でマッチして取り込むファイルを決める
- 取り込み先ではyyyy/yyyy-mm/yyyy-mm-ddのような撮影日の年月日ベースの階層構造でディレクトリを分けて格納する
- オプションで静止画のみ/動画のみの取り込みにも対応できるようにする
- ファイル作成日などのメタデータは保持してコピーしてくる、元ファイルは消さない
- 取り込み先に同一名のファイルがある場合にはサイズ比較で同一性を確かめてログに吐く
今回の変更点
- 静止画ファイルはExifから撮映日を取得する
Pillow
- コード冒頭の拡張子リストの大文字/小文字並記回避
- 要素機能の関数化
def
- for文制御のフィルタ使用
.filter()
lambda
- コマンドライン引数処理の分離
main()
ソースコード
#!/usr/bin/env python3 import sys import shutil import datetime from pathlib import Path from PIL import Image from PIL.ExifTags import TAGS # Define directory format dir_format = '%Y/%Y-%m/%Y-%m-%d' # Define copying file extentions image_ext_tuple = ('jpg', 'jpeg') movie_ext_tuple = ('mp4', 'm2ts') def get_exif_date(img): exif = img._getexif() try: for id, val in exif.items(): tg = TAGS.get(id, id) if tg == "DateTimeOriginal": # Convert 'yyyy:mm:dd hh:mm:ss' -> 'yyyy-mm-dd' date_val = val.split(' ') return date_val[0].replace(':', '-') # If there is no exif tags except AttributeError: return 'NON' # If there is no date exif value return 'NON' def copy_media_classify_by_date(path_obj_src, path_obj_dst, dir_format, ext_tuple): # Get file list src_path_list = list(path_obj_src.glob('**/*')) # Copy files to YYYY/MM/DD directory for src_path in list(filter(lambda path: path.suffix.replace('.', '').lower() in ext_tuple, src_path_list)): # Try to get image taken date try: img = Image.open(src_path, mode="r") taken_date = get_exif_date(img) except Exception: taken_date = 'NON' # Get create date create_date = datetime.datetime.fromtimestamp( src_path.stat().st_ctime).strftime('%Y-%m-%d') # Convert directory path string if taken_date == 'NON': date_dir = datetime.datetime.fromisoformat( create_date).strftime(dir_format) else: date_dir = datetime.datetime.fromisoformat( taken_date).strftime(dir_format) # Make YYYY/MM/DD directory if it does not exist path_obj_dst_date = path_obj_dst / date_dir if not path_obj_dst_date.exists(): path_obj_dst_date.mkdir(parents=True) print('\n Make directory : {dir}\n'.format( dir=str(path_obj_dst_date))) # Skip copying if target file is already exist file_name = src_path.name dst_path = path_obj_dst_date / file_name if dst_path.exists(): # Judge as 'same file' if target file size is same as source if dst_path.stat().st_size == src_path.stat().st_size: status = '...Skipped : Target file is already exist.' else: status = '...Skipped : Target file is already exist. (different size)' # Copy file with meta-data (copy2) else: shutil.copy2(src_path, dst_path) status = '...Copied to ' + str(path_obj_dst_date) # Print terminal message if taken_date == 'NON': print(' {filename} [{ctime}] {stat}'.format( filename=file_name, ctime=create_date, stat=status)) else: print(' {filename} [{ttime}] {stat}'.format( filename=file_name, ttime=taken_date, stat=status)) def main(): # Process arguments args = sys.argv usage = ' Usage : ' + \ args[0].replace('./', '') + ' [-h | -i | -m] source_dir target_dir' usage += """ -h Show usage. -i Copying image files only. -m Copying movie files only. """ for arg in args: if arg == args[0]: pass elif arg == '-h' or arg == '--help': print('\n' + usage) elif arg == '-i': ext_tuple = image_ext_tuple elif arg == '-m': ext_tuple = movie_ext_tuple elif 'path_obj_src' not in locals(): path_obj_src = Path(arg) elif 'path_obj_dst' not in locals(): path_obj_dst = Path(arg) if 'ext_tuple' not in locals(): ext_tuple = image_ext_tuple + movie_ext_tuple # Check input exeptions if 'path_obj_dst' not in locals(): print('\n Error : Too few arguments.') print(usage) sys.exit(1) # Print header print('-' * 50 + '\n') print(' ' + str(args[0]).replace('./', '') + '\n') print(' Source dir : \n ' + str(path_obj_src)) print(' Destination root dir : \n ' + str(path_obj_dst)) print(' File type : \n ' + str(ext_tuple) + '\n') print('-' * 50 + '\n') copy_media_classify_by_date( path_obj_src, path_obj_dst, dir_format, ext_tuple) if __name__ == '__main__': main()
結果と所感
- ちゃんとExifから撮映日を取得することで正しい動作が得られた
- 要素機能をくくり出し→関数化はもっと修行が必要
- filterとかlambda式とか便利やん
参考文献
2次元テーブルデータの内挿補間関数を書いてみた
どうも、キヨタです。
今まで謎に未経験でしたが、血迷ってExcel VBAデビューしてみました。
2次元テーブルデータから中間の値を内挿補間して求める作業を山ほどやらなければならなくなったのですが、MATCHとかVLOOKUPとかで管理するのがもう面倒になったので関数化してみた次第です。
結果的には大変快適でした。
要求仕様
想定しているデータはFig.1に示したような2次元の数値データで、x_range
を定義域とするx_val
とy_range
を定義域とするy_val
で示されたIndexに相当するデータをdata_range
内のデータから内挿補間して生成する関数を作ります。
作っている途中でx_val
y_val
が定義域から外れるとよくわからない謎の数値を返す事が分かったので、定義域外の入力に対しては#N/A
を返すことにしました。
内挿補間の方法は至ってシンプルで、VBAからMatch関数で引き当てたd11
d12
d21
d22
を用いて下記の計算をしています。
ソースコード
Function InterLin2D(x_val As Double, x_range As Range, y_val As Double, y_range As Range, data_range As Range) Dim x_index, y_index As Long Dim x1, x2, y1, y2, x_min, x_max, y_min, y_max, d11, d12, d21, d22, dm1, dm2, dmm As Double ' Get min/max value of x/y axis x_min = WorksheetFunction.Min(x_range) x_max = WorksheetFunction.Max(x_range) y_min = WorksheetFunction.Min(y_range) y_max = WorksheetFunction.Max(y_range) ' Get smaller value index x_index = WorksheetFunction.Match(x_val, x_range, 1) y_index = WorksheetFunction.Match(y_val, y_range, 1) ' Get reference axis value x1 = x_range(x_index) x2 = x_range(x_index + 1) y1 = y_range(y_index) y2 = y_range(y_index + 1) ' Get reference data value d11 = data_range(y_index, x_index) d12 = data_range(y_index + 1, x_index) d21 = data_range(y_index, x_index + 1) d22 = data_range(y_index + 1, x_index + 1) ' Calc interpolated data value for x-direction dm1 = d11 + (d21 - d11) * (x_val - x1) / (x2 - x1) dm2 = d12 + (d22 - d12) * (x_val - x1) / (x2 - x1) ' Calc 2D interpolated data value dmm = dm1 + (dm2 - dm1) * (y_val - y1) / (y2 - y1) ' If x_val/y_val is out of function domain, return #N/A If x_min <= x_val And x_val < x_max And y_min <= y_val And y_val < y_max Then InterLin2D = dmm Else InterLin2D = CVErr(xlErrNA) End If End Function
所感
カメラからのデータ取り込みコマンドを作ってみた
どうも、キヨタです。
プログラミングちゃんと勉強してこなかったので趣味がてら色々作ってみようと思ってPython始めました。 Pythonは初心者なのでお作法とかあまり分からず、とりあえずググりながら書いてみています。
今年の3月に人生初のMacを購入し、子供が寝静まった深夜にニヤニヤしながらカタカタしているのですが、Win機のころ使用していたカメラ画像の取り込みソフトがMacでサポートされておらず写真整理が滞っていました。 そこで記念すべき第1段はカメラからの動画/静止画取り込みを想定した俺得コマンドを作る事にしました。
仕様
- 取り込み元メディアでは指定ディレクトリ以下のサブディレクトリまで探索し、拡張子でマッチして取り込むファイルを決める
- 取り込み先ではyyyy/yyyy-mm/yyyy-mm-ddのような撮影日の年月日ベースの階層構造でディレクトリを分けて格納する
- オプションで静止画のみ/動画のみの取り込みにも対応できるようにする
- ファイル作成日などのメタデータは保持してコピーしてくる、元ファイルは消さない
- 取り込み先に同一名のファイルがある場合にはサイズ比較で同一性を確かめてログに吐く
試してみたこと
- コマンドライン引数の処理 (sys.argv)
- ファイルパス操作 (pathlib)
- ファイルのメタ情報の確認 (.stat())
- 文字列フォーマットの練習 (.format)
使い方と実行結果
使い方はシンプル。
コピー元とコピー先の親ディレクトリをコマンドライン引数で指定します。
-i オプションをつけると静止画のみ、-mをつけると動画のみをコピーします。
> python camera_import.py Error : Too few arguments. Usage : camera_import.py [-h | -i | -m] source_dir target_dir -h Show usage. -i Copying image files only. -m Copying movie files only. > python camera_import.py /Volumes/Source /Volumes/Destination -------------------------------------------------- camera_import.py Source dir : /Volumes/Source Target dir : /Volumes/Destination File type : ('JPG', 'jpg', 'mp4', 'MP4', 'm2ts', 'M2TS') -------------------------------------------------- Make directory : /Volumes/Destination/2020/2020-05/2020-05-20 DSC01052.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20 DSC01053.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20 DSC01054.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20 DSC01055.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20 DSC01056.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20 DSC01057.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20 DSC01058.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20 DSC01059.JPG [2020/05/20] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-20 Make directory : /Volumes/Destination/2020/2020-05/2020-05-21 DSC01071.JPG [2020/05/21] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-21 DSC01072.JPG [2020/05/21] ...Copied to /Volumes/Destination/2020/2020-05/2020-05-21 ...
ソースコード
#!/usr/bin/env python3 # # camera_import.py # # Author : Yuji.Kiyota@gmail.com # Create : 2020/05/22 # from pathlib import Path import sys import shutil import datetime # Define directory format dir_format = '%Y/%Y-%m/%Y-%m-%d' # Define copying file extentions (case sensitive) image_ext_tuple = ( 'JPG', 'jpg' ) movie_ext_tuple = ( 'mp4', 'MP4', 'm2ts', 'M2TS' ) # Process arguments args = sys.argv usage = ' Usage : ' + args[0].replace( './', '' ) + ' [-h | -i | -m] source_dir target_dir' usage += """ -h Show usage. -i Copying image files only. -m Copying movie files only. """ for arg in args : if arg == args[0] : pass elif arg == '-h' or arg == '--help' : print( '\n' + usage ) elif arg == '-i' : ext_tuple = image_ext_tuple elif arg == '-m' : ext_tuple = movie_ext_tuple elif 'path_obj_src' not in locals() : path_obj_src = Path( arg ) elif 'path_obj_dst' not in locals() : path_obj_dst = Path( arg ) if 'ext_tuple' not in locals() : ext_tuple = image_ext_tuple + movie_ext_tuple # Check input exeptions if 'path_obj_dst' not in locals() : print( '\n Error : Too few arguments.' ) print( usage ) sys.exit(1) # Print header print( '-' * 50 + '\n' ) print( ' ' + str( args[0] ).replace( './', '' ) + '\n' ) print( ' Source dir : \n ' + str( path_obj_src ) ) print( ' Destination root dir : \n ' + str( path_obj_dst ) ) print( ' File type : \n ' + str( ext_tuple ) + '\n' ) print( '-' * 50 + '\n' ) # Get file list src_file_path_list = [] for file_ext in ext_tuple : src_file_path_list += list( path_obj_src.glob( '**/*.' + file_ext ) ) # Copy files to YYYY/MM/DD directory for src_file_path in src_file_path_list : # Get file create time as YYYY/MM/DD format file_name = src_file_path.name create_date = datetime.datetime.fromtimestamp( src_file_path.stat().st_ctime ).strftime( '%Y/%m/%d' ) date_dir = datetime.datetime.fromtimestamp( src_file_path.stat().st_ctime ).strftime( dir_format ) # Make YYYY/MM/DD directory if it does not exist path_obj_dst_date = path_obj_dst / date_dir if not path_obj_dst_date.exists() : path_obj_dst_date.mkdir(parents=True) print( '\n Make directory : {dir}\n'.format( dir = str( path_obj_dst_date ) ) ) # Skip copying if target file is already exist dst_file_path = path_obj_dst_date / file_name if dst_file_path.exists() : # Judge as 'same file' if target file size is same as source if dst_file_path.stat().st_size == src_file_path.stat().st_size : status = '...Skipped : Target file is already exist.' else : status = '...Skipped : Target file is already exist. (different size)' # Copy file with meta-data (copy2) else : shutil.copy2( src_file_path, dst_file_path ) status = '...Copied to ' + str( path_obj_dst_date ) # Print terminal message print( ' {filename} [{ctime}] {stat}'.format( \ filename = file_name, \ ctime = create_date, \ stat = status ) )
まとめと残課題
- とりあえず思った通りの取り込み動作の実装はできたので良かった
- shutil.copy2()がコケると処理が止まってしまうので例外処理とか必要っぽい
- 内部処理を関数化?オブジェクト化?して、直接呼び出しの時のみ引数処理をするような書き方がもっとPythonらしい気がする
- GooglePhotoにUploadするコマンドも作りたい
今後もマイペースに続けていきたいと思います。