2021年2月25日木曜日

気象庁のホームページが更新

チラシの裏です。


個人用ホームページの天気予報欄の表示がおかしかったので調べたら、リニューアルなさったんですね。ちっとも知りませんでした。


Android用のウィジェットなど、いくつか依存するアプリを作っていたのですが、まあ、もう当時のような気力は戻ってこないでしょうねぇ。

もう10年近く前に作ったアプリが現役で動いていただけでも御の字ってところでしょうか。


本来なら気象業務支援センターからデータを買わないといけないんでしょうけど・・・やっぱり個人では難しいです。

結局、「気象庁ホームページのコンテンツの利用について」で許されている範囲内で気象庁の負荷にならないように極力注意をしたうえで、ページからスクレイピングを行って実装していました。


ただ一つだけ、冒頭に触れたページですが、具体的には単に注警報や天気図、天気予報とかアメダス値がパッと見られるページなのですが、これがまた個人的には実に便利でしたので、再度実装するとしたらどうしたらいいのかな、とちょっとhtmlページのソースを拝見しました。

設計思想からしてすごい変わりようですね。

従来はサーバ側で全部作っておいて、静的なページとして配信していましたが、今回はhtmlで記述する部分はひな形だけで、データ自身は別個にそれぞれのホームページの閲覧者のブラウザから直接サーバに非同期に通信して取得して、そのデータを(ある程度の加工を行っているケースもありましたが)ひな形に当てはめていく形式になっていました。


その非同期で取得するデータの形式はjsonでした。

jmaさんといえば一時期全然必要性を感じない電文やマイコンで直接処理するような電文にもXML化を推進する勢いでしたのに、どうしちゃったんでしょうか。

しかしjsonを採用してくれたおかげで、電文形式が極めて簡素で、かつ大変利用しやすい形式にまとめられているので、簡単に個人用ページを作り替えることができそうです。無論、ブラウザ上から直接ajaxでjsonを取得しちゃうと毎回ページを閲覧するたびに気象庁にクエリが飛ぶので、それは最低でも天気予報が更新される一日三回までに抑える必要があるので、ひと手間は必要ですが。


それにしても、ちょっと引っかかるのは、jmaさんがホームページに広告を掲載する決断を下したことです。

jsonのデータって果たしてjmaさん的に「気象庁ホームページで公開している情報」に該当するのでしょうか。

XMLデータは気象業務支援センターの営業を邪魔しない程度に遅らせてjmaさんのホームページから入手できますが、これは堂々とその配布のための専用ページも設けられているため、「気象庁ホームページで公開している情報」に該当するでしょう。

一方、jsonデータに関する記述は現時点ではどこにも見当たらず、かつ、それだけではホームページを構成していません(ひな形に当てはめて初めてホームページとして完成します)ので、jmaさんが何らかのアナウンスメントを出すまでは、愛知県岡崎市の例もありますし、当面は出来上がったページへリンクを張る程度にとどめ、様子を見たほうが良いかもしれません。

多少遲くていいなら(そして煩雑なXMLでもいいなら)府県予報でもいいわけですから、おいおい考えていきたいと思います。


それにしても、前代のホームページは19年間も現役だったそうです。

本当にお世話になりました。ありがとうございます。



2020年11月3日火曜日

MoUsoCoreWorkerがスリープを阻害する

ここ数か月か、Windows10がWindows Updateで何かしらシステムがアップデートされて以後、スリープができなくなるという現象に見舞われています。


その際、powercfg.exe /requestsを実行してみると、

C:\WINDOWS\system32>powercfg /requests
DISPLAY:
なし。

SYSTEM:
なし。

AWAYMODE:
なし。

実行:
[PROCESS] \Device\HarddiskVolumeX\Windows\System32\MoUsoCoreWorker.exe
USO Worker

PERFBOOST:
なし。

ACTIVELOCKSCREEN:
なし。

という結果が得られる場合があります。

これは太字のMoUsoCoreWorker.exeがシステムに対して「スリープしちゃイヤン」というお願いをし、それをシステムが聞き入れているという意味です。

「よし、MoUsoCoreWorkerが原因なんだな!ぶっ殺してやる!!」とか言ってタスクマネージャを起動してプロセスをkillする前に、まず試してもらいたいことがあります。


まず、このMoUsoCoreWorkerはWindows Updateの眷族で、Windows Updateとズブズブの関係です(そらそうだ)。

で、Windows Updateでアップデートを行わせたい場合、こいつが出張ってきてWindows Update本体(といえばいいのかな?)が活動中にシステムがスリープしてしまわないように邪魔をする仕事もします。

この時点で、新しいアップデートファイルがMS側に用意されていることはMoUsoCoreWorkerにはわかっています。

こういったケースの場合にMoUsoCoreWorkerによるスリープの阻害が発生していることを数回確認しました。


なお、これは、「オプションの品質更新プログラムがあります」という表示とは別物です。

重要なので再度繰り返させていただきますが、別物です。

ここからがお笑いなのですが、Windows Update本体はこの新しいアップデートファイルを何日も見つけられないまま最新の状態です」と強弁し続ける場合があります。


つまり、オプション以外のアップデートがあることはMoUsoCoreWorkerにはわかっているが、それをユーザに提示できず、かつWindows Update本体がそのアップデートをダウンロードしてシステムを更新できないという状況が現出します。


これは、システムを再起動しても、「設定」画面の「更新とセキュリティ」の「Windows Update」画面で「更新プログラムのチェック」ボタンを連打しても変わらないことがあります。

そうなると、ユーザには何が原因なのか外見からはわかりません。

憎きはMoUsoCoreWorkerただ一人、きゃつを封じるためにはどんな手でも使わなくては!という短絡的な思考に走っちゃいけません。


まず「サービス管理ツール」を開いて次の二つのサービスを再起動してみてください。

サービス名表示名
UsoSvcOrchestrator Service の更新
wuauservWindows Update

すると、あらびっくり、これまで見えていなかった更新ファイルが急に見えるようになっちゃったりするのです。

(繰り返しますが、これは「オプションの品質更新プログラム」ではありません。)


そして、その見えていなかったファイルがいったん見えるようになりますと、Windows Updadeを介して更新できるようになります。

そして、更新すると、MoUsoCoreWorkerはおとなしくなり、以後はスリープを阻害することがなくなります。


私の経験では、これまで3回MoUsoCoreWorkerが出張ってきていたうち、3回ともこの方法で問題を解消できました。


以上、どなたか様の参考になれば幸いです。

ここまでお読みいただき、ありがとうございました。


2020年11月2日月曜日

Kingdom: New Landsをいただいたのでやってみた

Epic Gamesさんから頂戴しました。ありがとうございます。


私はせっかくのEpic Gamesさんからの頂戴物は貰いっぱなしで、そもそもインストールをする気になっただけでも椿事だったのに、面白かったのでびっくりしてしまい、思わず記事にしようと思い立った次第です。


このゲームの最大の特徴は「説明しない」という点に尽きます。

ゲームの勝利条件すら説明しません。

当然、ユニットの説明もありません。

たとえば、私の場合ではこんなことがありました。


森の中にある家にコインを払うと出てくるキャラにさらにコインを払うと馬に載せられることがわかって、載せて本拠地に帰還しましたが、下乗させる方法が分かりません。仕方がないので載せたままあちこち作業をしていましたが、ただ載っているだけでそのキャラがいったいどういう意味を持つのかがわかりませんでした。

やがて大規模なモンスターの襲撃を蒙りました。

その際に二列目の壁の後ろの最大限に強化してあった塔の前で、一列目の壁で巨大な怪物の襲撃に対処している兵の様子を見ていた時のことです。

塔の前に立つと、なぜかさらなる塔のアップグレードできることが分かったのでやってみたところ、さらにコインを要求する施設が出来上がりました。

さっそくコインを投入すると、その建造物にコイン一つにつき一つのナニかが設置されることが分かりました。

そうこうしているうちに第一列目の壁が敵に突破され、第二列目の壁での防御戦が始まりましたが、何が起きているのかわかりませんが、その建造物で生産されたナニかがどんどん消費され、いくらコインを投入しても片っ端から消費されてゆくという事態に陥りました。


その時は何とか二列目の壁で敵をしのぎ切ったため、その建造物が何らかの攻撃兵器だったのだろうと思っていましたが、どうも釈然としません。


実は、これはパン屋でした。

生産された「ナニか」というのはパンで、そのパンは乞食を引き寄せる効果はありますが攻撃能力はありません。

どんどん「ナニか」が消費されたのは、一列目の壁でモンスターに襲われて、力戦奮闘の甲斐なくも乞食となってしまった臣民が大勢でパンを食べていたからでした。


こういう類の一切を、説明なしで観察だけで理解し、活用せよ、というのが本ゲームの特徴です。

私は無理でした。どうしてもピンクのキャラが何だったのかが知りたかったので、情報を求めて検索してしまったからです。


もう一つ無理だったのが、6番目の島、スカルアイランドの勝利条件です。

船も修理できないため、これまでの勝利条件とは違うことは明らかですが、ではどうすればいいのかわからない。

1回目の上陸の時は、それを探るどころか、序盤に偵察のため奥地へ馬を走らせている中、私の判断ミスであっけなく王冠を奪われて成仏してしまいました。

2回目にスカルアイランドに上陸したとき、折角ここまで来てまた勝利条件探しからか始めなければならない、と思ってしまい、ガックリきてしまいました。

結局勝利条件を検索してしまいました。


すると、すべてのポータルを島中から同時に一掃すればよいということが分かりました。

同時にというのは、島の左右のもっとも外側に存在するポータルは一定期間で復活してしまうためです。


勝利条件が分かってしまえばもうこれまでの経験を総動員するだけです。

二度目の上陸の際、遠く離れたところに乞食のテントが配置される場合に備えてパン屋を、資金繰りに備えてユニコーンを連れてきていましたので、もうこれだけで勝ったようなものです。

とにかくユニコーンに草を食ませてコインをひりださせ、乞食を集めて弓兵にするだけの簡単な作業で終わるだろうと楽観視していました。

とはいえ、四つ目か五つ目のポータルを破壊したときのモンスターの報復攻撃で、城壁を相次いで突破された挙句に王国中央を超えて反対側の壁まで破壊され、騎士全員とほとんどの弓兵、工作兵も乞食化させてしまうミスを犯しました。

この際は、王もモンスターにどつかれまくって、ついには王冠が逃げる前方に吹っ飛ばされてしまうところまで追い詰められましたが、返す返すも本当に幸運なことに、馬上で落ちてくる王冠をキャッチでき、残余のモンスターも、塔に残っていた射手のおかげで撃退することができました。


まるであまりの惨状に復興など可能なのか、なんて思ってしまいそうな状況ですが、実際には、ユニコーンがひりだしたコインと、モンスターに襲われても破壊されないパン屋および銀行家のおかげであっという間にリカバリが完了してしまいました。

パン屋とユニコーンは縛らないと別ゲームです。


とはいえ、次のポータルを破壊した際の報復はもっと激しさを増すわけですから、このままでは次回は支えることは困難な状況です。

こうなった原因は、偵察時に初めて見た砂時計の像(後から調べてあれが砂時計と知ったのですが)がとても重要なものだと思い込み、その像を確保するようにポータルの破壊を進めていたのですが、実際には砂時計の像は無視してかまわないものでした。

確かに砂時計の砂が尽きればゲームオーバーとなるのだそうですが、それは冬が訪れた先の事だそうです。


優先して確保すべきなのは弓の像とハンマーの像でした。

今回の島では、その二つの像は、砂時計の像の反対側の第二ポータルの先に配置されていました。

そのため、アクセスが困難で、これまでずっとお布施に行っていなかったために、今回の襲撃前も惰性でお布施しなかったから起きた、起こるべくして起きたミスでした。


前の島から連れてきた騎士もいなくなったため、片側4名いた騎士が2名に半減してしまいました。

初めての挑戦ですから、仮に最後の島だから最後のポータルの破壊時に特別な敵が出てきて2名の騎士では歯が立たなかもしれない、などという懸念も浮かびましたが、もしそうなら現状の保有戦力ではこの島のクリアは不可能です。

従って、もう取り返しはつきませんから、片側2名の騎士でポータルを早いうちに破壊することのほかにやることはもうありません。

今回はさっそくその二つの像にお布施を行い、さっそく残る小ポータルを破壊し、敵の報復攻撃を迎えましたが、お布施の効果は絶大で、犠牲皆無で撃退できました。


撃退直後はもう、両方の壁から十分にコインを持たせた騎士をそれぞれポータルの破壊に送り込むだけです。


最終決戦はどんなものかと思って左側担当の騎士隊についていったのですが、別にポータルから特殊な敵が出てくるわけでもなく、淡々と破壊が進み、あっけなくスカルアイランドをクリアできました。


とにかくこのゲームは「ルールを知ること」なんだということがつくづくわかりました。


Steamではまずこのタイトルにお目に掛かれなかったでしょう。

Humble Bundleでバンドルされていてもまず興味を示さないと思います。

Epic Gamesでいただいて、しかも貰いっ放しな中、聞いたこともないタイトルなのにインストールしてプレイして、さらに面白かったということがあるなんて思いにもよりませんでした。

不思議な因縁を感じたので、チラシの裏ながらつづった次第です。


以上、ここまでお読みいただいてありがとうございました。

2019年4月28日日曜日

ffplay+ffmpegでその場で二つの動画の右半分と左半分を同時に再生する方法

この記事では、エンコード前の動画とエンコード済みの動画の画質の差や、同一の動画を違う形式でエンコードしたときの画質の差を、それぞれの動画を単純に左右に並べるのではなく、動画を右半分と左半分に分割して比較したい場合を想定しています。
ほら、テレビショッピングとかでよくあるじゃないですか。
同じ人の顔を画面上で半分に割って、左半分はナントカクリームを塗らなかった場合、右半分はナントカクリームでお肌ツヤツヤってやつを見せるアレ。
ああいうイメージです。

さて、画質の差は結局のところ数値化などは意味がなくて、見ている本人が納得できるかできないか、という点に尽きると思います。

まず、最も簡単な比較方法は動画プレイヤーを二つ並べて再生することです。
が、同一シーンを同時比較するためには、神業のような再生ボタン押下処理が必要になります。神ならぬ身の上、凡人にはとてもできません。

また、もしできたとしても、元画像が1440x1080だったりFHDだったりした場合、素直に横に並べると大きすぎて位置合わせが難しかったりします。だからと言って普段見るはずのサイズでチェックしないと検査になってないので、縮小等リサイズして比較するのは避けたいところです。

ということで、表題の件のように、二つの動画の左半分と右半分を同時に再生して、すぐに官能検査してみようというのが今回の目論見です。

これを行うのに必要なプログラムはffmpegとffplayだけです。
ffplayはvlc playerでも代替できます。

まず結論として、コマンドライン一式をWindowsのバッチファイルとして作成すると以下になります。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
@echo off
setlocal
set SRC1=%1
set SRC2=%2
 
if not defined SRC1 goto invalidargument
 
REM remove doublequotation
set SRC1=%~1
 
if "%SRC1%"=="/?" goto help
if /i "%SRC1%" equ "/h" goto help
if /i "%SRC1%" equ "/help" goto help
if "%SRC1%"=="-?" goto help
if /i "%SRC1%" equ "-h" goto help
if /i "%SRC1%" equ "-help" goto help
 
if not defined SRC2 goto invalidargument
 
REM remove doublequotation
set SRC2=%~2
 
if not exist "%SRC1%" (
  echo エラー: %SRC1%がありません。
  goto help
)
if not exist "%SRC2%" (
  echo エラー: %SRC2%がありません。
  goto help
)
 
REM オーバーレイ文字列用ファイル名.拡張子抽出
set SRC1FNAME=%~nx1
set SRC2FNAME=%~nx2
REM 念のためアポストロフィを除去
set SRC1FNAME=%SRC1FNAME:'=%
set SRC2FNAME=%SRC2FNAME:'=%
 
REM 時間指定パラメタの記述場所判定用拡張子抽出
set SRC1EXT=%~x1
set SRC2EXT=%~x2
 
set STARTTIME=%~3
set ENDTIME=%~4
if defined STARTTIME ( set STARTTIME= -ss %STARTTIME% )
if defined ENDTIME ( set ENDTIME= -to %ENDTIME% )
 
set FFMPEG=ffmpeg.exe
set FFPLAY=ffplay.exe
set FFPROBE=ffprobe.exe
 
REM ぢでじ用パラメータ
set ASPECT=16:9
set MOVIE_WIDTH=1440
set MOVIE_HEIGHT=1080
set FPS=29.97
set CENTERLINE_WIDTH=2
 
if exist "%FFPROBE%" call :trygetparams
 
REM ファイル名表示用フォントファイル(要エスケープ: コロン及び¥)
set FONT=C\\:/WINDOWS/fonts/meiryob.ttc
REM 長いファイル名対策のため左右にテキストを流す
set DRAWTEXT_FNAME_OPT=fontfile=%FONT%:fontsize=24:fontcolor=darkgreen:shadowx=1:shadowy=1:x=w-mod(n*4\,w+tw):y=h-th-24:
REM 再生中動画のPTSとフレーム番号
set DRAWTEXT_PTS_OPT=fontfile=%FONT%:fontsize=12:fontcolor=white:shadowx=1:shadowy=1:text= pts\\:%%{pts\\:hms} %%{pts\\:flt} frame\\:%%{frame_num}
 
 
REM 再生開始及び終了時間指定
set DURATION1= %STARTTIME% %ENDTIME%
set DURATION2= %DURATION1%
REM (ffmpegではmpeg2tsは前置指定での時間指定不可なので引数の位置を後置する)
set DURATION_POSITION=PRE
if /i "%SRC1EXT%" equ ".ts" set DURATION_POSITION=POST
if /i "%SRC2EXT%" equ ".ts" set DURATION_POSITION=POST
if %DURATION_POSITION%==POST (
  set DURATION_POST=%DURATION1%
  set DURATION1=
  set DURATION2=
  echo  *警告: mpeg2tsのため指定時刻への頭出しに馬鹿みたいに時間がかかります*
  pause
)
 
set /A HALF_WIDTH=%MOVIE_WIDTH%/2
set /A RIGHT_HALF_X=%HALF_WIDTH%+%CENTERLINE_WIDTH%
 
set FILTER_OPT=^
    nullsrc=size=%MOVIE_WIDTH%x%MOVIE_HEIGHT%,fps=%FPS% [canvas]; ^
    [0:v] crop=%HALF_WIDTH%:%MOVIE_HEIGHT%:0:0,fps=%FPS%, ^
          drawtext=%DRAWTEXT_PTS_OPT% , ^
          drawtext=%DRAWTEXT_FNAME_OPT%:text=%SRC1FNAME% [left]; ^
    [1:v] crop=%HALF_WIDTH%:%MOVIE_HEIGHT%:%HALF_WIDTH%:0,fps=%FPS%,^
          drawtext=%DRAWTEXT_PTS_OPT% , ^
          drawtext=%DRAWTEXT_FNAME_OPT%:text=%SRC2FNAME% [right]; ^
    [canvas][left] overlay=x=0:shortest=1 [lefthalf]; ^
    [lefthalf][right] overlay=x=%RIGHT_HALF_X%:shortest=1
 
%FFMPEG% %DURATION1% -i "%SRC1%" %DURATION2% -i "%SRC2%" ^
 -filter_complex "%FILTER_OPT%" -f nut ^
 -aspect %ASPECT% -c:a copy -c:v rawvideo %DURATION_POST% pipe:1 ^
 | %FFPLAY% -i pipe:0 -fflags nobuffer
REM | %FFPLAY% -i pipe:0 -fflags nobuffer
REM VLCの場合は、「 | vlc.exe -」
goto end
 
:trygetparams
REM ffprobeの出力からfilter_complexの各値の取得を試みる
set FFPROBEOPT=%FFPROBE% -i "%SRC1%" -of csv -show_streams -hide_banner
REM ffprobeの結果をcsv形式で受け取り、カンマで分割した10,11,16,28の要素を取得
REM ※ここで直接リダイレクト先とパイプを指定しないとうまくいかないようだ
REM ※おまけにexeのパス名にスペースが入っていてもまともに動かないと来た。
for /f "usebackq tokens=10,11,16,28 delims=," %%a in (`%FFPROBEOPT% 2^>NUL ^| findstr video`) do (
  set MOVIE_WIDTH=%%a
  set MOVIE_HEIGHT=%%b
  set ASPECT=%%c
  set FPS=%%d
)
 
echo   ffprobe結果;
echo     size=%MOVIE_WIDTH%x%MOVIE_HEIGHT%
echo     aspect=%ASPECT%
echo     fps=%FPS%
pause
exit /b
 
:invalidargument
echo エラー: 引数が不足しています
:help
echo 使い方: %0 左半分を表示する動画 右半分を表示する動画 [ 再生開始時刻 [ 再生終了時刻 ] ]
echo         再生開始・終了時刻書式 HH:MM:SS または 秒
:end
endlocal
上記の内容を"左右2分割.bat"とでも名前を付けて保存して実行してください。使い方が表示されます。
ffmpegの特徴は複雑なことができる代償として呪文のような意味不明なコマンドラインを構築しなければならないところですが、今回も例にもれず一見意味不明な呪文が並びます。
おまけに当方の環境の都合で(サーバにはAV機能が皆無なので)Windows用のバッチとしたため、シェルスクリプトに比べて極端にひねくれています。
この両者の強力な組み合わせによって、上記のバッチは心の底からうんざりさせてくれるに十二分の威力を備えています。

さて、バッチの書式とか細かいことは放っておいて、重要なのは画像をそれぞれ左半分だけ、右半分だけ、それぞれ切り出して一枚の画像にすることですが、それをffmpegの -filter_complexオプションで行います。
このパラメータはffmpegの中でも極端に面倒くさいので、冗長になることを恐れずに説明したいと思います。

なお、行末の^(サーカムフレックス)はWindowsのcmd.exeで1行で記述しなければならない行を複数行に分割する記号ですので無視してください。
  1. 88行目のnullsrc=ですが、ffmpegが予め用意している多くの入力ソースの中の一つです。ここではこの上に2枚の動画を重ねてゆくベース動画として利用します。
    パラメータはsizeとfpsを指定しています。
    このバッチではsizeは最終的に出力される動画サイズ(1440x1080)、fpsは29.97が指定されています。これらの値はエンコード元が地デジのtsと仮定しています。ことさらにfpsを指定しているのはffmpegは無指定だと25fpsとして処理するからです。
    そして、このnullsrcから入力して加工(sizeとfps)した動画に対して[canvas]と名付けました。これの名づけルールは自由です。
    ここまででベース動画に対する定義は終わりました。続きがあるためセミコロンをつけます。
  2. 89行目の[0:v]ですが、これは1つ目の入力ソース(動画ファイル)コンテナ中で最初に見つかった動画を表しています。これを名付けたのはffmpegです。これに対してcropとfpsとdrawtextの指定をしています。
    cropは切り抜き範囲を指定します。このパラメータはffmpegのバージョンによって解釈される意味合いが変わるというとんでもない代物なので要注意です。
    バージョン20190426-f857753では切り抜き幅:同高さ:元動画に対する切り抜き開始位置X:同Yの順で解釈されます。
    上記のバッチでは720:1080:0:0として左半分を切り抜いています。言い換えると、1つ目の入力ソースの座標(左から0,上から0)から(幅720,高さ1080)だけ切り抜け、という意味合いになります。
    drawtextパラメータはどちらのファイルを切り抜いたかわかるようにファイル名を動画にオーバーレイするためのものです。
    以上を[left]と名付けました。これも名づけルールは自由です。続きがあるためセミコロンをつけます。
  3. 92行目の[1:v]ですが、これは2つ目の入力ソース(動画ファイル)中で最初に見つかった動画を表しています。これを名付けたのはffmpegです。これに対してもcropとfpsとdrawtextの指定をしています。
    異なるのは切り抜き位置で、上記のバッチでは720:1080:720:0を指定して右半分を切り抜いています。2つ目の入力ソースの座標(左から720,上から0)から(幅720,高さ1080)だけ切り抜けという意味合いになります。
    以上を[right]と名付けました。これも名づけルールは自由です。続きがあるためセミコロンをつけます。
  4. 95行目の[canvas][left]ですが、これは私がつけた名前です。この二つを重ね合わせるため、overlay=x=0:shortest=1というパラメータを指定しています。重ねる順番は左から順に重なります。この例では[canvas]の上に[left]が重なります。
    x=0は[left]を[canvas]上のどこに重ねるのかを指定しています。この場合は重ね合わせのx座標を0、つまり一番左としています。本来はこの記述は不要ですが、次の手順で右側に重ね合わせる際の座標指定との対比を分かりやすくするために付与しています。
    shortest=1はどちらかの動画の再生が終わったら処理を完了するというおまじないです。[canvas]の動画はnullsrc由来でdurationを指定していないので無限に続きますので、[left]の再生が終わったら完了するという意味になります。
    以上を[lefthalf]と名付けました。これも名づけルールは自由です。続きがあるためセミコロンをつけます。
  5. 96行目の[lefthalf][right]ですが、これは私がつけた名前です。この二つを重ね合わせるため、 overlay=x=%RIGHT_HALF_X%:shortest=1というパラメータを指定しています。重ねる順番は左から順に重なります。この例では[lefthalf]の上に[right]が重なります。
    x座標に指定している %RIGHT_HALF_X% の値は722ですので、2ピクセル分(本バッチの環境変数"CENTERLINE_WIDTH=2"として定義しています)だけ右にずれて重ね合わされます。これによって、隙間に[canvas]の地の動画が出ます(緑単色)。これは、左右の動画がどこで分割されているのかを分かりやすくするためにあえて行っています。
    以上でベース動画に右半分と左半分をオーバーレイ出来ましたので続きはありませんのでセミコロンをつけません
    なお、ここでセミコロンをつけますとffmpeg様に激烈に叱られた挙句ABENDしてくださいます。
というようなことをやった挙句、ようやく--filter_complex=オプションにダブルクォーテーションで囲んで指定します。

ここまでで2つの入力ソース(動画ファイル)が合成されているので、これをどこかに吐き出さないと人間様には見えません。
今回はいったんファイル化するのではなく、いきなり再生してしまおうというのが趣旨ですので、ffmpegにもれなくついてくるffplayで直接再生させたいと思います。

そのためのオプションは
-f nut -aspect %ASPECT% -c:a copy -c:v rawvideo pipe:1 | %FFPLAY% -i pipe:0 -fflags nobuffer
となります。

出力フォーマットにnut, ソースのサイズは1440x1080なのでぢでじのアスペクト比16:9を明示的に指定して、音声は再変換せずコピーして動画のコーデックをrawvideoとしてパイプ経由でffplayに出力しています。
vlc playerで再生したい場合は、パイプでつなぐアプリをffplayからvlc -(ハイフン)に置き換えてください。こちらもstdinから読み込んで再生してくれます。
率直に言ってffplayはとても使いづらいので、普段ffplayで動画を見ている人というのも少ないでしょうし、vlcも多くのOSに移植されていますから、vlcで官能試験を行ったほうが普段の視聴環境により近いかもしれません。

出力フォーマットのnutというのは単純にピクセルの羅列を扱えますので、rawvideoな動画を扱う際のフォーマットとして優れています。お望みであればこれをファイルに保存してもいいのですが、大変愉快なファイルサイズになります。

蛇足ですが、上記のバッチの例をお読みになれば上下分割や四分割なんかも簡単にできることはご理解いただけると思いますので、見やすいように改造してみてください。

なお、入力ソース次第では-ssが効かない場合があるとか使用するffmepgのバージョンが違うので挙動が違うとかザラです。
文中にもありますが、本稿は20190426-f857753を対象に記事を書いています。

個人的にはffmpegって強力で頼もしいけど融通は利かないしコロコロ仕様が変わるんで尊敬はするけど近づきたくはないです。

以上、大変くだらない記事で恐縮ですが、ここまでお読みいただいてありがとうございました。

以下、駄文です。
なんでこんなことをしようと思ったのかというと、HandBrakeのバージョンが上がるたびに同じH.265のエンコードパラメータを与えても妙に平均ビットレートが上がってきていて、同じCRF値でも1タモリ倶楽部単位(エンコネタではタモリ倶楽部1話分を当blogの基本単位とさせていただいております)の保存ファイルサイズが100MBほど大きくなってきたのが発端です。

なぜそうなるのかはHanbBrakeのせいなのか、libx265のせいなのかはわかりませんが、同一CRF値を指定してもエンコードされたファイルの平均ビットレートが上がっているのでファイルサイズが大きくなっているのが原因なのはわかったので、そっちが同じパラメータでも挙動を変えようって魂胆なら、こっちはもうCRF値を下げちまえ、もともとCRF値は28という(劣化に厳しい諸兄にはありえない)数値でエンコードしていましたが、今回は大台の30にして比較してみよう、でも画質はどうなるのかな?という素朴な疑問を持ったため、尊敬はするけど近寄りたくないffmpeg様のお力を拝借することにした次第です。

やってみてわかったことは、H.265の低ビットレートにおける再現力の高さです。
確かにわずかに28と30では違うかもしれないが、そもそもいずれもオリジナルのtsとの差ともわずかだ、という官能評価に至りました。
ぶっちゃけ、個人的評価ですがエンコ元先を左右に並べても上下に並べてもいずれも全然気にならないことが確認できました。

HEVC、恐るべし。そんなことをつらつら思う平成最後の日曜日でございます。
駄文までお読みいただき、誠にありがとうございました。

2019年4月21日日曜日

空き時間に合わせて本を一冊、無作為に青空文庫の公開中の作品から提案するページ

あまりにもgoogle app engine上でphpを動かすのが私でも楽過ぎたので、調子に乗って表題のようなものを作ってしまいました。
見てくれは地味で恐縮ですが、こんな感じです(javascriptで動作します)。


今回の一冊 🔄
リクエスト中...


ご希望の読書量は:
+その他の検索条件  
児童書は?
旧字は?
旧かなは?
読みたいジャンルは?
著者名(and,or,notは正規表現でお願いします)


とりあえず何でもいいからランダムに一冊選べるといいなあ、とずっと思っていたのですが、ついでに時間をつぶしたい時間を指定できればいいかな、と思って作ってみたところ、思いのほか気に入ったので、さらに提案機能をオリジン間リソース共有可能なAPI化して公開してみました。
この上の画面は、そのAPIをajaxで呼び出して実装しています。

作品が気に入らなかったらリロード絵文字(絵文字が見えない場合は「今回の一冊」という行)をクリックしますと、読書時間に合わせた別の作品を提案します。

それにしても、こういう一発ネタも膨大なデータをそろえて公開してくれている人や組織、団体様あってこそでございます。
今回は青空文庫さんからGitHubさん経由でデータを拝借して実現できました。
ありがとうございます。

geocitiesや大学のサイトなどで公開されている作品は除外しましたが、提案候補は一万五千冊を超えてます。
すごい。

なお、この例のようなデータを取得するAPIの詳細についてはこちらをご参照ください。
お読みいただいてありがとうございました。

---
2019/07/13 選択対象元リストを表示する機能(Selection candidate listボタン)を追加しました
2019/08/27 著者名を正規表現(perl互換)を用いて絞り込む機能を追加しました。

2019年4月19日金曜日

samba上で共有中のファイルをVisual Studioで編集すると実行可能になってしまう

sambaで共有中のファイルをうっかり Visual Studio から編集したファイルが、Linux上でlsすると実行可能になっているように見えてしまうケースのお話です。

結論から述べると、原因は保存されたファイルに付与されたACLのマスク値です。

以上です。

以下、駄文です。
祝Visual Studio 2019正式リリース、ということで思い出したのでこの記事を書いたのですが、これは2019以前から同じです。

以下ダラダラと書き綴りますが、その前に要約すると、Visual Studioはファイルを変更したときの保存方法は、実はファイルを保存する毎に新規作成をしてエディタの中身を保存してから、元のファイルを消しています。ファイルの内容の保護のためと思います。
従って、その副作用として、Windowsから見た場合はACL、Linuxから見た場合はパーミッションが、新規作成時のものになります。
平凡なアプリは気にもしてくれませんが、Visual Studioはさすがによくできていて、元のファイルのACLを新しいファイルへ再設定しようとします。
そのため、samba側も頑張って異文化のACLとの共存を図るべく必死に対応しようとするのですが、ここでlsコマンドさんが乱入してきてナンダコレ、というのが以下のあらすじです。

一応触れておきますが、以上の理由から、シェルスクリプトなど、パーミッションが重要なたぐいのファイルに対してはカジュアルにsamba越しにVisual Studioでファイルを編集してはいけません。

では、順次どうなっているのか見ていきたいと思います。
前提としてsambaは4.8.3、設定はほとんどデフォルト、ユーザ認証でホームディレクトリのcreate maskは0644でserver roleはstandaloneです。

さて、まず、Visual Studioでファイルを新規に作成し、sambaで共有しているディレクトリに保存した場合、lsコマンドで見てもごく普通のファイルに見えます。

たとえば、以下のように。
[ayumi@test ~]$ ls -la acltest.txt
-rw-r--r-- 1 ayumi ayumi 4  4月 19 21:55 acltest.txt

ついでにACLも確認しておきます。
[ayumi@test ~]$ getfacl acltest.txt
# file: acltest.txt
# owner: ayumi
# group: ayumi
user::rw-
group::r--
other::r--

どちらもsambaの設定どおりの挙動です。ACLは設定されていません。

さらに次いでに、WindowsからACLを確認してみます。
X:>icacls acltest.txt
acltest.txt S-1-5-21-3384635174-2336484230-2688597162-1001:(R,W)
            S-1-22-2-1000:(R)
            Everyone:(R)

長いSIDはsambaが出鱈目につけたayumiのSID, 短いSIDはグループのSID、EveryoneはotherのSIDですから、getfaclコマンドの結果と完全に一致しています。

さて、続けて同じファイルをVisual Studioから保存してみます。
すると、lsコマンドで見るとこうなります。
[ayumi@test ~]$ ls -la acltest.txt
-rw-rwxr--+ 1 ayumi ayumi 6  4月 19 22:03 acltest.txt*

ファイル名末尾にアスタリスク*がついて、グループにxがついていますからグループayumiに属する人に実行権が与えられているように見えることが確認できます。

ここで、パーミッションリストの末尾にプラス+がついているのでACLが追加されていることも同時にわかります。
getfaclコマンドの結果は以下のようになっています。
[ayumi@test ~]$ getfacl acltest.txt
# file: acltest.txt
# owner: ayumi
# group: ayumi
user::rw-
user:ayumi:rw-
group::r--
group:ayumi:r--
mask::rwx
other::r--

確かに元からのowner:groupに加えて、名指しでayumi:ayumiが追加されています。Windowsから見たらもともとの伝統的ファイルパーミッションはACLにしか見えませんから、それをコピーしただけの事ですが、Linux上ではパーミッションとACLは別々に管理されるため、新しく増えてしまったわけです。
しかし、read,write,executeのフラグはowner,group,otherすべてが644のままです。
実際に、先ほどのテキストが本当に実行可能なのか試してみると・・・

[ayumi@test ~]$ ./acltest.txt
-bash: ./acltest.txt: 許可がありません

と叱られます。

さて、ついでですからwindowsから見るとどうなっているでしょうか。
X:>icacls acltest.txt
acltest.txt S-1-5-21-3384635174-2336484230-2688597162-1001:(R,W)
            S-1-22-2-1000:(R)
            S-1-22-2-1000:(R)
            S-1-5-21-3384635174-2336484230-2688597162-1001:(R,W)
            Everyone:(R)

確かに、同じユーザとグループがリストに追加されているという奇妙な状態ではありますが、ReadとWriteしか付与されておらず、実行権はありません。getfaclの結果と同様で、矛盾はありません。

しかし、lsコマンドで見ると、確かに実行権が付いているようにしか見えません。
オイ、maskがrwxじゃねえか!!!とお叱りの向きもございましょう。
しかし、このmaskはorではなくてandのために使用されます。
つまり、ユーザーがいっくらchmod 0777としても、maskが0600だった場合は、実際には0600として動作しますよ、というように、です。

実際にmaskをreadだけにしてみます。
[ayumi@test ~]$ setfacl -m mask:r acltest.txt

すると、それぞれ以下のように見えます。
[ayumi@test ~]$ ls -l acltest.txt
-rw-r--r--+ 1 ayumi ayumi 34  4月 19 22:08 acltest.txt
[ayumi@test ~]$ getfacl acltest.txt
# file: acltest.txt
# owner: ayumi
# group: ayumi
user::rw-
user:ayumi:rw-                  #effective:r--
group::r--
group:ayumi:r--
mask::r--
other::r--

lsで見ると実行権は消えても相変わらずwrite権限を持っているように見えますが、getfaclコマンドの結果では
#effective:r--
とあります。これは「設定ではrw-だけど実際の効果はr--だよ」ということを示しています。
ここから実際のACLリストに追加されたユーザはすべてread権限しかないことがわかります。

windowsから見ると次のように見えます。
X:>icacls acltest.txt
acltest.txt S-1-5-21-3384635174-2336484230-2688597162-1001:(R,W)
            S-1-22-2-1000:(R)
            S-1-22-2-1000:(R)
            S-1-5-21-3384635174-2336484230-2688597162-1001:(R)
            Everyone:(R)

getfaclの結果と矛盾していません。

結局、ACLが付与されたファイルの場合はlsコマンドは普段と表示方法を変更しているのですが、manページにも記載がなく、大変迷惑な話です。
centos7のgitは古いからかもしれませんが、実際にgitコマンドでもcommit時のパーミッションの記録時に騙されたりしちゃったりして実害もあります。

そこで、じゃあ、マスク値をsamba側でコントロールできないのかという疑問が出てきますが、マニュアルを読む限りどこにもありませんでした。
そりゃそうですよね、最初っからマスクを下手に最小限にしたら、誰にもアクセスできないファイルが簡単にできてしまいます。

ではVisual StudioでACLを追加させない方法があるのか、または編集内容の保存方法を変える方法があるのか、という疑問が出てきますが、設定全般を見る限りはないようです。
まあ、繰り返しになりますが、もともとのVisual Studioが編集結果をファイルに保存する際は常に
  1. 元のファイルをテンポラリな名前に変更し、
  2. 新規に元のファイル名でファイルを作成し、
  3. 新規に作ったファイルにエディタ上の中身を保存し、
  4. ACLを新規ファイルに再設定し、
  5. 元のファイルを削除する
という保存方法のため、Visual Studioの挙動も、保存中にファイルが壊れた、なんてことがないようにする配慮ですからごもっともとしか言いようがありません。

lsコマンドにもlsコマンドなりの理由があって、+がついてたらACLが設定されてるよ、ACLに設定されてる情報をなるべく伝えたいよという(でもACLの中身とかattrとかはlsは詳しいことは見せないよ、というちょっと意地悪でもある)善意からのものですので、これもまた責めるのはかわいそうです。

それぞれの言い分が鋭く対立した結果、こうなってしまったわけですが、結局、どこかで誰かに妥協してもらわないと実際に使っている側が困ってしまいます。

仕方がないので、smb.confの以下のパラメータを順次ひとつづつ"No"にして再起動(reloadだと効果が出ないものもありますので)をしたらどうなるかを試した結果をまとめました。

実行順パラメタ既定値結果
1map archiveYes変化なし
2map hiddenNo変化なし
3map read onlyyes変化なし
4map systemNo変化なし
5store dos attributes記載なし変化なし
6acl map full controlYes変化なし
7acl allow execute alwaysNo変化なし
8acl check permissionsYes変化なし
9acl group controlNo変化なし
10nt acl supportYes効果あり

※既定値はtestparm -vコマンドで表示したときのものです。map read onlyの既定値が小文字なのも原文ママです。
変化なしの意味はlsでの見え方です。効果ありの意味はACLそのものが付与されていないことを意味しています。

この表からお分かりの通り、結局、ACLの取り扱いそのものを停止しないとダメでした!わはは

だから何だって?

以上、お読みいただいてありがとうございました。

2019年4月14日日曜日

CentOS7.6における2019年4月14日現在でのLet's encryptの証明書の取得方法

*** この記事は2019年4月14日現在のものです。 ***

Let's encryptって得られるのはサーバ証明書だけですが、その目的のためならばオレオレ証明書を作るよりよほど簡単に証明書が得られて本当に助かります。
androidやiosのようなオレオレ証明書を使うための設定が難しかったりする端末がある環境だと、ことさらありがたさが身に沁みます。

ただ、困ったことに、Let's Encryptの情報を検索すると、古いのと新しい情報がぐっちゃぐっちゃに混在した状況で、まことに混乱をしてしまいます。

そこで、本日、実際にCentOS7.6上でcertbotを用いてLet's Encryptの証明書を取得する機会を得ましたので、その手順と結果を記録しておきます。
当然ながらこの手順で得られた証明書は、apache,postfix,dovecotをはじめとしてサーバ証明書を利用できるアプリケーションで使いまわせます。

前提条件
  1. epelリポジトリから取得できるcertbot 0.31.0を使用して証明書を得る。
    理由:2019年4月14日にyum install certbot でインストールされるcertbotのバージョンが0.31.0だというだけの話です。
  2. 複数のサブドメインに対応した証明書を求めるがワイルドカードな証明書は求めない。
    理由:サブドメイン毎に証明書を変えられるのと、http-01での認証が可能なため、ワイルドカードな証明書を得るために必要なdns-01の場合に必須となるdnsのTXTの更新が不要だからです。
  3. Webサーバの構築が正しく行われていること。
    言い直すと、申請するドメイン名およびサブドメイン名すべてでhttp経由で/.well-known/acme-challenge/ ディレクトリの中身が外部から読み出せること。
    理由:ACME v2でhttp-01を用いて認証してもらうために一時的にLet's Encrypt サーバからトークンを取得して/.well-known/acme-challenge/ ディレクトリに配置し、Let's Encryptのサーバから読み出してもらうことで認証を行うためです。
    ドットから始まるファイルやディレクトリを外部から見えない設定にしていたり、アクセス元をIPアドレスやドメイン名などで制限していると失敗します。
    なお、このディレクトリはcertbotコマンドが勝手に掘って、認証が終わり次第削除されます。
    httpのポート番号はデフォルトでは80ですが、certbotに --http-01-port オプションを与えると、ポート番号を変更できます。
    https経由で認証をしてもらうtls-sni-01はもはや利用できません。
では実際に申請してみたいと思います。
このサンプルではドメイン名一つ(xxx.yyy)と5つのサブドメイン名を一度に認証して1つにまとめた証明書を発行してもらうコマンドでテストしてみる例を示します。

certbot certonly \
    --dry-run \
    --agree-tos \
    --webroot \
    -w /home/httpd/site0 -d xxx.yyy \
    -w /home/httpd/site1 -d site1.xxx.yyy \
    -w /home/httpd/site2 -d site2.xxx.yyy \
    -w /home/httpd/site3 -d site3.xxx.yyy \
    -w /home/httpd/site4 -d site4.xxx.yyy \
    -w /home/httpd/site5 -d site5.xxx.yyy \
    -m 更新切れ通知などを受け取るメールアドレス

certbotへ与えたオプションはそれぞれ以下の通りです。
  • certonly
    証明書の取得だけを行います。
  • --dry-run
    実際には証明書の取得を行わずに、取得の手順を一通り行ってくれます。
    Webサーバの設定が誤っていないかどうかもチェックしてもらえますので、いきなり証明書を取得せずにこのオプションをつけてテストすることをお勧めします。
    当然、証明書を実際に取得する際にはこのオプションを外して実行してください。
  • --agree-tos
    利用規約を読みもせずに無条件にやみくもに受け入れます。
    初回くらいこのオプションを外して読んだほうがいいとは思いますが、思うだけです。
  • --webroot
    この後に続く-wオプションで指定されたディレクトリにcertbotが/.well-known/acme-challenge/ディレクトリを掘って、トークンを配置して、-dで指定されたドメイン名でLet's Encryptサーバがアクセスを行い、読み出しを行って認証するhttp-01手順で行うことを指示しています。
  • -w と -d のペア
    -wは--webroot-pathの省略形です。-dで定義されるドメイン名のドキュメントルートディレクトリを記述します。
    -dは--domainsおよび--domainの省略形です。ドメイン名を表します。
    なお、-wは-dより先に記述する必要があります。
    その理由は、-dで指定されたドメイン名のドキュメントルートディレクトリは、直前の-wで指定された値を使うということになっているためです。
    この理由で、各ドメインで/.well-known/acme-challenge/以下を共有する設定にしてあるなら、一対一で記述する必要は特にありません。
上記コマンドを実行すると、一回だけ
Would you be willing to share your email address with the Electronic Frontier Foundation, a founding partner of the Let's Encrypt project and the non-profit organization that develops Certbot? We'd like to send you email about our work encrypting the web, EFF news, campaigns, and ways to support digital freedom.
と聞かれて、Yes/Noの入力を求められます。電子フロンティア財団の最新情報などが欲しければYesに、更新時期が近付いたなどの通知が欲しいだけならNoを選択することになるかと思います。

この選択が終わったら実際のテストが始まります。
各ドメイン名に実際にアクセスが行われ、その結果が逐次表示されていきます。
すべてうまくいけば、
IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
云々と表示されます。
エラーが出たら「おめでとう」とは言ってくれません。エラーになった原因が明確に示されますので、その情報に基づいて修正を行ってください。

おめでとう、と言われたら、IMPORTANT NOTES:欄ではどこに何の証明書を作ったか、その証明書の失効日はいつか、ということが明記されていますので一応一通り目を通しておいてください。

今回の場合は/etc/letsencrypt/live/ドメイン名/にドメイン名とサブドメイン名がまとめて証明された証明書が1つ配置されていますので、各サーバに証明書を適用していきます。
  • httpd 2.4.6-88(CentOS7.6ではapache2.4.6)
    今回申請した各VirtualHostにサーバ証明書(cert.pem)と秘密鍵(privkey.pem)と中間証明書(chain.pem)を設定します。
    SSLCertificateFile /etc/letsencrypt/live/ドメイン名/cert.pem
    SSLCertificateKeyFile  /etc/letsencrypt/live/ドメイン名/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/ドメイン名/chain.pem
  • postfix 2.10.1-7
    /etc/postfix/main.cfでサーバ証明書+中間証明書(fullchain.pem)と秘密鍵を設定します。fullchain.pemの中身はcert.pemとchain.pemが1ファイルにまとまっているものです。
    smtpd_tls_cert_file = /etc/letsencrypt/live/ドメイン名/fullchain.pem
    smtp_tls_cert_file = /etc/letsencrypt/live/ドメイン名/fullchain.pem
    smtpd_tls_key_file = /etc/letsencrypt/live/ドメイン名/privkey.pem
    smtp_tls_key_file = /etc/letsencrypt/live/ドメイン名/privkey.pem
  • dovecot 2.2.36-3
    /etc/dovecot/conf.d/10-ssl.conにpostfixと同様にサーバ証明書+中間証明書と秘密鍵を設定します。
    ssl_cert = </etc/letsencrypt/live/ドメイン名/fullchain.pem
    ssl_key = </etc/letsencrypt/live/ドメイン名/privkey.pem
それぞれ、設定が終わったら
systemctl reload httpd
systemctl reload postfix
systemctl reload dovecot
を行って設定を適用してください。

ここまでで各サーバに証明書が適用されました。

今度は、証明書の自動更新を行う手続きを定義します。
Let's Encryptの証明書の有効期限は90日固定なので、手動で更新するにはちと煩瑣ですので、cronで毎日更新を試みることにする場合は、/etc/cron.dailyに以下の内容を実行するシェルスクリプトを配置してください。

/usr/bin/certbot renew --quiet --max-log-backups 10 --post-hook "systemctl reload httpd;systemctl reload postfix; systemctl reload dovecot"

オプションの意味は以下の通りです。
  • renew
    更新が必要な証明書だけ更新します。
    更新が必要なドメイン、サブドメインなどの情報は、ドメインを申請したときの情報が/etc/letsencrypt/renewal/ドメイン名.confに保存されていますので、この情報をもとに更新を行ってくれます。
    有効期限が差し迫らない限り(現状では30日前)更新しません。一日二回がお勧めとのことですが、まあ、1回でいいでしょう。
  • --quiet
    エラー時を除いてメッセージを出力しません。
  • --max-log-backups 10
    デフォルトだと/var/log/letsencrypt/letsencrypt.log.*が最大1000個できちゃうので、せめて10個くらいにしてくださいというお願いです。
  • --post-hook
    更新がうまくいった際に実行するコマンドを定義します。
    この場合はhttpdとpostfixとdovecotに証明書をリロードさせています。
このコマンドをテストする場合は、--dry-runをオプションに加えてください。
そうすると、実際には更新を行いませんが、更新がかかったものとして--post-hookの内容を含めて実行してくれますので、一回は手動でやっておいたほうがいいと思います。

以上です。
お読みいただいてありがとうございました。