<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>もざぶれなるままに</title><link>https://mitz17.com/</link><description>Recent content on もざぶれなるままに</description><generator>Hugo -- gohugo.io</generator><language>ja-JP</language><lastBuildDate>Fri, 20 Mar 2026 12:00:00 +0900</lastBuildDate><atom:link href="https://mitz17.com/index.xml" rel="self" type="application/rss+xml"/><item><title>Pythonで自作した将棋AIと対局してみた｜学習前でも探索回数がかなり重要だった実装ログ</title><link>https://mitz17.com/blog/shogi-ai-before-training-matchlog/</link><pubDate>Fri, 20 Mar 2026 12:00:00 +0900</pubDate><guid>https://mitz17.com/blog/shogi-ai-before-training-matchlog/</guid><description>&lt;h2 id="今回は学習前のaiと対局してみた"&gt;今回は「学習前のAI」と対局してみた
&lt;/h2&gt;&lt;p&gt;この記事は、&lt;strong&gt;「自前で将棋AIを作ろうプロジェクト」の第二弾&lt;/strong&gt;です。&lt;/p&gt;
&lt;p&gt;第一弾はこちらです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/shogi-GUI/" &gt;Pythonで将棋AIを作るならまずGUI｜実装した理由と設計&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前回は、将棋AI開発の土台としてGUIを先に作った、という話を書きました。&lt;/p&gt;
&lt;p&gt;今回はその続きで、&lt;strong&gt;まだ強化学習を本格的に回す前の将棋AIと実際に対局してみたログ&lt;/strong&gt;です。&lt;/p&gt;
&lt;p&gt;この段階のAIは、ニューラルネットワークで強くなったAIではありません。&lt;br&gt;
中身はあくまで、&lt;strong&gt;手作り評価関数 + MCTS（モンテカルロ木探索）&lt;/strong&gt; をベースにした「学習前の将棋AI」です。
MCTS は、候補手を試しながら有望そうな手を重点的に読む探索手法です。&lt;/p&gt;
&lt;p&gt;やってみて一番はっきり分かったのは、&lt;strong&gt;同じ評価関数でも探索回数で強さがかなり変わる&lt;/strong&gt; ということでした。&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;※ ここでいう探索回数は、探索AIが次の一手を決める前にどれだけ候補手を試しながら読むかの回数です。実装上の引数名では &lt;code&gt;simulations&lt;/code&gt; に当たるものです。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;h2 id="この時点の将棋aiの中身"&gt;この時点の将棋AIの中身
&lt;/h2&gt;&lt;p&gt;現時点の &lt;code&gt;ML-shogi&lt;/code&gt; は、純粋な学習済みモデルではなく、まずは&lt;strong&gt;評価関数ベースで最低限戦えること&lt;/strong&gt;を優先して作っています。&lt;/p&gt;
&lt;p&gt;評価関数は、&lt;strong&gt;駒の損得が大きくズレないこと&lt;/strong&gt;を軸に、将棋らしい補正を少しずつ足した形です。基準値は次の通りです。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;駒&lt;/th&gt;
 &lt;th style="text-align: right"&gt;評価値&lt;/th&gt;
 &lt;th&gt;駒&lt;/th&gt;
 &lt;th style="text-align: right"&gt;評価値&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;歩&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;100&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;と&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;600&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;香&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;300&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;成香&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;450&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;桂&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;350&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;成桂&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;500&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;銀&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;550&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;成銀&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;550&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;金&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;600&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;角&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;800&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;馬&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;1200&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;飛&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;1000&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;龍&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;code&gt;1300&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;玉は通常の駒価値には入れず、詰みや終局判定で別に扱っています。&lt;/p&gt;
&lt;p&gt;補正は次の通りです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;駒価値ベースの静的評価を軸にする&lt;/li&gt;
&lt;li&gt;持ち駒は盤上より少し高く見る&lt;/li&gt;
&lt;li&gt;王手した側には &lt;code&gt;+200&lt;/code&gt;、王手されている側には &lt;code&gt;-200&lt;/code&gt; を入れる&lt;/li&gt;
&lt;li&gt;銀、桂馬、香車は自玉の近くにいないと少し下げる&lt;/li&gt;
&lt;li&gt;角と飛車は利きマス数と対玉プレッシャーで加点する&lt;/li&gt;
&lt;li&gt;馬は自玉近くの守備駒としても加点する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;持ち駒ボーナスは固定加点で、飛車 &lt;code&gt;+100&lt;/code&gt;、角 &lt;code&gt;+80&lt;/code&gt;、金と銀 &lt;code&gt;+50&lt;/code&gt;、桂 &lt;code&gt;+40&lt;/code&gt;、香 &lt;code&gt;+30&lt;/code&gt; です。&lt;/p&gt;
&lt;p&gt;銀・桂馬・香車には守備補正も入れています。&lt;br&gt;
銀は自玉の近くにいないと &lt;code&gt;-40&lt;/code&gt;、桂は &lt;code&gt;-30&lt;/code&gt;、香は &lt;code&gt;-25&lt;/code&gt; です。&lt;br&gt;
考え方は単純で、&lt;strong&gt;玉そばの銀・桂馬・香車の価値は高い&lt;/strong&gt; というものです。&lt;/p&gt;
&lt;p&gt;金はもともと守備駒として使う前提が強いので、位置補正は入れていません。&lt;/p&gt;
&lt;p&gt;大駒は働きも見ています。&lt;br&gt;
角は利き1マスごとに &lt;code&gt;+8&lt;/code&gt;、相手玉と角のラインが通っていれば追加で &lt;code&gt;+80&lt;/code&gt;、飛車は利き1マスごとに &lt;code&gt;+7&lt;/code&gt;、相手玉への縦横の圧力が通っていれば &lt;code&gt;+100&lt;/code&gt; です。&lt;br&gt;
馬は自玉の周囲2マス以内なら &lt;code&gt;+100&lt;/code&gt;、3マス以内なら &lt;code&gt;+40&lt;/code&gt; を足して、攻守両用の駒として少し評価しています。&lt;/p&gt;
&lt;p&gt;要するに、最初から複雑にしすぎず、&lt;strong&gt;探索を回した時に将棋として破綻しにくい評価&lt;/strong&gt; を優先しています。&lt;/p&gt;
&lt;h2 id="実際に対局してみた印象"&gt;実際に対局してみた印象
&lt;/h2&gt;&lt;p&gt;今回は、&lt;strong&gt;探索回数 32 / 500 / 1024&lt;/strong&gt; の3局を見ています。&lt;/p&gt;
&lt;div
 class="shogi-kifu-viewer"
 data-title="先手 私 / 後手 AI（探索回数 32） の棋譜"
 data-black-label="先手 私"
 data-white-label="後手 AI"
 data-ordinal="0"
&gt;
 &lt;div class="shogi-kifu-viewer__header"&gt;
 &lt;strong&gt;先手 私 / 後手 AI（探索回数 32） の棋譜&lt;/strong&gt;
 &lt;/div&gt;
 &lt;div class="shogi-kifu-viewer__app"&gt;&lt;/div&gt;
 &lt;script type="text/plain" class="shogi-kifu-viewer__data"&gt;#KIF version=2.0
先手：Human
後手：標準AI (MCTS)
手数----指手---------
1 7六歩(77)
2 4四歩(43)
3 4四角(88)
4 4二金(41)
5 2六歩(27)
6 8四歩(83)
7 2五歩(26)
8 8五歩(84)
9 7七角(44)
10 8四飛(82)
11 6八玉(59)
12 9二香(91)
13 7八玉(68)
14 7二銀(71)
15 4八銀(39)
16 8六歩(85)
17 8六歩(87)
18 6二金(61)
19 5六歩(57)
20 5二玉(51)
21 5七銀(48)
22 3二銀(31)
23 8八玉(78)
24 8六飛(84)
25 8六角(77)
26 3四歩(33)
27 7七角(86)
28 7七角成(22)
29 7七桂(89)
30 3三桂(21)
31 8二歩打
32 1二香(11)
33 8一歩成(82)
34 9四歩(93)
35 8二と(81)
36 7四歩(73)
37 7二と(82)
38 6四歩(63)
39 6二と(72)
40 6二玉(52)
41 9一飛打
42 5四歩(53)
43 7一角打
44 5一玉(62)
45 5三角成(71)
46 8一歩打
47 8一飛成(91)
48 7一角打
49 7一龍(81)
&lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;いきなりパックマンを誘ってきたと思った。パックマンに乗ったらパックマンせえへんのかーい！&lt;/p&gt;
&lt;div
 class="shogi-kifu-viewer"
 data-title="先手 AI（探索回数 500） / 後手 私 終盤の謎王手が出た棋譜"
 data-black-label="先手 AI"
 data-white-label="後手 私"
 data-ordinal="1"
&gt;
 &lt;div class="shogi-kifu-viewer__header"&gt;
 &lt;strong&gt;先手 AI（探索回数 500） / 後手 私 終盤の謎王手が出た棋譜&lt;/strong&gt;
 &lt;/div&gt;
 &lt;div class="shogi-kifu-viewer__app"&gt;&lt;/div&gt;
 &lt;script type="text/plain" class="shogi-kifu-viewer__data"&gt;#KIF version=2.0
先手：標準AI (MCTS)
後手：Human
手数----指手---------
1 7六歩(77)
2 8四歩(83)
3 4八玉(59)
4 8五歩(84)
5 2六歩(27)
6 8六歩(85)
7 8六歩(87)
8 8六飛(82)
9 5九玉(48)
10 7六飛(86)
11 6八銀(79)
12 3二金(41)
13 4八玉(59)
14 3四歩(33)
15 2二角成(88)
16 2二銀(31)
17 6六歩(67)
18 6六飛(76)
19 1五角打
20 3三角打
21 3三角成(15)
22 3三銀(22)
23 6七歩打
24 8六飛(66)
25 7七桂(89)
26 8九飛成(86)
27 5九金(49)
28 6二銀(71)
29 4九玉(48)
30 6四歩(63)
31 2五歩(26)
32 6三銀(62)
33 2六飛(28)
34 7二金(61)
35 2四歩(25)
36 2四歩(23)
37 7九金(69)
38 9九龍(89)
39 8六飛(26)
40 8三歩打
41 8九金(79)
42 9八龍(99)
43 9八金(89)
44 9四歩(93)
45 9六歩(97)
46 6二玉(51)
47 1六歩(17)
48 1四歩(13)
49 4八銀(39)
50 2八角打
51 1八香(19)
52 1九角成(28)
53 6五桂(77)
54 6五歩(64)
55 5五角打
56 1八馬(19)
57 3三角成(55)
58 3三桂(21)
59 2六飛(86)
60 2五歩(24)
61 7六飛(26)
62 7四香打
63 7四飛(76)
64 7四歩(73)
65 6一飛打
66 6一玉(62)
67 6二香打
68 6二玉(61)
69 7一銀打
70 7一玉(62)
71 3六歩(37)
72 3五歩(34)
73 3五歩(36)
74 3六桂打
75 5六歩(57)
76 4八桂成(36)
77 4八金(59)
78 2九馬(18)
79 5七銀(68)
80 3九飛打
81 5八玉(49)
82 5九飛打
83 6八玉(58)
84 7九飛成(59)
85 5八玉(68)
86 5九飛成(39)
&lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;うっかり龍をただで取られているのは秘密です。&lt;br&gt;
65手目からAIあるあるの終盤での謎王手が来ました。AIやってるって感じがして嬉しくなった。これの要因は、王手に評価ボーナスを入れていることです。&lt;/p&gt;
&lt;div
 class="shogi-kifu-viewer"
 data-title="先手 私 / 後手 AI（探索回数 1024） の棋譜"
 data-black-label="先手 私"
 data-white-label="後手 AI"
 data-ordinal="2"
&gt;
 &lt;div class="shogi-kifu-viewer__header"&gt;
 &lt;strong&gt;先手 私 / 後手 AI（探索回数 1024） の棋譜&lt;/strong&gt;
 &lt;/div&gt;
 &lt;div class="shogi-kifu-viewer__app"&gt;&lt;/div&gt;
 &lt;script type="text/plain" class="shogi-kifu-viewer__data"&gt;#KIF version=2.0
先手：Human
後手：標準AI (MCTS)
手数----指手---------
1 7六歩(77)
2 4二飛(82)
3 6八飛(28)
4 3二飛(42)
5 4八玉(59)
6 4二飛(32)
7 6六歩(67)
8 3四歩(33)
9 3八玉(48)
10 5五角(22)
11 7八銀(79)
12 3三桂(21)
13 6七銀(78)
14 7四歩(73)
15 5六銀(67)
16 4四角(55)
17 2八玉(38)
18 7三桂(81)
19 3八銀(39)
20 5四歩(53)
21 6五歩(66)
22 8八角(44)
23 8八飛(68)
24 5二飛(42)
25 7七桂(89)
26 5三角打
27 8六歩(87)
28 9二香(91)
29 8五歩(86)
30 1二香(11)
31 8四歩(85)
32 8四歩(83)
33 8四飛(88)
34 8五歩打
35 8三飛成(84)
36 8六角(53)
37 7八金(69)
38 5五歩(54)
39 6七銀(56)
40 9五角(86)
41 9六歩(97)
42 8二銀(71)
43 9二龍(83)
44 8六角(95)
45 6四歩(65)
46 6四角(86)
47 6六香打
48 8六角(64)
49 6三香成(66)
50 4二飛(52)
51 5三成香(63)
52 5三角(86)
53 6三歩打
54 7二飛(42)
55 6六銀(67)
56 7一金(61)
57 5五銀(66)
58 2四歩(23)
59 5四銀(55)
60 8六角(53)
61 4三銀成(54)
62 6四角(86)
63 3三成銀(43)
64 5二飛(72)
65 6八金(78)
66 6一玉(51)
67 4三角打
68 9一銀(82)
69 9一龍(92)
70 3七角成(64)
71 3七桂(29)
72 5一玉(61)
73 7一龍(91)
74 6一香打
75 6一龍(71)
76 6一玉(51)
77 6二金打
&lt;/script&gt;
&lt;/div&gt;

&lt;p&gt;学習前でも、合法手が壊れている感じはなく、飛車角交換や持ち駒の打ちもそれなりに将棋っぽく進みます。&lt;/p&gt;
&lt;p&gt;一方で、読みが浅い設定では不自然な手もまだ残っていて、&lt;strong&gt;評価関数を使ってどれだけ先まで読むか&lt;/strong&gt; の影響がかなり大きいと感じました。&lt;/p&gt;
&lt;h2 id="学習前のaiでは探索回数がかなり重要"&gt;学習前のAIでは探索回数がかなり重要
&lt;/h2&gt;&lt;p&gt;特に探索回数 &lt;code&gt;32&lt;/code&gt; あたりの低め設定では、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;交換の損得はそれっぽくても、その後が続かない&lt;/li&gt;
&lt;li&gt;受けない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という感じで、話にならないです。&lt;/p&gt;
&lt;p&gt;逆に探索回数 &lt;code&gt;1024&lt;/code&gt; まで上げると、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;明らかな取り逃しが減る&lt;/li&gt;
&lt;li&gt;飛車や角の圧力を活かす手が増える&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という変化が出ました。&lt;br&gt;
評価関数そのものを変えていないのに対局感がかなり変わるので、&lt;strong&gt;少なくとも学習前のAIでは探索回数がかなり重要&lt;/strong&gt; だと分かりました。&lt;/p&gt;
&lt;h2 id="開発ログとしての収穫"&gt;開発ログとしての収穫
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;学習前でも人間相手に触れる段階まで持っていく価値は大きい&lt;/strong&gt; です。&lt;br&gt;
自己対局ログや損失だけでは見えにくい「変な指し回し」が、実際に対局するとすぐ分かります。&lt;/p&gt;
&lt;p&gt;次に、自己対局の探索回数を &lt;code&gt;8&lt;/code&gt; から &lt;code&gt;128&lt;/code&gt; に上げたことで学習が進み始めたのも、かなり納得感がありました。&lt;br&gt;
実際に人間が相手をすると、&lt;strong&gt;探索回数が少ないAIは教師データ生成にも不利そう&lt;/strong&gt; だと体感できます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;評価関数ベースでも一応対局は成立する&lt;/li&gt;
&lt;li&gt;探索回数で強さが大きく変わる&lt;/li&gt;
&lt;li&gt;学習前のボトルネックが、評価関数そのものだけでなく探索品質にもあると分かった&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特に今回は、&lt;strong&gt;「強化学習を始める前に、まず評価関数 + 探索だけでどこまで戦えるかを見る」&lt;/strong&gt; という意味でかなり良い確認になりました。&lt;br&gt;
学習に入る前の土台として、どこが弱くて、どこは意外と戦えているのかが少しずつ見えてきました。&lt;/p&gt;
&lt;h2 id="次にやりたいこと"&gt;次にやりたいこと
&lt;/h2&gt;&lt;p&gt;次は学習です。&lt;br&gt;
少なくとも学習前のAIでは探索回数がかなり重要だと分かったので、この土台の上で自己対局と学習を回して、どこまで指し回しが変わるかを見ていきます。&lt;/p&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;p&gt;学習前の将棋AIでも、手作り評価関数 + MCTS だけである程度は対局になります。&lt;br&gt;
ただし強さは「評価関数の出来」だけでは決まらず、&lt;strong&gt;探索回数でかなり別物になる&lt;/strong&gt; というのが今回の一番大きな発見でした。&lt;/p&gt;
&lt;p&gt;同じ評価関数でも、読む回数が少ないとかなり軽い手を指し、増やすと急にしぶとくなります。&lt;/p&gt;
&lt;p&gt;次は、この土台の上で実際に学習を回していきます。&lt;/p&gt;
&lt;h2 id="関連記事"&gt;関連記事
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/shogi-GUI/" &gt;Pythonで将棋AIを作るならまずGUI｜実装した理由と設計&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/get-chrome-driver-python/" &gt;Selenium使用時にChromeDriverを自動更新する方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/mp3-lufs-normalizer-python/" &gt;音楽の音量を自動調整するツールをPythonで作る LUFS正規化開発ログ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Ansysのバージョン選択ツールを作った理由｜古い解析ファイルを別バージョンで開くリスクを減らす</title><link>https://mitz17.com/blog/ansys-version-selector/</link><pubDate>Wed, 18 Mar 2026 11:00:00 +0900</pubDate><guid>https://mitz17.com/blog/ansys-version-selector/</guid><description>&lt;p&gt;GitHub: &lt;a class="link" href="https://github.com/mitz17/ANSYS_Version_Selector" target="_blank" rel="noopener"
 &gt;mitz17/ANSYS_Version_Selector&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;このツールは GitHub で公開しています。興味があればコードや README もそのまま確認できます。
この記事で紹介するツールは個人が作成した非公式ツールであり、Ansys の公式製品・公式サポートとは関係ありません。&lt;/p&gt;
&lt;h2 id="作った理由"&gt;作った理由
&lt;/h2&gt;&lt;p&gt;自分の環境では、Ansys® Fluent、Ansys SpaceClaim、Ansys Workbench を複数バージョン入れていると、「どのバージョンで開くか」を毎回きちんと制御しづらいことがありました。&lt;/p&gt;
&lt;p&gt;手元では、バージョン選択が素直に動かず、&lt;code&gt;2025 R2&lt;/code&gt; を押したのに &lt;code&gt;2025 R1&lt;/code&gt; が開いてしまうことがありました。&lt;br&gt;
しかも、既定のアプリを新しいバージョンに設定していると、古い解析ファイルをうっかり新しいバージョンで開いてしまうことがあります。&lt;/p&gt;
&lt;p&gt;この状態で何も考えず保存すると、今度は古いバージョンで開けなくなることがあります。&lt;br&gt;
実際の運用ではこれがかなり厄介で、「どのバージョンで開くか」を毎回明示的に選べる仕組みが欲しくなりました。&lt;/p&gt;
&lt;p&gt;もちろん、Ansys 製品そのものは解析機能として非常に強力で、実務でも頼る場面が多いです。&lt;br&gt;
今回作ったのはその代替ではなく、複数バージョン運用時の起動まわりを少し安全にするための補助ツールです。&lt;/p&gt;
&lt;p&gt;そこで作ったのが、&lt;code&gt;Fluent&lt;/code&gt;、&lt;code&gt;SpaceClaim&lt;/code&gt;、&lt;code&gt;Workbench&lt;/code&gt; 向けのバージョン選択ツールです。&lt;/p&gt;
&lt;h2 id="何をするツールか"&gt;何をするツールか
&lt;/h2&gt;&lt;p&gt;このツールは、Windows 上で Ansys 製品の起動前にバージョンを選ぶための小さな GUI ランチャーです。&lt;/p&gt;
&lt;p&gt;対象は次の 3 つです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fluent&lt;/li&gt;
&lt;li&gt;SpaceClaim&lt;/li&gt;
&lt;li&gt;Workbench&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;やっていること自体はシンプルで、インストール済みの実行ファイルをバージョンごとに登録しておき、開きたいファイルに対して「どの版で起動するか」を選べるようにしています。&lt;/p&gt;
&lt;p&gt;これにより、次のようなリスクを減らしやすくなります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;古い解析ファイルを新しい版で開いてしまう&lt;/li&gt;
&lt;li&gt;複数バージョン環境で意図しない版が起動する&lt;/li&gt;
&lt;li&gt;実行ファイルの場所を毎回手で探す&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="対応しているもの"&gt;対応しているもの
&lt;/h2&gt;&lt;h3 id="fluent"&gt;Fluent
&lt;/h3&gt;&lt;p&gt;対応拡張子は以下です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.msh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.msh.h5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cas&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cas.h5&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dat&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dat.h5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Fluent については、単に EXE を起動するだけではなく、読ませるファイルに応じて一時 &lt;code&gt;.jou&lt;/code&gt; ファイルを生成して自動読込するようにしています。&lt;br&gt;
これは、&lt;code&gt;.msh&lt;/code&gt;、&lt;code&gt;.cas&lt;/code&gt;、&lt;code&gt;.dat&lt;/code&gt; で読み込み手順が異なるためで、起動後に毎回手作業で読み込みコマンドを入れなくて済むようにするためです。&lt;/p&gt;
&lt;p&gt;さらに、次のような指定にも対応しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ソルバ / メッシング切り替え&lt;/li&gt;
&lt;li&gt;2D / 3D&lt;/li&gt;
&lt;li&gt;Double Precision&lt;/li&gt;
&lt;li&gt;並列数指定&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="./blog/ansys-version-selector/Fluent.png"
	width="822"
	height="552"
	loading="lazy"
	
		alt="Fluent バージョン選択ツールの画面"
	
 
	
		class="gallery-image" 
		data-flex-grow="148"
		data-flex-basis="357px"
	
&gt;&lt;/p&gt;
&lt;p&gt;Fluent 向けの画面では、バージョン選択に加えて、製品モード、次元、精度、並列数までまとめて指定できます。&lt;br&gt;
単に「どの版を開くか」だけでなく、起動条件までワンステップで決められるようにしました。&lt;/p&gt;
&lt;h3 id="spaceclaim"&gt;SpaceClaim
&lt;/h3&gt;&lt;p&gt;対応拡張子は以下です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.scdoc&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.step&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.stp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.iges&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.igs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;こちらは比較的単純で、選択したバージョンの &lt;code&gt;SpaceClaim.exe&lt;/code&gt; に対象ファイルをそのまま渡して起動します。&lt;/p&gt;
&lt;p&gt;&lt;img src="./blog/ansys-version-selector/SCDM.png"
	width="822"
	height="392"
	loading="lazy"
	
		alt="SpaceClaim バージョン選択ツールの画面"
	
 
	
		class="gallery-image" 
		data-flex-grow="209"
		data-flex-basis="503px"
	
&gt;&lt;/p&gt;
&lt;p&gt;SpaceClaim は起動引数が比較的単純なので、画面もかなりミニマルです。&lt;br&gt;
対応ファイルを選んで、どのバージョンで開くかを決めることに役割を絞っています。&lt;/p&gt;
&lt;h3 id="workbench"&gt;Workbench
&lt;/h3&gt;&lt;p&gt;対応拡張子は以下です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.wbpj&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Workbench は、選択した実行ファイルに対して &lt;code&gt;-F &amp;lt;filepath&amp;gt;&lt;/code&gt; を付けて起動します。&lt;/p&gt;
&lt;p&gt;&lt;img src="./blog/ansys-version-selector/WB.png"
	width="822"
	height="392"
	loading="lazy"
	
		alt="Workbench バージョン選択ツールの画面"
	
 
	
		class="gallery-image" 
		data-flex-grow="209"
		data-flex-basis="503px"
	
&gt;&lt;/p&gt;
&lt;p&gt;Workbench も基本はシンプルで、&lt;code&gt;.wbpj&lt;/code&gt; を選び、開く版を選んで起動する構成です。&lt;br&gt;
古い案件をどの版で開くかを明示できるだけでも、うっかり事故はかなり減らせます。&lt;/p&gt;
&lt;h2 id="共通インターフェース"&gt;共通インターフェース
&lt;/h2&gt;&lt;p&gt;各ランチャーは個別スクリプトですが、設定保存やバージョン管理 UI は共通化しています。&lt;br&gt;
その共通処理をまとめているのが &lt;code&gt;launcher_common.py&lt;/code&gt; です。&lt;/p&gt;
&lt;p&gt;共通化している処理の中でも特に大きいのが、設定ファイルの保存と、バージョン一覧を編集するためのダイアログです。&lt;br&gt;
その設定情報は JSON で保存します。&lt;br&gt;
保存されるのは「バージョン名」と「そのバージョンで使う実行ファイルの絶対パス」の対応です。&lt;/p&gt;
&lt;p&gt;たとえば Fluent なら、こんなイメージです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;versions&amp;#34;&lt;/span&gt;: {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;v252&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;C:\\Program Files\\ANSYS Inc\\v252\\fluent\\ntbin\\win64\\fluent.exe&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;この形式にしておくと、人間が見ても分かりやすく、あとから手修正もしやすいです。&lt;/p&gt;
&lt;p&gt;&lt;img src="./blog/ansys-version-selector/version_set.png"
	width="833"
	height="512"
	loading="lazy"
	
		alt="バージョン設定ダイアログ"
	
 
	
		class="gallery-image" 
		data-flex-grow="162"
		data-flex-basis="390px"
	
&gt;&lt;/p&gt;
&lt;p&gt;設定画面では、検出したバージョンを一覧で管理できます。&lt;br&gt;
追加・更新・削除だけでなく、&lt;code&gt;上へ&lt;/code&gt; / &lt;code&gt;下へ&lt;/code&gt; ボタンで並び順も変更できるようにしてあり、よく使う版を上に寄せておけます。&lt;br&gt;
自動検出した結果をそのまま使うだけでなく、現場の運用に合わせて順番を整えられるようにしました。&lt;/p&gt;
&lt;h2 id="実装方針"&gt;実装方針
&lt;/h2&gt;&lt;p&gt;GUI は &lt;code&gt;tkinter&lt;/code&gt; で作っています。&lt;br&gt;
理由は単純で、Windows 上で配ることを考えたときに依存を増やしたくなかったからです。&lt;/p&gt;
&lt;p&gt;このツール全体の設計では、次の方針を優先しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows 専用前提で割り切る&lt;/li&gt;
&lt;li&gt;依存を増やしすぎず、配布しやすくする&lt;/li&gt;
&lt;li&gt;製品ごとの違いは吸収しつつ、責務は分ける&lt;/li&gt;
&lt;li&gt;自動化しすぎるより、現場で直しやすい形を優先する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;たとえば GUI を &lt;code&gt;tkinter&lt;/code&gt; にしたのは、見た目の派手さよりも「Python 標準に近い構成でそのまま動く」ことを優先したからです。&lt;br&gt;
複数人に配る可能性や、あとから EXE 化することも考えると、追加依存が少ない方が扱いやすいと判断しました。&lt;/p&gt;
&lt;p&gt;また、Fluent / SpaceClaim / Workbench を全部ひとつの巨大なランチャーにまとめるのではなく、製品ごとにスクリプトを分けています。&lt;br&gt;
これは、Fluent だけ起動オプションや読み込みロジックが明らかに重く、SpaceClaim や Workbench とは責務が違うからです。&lt;/p&gt;
&lt;p&gt;そのうえで、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;設定保存&lt;/li&gt;
&lt;li&gt;バージョン管理 UI&lt;/li&gt;
&lt;li&gt;旧設定の移行&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;のような共通部分だけを &lt;code&gt;launcher_common.py&lt;/code&gt; に寄せています。&lt;br&gt;
つまり、全部を抽象化するのではなく、「同じ処理だけ共通化し、違う処理は分ける」という方針です。&lt;/p&gt;
&lt;p&gt;バージョン検出も同じ考え方で、何でも自動判定するより、典型的なインストール先をまず確実に拾い、ダメなら手で修正できる構成にしています。&lt;br&gt;
実際の現場では、検出ロジックが賢すぎることより、意図が読めて自分で直せることの方が重要だと感じたためです。&lt;/p&gt;
&lt;h2 id="コード上ではどう動いているか"&gt;コード上ではどう動いているか
&lt;/h2&gt;&lt;p&gt;ランチャー本体は 3 ファイルに分かれています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Fluent_Launcher_from_md.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SpaceClaim_Launcher.py&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Workbench_Launcher.py&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;共通部分は &lt;code&gt;launcher_common.py&lt;/code&gt; に寄せています。&lt;/p&gt;
&lt;h3 id="共通処理-設定保存と設定ダイアログ"&gt;共通処理: 設定保存と設定ダイアログ
&lt;/h3&gt;&lt;p&gt;設定ファイルの保存場所は &lt;code&gt;launcher_common.py&lt;/code&gt; の &lt;code&gt;config_base_dir()&lt;/code&gt; で決めています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;config_base_dir&lt;/span&gt;() &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; Path:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; base &lt;span style="color:#f92672"&gt;=&lt;/span&gt; app_base_dir()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; getattr(sys, &lt;span style="color:#e6db74"&gt;&amp;#34;frozen&amp;#34;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; base
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; appdata &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;environ&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;APPDATA&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; appdata:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; candidate &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Path(appdata) &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;AnsysLaunchers&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;.py&lt;/code&gt; として実行する場合はスクリプト配置フォルダを使い、EXE 化後は &lt;code&gt;%APPDATA%\AnsysLaunchers&lt;/code&gt; を優先します。&lt;br&gt;
この分岐を入れておくことで、開発中の扱いやすさと、配布後の設定保存先の安定性を両立しています。&lt;/p&gt;
&lt;p&gt;設定画面そのものは &lt;code&gt;BaseSettingsDialog&lt;/code&gt; として共通化してあり、各ランチャーは「何をスキャンするか」だけを差し替える形です。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ttk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Button(btn_frm, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;追加/更新&amp;#34;&lt;/span&gt;, command&lt;span style="color:#f92672"&gt;=&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add_update)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack(side&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;left&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ttk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Button(btn_frm, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;削除&amp;#34;&lt;/span&gt;, command&lt;span style="color:#f92672"&gt;=&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;delete_item)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack(side&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;left&amp;#34;&lt;/span&gt;, padx&lt;span style="color:#f92672"&gt;=&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ttk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Button(btn_frm, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;上へ&amp;#34;&lt;/span&gt;, command&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;lambda&lt;/span&gt;: self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;move_item(&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;))&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack(side&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;left&amp;#34;&lt;/span&gt;, padx&lt;span style="color:#f92672"&gt;=&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ttk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Button(btn_frm, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;下へ&amp;#34;&lt;/span&gt;, command&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;lambda&lt;/span&gt;: self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;move_item(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;))&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack(side&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;left&amp;#34;&lt;/span&gt;, padx&lt;span style="color:#f92672"&gt;=&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ttk&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Button(btn_frm, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;スキャン&amp;#34;&lt;/span&gt;, command&lt;span style="color:#f92672"&gt;=&lt;/span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;scan_versions)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;pack(side&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;left&amp;#34;&lt;/span&gt;, padx&lt;span style="color:#f92672"&gt;=&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;move_item()&lt;/code&gt; で辞書順ではなく表示順を入れ替えて保存しているので、一覧の先頭に「よく使う版」を持ってきやすくしています。&lt;/p&gt;
&lt;h3 id="fluent-ファイル種別ごとにジャーナルを作る"&gt;Fluent: ファイル種別ごとにジャーナルを作る
&lt;/h3&gt;&lt;p&gt;Fluent では、どの拡張子を開くかによって起動時の処理を変えています。&lt;br&gt;
その中核が &lt;code&gt;build_journal_for_file()&lt;/code&gt; です。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; base_ext &lt;span style="color:#f92672"&gt;in&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;.msh&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;.msh.h5&amp;#34;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cmds&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/file/read-mesh &lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;p&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; base_ext &lt;span style="color:#f92672"&gt;in&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;.cas&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;.cas.h5&amp;#34;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cmds&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/file/read-case &lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;p&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; base_ext &lt;span style="color:#f92672"&gt;in&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;.dat&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;.dat.h5&amp;#34;&lt;/span&gt;]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cas_candidates &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; filepath&lt;span style="color:#f92672"&gt;.&lt;/span&gt;with_name(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;base_root&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.cas&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; filepath&lt;span style="color:#f92672"&gt;.&lt;/span&gt;with_name(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;base_root&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;.cas.h5&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ここで、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.msh&lt;/code&gt; なら &lt;code&gt;read-mesh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cas&lt;/code&gt; なら &lt;code&gt;read-case&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dat&lt;/code&gt; なら対応する &lt;code&gt;.cas&lt;/code&gt; を探してから &lt;code&gt;read-data&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という流れを組み立てています。&lt;/p&gt;
&lt;p&gt;その後 &lt;code&gt;launch_fluent()&lt;/code&gt; で一時 &lt;code&gt;.jou&lt;/code&gt; を作って &lt;code&gt;-i&lt;/code&gt; 付きで Fluent に渡します。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; tempfile&lt;span style="color:#f92672"&gt;.&lt;/span&gt;NamedTemporaryFile(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;w&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; delete&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; prefix&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;ansys_launcher_&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; suffix&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;.jou&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; encoding&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;utf-8&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; newline&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\n&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; tf:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write(journal_text)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; journal_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tf&lt;span style="color:#f92672"&gt;.&lt;/span&gt;name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cmd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;extend([&lt;span style="color:#e6db74"&gt;&amp;#34;-i&amp;#34;&lt;/span&gt;, journal_path])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;これで、GUI からバージョンを選ぶだけで、対象ファイルに応じた読込処理まで自動で流せるようにしています。&lt;/p&gt;
&lt;h3 id="fluent-2d3ddp並列数も起動引数に反映"&gt;Fluent: 2D/3D、DP、並列数も起動引数に反映
&lt;/h3&gt;&lt;p&gt;Fluent は版選択だけではなく、起動条件も GUI から変えられるようにしています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resolve_mode&lt;/span&gt;(self) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; str:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;product_var&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get() &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;meshing&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;3d&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dim &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;dim_var&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dp &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;dp_var&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mode &lt;span style="color:#f92672"&gt;=&lt;/span&gt; dim
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; dp:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mode &lt;span style="color:#f92672"&gt;+=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;dp&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; mode
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;この返り値をそのまま起動引数に使い、さらに並列数も &lt;code&gt;-t&lt;/code&gt; で付けています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; n_procs &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cmd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(&lt;span style="color:#e6db74"&gt;&amp;#34;-t&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;+&lt;/span&gt; str(n_procs))
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="spaceclaim--workbench-できるだけ単純に起動"&gt;SpaceClaim / Workbench: できるだけ単純に起動
&lt;/h3&gt;&lt;p&gt;SpaceClaim と Workbench は、Fluent のような追加ロジックを極力持たせていません。&lt;br&gt;
必要な引数だけ渡して、あとは選んだ版で開くことに責務を絞っています。&lt;/p&gt;
&lt;p&gt;SpaceClaim はこうです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cmd &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [exe]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; filepath:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cmd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(filepath)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Workbench は &lt;code&gt;.wbpj&lt;/code&gt; を &lt;code&gt;-F&lt;/code&gt; 付きで渡します。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cmd &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [exe]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; filepath:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cmd&lt;span style="color:#f92672"&gt;.&lt;/span&gt;extend([&lt;span style="color:#e6db74"&gt;&amp;#34;-F&amp;#34;&lt;/span&gt;, filepath])
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;どちらも「起動する版を間違えない」ことが主目的なので、実装もその目的に対して必要最小限にしています。&lt;/p&gt;
&lt;h2 id="バージョン検出の考え方"&gt;バージョン検出の考え方
&lt;/h2&gt;&lt;p&gt;完全自動で何でも見つけるより、まずは「よくあるインストール先を確実に拾う」方針にしています。&lt;/p&gt;
&lt;p&gt;たとえば Fluent なら、次のような典型パスを見に行きます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;C:\Program Files\ANSYS Inc\v252\fluent\ntbin\win64\fluent.exe
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;SpaceClaim や Workbench も同様に、典型的なインストール先を優先してスキャンします。&lt;br&gt;
見つからない場合は、設定ダイアログから手で実行ファイルを登録できるようにしています。&lt;/p&gt;
&lt;p&gt;このあたりは「賢すぎる自動検出」より、「現場で確実に直せること」を優先しました。&lt;/p&gt;
&lt;p&gt;実際の検出も、かなり素直です。&lt;br&gt;
たとえば Fluent は &lt;code&gt;v***\fluent\ntbin\win64\fluent.exe&lt;/code&gt; を見ています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;exe &lt;span style="color:#f92672"&gt;=&lt;/span&gt; vdir &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fluent&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ntbin&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;win64&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;/&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fluent.exe&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; exe&lt;span style="color:#f92672"&gt;.&lt;/span&gt;exists():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; found[vdir&lt;span style="color:#f92672"&gt;.&lt;/span&gt;name] &lt;span style="color:#f92672"&gt;=&lt;/span&gt; str(exe)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Workbench では &lt;code&gt;RunWB2.exe&lt;/code&gt; や &lt;code&gt;ansyswb.exe&lt;/code&gt; を探し、SpaceClaim では &lt;code&gt;SpaceClaim.exe&lt;/code&gt; や &lt;code&gt;SCDM.exe&lt;/code&gt; を見ています。&lt;br&gt;
見つからないときは設定画面から手で登録できるので、多少環境差があっても逃げ道を残しています。&lt;/p&gt;
&lt;h2 id="fluent-だけ少しロジックが重い"&gt;Fluent だけ少しロジックが重い
&lt;/h2&gt;&lt;p&gt;3 つの中でいちばん実装が厚いのは Fluent です。&lt;/p&gt;
&lt;p&gt;理由は、単に EXE を開くだけでは済まず、読み込むファイルの種類によって処理を変える必要があるからです。&lt;/p&gt;
&lt;p&gt;たとえば、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.msh&lt;/code&gt; なら &lt;code&gt;read-mesh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.cas&lt;/code&gt; なら &lt;code&gt;read-case&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.dat&lt;/code&gt; なら必要に応じて対応する &lt;code&gt;.cas&lt;/code&gt; を先に探してから &lt;code&gt;read-data&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;というように、起動時に流すジャーナルを変えています。&lt;/p&gt;
&lt;p&gt;親プロセス側でジャーナルをすぐ消してしまうと、Fluent 側の読み込みタイミング次第で失敗する可能性があるため、一時 &lt;code&gt;.jou&lt;/code&gt; は即削除しない設計にしました。&lt;br&gt;
代わりに、48 時間以上古いランチャー由来のジャーナルを次回起動時に掃除するようにしています。&lt;/p&gt;
&lt;p&gt;このあたりは地味ですが、実際に安定して使うには必要な処理でした。&lt;/p&gt;
&lt;p&gt;ジャーナルを即削除しない理由もコード側で明示してあります。&lt;br&gt;
親プロセスが先に消すと Fluent 側の読込タイミングと競合する可能性があるため、&lt;code&gt;cleanup_old_journals()&lt;/code&gt; で次回起動時に古いものだけ掃除する方式にしました。&lt;/p&gt;
&lt;h2 id="使い方"&gt;使い方
&lt;/h2&gt;&lt;h3 id="python-スクリプトとして使う"&gt;Python スクリプトとして使う
&lt;/h3&gt;&lt;p&gt;各スクリプトは単体で起動できます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py &lt;span style="color:#ae81ff"&gt;-3&lt;/span&gt; .\Fluent_Launcher_from_md.py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py &lt;span style="color:#ae81ff"&gt;-3&lt;/span&gt; .\SpaceClaim_Launcher.py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py &lt;span style="color:#ae81ff"&gt;-3&lt;/span&gt; .\Workbench_Launcher.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ファイルを引数で渡して起動することもできます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py &lt;span style="color:#ae81ff"&gt;-3&lt;/span&gt; .\Fluent_Launcher_from_md.py D:\work\sample.cas.h5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py &lt;span style="color:#ae81ff"&gt;-3&lt;/span&gt; .\SpaceClaim_Launcher.py D:\cad\part.scdoc
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;py &lt;span style="color:#ae81ff"&gt;-3&lt;/span&gt; .\Workbench_Launcher.py D:\project\model.wbpj
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;普段はこれを既定アプリや右クリックメニューと組み合わせて使う想定です。&lt;br&gt;
ダブルクリック直後に版を選べるようにしておけば、「そのまま新しい版で開いてしまった」をかなり防げます。&lt;/p&gt;
&lt;h3 id="初回起動時の流れ"&gt;初回起動時の流れ
&lt;/h3&gt;&lt;p&gt;初回起動時に設定ファイルがなければ、典型的なインストール先をスキャンします。&lt;br&gt;
検出に成功すれば、その結果を JSON として保存します。&lt;/p&gt;
&lt;p&gt;以後は、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;登録済みバージョンを選ぶ&lt;/li&gt;
&lt;li&gt;必要なら設定画面で追加・修正する&lt;/li&gt;
&lt;li&gt;ファイルを選んで起動する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;という流れです。&lt;/p&gt;
&lt;h2 id="exe-化の方法"&gt;EXE 化の方法
&lt;/h2&gt;&lt;p&gt;このツールは PyInstaller による単一 EXE 化を前提にしています。&lt;/p&gt;
&lt;p&gt;通常ビルド:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\build_all_exe.ps1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;クリーンビルド:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\build_all_exe.ps1 -Clean
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;README にあるとおり、ビルドすると次の EXE を作る構成です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FluentVersionSelector.exe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SpaceClaimVersionSelector.exe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WorkbenchVersionSelector.exe&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PyInstaller が見つからない場合だけ &lt;code&gt;python -m pip install pyinstaller&lt;/code&gt; を実行する想定なので、毎回余計なことをしない構成になっています。&lt;/p&gt;
&lt;p&gt;EXE 化した場合は設定保存先が &lt;code&gt;%APPDATA%\AnsysLaunchers&lt;/code&gt; に切り替わるので、スクリプト直実行時と設定の置き場が混ざりにくいのも利点です。&lt;/p&gt;
&lt;p&gt;ちなみに、各ランチャーに使っているアイコン画像は生成AIで作成したものです。&lt;br&gt;
ツールの機能自体は地味ですが、配布物として見たときに判別しやすいよう、Fluent / SpaceClaim / Workbench でそれぞれ見分けがつくようにしています。&lt;/p&gt;
&lt;h2 id="設定保存まわり"&gt;設定保存まわり
&lt;/h2&gt;&lt;p&gt;スクリプトを &lt;code&gt;.py&lt;/code&gt; のまま使う場合は、設定 JSON をスクリプトと同じフォルダに保存します。&lt;br&gt;
一方、PyInstaller で EXE 化した場合は &lt;code&gt;%APPDATA%\AnsysLaunchers&lt;/code&gt; を優先して保存します。&lt;/p&gt;
&lt;p&gt;これにより、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;手元で Python スクリプトとして使う&lt;/li&gt;
&lt;li&gt;EXE にして配布する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;の両方に対応しやすくしています。&lt;/p&gt;
&lt;p&gt;また、旧配置に JSON がある場合は新しい保存先へ移行する処理も入れています。&lt;br&gt;
こういう移行処理は地味ですが、後から置き場を変えるときに効いてきます。&lt;/p&gt;
&lt;h2 id="exe-化も前提にした"&gt;EXE 化も前提にした
&lt;/h2&gt;&lt;p&gt;このツールは自分用に始めましたが、最初から「Windows 上でそのまま置いて使える」ことも意識していました。&lt;/p&gt;
&lt;p&gt;そのため、PyInstaller で単一 EXE にできる構成にしてあります。&lt;/p&gt;
&lt;p&gt;ビルドすると、次のような実行ファイルを作れます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FluentVersionSelector.exe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SpaceClaimVersionSelector.exe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WorkbenchVersionSelector.exe&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python が入っていない環境でも扱いやすくしたかったので、ここは早めに整えました。&lt;/p&gt;
&lt;h2 id="使ってみて良かった点"&gt;使ってみて良かった点
&lt;/h2&gt;&lt;p&gt;この手のツールは派手ではありませんが、複数バージョンを触る現場だと地味に効きます。&lt;/p&gt;
&lt;p&gt;特に良かったのは次の点です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「どのバージョンで開くか」を毎回明示できる&lt;/li&gt;
&lt;li&gt;古い案件を新しい版で誤って開くリスクを下げられる&lt;/li&gt;
&lt;li&gt;実行ファイルの場所を覚えなくてよい&lt;/li&gt;
&lt;li&gt;Fluent だけ起動オプションが複雑、という差も吸収できる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ansys 本体の大きな機能追加ではなく、運用上の誤操作や取り違えを減らすための小さな補助ツール、という立ち位置です。&lt;/p&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;p&gt;このツールを作ったきっかけは、自分の環境で Ansys 製品のバージョン選択が期待通りに動かず、意図しないバージョンが開くことがあったからです。&lt;br&gt;
さらに、既定アプリを新しい版にしていると、古い解析ファイルをうっかり新しい版で開いて保存してしまい、後戻りしにくくなる問題もありました。&lt;/p&gt;
&lt;p&gt;そこで、「起動前に必ず版を選ぶ」ための小さなランチャーを自作しました。&lt;/p&gt;
&lt;p&gt;やっていることは地味ですが、複数バージョン運用ではかなり実用的です。&lt;br&gt;
同じように、Fluent や Workbench の版違いでヒヤッとしたことがあるなら、こういうワンクッションを入れるだけでもだいぶ楽になります。&lt;/p&gt;
&lt;h2 id="関連記事"&gt;関連記事
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/get-chrome-driver-python/" &gt;SeleniumでChromeDriverを自動取得する方法｜バージョン不一致対策&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/dev-pc-build-ryzen7600-rtx4070s/" &gt;Ryzen 7600＋RTX 4070 Superの自作PC構成例｜購入価格16.3万円の実例紹介&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;※ Ansys、Ansys Fluent、Ansys SpaceClaim、Ansys Workbench は Ansys, Inc. またはその関連会社の商標または登録商標です。本記事は第三者による非公式な紹介です。&lt;/p&gt;</description></item><item><title>Pythonで将棋AIを作るならまずGUI｜実装した理由と設計</title><link>https://mitz17.com/blog/shogi-gui/</link><pubDate>Wed, 18 Mar 2026 00:00:00 +0900</pubDate><guid>https://mitz17.com/blog/shogi-gui/</guid><description>&lt;h2 id="開発のきっかけ"&gt;開発のきっかけ
&lt;/h2&gt;&lt;p&gt;この記事は、&lt;strong&gt;「自前で将棋AIを作ろうプロジェクト」の第一弾&lt;/strong&gt;です。&lt;/p&gt;
&lt;p&gt;続き（第二弾）はこちらです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/shogi-ai-before-training-matchlog/" &gt;Pythonで自作した将棋AIと対局してみた｜学習前でも探索回数がかなり重要だった実装ログ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;将棋AIを作ろうとすると、
「まず何から作るべきか？」で悩むと思います。&lt;/p&gt;
&lt;p&gt;強化学習、探索、評価関数あたりから入りたくなりますが、実際にはその前に、
「盤面が正しく動いているかを確認できる環境」
を作るのがかなり重要です。&lt;/p&gt;
&lt;p&gt;この記事では、Pythonで将棋AIを作る第一歩として、強化学習に入る前にGUIを先に実装した理由と設計について解説します。&lt;/p&gt;
&lt;p&gt;将来的には、強化学習で将棋AIを作ってみたいと思っています。&lt;/p&gt;
&lt;p&gt;ただ、いきなり学習だけを回し始めても、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;盤面が正しく更新されているのか&lt;/li&gt;
&lt;li&gt;合法手生成が壊れていないか&lt;/li&gt;
&lt;li&gt;成りや持ち駒の扱いが想定通りか&lt;/li&gt;
&lt;li&gt;探索結果がそれっぽく動いているか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;を確認しづらいです。&lt;/p&gt;
&lt;p&gt;ログだけ見て追うやり方もありますが、将棋みたいにルールが多いゲームではやはり盤面を直接見られる方が早い。ということで、この「自前で将棋AIを作ろうプロジェクト」の第一歩として、まずは将棋GUIを作りました。&lt;/p&gt;
&lt;p&gt;このあと実際に、&lt;strong&gt;学習前の評価関数ベースAIと対局してみた記録&lt;/strong&gt; は次の記事にまとめています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/shogi-ai-before-training-matchlog/" &gt;将棋AIを強化学習前に対局テスト｜MCTSのsimulation数で強さが激変した開発ログ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="最終目標は自前の強化学習将棋aiを育てること"&gt;最終目標は「自前の強化学習将棋AIを育てること」
&lt;/h2&gt;&lt;p&gt;今作っている &lt;code&gt;ML-shogi&lt;/code&gt; では、単に「将棋が動くプログラム」を作りたいわけではなく、&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;将棋ルールをコードで扱えるようにする&lt;/li&gt;
&lt;li&gt;盤面を特徴量に変換する&lt;/li&gt;
&lt;li&gt;自己対局で &lt;code&gt;(state, policy, value)&lt;/code&gt; を集める&lt;/li&gt;
&lt;li&gt;それをもとに方策・価値ネットワークを育てる&lt;/li&gt;
&lt;li&gt;最終的に探索と組み合わせて対局できるAIにする&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;という流れを目標にしています。&lt;/p&gt;
&lt;p&gt;その意味でGUIは完成品ではなく、&lt;strong&gt;学習基盤を人間が検証するための観測装置&lt;/strong&gt;に近い立ち位置です。&lt;/p&gt;
&lt;h2 id="将棋ai開発でguiを先に作った理由"&gt;将棋AI開発でGUIを先に作った理由
&lt;/h2&gt;&lt;p&gt;強化学習のコードを書いていると、ボトルネックは「学習アルゴリズムそのもの」だけではありません。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;合法手生成のバグ&lt;/li&gt;
&lt;li&gt;打ち歩詰めや成り判定の抜け&lt;/li&gt;
&lt;li&gt;千日手や入玉まわりの終局処理&lt;/li&gt;
&lt;li&gt;評価値や探索結果の向きが逆になる問題&lt;/li&gt;
&lt;li&gt;人間が見ると明らかにおかしい手をAIが選んでいるのに、ログだけだと気づきにくい問題&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このあたりは、学習が弱いのか、ルール実装が壊れているのか、探索が悪いのかを切り分けるのが面倒です。&lt;/p&gt;
&lt;p&gt;そこで、盤面・持ち駒・最終手・評価推移・対局状態を一画面で見られるGUIを先に作っておけば、将来の自己対局や学習結果の確認がかなり楽になるはずだと考えました。&lt;/p&gt;
&lt;h2 id="python製の将棋guiでできること"&gt;Python製の将棋GUIでできること
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;9x9 の盤面表示&lt;/li&gt;
&lt;li&gt;先手/後手の持ち駒表示&lt;/li&gt;
&lt;li&gt;人間 vs AI の対局&lt;/li&gt;
&lt;li&gt;AI 同士の自動対局観戦&lt;/li&gt;
&lt;li&gt;最終手の強調表示&lt;/li&gt;
&lt;li&gt;王手・詰み・投了など終局状態の表示&lt;/li&gt;
&lt;li&gt;評価値の簡易グラフ表示&lt;/li&gt;
&lt;li&gt;&lt;code&gt;negamax&lt;/code&gt; と &lt;code&gt;MCTS&lt;/code&gt; の切り替え&lt;/li&gt;
&lt;li&gt;棋譜の &lt;code&gt;.kif&lt;/code&gt; 保存&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;見た目はこんな感じです。&lt;/p&gt;
&lt;p&gt;&lt;img src="./blog/shogi-gui/GUI2.png"
	width="1422"
	height="1035"
	loading="lazy"
	
		alt="将棋GUIの全体画面"
	
 
 title="将棋GUIの全体画面"
 data-title-escaped="将棋GUIの全体画面"
 
	
		class="gallery-image" 
		data-flex-grow="137"
		data-flex-basis="329px"
	
&gt;&lt;/p&gt;
&lt;p&gt;盤面だけでなく、右側に状態表示、評価値、持ち駒、操作ボタン、評価推移グラフをまとめています。&lt;br&gt;
「いま何が起きているか」をなるべくログを読まなくても分かるようにしたかったので、観戦ツール寄りの構成にしました。&lt;/p&gt;
&lt;p&gt;盤面上の駒画像には &lt;code&gt;koma.png&lt;/code&gt; を使っています。&lt;/p&gt;
&lt;p&gt;&lt;img src="koma.png" alt="将棋GUIで使っている駒画像" loading="lazy" style="max-width:220px; width:100%; border-radius:12px; box-shadow:0 6px 18px rgba(15,23,42,0.12);"&gt;&lt;/p&gt;
&lt;p&gt;これは生成AIで作った画像です。もうちょっと本物の将棋駒っぽい質感にしたかったのですが、今回はひとまず使える見た目になったところで妥協しました。&lt;br&gt;
とはいえ、GUIの操作感を確認する用途としては十分だったので、まずはこの画像で進めています。&lt;/p&gt;
&lt;h2 id="将棋guiで特に欲しかった確認ポイント"&gt;将棋GUIで特に欲しかった確認ポイント
&lt;/h2&gt;&lt;h3 id="1-合法手が直感どおりに選べること"&gt;1. 合法手が直感どおりに選べること
&lt;/h3&gt;&lt;p&gt;人間側で駒をクリックしたとき、移動可能なマスだけが候補として出るようにしています。&lt;br&gt;
これで「その局面で本当にその手が合法なのか」を視覚的に確認できます。&lt;/p&gt;
&lt;p&gt;持ち駒を打つ処理も含めて見られるので、将棋ルールの土台チェックとしてかなり便利です。&lt;/p&gt;
&lt;p&gt;コードは例えばこんな感じで、GUI側では合法手生成の結果をそのまま使って移動候補を出しています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_targets_from_square&lt;/span&gt;(position: Position, square: tuple[int, int]) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; set[tuple[int, int]]:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; move&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_square
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; move &lt;span style="color:#f92672"&gt;in&lt;/span&gt; generate_legal_moves(position)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; move&lt;span style="color:#f92672"&gt;.&lt;/span&gt;from_square &lt;span style="color:#f92672"&gt;==&lt;/span&gt; square
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;やっていること自体は単純で、あるマスを始点に持つ合法手だけを抜き出して、移動先の集合を作っているだけです。&lt;br&gt;
ただ、この「エンジン側の合法手生成をそのままGUIに流す」形にしておくと、表示と内部ロジックのズレが起きにくいので、将棋AI開発の初期段階ではかなり扱いやすいです。&lt;/p&gt;
&lt;h3 id="2-成りの扱いをその場で確認できること"&gt;2. 成りの扱いをその場で確認できること
&lt;/h3&gt;&lt;p&gt;成り・不成の両方がありえる局面では、ポップアップで選べるようにしました。&lt;/p&gt;
&lt;p&gt;&lt;img src="./blog/shogi-gui/%E6%88%90%E9%81%B8%E6%8A%9E.png"
	width="202"
	height="152"
	loading="lazy"
	
		alt="成り選択ダイアログ"
	
 
 title="成り選択ダイアログ"
 data-title-escaped="成り選択ダイアログ"
 
	
		class="gallery-image" 
		data-flex-grow="132"
		data-flex-basis="318px"
	
&gt;&lt;/p&gt;
&lt;p&gt;これを入れておくと、成り判定の境界がおかしくなっていないかを実際の操作で確認できます。&lt;br&gt;
学習前の段階では、こういう地味な確認が意外と大事です。&lt;/p&gt;
&lt;h3 id="3-終局状態を見逃さないこと"&gt;3. 終局状態を見逃さないこと
&lt;/h3&gt;&lt;p&gt;詰みや投了時は、終局が分かるように強めに表示しています。&lt;/p&gt;
&lt;p&gt;&lt;img src="./blog/shogi-gui/%E7%B5%82%E5%B1%80.png"
	width="1422"
	height="1033"
	loading="lazy"
	
		alt="終局表示の画面"
	
 
 title="終局表示の画面"
 data-title-escaped="終局表示の画面"
 
	
		class="gallery-image" 
		data-flex-grow="137"
		data-flex-basis="330px"
	
&gt;&lt;/p&gt;
&lt;p&gt;終局判定そのものは内部ロジック側で持っていますが、GUI上でも見えていないと「本当に正しく終わったのか」が分かりにくい。&lt;br&gt;
詰み、千日手、入玉宣言勝ちは内部ロジックとしてすでに実装してあるので、GUI側ではそれらの終局状態を人間が確認しやすい形で見えるようにしています。&lt;/p&gt;
&lt;p&gt;終局判定のコードも雰囲気としてはかなり素直で、合法手が残っているか、千日手か、入玉条件を満たしているかを順番に見ています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;terminal_result&lt;/span&gt;(position: Position) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; TerminalResult:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; entering_king &lt;span style="color:#f92672"&gt;=&lt;/span&gt; entering_king_declaration_result(position, position&lt;span style="color:#f92672"&gt;.&lt;/span&gt;side_to_move)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; entering_king &lt;span style="color:#f92672"&gt;is&lt;/span&gt; &lt;span style="color:#f92672"&gt;not&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; entering_king
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; is_repetition(position):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; TerminalResult(status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;TerminalStatus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;REPETITION, winner&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; legal_moves &lt;span style="color:#f92672"&gt;=&lt;/span&gt; generate_legal_moves(position)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; legal_moves:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; TerminalResult(status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;TerminalStatus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ONGOING, winner&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; is_in_check(position, position&lt;span style="color:#f92672"&gt;.&lt;/span&gt;side_to_move):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; TerminalResult(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;TerminalStatus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CHECKMATE,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; winner&lt;span style="color:#f92672"&gt;=&lt;/span&gt;position&lt;span style="color:#f92672"&gt;.&lt;/span&gt;side_to_move&lt;span style="color:#f92672"&gt;.&lt;/span&gt;opponent&lt;span style="color:#f92672"&gt;.&lt;/span&gt;value,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; TerminalResult(status&lt;span style="color:#f92672"&gt;=&lt;/span&gt;TerminalStatus&lt;span style="color:#f92672"&gt;.&lt;/span&gt;STALEMATE, winner&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;このあたりを GUI 上で即確認できるようにしておくと、「終局判定の実装が正しいか」と「その結果の見せ方が分かりやすいか」を同時に潰せます。&lt;/p&gt;
&lt;h2 id="中身は見た目より将棋aiの検証しやすさ重視"&gt;中身は「見た目」より「将棋AIの検証しやすさ」重視
&lt;/h2&gt;&lt;p&gt;今回のGUIは Tkinter ベースで、見た目の豪華さよりもまずは開発速度と検証効率を優先しています。&lt;/p&gt;
&lt;p&gt;内部では、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;将棋の盤面状態&lt;/li&gt;
&lt;li&gt;合法手生成&lt;/li&gt;
&lt;li&gt;局面評価&lt;/li&gt;
&lt;li&gt;&lt;code&gt;negamax&lt;/code&gt; 探索&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MCTS&lt;/code&gt; による探索&lt;/li&gt;
&lt;li&gt;自己対局用の状態遷移&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といったAI側の処理を同じコードベースで使い回しています。&lt;/p&gt;
&lt;p&gt;つまりこのGUIは、単なる「盤面を表示するアプリ」ではなく、今後の強化学習や自己対局の挙動確認にもそのまま使う前提のフロントエンドです。&lt;/p&gt;
&lt;h2 id="将棋ai開発の初期段階でよかった点"&gt;将棋AI開発の初期段階でよかった点
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;ルール実装の違和感に早く気づける&lt;/li&gt;
&lt;li&gt;AIの手が弱いのか、そもそも探索や終局判定が怪しいのかを切り分けやすい&lt;/li&gt;
&lt;li&gt;自己対局や学習結果をあとで可視化する土台になる&lt;/li&gt;
&lt;li&gt;棋譜保存があるので、気になった局面を後から追いやすい&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特に「強化学習に入る前の将棋エンジン確認ツール」としてはかなり有効でした。&lt;br&gt;
見えているだけで、デバッグの心理的ハードルがかなり下がります。&lt;/p&gt;
&lt;h2 id="今後やりたいこと"&gt;今後やりたいこと
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;学習済みチェックポイントをGUIから切り替えて対局できるようにする&lt;/li&gt;
&lt;li&gt;自己対局の進行状況や勝率をもっと見やすくする&lt;/li&gt;
&lt;li&gt;評価推移だけでなく、候補手や探索回数も表示する&lt;/li&gt;
&lt;li&gt;棋譜の読み込み対応&lt;/li&gt;
&lt;li&gt;強化学習で育ったモデルと人間が対局しやすい形に整える&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最終的には、「将棋GUIを作った」で終わりではなく、ここから自己対局と学習を回して、少しずつ自前の将棋AIとして形にしていきたいと思っています。&lt;/p&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;p&gt;自前で将棋AIを作るつもりなら、いきなり学習コードだけを書くより、先に盤面をちゃんと見られる環境を作っておく方がたぶん楽です。&lt;/p&gt;
&lt;p&gt;今回作った将棋GUIは、そのための土台であり、&lt;strong&gt;「自前で将棋AIを作ろうプロジェクト」の第一弾&lt;/strong&gt;でもあります。&lt;br&gt;
将棋AI本体より一見地味ですが、合法手、成り、持ち駒、終局、探索結果を人間が直接確認できる環境があるだけで、後の開発がかなり進めやすくなります。&lt;/p&gt;
&lt;p&gt;次はこの土台の上で、評価関数を自作して、強化学習なしの状態でどこまで戦えるかを試す予定です。&lt;/p&gt;
&lt;p&gt;要するに第二弾は、&lt;strong&gt;「評価関数を自作して強化学習0で対戦してみる」&lt;/strong&gt; になります。&lt;/p&gt;
&lt;p&gt;続き（第二弾）はこちらです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/shogi-ai-before-training-matchlog/" &gt;Pythonで自作した将棋AIと対局してみた｜学習前でも探索回数がかなり重要だった実装ログ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;その先では、どれだけ学習させたら自分（将棋ウォーズ初段）が敵わなくなるのか、そしてそこまで本当に自力で学習させられるのかを試してみたいと思っています。&lt;br&gt;
今回は外部の棋譜や定石は学習させない前提なので、かなり遠回りになるはずですが、そのぶんどこまで伸びるのかは普通に楽しみです。&lt;/p&gt;
&lt;h2 id="関連記事"&gt;関連記事
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/shogi-ai-before-training-matchlog/" &gt;将棋AIを強化学習前に対局テスト｜MCTSのsimulation数で強さが激変した開発ログ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/get-chrome-driver-python/" &gt;Selenium使用時にChromeDriverを自動更新する方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/mp3-lufs-normalizer-python/" &gt;音楽の音量を自動調整するツールをPythonで作る LUFS正規化開発ログ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/power-automate-sharepoint-excel/" &gt;Power Automateで前月分をSharePoint→Excelへ送るときの落とし穴&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>ffmpeg loudnorm の使い方｜LUFS正規化と2pass設定をコマンド例で解説</title><link>https://mitz17.com/blog/ffmpeg-loudnorm-guide/</link><pubDate>Sun, 08 Mar 2026 00:00:00 +0900</pubDate><guid>https://mitz17.com/blog/ffmpeg-loudnorm-guide/</guid><description>&lt;h2 id="この記事でやること"&gt;この記事でやること
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;ffmpeg&lt;/code&gt; の &lt;code&gt;loudnorm&lt;/code&gt; フィルタを使って、音声ファイルの音量を LUFS ベースで揃える方法をまとめます。&lt;/p&gt;
&lt;p&gt;「コマンドを貼って終わり」ではなく、次の点まで押さえます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;LUFS&lt;/code&gt; とは何か&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loudnorm&lt;/code&gt; の主要パラメータの意味&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1pass&lt;/code&gt; と &lt;code&gt;2pass&lt;/code&gt; の使い分け&lt;/li&gt;
&lt;li&gt;正規化後に必要な再エンコードの考え方&lt;/li&gt;
&lt;li&gt;MP3 のタグやアートワークを落とさない方法&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python から ffmpeg を呼び出してまとめて処理する実装については、&lt;a class="link" href="./blog/mp3-lufs-normalizer-python/" &gt;音楽の音量を自動調整するツールをPythonで作る LUFS正規化開発ログ&lt;/a&gt; を参照してください。&lt;/p&gt;
&lt;h2 id="目次"&gt;目次
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="#%e5%89%8d%e6%8f%90" &gt;前提&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#lufs-%e3%81%a8%e3%81%af" &gt;LUFS とは&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e3%81%be%e3%81%9a%e8%a9%a6%e3%81%99%e5%9f%ba%e6%9c%ac%e3%82%b3%e3%83%9e%e3%83%b3%e3%83%891pass" &gt;まず試す：基本コマンド（1pass）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#loudnorm-%e3%81%ae%e4%b8%bb%e8%a6%81%e3%83%91%e3%83%a9%e3%83%a1%e3%83%bc%e3%82%bf" &gt;loudnorm の主要パラメータ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#1pass-%e3%81%a8-2pass-%e3%81%ae%e4%bd%bf%e3%81%84%e5%88%86%e3%81%91" &gt;1pass と 2pass の使い分け&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#loudnorm-%e3%81%a7%e3%82%a4%e3%83%b3%e3%83%88%e3%83%ad%e3%81%8c%e5%a4%a7%e3%81%8d%e3%81%8f%e3%81%aa%e3%82%8b%e7%90%86%e7%94%b1" &gt;loudnorm でイントロが大きくなる理由&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e6%ad%a3%e8%a6%8f%e5%8c%96%e5%be%8c%e3%81%af%e5%86%8d%e3%82%a8%e3%83%b3%e3%82%b3%e3%83%bc%e3%83%89%e3%81%8c%e5%bf%85%e8%a6%81" &gt;正規化後は再エンコードが必要&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e3%82%bf%e3%82%b0%e3%81%a8%e3%82%a2%e3%83%bc%e3%83%88%e3%83%af%e3%83%bc%e3%82%af%e3%82%92%e4%bf%9d%e6%8c%81%e3%81%97%e3%82%84%e3%81%99%e3%81%8f%e3%81%99%e3%82%8b%e5%9f%ba%e6%9c%ac%e8%a8%ad%e5%ae%9a" &gt;タグとアートワークを保持しやすくする基本設定&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e3%83%95%e3%82%a3%e3%83%ab%e3%82%bf%e3%83%81%e3%82%a7%e3%83%bc%e3%83%b3%e3%81%ae%e8%80%83%e3%81%88%e6%96%b9" &gt;フィルタチェーンの考え方&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e5%ae%9f%e7%94%a8%e3%83%86%e3%83%b3%e3%83%97%e3%83%ac%e3%83%bc%e3%83%88" &gt;実用テンプレート&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e3%81%be%e3%81%a8%e3%82%81" &gt;まとめ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="前提"&gt;前提
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ffmpeg&lt;/code&gt; 6.x 系が利用できること（導入は &lt;a class="link" href="https://qiita.com/Tadataka_Takahashi/items/9dcb0cf308db6f5dc31b" target="_blank" rel="noopener"
 &gt;この Qiita 記事&lt;/a&gt; を参考）&lt;/li&gt;
&lt;li&gt;主な対象は MP3 だが、考え方は AAC / WAV / FLAC でもほぼ同じ&lt;/li&gt;
&lt;li&gt;コマンド例は PowerShell・bash どちらでも読み替えやすい形で記載&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="lufs-とは"&gt;LUFS とは
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;LUFS（Loudness Units relative to Full Scale）&lt;/code&gt; は、ITU-R BS.1770 をベースにした、人が感じる音の大きさ（ラウドネス）を表す単位です。値は通常負の数で表され、0 に近いほど音が大きいことを意味します。&lt;/p&gt;
&lt;p&gt;なお、デジタル音声の最大振幅の基準は &lt;code&gt;0 dBFS&lt;/code&gt; であり、LUFS はそれとは別の「知覚的なラウドネス」の指標です。&lt;code&gt;TP&lt;/code&gt;（True Peak）の上限として &lt;code&gt;0 dBFS&lt;/code&gt; を参照するのはそのためです。&lt;/p&gt;
&lt;p&gt;プラットフォーム別の配信実務における目安は次のとおりです。厳密な規格値ではなく、実運用上よく使われる参考値です。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;プラットフォーム&lt;/th&gt;
 &lt;th&gt;目安 LUFS&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;YouTube / Spotify&lt;/td&gt;
 &lt;td&gt;-14 LUFS 前後&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Podcast&lt;/td&gt;
 &lt;td&gt;-16 LUFS 前後&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TV 放送（EBU R 128）&lt;/td&gt;
 &lt;td&gt;-23 LUFS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;注記：&lt;/strong&gt; Spotify は再生時にラウドネス正規化を行いますが、設定によって基準値が異なる場合があります（Loud / Normal / Quiet モード）。YouTube も「-14 LUFS を目標に書き出すべき」と公式仕様として明示しているわけではありません。配信先の最新仕様は各プラットフォームの公式ドキュメントで確認してください。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;figure&gt;&lt;img src="./blog/ffmpeg-loudnorm-guide/lufs-reference.png"
 alt="LUFS基準の目安。YouTube / Spotify は -14 LUFS 前後、Podcast は -16 LUFS 前後、TV放送は -23 LUFS を示すグラフ。"&gt;
&lt;/figure&gt;

&lt;h2 id="まず試す基本コマンド1pass"&gt;まず試す：基本コマンド（1pass）
&lt;/h2&gt;&lt;p&gt;細かい説明の前に、すぐ使える形を示します。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ffmpeg -i input.mp3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -map &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -map_metadata &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -c:v copy &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -af &lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=-14:TP=-1.5:LRA=11&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -c:a libmp3lame -q:a &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output.mp3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;各オプションの意味は以下のとおりです。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;オプション&lt;/th&gt;
 &lt;th&gt;意味&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-i input.mp3&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;入力ファイル&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-map 0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;全ストリーム（音声・アートワークなど）を出力に引き継ぐ&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-map_metadata 0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;ID3 タグなどのメタデータをコピーする&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-c:v copy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;アルバムアートを再エンコードせずそのままコピーする&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-af &amp;quot;loudnorm=...&amp;quot;&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;ラウドネス正規化フィルタを適用する&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-c:a libmp3lame&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;MP3 として再エンコードする&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-q:a 2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;VBR 高品質（平均約 190 kbps）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;より正確に目標値へ合わせたいときは &lt;code&gt;2pass&lt;/code&gt; を使います。次のセクションから順に見ていきます。&lt;/p&gt;
&lt;h2 id="loudnorm-の主要パラメータ"&gt;loudnorm の主要パラメータ
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;loudnorm&lt;/code&gt; で押さえるべきパラメータは 5 系統です。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;パラメータ&lt;/th&gt;
 &lt;th&gt;意味&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;I&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;目標 LUFS（Integrated Loudness）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;TP&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;True Peak の上限&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;LRA&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Loudness Range の目標値&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;linear&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;線形補正を使うかどうか&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;measured_*&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;2pass 用の測定結果を渡すパラメータ群&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="i目標-lufs"&gt;I（目標 LUFS）
&lt;/h3&gt;&lt;p&gt;「最終的にどの LUFS へ寄せたいか」を決める中心パラメータです。用途に合わせて次の値を基準にしてください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;I=-14&lt;/code&gt;：YouTube、Spotify&lt;/li&gt;
&lt;li&gt;&lt;code&gt;I=-16&lt;/code&gt;：Podcast&lt;/li&gt;
&lt;li&gt;&lt;code&gt;I=-23&lt;/code&gt;：TV など&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tptrue-peak-の上限"&gt;TP（True Peak の上限）
&lt;/h3&gt;&lt;p&gt;サンプル間ピークまで含めた実効ピーク値の上限を設定します。波形上ではクリップしていなくても、再生・変換の過程でピークが 0 dBFS を超えてノイズが発生することがあります。これを防ぐため、少し余裕を持たせるのが一般的です。&lt;/p&gt;
&lt;p&gt;迷ったら &lt;code&gt;-1.5 dBTP&lt;/code&gt; 前後から始めるのが安全です。&lt;/p&gt;
&lt;h3 id="lraloudness-range"&gt;LRA（Loudness Range）
&lt;/h3&gt;&lt;p&gt;曲の中の音量変動の広さを示します。値が大きいほど静かな部分と大きい部分の差が大きく、クラシックや映画音声では高くなりやすい傾向があります。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;ジャンル / 用途&lt;/th&gt;
 &lt;th&gt;LRA の目安&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;ポップス / ロック&lt;/td&gt;
 &lt;td&gt;6〜10&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;EDM / ダンス&lt;/td&gt;
 &lt;td&gt;4〜8&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;一般的な音楽&lt;/td&gt;
 &lt;td&gt;6〜12&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;クラシック / 映画音声&lt;/td&gt;
 &lt;td&gt;12〜20&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;まずは &lt;code&gt;I&lt;/code&gt; と &lt;code&gt;TP&lt;/code&gt; を固めることを優先し、&lt;code&gt;LRA&lt;/code&gt; は素材のジャンルや用途が明確なときだけ調整するのが実務上の流れです。迷ったら &lt;code&gt;LRA=11&lt;/code&gt; のままにしておいて問題ありません。&lt;/p&gt;
&lt;h3 id="linear"&gt;linear
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;linear=true&lt;/code&gt; を指定すると、音声全体を同じ倍率で上げ下げする線形補正になります。曲の抑揚はそのままに、全体を一様にシフトするイメージです。&lt;/p&gt;
&lt;p&gt;2pass で測定結果を渡すときによく使われます。1pass では線形補正が完全には効かない場合があります。&lt;/p&gt;
&lt;h3 id="measured_"&gt;measured_*
&lt;/h3&gt;&lt;p&gt;1 回目の測定で得た値を 2 回目に引き渡すためのパラメータ群です。対応関係は次のとおりです。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;measured_*&lt;/th&gt;
 &lt;th&gt;1回目の出力値&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;measured_I&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;input_i&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;measured_TP&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;input_tp&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;measured_LRA&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;input_lra&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;measured_thresh&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;input_thresh&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;offset&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;target_offset&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="1pass-と-2pass-の使い分け"&gt;1pass と 2pass の使い分け
&lt;/h2&gt;&lt;h3 id="1pass"&gt;1pass
&lt;/h3&gt;&lt;p&gt;1 回の実行で解析と補正を同時に行います。シンプルで速いため、バッチ処理や手早い試行に向いています。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ffmpeg -i input.mp3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -af &lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=-14:TP=-1.5:LRA=11&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -c:a libmp3lame -q:a &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output.mp3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ただし、厳密に目標値へ合わせたい場面では 2pass よりブレが出やすくなります。&lt;/p&gt;
&lt;h3 id="2pass"&gt;2pass
&lt;/h3&gt;&lt;p&gt;1 回目で測定し、その結果を 2 回目に渡して本番変換する方法です。目標ラウドネスへより安定して寄せられます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1 回目：測定のみ（ファイルを書き出さない）&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ffmpeg -i input.mp3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -af &lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=-14:TP=-1.5:LRA=11:print_format=json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -f null -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-f null -&lt;/code&gt; により出力ファイルを作らず、JSON だけを取得できます。出力から次の値を拾います。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;input_i&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input_tp&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input_lra&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;input_thresh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;target_offset&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2 回目：測定値を渡して本番変換&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ffmpeg -i input.mp3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -map &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -map_metadata &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -c:v copy &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -af &lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=-14:TP=-1.5:LRA=11:linear=true:measured_I=-18.3:measured_TP=-0.7:measured_LRA=9.5:measured_thresh=-29.2:offset=-0.1&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -c:a libmp3lame -q:a &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output.mp3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ツールとして実装する場合は、測定フェーズと本番フェーズを分けて設計しておくと扱いやすくなります。&lt;/p&gt;
&lt;h2 id="loudnorm-でイントロが大きくなる理由"&gt;loudnorm でイントロが大きくなる理由
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;loudnorm&lt;/code&gt; は&lt;strong&gt;曲全体の Integrated Loudness&lt;/strong&gt; を見て補正量を決めます。静かなイントロだけを個別に判定しているわけではないため、次のような曲ではイントロが想定以上に持ち上がって聞こえることがあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;イントロだけ極端に静かで、途中から急に音圧が上がる曲&lt;/li&gt;
&lt;li&gt;もともとダイナミクスが広い音源&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;特に &lt;code&gt;linear=true&lt;/code&gt; では全体を一様に上げ下げするため、サビに合わせて補正すると静かなイントロもまとめて大きくなります。&lt;code&gt;TP&lt;/code&gt; はピークの上限を抑えるもので、体感音量が上がること自体は防げません。&lt;/p&gt;
&lt;p&gt;対策として有効なのは次のとおりです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1pass より 2pass でより安定した補正をかける&lt;/li&gt;
&lt;li&gt;目標 LUFS を少し下げる&lt;/li&gt;
&lt;li&gt;素材によっては前段の編集や別のダイナミクス処理を組み合わせる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="正規化後は再エンコードが必要"&gt;正規化後は再エンコードが必要
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;loudnorm&lt;/code&gt; のような音声フィルタを使う場合、音声はデコード → 処理 → エンコードの流れが必要になります。ストリームコピー（&lt;code&gt;-c:a copy&lt;/code&gt;）では済まないため、必ずコーデックを指定します。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-c:a libmp3lame -q:a &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;よく使う指定の考え方は次のとおりです。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;オプション&lt;/th&gt;
 &lt;th&gt;説明&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-c:a libmp3lame&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;MP3 として再エンコードする&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-q:a 2&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;VBR 品質指定。数値が低いほど高音質（0〜9）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-b:a 192k&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;CBR や ABR でビットレートを明示したいときに使う&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;音質とサイズのバランスを取りたいなら、まずは &lt;code&gt;-q:a 2&lt;/code&gt; か &lt;code&gt;-q:a 3&lt;/code&gt; から始めるのが実用的です。&lt;/p&gt;
&lt;h2 id="タグとアートワークを保持しやすくする基本設定"&gt;タグとアートワークを保持しやすくする基本設定
&lt;/h2&gt;&lt;p&gt;MP3 には音声以外にも、メタデータ（ID3 タグ）とアルバムアート（画像ストリーム）が含まれています。オプションを省略すると「音は出るがジャケットが消えた」「タグが落ちた」という事故が起きやすくなります。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-map &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -map_metadata &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -c:v copy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;オプション&lt;/th&gt;
 &lt;th&gt;意味&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-map 0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;入力の全ストリームを対象にする&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-map_metadata 0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;メタデータを入力からコピーする&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;-c:v copy&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;画像ストリームを再エンコードせずコピーする&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;MP3 の埋め込みジャケットは内部的に画像ストリームとして扱われます。音声だけを明示して出力すると、画像ストリームがマッピング対象から外れて消えます。アートワークを保持したいなら、この 3 つをセットで指定するのが基本です。&lt;/p&gt;
&lt;p&gt;ただし、独自の ID3 フレームや埋め込み歌詞など、入力ファイルの構造によってはこの設定だけでは完全に保持されないケースもあります。変換後にタグの欠損が気になる場合は &lt;code&gt;id3v2&lt;/code&gt; や &lt;code&gt;eyeD3&lt;/code&gt; などの専用ツールで確認・修正するのが安全です。&lt;/p&gt;
&lt;h2 id="フィルタチェーンの考え方"&gt;フィルタチェーンの考え方
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;-af&lt;/code&gt; を使うと、複数のフィルタをカンマでつないで順番に適用できます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ffmpeg -i input.mp3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -af &lt;span style="color:#e6db74"&gt;&amp;#34;afftdn,agate,loudnorm=I=-14:TP=-1.5:LRA=11&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -c:a libmp3lame -q:a &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output.mp3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;よく使われるフィルタの役割は次のとおりです。&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;フィルタ&lt;/th&gt;
 &lt;th&gt;用途&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;loudnorm&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;ラウドネス正規化&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;afftdn&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;ノイズ除去&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;agate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;ノイズゲート&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;ただし、ノイズ除去やゲートは素材によって効きすぎることがあります。まずは &lt;code&gt;loudnorm&lt;/code&gt; 単独で正規化を安定させてから、必要であれば前処理を足すほうが安全です。&lt;/p&gt;
&lt;h2 id="実用テンプレート"&gt;実用テンプレート
&lt;/h2&gt;&lt;h3 id="まず試す1pass"&gt;まず試す：1pass
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ffmpeg -i input.mp3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -map &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -map_metadata &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -c:v copy &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -af &lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=-14:TP=-1.5:LRA=11&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -c:a libmp3lame -q:a &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output.mp3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="精度を上げる2pass"&gt;精度を上げる：2pass
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;1 回目（測定）&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ffmpeg -hide_banner -i input.mp3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -af &lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=-14:TP=-1.5:LRA=11:print_format=json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -f null -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;2 回目（本番変換）&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ffmpeg -i input.mp3 &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -map &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -map_metadata &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; -c:v copy &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -af &lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=-14:TP=-1.5:LRA=11:linear=true:measured_I=...:measured_TP=...:measured_LRA=...:measured_thresh=...:offset=...&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; -c:a libmp3lame -q:a &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output.mp3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;...&lt;/code&gt; の部分に 1 回目の JSON から拾った値を入れます。&lt;/p&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;ポイント&lt;/th&gt;
 &lt;th&gt;内容&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;目標値の決め方&lt;/td&gt;
 &lt;td&gt;まず &lt;code&gt;I&lt;/code&gt; を決め、&lt;code&gt;TP=-1.5&lt;/code&gt;、&lt;code&gt;LRA=11&lt;/code&gt; を基準に調整&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1pass vs 2pass&lt;/td&gt;
 &lt;td&gt;手軽さなら 1pass、精度重視なら 2pass&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;再エンコード&lt;/td&gt;
 &lt;td&gt;フィルタ使用時は &lt;code&gt;-c:a libmp3lame -q:a 2&lt;/code&gt; が必須&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;タグ・アートワーク保持&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;-map 0 -map_metadata 0 -c:v copy&lt;/code&gt; をセットで指定&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ツール化するなら&lt;/td&gt;
 &lt;td&gt;測定フェーズ（&lt;code&gt;-f null -&lt;/code&gt;）と本番フェーズを分けて設計&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Python からまとめて処理したい場合や、CLI で自動化したい場合は &lt;a class="link" href="./blog/mp3-lufs-normalizer-python/" &gt;音楽の音量を自動調整するツールをPythonで作る LUFS正規化開発ログ&lt;/a&gt; を参照してください。&lt;/p&gt;
&lt;h2 id="関連記事"&gt;関連記事
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/mp3-lufs-normalizer-python/" &gt;Pythonとffmpegで音量自動調整ツールを作る｜LUFS正規化の開発ログ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/dev-pc-build-ryzen7600-rtx4070s/" &gt;Ryzen 7600＋RTX 4070 Superの自作PC構成例｜購入価格16.3万円の実例紹介&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Ryzen 5 7600＋RTX 4070 Superの自作PC構成例｜16.3万円の開発・AI実験向け実例</title><link>https://mitz17.com/blog/dev-pc-build-ryzen7600-rtx4070s/</link><pubDate>Thu, 05 Mar 2026 00:00:00 +0900</pubDate><guid>https://mitz17.com/blog/dev-pc-build-ryzen7600-rtx4070s/</guid><description>&lt;img src="https://mitz17.com/" alt="Featured image of post Ryzen 5 7600＋RTX 4070 Superの自作PC構成例｜16.3万円の開発・AI実験向け実例" /&gt;&lt;p&gt;2024年11月のブラックフライデーセールで購入した構成です。&lt;br&gt;
最新ではないものの、開発・ローカルAI実験の両立という観点ではいまも満足度が高いので、実例として紹介します。&lt;/p&gt;
&lt;h2 id="目次"&gt;目次
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="#%e4%b8%bb%e3%81%aa%e7%94%a8%e9%80%94" &gt;主な用途&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e8%b2%b7%e3%81%84%e6%9b%bf%e3%81%88%e3%81%9f%e7%90%86%e7%94%b1" &gt;買い替えた理由&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e6%a7%8b%e6%88%90%e3%81%a8%e8%b3%bc%e5%85%a5%e4%be%a1%e6%a0%bc" &gt;構成と購入価格&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e5%90%84%e3%83%91%e3%83%bc%e3%83%84%e3%81%ae%e9%81%b8%e5%ae%9a%e7%90%86%e7%94%b1" &gt;各パーツの選定理由&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e7%9c%81%e3%82%b9%e3%83%9a%e3%83%bc%e3%82%b9%e5%8c%96microatx%e3%82%92%e9%81%b8%e3%82%93%e3%81%a0%e7%b5%8c%e7%b7%af" &gt;省スペース化：microATXを選んだ経緯&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e6%a7%8b%e6%88%90%e3%81%ae%e5%89%b2%e3%82%8a%e5%88%87%e3%82%8a%e3%83%9d%e3%82%a4%e3%83%b3%e3%83%88" &gt;構成の割り切りポイント&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e7%b5%84%e3%81%bf%e7%ab%8b%e3%81%a6%e6%99%82%e3%81%ae%e6%b3%a8%e6%84%8f%e7%82%b9" &gt;組み立て時の注意点&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="#%e3%81%be%e3%81%a8%e3%82%81" &gt;まとめ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="主な用途"&gt;主な用途
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;日常的な開発作業（エディタ・ビルド・Docker・DB）&lt;/li&gt;
&lt;li&gt;ローカルLLM・AI実験&lt;/li&gt;
&lt;li&gt;重すぎないゲーム&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="買い替えた理由"&gt;買い替えた理由
&lt;/h2&gt;&lt;p&gt;前のGPUは &lt;code&gt;GTX 760&lt;/code&gt;（Kepler世代、CUDA Compute Capability 3.0）だった。&lt;br&gt;
PyTorchやTensorFlowの近年のバージョンはCUDA 3.5以上、あるいは5.0以上を要件とするものが多く、ライブラリの要件を満たせずローカルでのAI実験が実質困難な状況だった。&lt;/p&gt;
&lt;p&gt;ローカルで推論・ファインチューニングを試せる環境を整えるために、今回の構成へ移行した。&lt;/p&gt;
&lt;h2 id="構成と購入価格"&gt;構成と購入価格
&lt;/h2&gt;&lt;figure&gt;&lt;img src="./blog/dev-pc-build-ryzen7600-rtx4070s/%E8%B3%BC%E5%85%A5%E3%83%91%E3%83%BC%E3%83%84%E4%B8%80%E5%BC%8F.jpg"
 alt="購入パーツ一式"&gt;
&lt;/figure&gt;

&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;パーツ&lt;/th&gt;
 &lt;th&gt;型番・仕様&lt;/th&gt;
 &lt;th style="text-align: right"&gt;価格&lt;/th&gt;
 &lt;th&gt;購入店&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;CPU＋MB＋RAM セット&lt;/td&gt;
 &lt;td&gt;Ryzen 5 7600 / B650M Pro RS / DDR5 32GB&lt;/td&gt;
 &lt;td style="text-align: right"&gt;¥49,800&lt;/td&gt;
 &lt;td&gt;ソフマップ&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GPU&lt;/td&gt;
 &lt;td&gt;RTX 4070 Super（VRAM 12GB）&lt;/td&gt;
 &lt;td style="text-align: right"&gt;¥93,500&lt;/td&gt;
 &lt;td&gt;ドスパラ&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SSD&lt;/td&gt;
 &lt;td&gt;M.2 NVMe 500GB&lt;/td&gt;
 &lt;td style="text-align: right"&gt;¥5,590&lt;/td&gt;
 &lt;td&gt;ツクモ&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;ケース&lt;/td&gt;
 &lt;td&gt;Antec CX200 RGB Elite&lt;/td&gt;
 &lt;td style="text-align: right"&gt;¥6,380&lt;/td&gt;
 &lt;td&gt;PC工房&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;電源&lt;/td&gt;
 &lt;td&gt;650W 80PLUS Bronze&lt;/td&gt;
 &lt;td style="text-align: right"&gt;¥7,645&lt;/td&gt;
 &lt;td&gt;Joshin&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;合計: ¥162,915（税込）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;※ すべてネット通販で購入。OS（Ubuntu）はライセンス費用なし。&lt;br&gt;
※ HDD・サブSSDは既存資産を流用のため上記に含まず。&lt;/p&gt;
&lt;h2 id="各パーツの選定理由"&gt;各パーツの選定理由
&lt;/h2&gt;&lt;h3 id="ryzen-5-7600"&gt;Ryzen 5 7600
&lt;/h3&gt;&lt;p&gt;AM5プラットフォームのエントリークラスに位置するが、シングルスレッド性能は高く、開発中の体感（エディタ操作・ビルド・日常操作）には十分と判断した。&lt;br&gt;
マルチスレッドが主役になる用途（動画エンコード・大規模コンパイル等）ではより上位のCPUが有利になる場面もあるが、本構成の主目的には過剰投資と判断してコスト圧縮に充てた。&lt;/p&gt;
&lt;h3 id="rtx-4070-supervram-12gb"&gt;RTX 4070 Super（VRAM 12GB）
&lt;/h3&gt;&lt;p&gt;ローカルAI実験では、VRAMがロードできるモデルサイズの上限を直接規定する。&lt;br&gt;
7B〜13Bパラメータクラスのモデルをfloat16で動かす場合、目安として14〜26GB前後のVRAMが理想だが、量子化（GGUF / AWQ等）を活用すれば12GBでも7B〜13Bクラスは実用的に扱える。&lt;br&gt;
予算内でVRAMを最大化する選択として採用した。&lt;/p&gt;
&lt;h3 id="メモリ-32gbddr5"&gt;メモリ 32GB（DDR5）
&lt;/h3&gt;&lt;p&gt;Docker・IDE・ブラウザ・DBを同時に起動する開発環境では、16GBでは用途によって不足が生じやすい。&lt;br&gt;
32GBを選択したのは「余裕を持った実用容量の確保」が主な理由で、当時の価格が安かったことも追い風になった。&lt;br&gt;
なお、必要な容量は用途やDockerイメージの構成次第で変わるため、あくまで本構成の判断基準として参考にしてほしい。&lt;/p&gt;
&lt;h3 id="ubuntu運用"&gt;Ubuntu運用
&lt;/h3&gt;&lt;p&gt;OS費用をゼロにすることで、削減分をGPU・メモリに充当した。&lt;br&gt;
AI・開発ツールのエコシステムとの親和性も高く、本用途では合理的な選択と判断している。&lt;/p&gt;
&lt;h2 id="省スペース化microatxを選んだ経緯"&gt;省スペース化：microATXを選んだ経緯
&lt;/h2&gt;&lt;p&gt;前のPCはATXフルタワーで、設置スペースを取りすぎていた。&lt;br&gt;
今回はmicroATX構成に切り替えて省スペース化を図った。&lt;/p&gt;
&lt;p&gt;ケース（Antec CX200 RGB Elite）はレビューでも指摘されているとおり組み立てに難があり、内部スペースが限られるため配線はやや窮屈になる。&lt;br&gt;
ただし完成後の外観は満足度が高く、トータルでは及第点以上の選択だった。&lt;/p&gt;
&lt;h2 id="構成の割り切りポイント"&gt;構成の割り切りポイント
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;電源：非モジュラーを選択&lt;/strong&gt;&lt;br&gt;
650W・80PLUS BronzeはRTX 4070 Super（TDP220W）＋Ryzen 5 7600（65W）の構成に対して容量的に問題ない。&lt;br&gt;
フルモジュラー電源は余剰ケーブルの取り回しが楽になるメリットがあるが、価格差を他パーツに回す判断でノンモジュラーを選んだ。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;見た目より性能単価を優先&lt;/strong&gt;&lt;br&gt;
ケースが白のため、GPUも白で揃えたかったが、外装カラーだけで数万円高くなる選択肢は費用対効果が見合わないと判断して断念した。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ストレージは既存資産を流用&lt;/strong&gt;&lt;br&gt;
HDDとサブSSDは旧構成から持ち越し。新規購入はOS用のM.2 NVMe（500GB）のみとし、総額を抑えた。&lt;/p&gt;
&lt;h2 id="組み立て時の注意点"&gt;組み立て時の注意点
&lt;/h2&gt;&lt;h3 id="m2-wi-fiカードを後付けする場合"&gt;M.2 Wi-Fiカードを後付けする場合
&lt;/h3&gt;&lt;p&gt;このケースでM.2 Wi-Fiアダプタを後付けする場合、マザーボードから背面スロットに引くアンテナ線の経路が長くなり、取り回しがかなりタイトになる。&lt;br&gt;
後から追加する予定がある場合は、組み立て前にアンテナ線のルートを確認しておくことを勧める。&lt;/p&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;p&gt;「使うところにだけ予算をかける」方針で組んだ構成。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GPU&lt;/strong&gt;：VRAMを重視し、予算の過半を投入&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU&lt;/strong&gt;：開発用途の体感を確保しつつコスト圧縮&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;メモリ&lt;/strong&gt;：Docker・IDE同時利用を想定して32GBを確保&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OS・ストレージ&lt;/strong&gt;：無料OS＋既存資産流用で節約&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;合計16.3万円で、開発とローカルAI実験を現実的に回せる環境を構築できた。&lt;br&gt;
購入当時の価格のため現在の市場価格とは異なるが、構成の参考になれば幸い。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="関連記事"&gt;関連記事
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/ansys-version-selector/" &gt;Ansysのバージョン選択ツールを作った理由｜古い解析ファイルを別バージョンで開くリスクを減らす&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/mp3-lufs-normalizer-python/" &gt;Pythonとffmpegで音量自動調整ツールを作る｜LUFS正規化の開発ログ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>SeleniumでChromeDriverを自動取得するPythonツールを作った</title><link>https://mitz17.com/blog/get-chrome-driver-python/</link><pubDate>Wed, 04 Mar 2026 00:00:00 +0900</pubDate><guid>https://mitz17.com/blog/get-chrome-driver-python/</guid><description>&lt;p&gt;GitHub: &lt;a class="link" href="https://github.com/mitz17/get-chrome-driver" target="_blank" rel="noopener"
 &gt;mitz17/get-chrome-driver&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="作った理由"&gt;作った理由
&lt;/h2&gt;&lt;p&gt;Selenium でスクレイピングやブラウザ自動操作をしていると、Chrome の自動更新が原因で ChromeDriver とのバージョン不一致が突然起きることがあります。実際に次のようなエラーでテストが止まる場面が何度かありました。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;SessionNotCreatedException
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;This version of ChromeDriver only supports Chrome version XX
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Current browser version is YY
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Selenium で Chrome を操作するには、インストール済みの Chrome と互換性のある ChromeDriver が必要です。ただし Chrome は自動更新されるため、毎回対応する ZIP を探して展開し、実行環境へ配る運用はすぐに面倒になります。&lt;/p&gt;
&lt;p&gt;さらに 2023 年以降は、ChromeDriver の配布が &lt;strong&gt;Chrome for Testing (CfT) API&lt;/strong&gt; に集約され、過去の固定 URL を前提にした方法では最新の互換ドライバを安定して取れなくなりました。そこで、次の処理を自動化する &lt;code&gt;get-chrome-driver&lt;/code&gt; を作りました。&lt;/p&gt;
&lt;h2 id="selenium-実行前に-chromedriver-のバージョン不一致を自動解消する"&gt;Selenium 実行前に ChromeDriver のバージョン不一致を自動解消する
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;実行環境に入っている Chrome のバージョンを調べる&lt;/li&gt;
&lt;li&gt;CfT API から一致する ChromeDriver を取得して保存する&lt;/li&gt;
&lt;li&gt;必要なら Selenium で実行確認まで行う&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この記事では、その処理の流れと使い方をまとめます。&lt;/p&gt;
&lt;h2 id="処理の流れ"&gt;処理の流れ
&lt;/h2&gt;&lt;h3 id="1-インストール済み-chrome-のバージョンを取得する"&gt;1. インストール済み Chrome のバージョンを取得する
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# get_chrome_driver/utils.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_chrome_version&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; system &lt;span style="color:#f92672"&gt;=&lt;/span&gt; platform&lt;span style="color:#f92672"&gt;.&lt;/span&gt;system()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; system &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Windows&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;import&lt;/span&gt; locale
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; encoding &lt;span style="color:#f92672"&gt;=&lt;/span&gt; locale&lt;span style="color:#f92672"&gt;.&lt;/span&gt;getpreferredencoding()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; paths &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;reg query &amp;#34;HKEY_CURRENT_USER\Software\Google\Chrome\BLBeacon&amp;#34; /v version&amp;#39;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;reg query &amp;#34;HKEY_LOCAL_MACHINE\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Google Chrome&amp;#34; /v version&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; cmd &lt;span style="color:#f92672"&gt;in&lt;/span&gt; paths:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; raw_output &lt;span style="color:#f92672"&gt;=&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;check_output(cmd, shell&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, stderr&lt;span style="color:#f92672"&gt;=&lt;/span&gt;subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;DEVNULL)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output &lt;span style="color:#f92672"&gt;=&lt;/span&gt; raw_output&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(encoding)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;UnicodeDecodeError&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output &lt;span style="color:#f92672"&gt;=&lt;/span&gt; raw_output&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(&lt;span style="color:#e6db74"&gt;&amp;#39;cp932&amp;#39;&lt;/span&gt;, errors&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;replace&amp;#39;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; re&lt;span style="color:#f92672"&gt;.&lt;/span&gt;search(&lt;span style="color:#e6db74"&gt;r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;version\s+REG_SZ\s+([\d\.]+)&amp;#39;&lt;/span&gt;, output)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;group(&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CalledProcessError:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; system &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Linux&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; commands &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;google-chrome&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;google-chrome-stable&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;chromium&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;chromium-browser&amp;#34;&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; cmd &lt;span style="color:#f92672"&gt;in&lt;/span&gt; commands:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output &lt;span style="color:#f92672"&gt;=&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;check_output([cmd, &lt;span style="color:#e6db74"&gt;&amp;#34;--version&amp;#34;&lt;/span&gt;])&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;strip()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; re&lt;span style="color:#f92672"&gt;.&lt;/span&gt;search(&lt;span style="color:#e6db74"&gt;r&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;[\d\.]+&amp;#39;&lt;/span&gt;, output)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;match&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;group(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; (subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;CalledProcessError, &lt;span style="color:#a6e22e"&gt;FileNotFoundError&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Windows ではまずレジストリを確認し、見つからなければ後続処理で &lt;code&gt;chrome.exe&lt;/code&gt; の &lt;code&gt;ProductVersion&lt;/code&gt; も参照します。Linux では &lt;code&gt;google-chrome&lt;/code&gt; や &lt;code&gt;chromium-browser&lt;/code&gt; など複数の候補コマンドを順番に試し、macOS では &lt;code&gt;--version&lt;/code&gt; の出力を解析します。&lt;/p&gt;
&lt;h3 id="2-cft-api-用のプラットフォーム名を決める"&gt;2. CfT API 用のプラットフォーム名を決める
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# get_chrome_driver/api.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_platform_string&lt;/span&gt;():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; os_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; platform&lt;span style="color:#f92672"&gt;.&lt;/span&gt;system()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; arch &lt;span style="color:#f92672"&gt;=&lt;/span&gt; platform&lt;span style="color:#f92672"&gt;.&lt;/span&gt;machine()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; os_name &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Linux&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;linux64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; os_name &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Darwin&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; arch &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;arm64&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;mac-arm64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;mac-x64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;elif&lt;/span&gt; os_name &lt;span style="color:#f92672"&gt;==&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Windows&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; sys&lt;span style="color:#f92672"&gt;.&lt;/span&gt;maxsize &lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&lt;span style="color:#f92672"&gt;**&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;32&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;win64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;win32&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;CfT API の JSON に合わせて、実行環境を &lt;code&gt;linux64&lt;/code&gt; &lt;code&gt;mac-arm64&lt;/code&gt; &lt;code&gt;mac-x64&lt;/code&gt; &lt;code&gt;win64&lt;/code&gt; &lt;code&gt;win32&lt;/code&gt; のような識別子に変換します。Windows では Python が 64bit かどうかを基準に判定しています。&lt;/p&gt;
&lt;h3 id="3-cft-api-から対応するダウンロード-url-を探す"&gt;3. CfT API から対応するダウンロード URL を探す
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# get_chrome_driver/api.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;get_driver_download_url&lt;/span&gt;(chrome_version, platform_name):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; major_version &lt;span style="color:#f92672"&gt;=&lt;/span&gt; chrome_version&lt;span style="color:#f92672"&gt;.&lt;/span&gt;split(&lt;span style="color:#e6db74"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;)[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(KNOWN_GOOD_URL, timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;raise_for_status()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version, url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _extract_from_versions(response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#39;versions&amp;#39;&lt;/span&gt;, []), major_version, platform_name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; url:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; url
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; e:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;CfT known-good lookup failed: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;e&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;try&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(LAST_KNOWN_GOOD_URL, timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;raise_for_status()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; payload &lt;span style="color:#f92672"&gt;=&lt;/span&gt; response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;json()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version, url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _extract_from_versions(payload&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#39;versions&amp;#39;&lt;/span&gt;, []), major_version, platform_name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; url:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; url
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version, url &lt;span style="color:#f92672"&gt;=&lt;/span&gt; _extract_from_channels(payload&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#39;channels&amp;#39;&lt;/span&gt;, {}), major_version, platform_name)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; url:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; url
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;except&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exception&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; e:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;CfT last-known-good lookup failed: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;e&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;まず &lt;code&gt;known-good-versions-with-downloads.json&lt;/code&gt; を参照し、見つからなければ &lt;code&gt;last-known-good-versions-with-downloads.json&lt;/code&gt; の &lt;code&gt;versions&lt;/code&gt; と &lt;code&gt;channels&lt;/code&gt; を順に確認します。完全一致ではなくメジャーバージョン一致で探すため、細かいビルド番号の差があっても実用上の互換ドライバを見つけやすくしています。&lt;/p&gt;
&lt;h3 id="4-既存のドライバが使えるかを先に判定する"&gt;4. 既存のドライバが使えるかを先に判定する
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# get_chrome_driver/core.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;existing_version &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;_get_installed_driver_version()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;target_major &lt;span style="color:#f92672"&gt;=&lt;/span&gt; version&lt;span style="color:#f92672"&gt;.&lt;/span&gt;split(&lt;span style="color:#e6db74"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;)[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; existing_version:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; existing_major &lt;span style="color:#f92672"&gt;=&lt;/span&gt; existing_version&lt;span style="color:#f92672"&gt;.&lt;/span&gt;split(&lt;span style="color:#e6db74"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;)[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; existing_major &lt;span style="color:#f92672"&gt;==&lt;/span&gt; target_major &lt;span style="color:#f92672"&gt;and&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;driver_path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;exists():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Existing ChromeDriver version &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;existing_version&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; is compatible.&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; str(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;driver_path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Updating ChromeDriver: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;existing_version&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt; -&amp;gt; major &lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;target_major&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;すでに保存済みの ChromeDriver があり、メジャーバージョンが一致していれば再ダウンロードはしません。互換性が崩れたときだけ更新します。&lt;/p&gt;
&lt;h3 id="5-zip-を安全に展開して保存する"&gt;5. ZIP を安全に展開して保存する
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# get_chrome_driver/core.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; requests&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(url, timeout&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;30&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; zipfile&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ZipFile(io&lt;span style="color:#f92672"&gt;.&lt;/span&gt;BytesIO(response&lt;span style="color:#f92672"&gt;.&lt;/span&gt;content)) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; z:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; driver_member &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; member &lt;span style="color:#f92672"&gt;in&lt;/span&gt; z&lt;span style="color:#f92672"&gt;.&lt;/span&gt;infolist():
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; member_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;normpath(member&lt;span style="color:#f92672"&gt;.&lt;/span&gt;filename)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;isabs(member_path) &lt;span style="color:#f92672"&gt;or&lt;/span&gt; member_path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;startswith(&lt;span style="color:#e6db74"&gt;&amp;#34;..&amp;#34;&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;basename(member_path) &lt;span style="color:#f92672"&gt;==&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;driver_name:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; driver_member &lt;span style="color:#f92672"&gt;=&lt;/span&gt; member
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; z&lt;span style="color:#f92672"&gt;.&lt;/span&gt;open(driver_member) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; source, open(self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;driver_path, &lt;span style="color:#e6db74"&gt;&amp;#34;wb&amp;#34;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;as&lt;/span&gt; target:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; shutil&lt;span style="color:#f92672"&gt;.&lt;/span&gt;copyfileobj(source, target)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; os&lt;span style="color:#f92672"&gt;.&lt;/span&gt;name &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;nt&amp;#34;&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;driver_path&lt;span style="color:#f92672"&gt;.&lt;/span&gt;chmod(&lt;span style="color:#ae81ff"&gt;0o755&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ZIP 内に &lt;code&gt;..&lt;/code&gt; や絶対パスを含むエントリがあっても無視し、&lt;code&gt;chromedriver&lt;/code&gt; または &lt;code&gt;chromedriver.exe&lt;/code&gt; 本体だけを取り出します。macOS と Linux では展開後に実行権限も付けます。&lt;/p&gt;
&lt;h3 id="6-selenium-で起動確認する"&gt;6. Selenium で起動確認する
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# get_chrome_driver/core.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;options &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Options()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;options&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add_argument(&lt;span style="color:#e6db74"&gt;&amp;#34;--headless&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;options&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add_argument(&lt;span style="color:#e6db74"&gt;&amp;#34;--no-sandbox&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;options&lt;span style="color:#f92672"&gt;.&lt;/span&gt;add_argument(&lt;span style="color:#e6db74"&gt;&amp;#34;--disable-dev-shm-usage&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Service(executable_path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;driver_path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;driver &lt;span style="color:#f92672"&gt;=&lt;/span&gt; webdriver&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Chrome(service&lt;span style="color:#f92672"&gt;=&lt;/span&gt;service, options&lt;span style="color:#f92672"&gt;=&lt;/span&gt;options)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;driver&lt;span style="color:#f92672"&gt;.&lt;/span&gt;get(&lt;span style="color:#e6db74"&gt;&amp;#34;https://google.com&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;title &lt;span style="color:#f92672"&gt;=&lt;/span&gt; driver&lt;span style="color:#f92672"&gt;.&lt;/span&gt;title
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;driver&lt;span style="color:#f92672"&gt;.&lt;/span&gt;quit()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;GetChromeDriver.validate()&lt;/code&gt; はこの確認を実行し、Chrome が起動して Google に到達できれば成功とみなします。CI や閉域環境のように検証を省きたい場合は、&lt;code&gt;main.py --no-validate&lt;/code&gt; でスキップできます。&lt;/p&gt;
&lt;h2 id="使い方"&gt;使い方
&lt;/h2&gt;&lt;h3 id="前提"&gt;前提
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Python 3.8 以上&lt;/li&gt;
&lt;li&gt;Chrome がインストールされていること&lt;/li&gt;
&lt;li&gt;ネットワークに接続できること&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="セットアップ"&gt;セットアップ
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# リポジトリを取得して仮想環境を作成&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd C:\workspace\projects
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;.\.venv\Scripts\Activate.ps1
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip install -r get-chrome-driver\requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="基本コマンド"&gt;基本コマンド
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ChromeDriver を取得し、ヘッドレス Selenium で動作確認する&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 現在の Chrome バージョンだけ確認する&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --check
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ドライバ取得だけ行い、Selenium 検証は省略する&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --no-validate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;取得した ChromeDriver は &lt;code&gt;~/.get-chrome-driver/&lt;/code&gt; に保存されます。Windows では通常 &lt;code&gt;C:\Users\&amp;lt;username&amp;gt;\.get-chrome-driver\&lt;/code&gt; です。&lt;code&gt;PATH&lt;/code&gt; に追加しなくても、Selenium 側で &lt;code&gt;driver_path&lt;/code&gt; を明示すれば使えます。&lt;/p&gt;
&lt;h3 id="selenium-スクリプトから直接使う"&gt;Selenium スクリプトから直接使う
&lt;/h3&gt;&lt;p&gt;CLI を単発で使うだけでなく、テストコードの中で &lt;code&gt;GetChromeDriver&lt;/code&gt; を呼ぶと、実行前に毎回ドライバをそろえられます。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; get_chrome_driver.core &lt;span style="color:#f92672"&gt;import&lt;/span&gt; GetChromeDriver
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; selenium &lt;span style="color:#f92672"&gt;import&lt;/span&gt; webdriver
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; selenium.webdriver.chrome.service &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Service
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;installer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; GetChromeDriver()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;driver_path &lt;span style="color:#f92672"&gt;=&lt;/span&gt; installer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;install()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;service &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Service(executable_path&lt;span style="color:#f92672"&gt;=&lt;/span&gt;driver_path)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;driver &lt;span style="color:#f92672"&gt;=&lt;/span&gt; webdriver&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Chrome(service&lt;span style="color:#f92672"&gt;=&lt;/span&gt;service)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ここから Selenium の処理を書く&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Chrome が自動更新された直後でも、先にこの処理を通しておけば対応する ChromeDriver を取得できます。ダウンロード結果は &lt;code&gt;~/.get-chrome-driver/&lt;/code&gt; にキャッシュされるので、毎回取り直す必要もありません。&lt;/p&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;get-chrome-driver&lt;/code&gt; を使うと、ChromeDriver のバージョン不一致を実行前に自動で解消できます。CfT API の仕様変更があっても、基本的には &lt;code&gt;api.py&lt;/code&gt; の取得ロジックを追従すれば対応しやすい構成にしてあります。Selenium の準備作業を減らしたいときに便利な小さなツールです。&lt;/p&gt;</description></item><item><title>Power Automateで前月分データをSharePointからExcelへ出力する方法｜落とし穴も解説</title><link>https://mitz17.com/blog/power-automate-sharepoint-excel/</link><pubDate>Tue, 03 Mar 2026 18:05:00 +0900</pubDate><guid>https://mitz17.com/blog/power-automate-sharepoint-excel/</guid><description>&lt;h1 id="power-automateで前月分データをsharepointからexcelへ出力する方法落とし穴も解説"&gt;Power Automateで前月分データをSharePointからExcelへ出力する方法｜落とし穴も解説
&lt;/h1&gt;&lt;p&gt;この記事では、Power Automate を使って次の処理を自動化する際に詰まりやすかったポイントをまとめます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;毎月1日に自動実行する&lt;/li&gt;
&lt;li&gt;SharePoint Online リストから前月分のデータを取得する&lt;/li&gt;
&lt;li&gt;Excel テンプレートをコピーして新しいファイルを作る&lt;/li&gt;
&lt;li&gt;取得したデータを1件ずつ Excel テーブルに追加する&lt;/li&gt;
&lt;li&gt;必要に応じて PDF 化する&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="先に結論"&gt;先に結論
&lt;/h2&gt;&lt;p&gt;Power Automate で前月分データを SharePoint から Excel に出力する場合、特に詰まりやすいのは次の4点です。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SharePoint の列は表示名ではなく内部名で扱う必要がある&lt;/li&gt;
&lt;li&gt;前月フィルターは日付条件を明示しないと取りこぼしや余計な取得が起きやすい&lt;/li&gt;
&lt;li&gt;Excel 側はテーブル化が必須で、&lt;code&gt;表に行を追加&lt;/code&gt; には 1 行オブジェクトが必要&lt;/li&gt;
&lt;li&gt;実行成功直後でも、Excel への保存反映に少し時間差がある&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この4点を先に理解しておくと、かなり詰まりにくくなります。&lt;/p&gt;
&lt;h2 id="前提条件"&gt;前提条件
&lt;/h2&gt;&lt;p&gt;今回の前提は次のとおりです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Power Automate のクラウドフローを使う&lt;/li&gt;
&lt;li&gt;データ元は SharePoint Online リスト&lt;/li&gt;
&lt;li&gt;出力先は Excel Online のテーブル&lt;/li&gt;
&lt;li&gt;毎月1日に前月分だけを抽出して出力する&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="フロー全体"&gt;フロー全体
&lt;/h2&gt;&lt;p&gt;今回やりたかったフロー全体はこうです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;スケジュールで毎月1日に実行&lt;/li&gt;
&lt;li&gt;SharePoint Online リストから前月分データを取得&lt;/li&gt;
&lt;li&gt;Excel テンプレートをコピーして当月用ファイルを作成&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Apply to each&lt;/code&gt; で 1 件ずつテーブルへ追加&lt;/li&gt;
&lt;li&gt;保存反映を待ってから後続処理を実行&lt;/li&gt;
&lt;li&gt;必要に応じて PDF 化&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;個々のアクション自体は珍しくありませんが、つなぎ込みでハマるポイントが多い構成でした。&lt;/p&gt;
&lt;h2 id="用語を最小限だけ整理"&gt;用語を最小限だけ整理
&lt;/h2&gt;&lt;h3 id="power-automate"&gt;Power Automate
&lt;/h3&gt;&lt;p&gt;Microsoft の自動化ツールです。&lt;br&gt;
トリガーとアクションをつないで、クラウド上で処理を流せます。&lt;/p&gt;
&lt;h3 id="sharepoint-online-リスト"&gt;SharePoint Online リスト
&lt;/h3&gt;&lt;p&gt;表形式でデータを持てるリストです。&lt;br&gt;
今回の用途では、利用日や所属、氏名などを持つ一覧データとして使っています。&lt;/p&gt;
&lt;h3 id="フィルタークエリ"&gt;フィルタークエリ
&lt;/h3&gt;&lt;p&gt;SharePoint Online から取得するデータを条件付きで絞り込むための式です。&lt;br&gt;
前月分だけ取得したいときはここが重要になります。&lt;/p&gt;
&lt;h3 id="excel-テーブル"&gt;Excel テーブル
&lt;/h3&gt;&lt;p&gt;Power Automate の &lt;code&gt;表に行を追加&lt;/code&gt; は、ただのセル範囲ではなく Excel のテーブルを対象にします。&lt;br&gt;
テーブル化されていないシートに対しては基本的にうまく動きません。&lt;/p&gt;
&lt;h3 id="内部名"&gt;内部名
&lt;/h3&gt;&lt;p&gt;SharePoint Online の列には、画面上の表示名とは別に内部名があります。&lt;br&gt;
日本語列では特に、内部名がエンコードされたような形になりやすいです。&lt;/p&gt;
&lt;h2 id="1-sharepoint-の列名が表示名のままでは使えない"&gt;1. SharePoint の列名が表示名のままでは使えない
&lt;/h2&gt;&lt;h3 id="起きたこと"&gt;起きたこと
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;利用日&lt;/code&gt;、&lt;code&gt;所属&lt;/code&gt;、&lt;code&gt;氏名&lt;/code&gt; のような列をそのまま参照しても、式の中で正しく読めませんでした。&lt;/p&gt;
&lt;p&gt;保存時には、&lt;code&gt;Apply_to_each&lt;/code&gt; や &lt;code&gt;表に行を追加&lt;/code&gt; まわりでテンプレートエラーが出ることもありました。&lt;/p&gt;
&lt;h3 id="原因"&gt;原因
&lt;/h3&gt;&lt;p&gt;SharePoint の取得結果では、表示名と内部名が一致しないことがあります。&lt;br&gt;
たとえば &lt;code&gt;利用日&lt;/code&gt; は、実際には次のようなキー名になっていました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;表示名: &lt;code&gt;利用日&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;内部名: &lt;code&gt;OData__x5229__x7528__x65e5_&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このため、式で &lt;code&gt;['利用日']&lt;/code&gt; を指定しても存在しない扱いになります。&lt;/p&gt;
&lt;h3 id="対処"&gt;対処
&lt;/h3&gt;&lt;p&gt;フローをテスト実行して、&lt;code&gt;複数の項目の取得&lt;/code&gt; の出力を確認します。&lt;br&gt;
生データを見ると、実際に使うべきキー名が分かります。&lt;/p&gt;
&lt;p&gt;Power Automate で SharePoint を扱うときは、「表示名ではなく内部名を使う」が基本だと考えた方が安全です。&lt;/p&gt;
&lt;h2 id="2-前月フィルターは日付条件を具体化した方がよい"&gt;2. 前月フィルターは日付条件を具体化した方がよい
&lt;/h2&gt;&lt;p&gt;タイトルどおり「前月分データを出力する」フローにしたいなら、フィルター条件を曖昧にしない方がよいです。&lt;/p&gt;
&lt;p&gt;考え方としては、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前月の開始日以上&lt;/li&gt;
&lt;li&gt;今月の開始日未満&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;の 2 条件で範囲指定するのが分かりやすいです。&lt;/p&gt;
&lt;p&gt;たとえば利用日列の内部名が &lt;code&gt;OData__x5229__x7528__x65e5_&lt;/code&gt; なら、概念的には次のような条件になります。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;OData__x5229__x7528__x65e5_ ge 前月初日
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;and
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;OData__x5229__x7528__x65e5_ lt 今月初日
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;実際の式は環境や列型に合わせて調整が必要ですが、&lt;br&gt;
「前月っぽい文字列でなんとなく絞る」のではなく、日付の開始・終了で範囲を切る方が安定します。&lt;/p&gt;
&lt;h2 id="3-excel-の-表に行を追加-はテーブル前提"&gt;3. Excel の &lt;code&gt;表に行を追加&lt;/code&gt; はテーブル前提
&lt;/h2&gt;&lt;h3 id="起きたエラー"&gt;起きたエラー
&lt;/h3&gt;&lt;p&gt;最初の方で出たのがこれです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;A value must be provided for item
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="原因-1"&gt;原因
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;表に行を追加&lt;/code&gt; の &lt;code&gt;item&lt;/code&gt; は、単一セルの値ではなく「1 行分のオブジェクト」を要求します。&lt;br&gt;
さらに、追加先の Excel 側はテーブル化されている必要があります。&lt;/p&gt;
&lt;p&gt;つまり必要なのは、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Excel シートをテーブル化しておく&lt;/li&gt;
&lt;li&gt;1 行分のデータをオブジェクトとして渡す&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;の 2 点です。&lt;/p&gt;
&lt;h3 id="実際のイメージ"&gt;実際のイメージ
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;利用日&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;2026/02/17&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;所属&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;鰻屋&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;#34;氏名&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;藤井&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;この形で 1 件ずつ &lt;code&gt;Apply to each&lt;/code&gt; から渡していくと、意図した動きに近づきます。&lt;/p&gt;
&lt;h2 id="4-成功しているのに-excel-が空に見えることがある"&gt;4. 成功しているのに Excel が空に見えることがある
&lt;/h2&gt;&lt;h3 id="起きたこと-1"&gt;起きたこと
&lt;/h3&gt;&lt;p&gt;Power Automate 上では成功になっているのに、Excel を見ると何も入っていないように見えました。&lt;/p&gt;
&lt;h3 id="実際はどうだったか"&gt;実際はどうだったか
&lt;/h3&gt;&lt;p&gt;実行履歴を見ると、&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;statusCode: 200&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;追加した行データ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;は返ってきていました。&lt;br&gt;
つまり処理そのものは成功していました。&lt;/p&gt;
&lt;h3 id="原因-2"&gt;原因
&lt;/h3&gt;&lt;p&gt;問題は、処理成功から実際の保存・反映まで少し時間差があることでした。&lt;br&gt;
体感では 30 秒程度のラグがあるケースがありました。&lt;/p&gt;
&lt;h3 id="対処-1"&gt;対処
&lt;/h3&gt;&lt;p&gt;後続処理に進む前に &lt;code&gt;遅延&lt;/code&gt; を入れて、保存反映の待ち時間を確保しました。&lt;br&gt;
クラウドフローの成功表示だけを見てすぐ次へ進むと、見た目上「空に見える」ことがあります。&lt;/p&gt;
&lt;h2 id="5-本番移行時に接続破損が起きることがある"&gt;5. 本番移行時に接続破損が起きることがある
&lt;/h2&gt;&lt;h3 id="発生したエラー"&gt;発生したエラー
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;#39;表に行を追加&amp;#39; の接続が破損しています。
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;接続を修正してください。
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="原因-3"&gt;原因
&lt;/h3&gt;&lt;p&gt;Power Automate の接続は、次の情報に紐づきます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ユーザーアカウント&lt;/li&gt;
&lt;li&gt;環境（開発 / 本番）&lt;/li&gt;
&lt;li&gt;認証トークン&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;そのため、開発環境で作った接続が本番環境ではそのまま使えないことがあります。&lt;/p&gt;
&lt;h3 id="確認方法"&gt;確認方法
&lt;/h3&gt;&lt;h4 id="方法1-アクション単位で確認"&gt;方法1: アクション単位で確認
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;フローを編集モードで開く&lt;/li&gt;
&lt;li&gt;問題のアクションをクリック&lt;/li&gt;
&lt;li&gt;右側パネル上部の接続欄を確認する&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="方法2-接続一覧から確認"&gt;方法2: 接続一覧から確認
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;左メニューから &lt;code&gt;データ&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;接続&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;エラー状態の接続を探す&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="対処-2"&gt;対処
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;接続を修正&lt;/code&gt; を押す&lt;/li&gt;
&lt;li&gt;新しい接続を作成する&lt;/li&gt;
&lt;li&gt;必要なら問題のアクションを作り直す&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;私の環境では、再認証だけで直らず、失敗していたブロックを削除して作り直した方が早いケースもありました。&lt;/p&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;p&gt;Power Automate で SharePoint Online の前月分データを Excel に出力する処理自体は実現できますが、次の点でつまずきやすいです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SharePoint は内部名で扱う&lt;/li&gt;
&lt;li&gt;前月フィルターは日付範囲で考える&lt;/li&gt;
&lt;li&gt;Excel 側はテーブル化が必須&lt;/li&gt;
&lt;li&gt;保存反映の遅延を考慮する&lt;/li&gt;
&lt;li&gt;本番環境では接続破損も疑う&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;個々のアクションだけを見ると簡単そうでも、実際には「つなぎ込み」でハマりやすいので、&lt;br&gt;
最初にこの5点を意識しておくとかなり進めやすくなるはずです。&lt;/p&gt;</description></item><item><title>【初心者向け】Cloudflare Email Routingの設定方法｜独自ドメインのメールをGmailで受信する手順</title><link>https://mitz17.com/blog/receive-email/</link><pubDate>Mon, 02 Mar 2026 12:00:00 +0900</pubDate><guid>https://mitz17.com/blog/receive-email/</guid><description>&lt;h2 id="この記事でやること"&gt;この記事でやること
&lt;/h2&gt;&lt;p&gt;独自ドメイン &lt;code&gt;mitz17.com&lt;/code&gt; で メールアドレスを作り、Cloudflare Email Routing を使って Gmail へ無料で転送。サーバー構築なし、ほぼノーコードで完結した。&lt;/p&gt;
&lt;h2 id="参考にした資料"&gt;参考にした資料
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://note.com/yetanother_yk/n/ncdbb530e1cc5" target="_blank" rel="noopener"
 &gt;CloudflareのEmail Routingでカスタムドメイン宛メールを受信する手順&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="操作1-cloudflare-でドメインの管理画面を表示"&gt;操作1: Cloudflare でドメインの管理画面を表示
&lt;/h2&gt;&lt;h3 id="手順"&gt;手順
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;Cloudflare にログイン。&lt;/li&gt;
&lt;li&gt;対象ドメイン（今回は &lt;code&gt;mitz17.com&lt;/code&gt;）を選択。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="./blog/receive-email/domain-dashboard.png"
	width="688"
	height="631"
	loading="lazy"
	
		alt="ドメインのダッシュボード"
	
 
 title="Cloudflare でドメインの管理画面"
 data-title-escaped="Cloudflare でドメインの管理画面"
 
	
		class="gallery-image" 
		data-flex-grow="109"
		data-flex-basis="261px"
	
&gt;&lt;/p&gt;
&lt;h2 id="操作2-開始ボタンを押す"&gt;操作2: 開始ボタンを押す
&lt;/h2&gt;&lt;h3 id="手順-1"&gt;手順
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;左メニューの「メールアドレス」→「Email Routing」を開く。&lt;/li&gt;
&lt;li&gt;「開始」をクリック。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="./blog/receive-email/email-routing-rule.png"
	width="689"
	height="632"
	loading="lazy"
	
		alt="転送ルールの設定"
	
 
	
		class="gallery-image" 
		data-flex-grow="109"
		data-flex-basis="261px"
	
&gt;&lt;/p&gt;
&lt;h2 id="操作3-アドレスの設定"&gt;操作3: アドレスの設定
&lt;/h2&gt;&lt;h3 id="手順-2"&gt;手順
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;「カスタムアドレス」に好きなメールアドレスを入力&lt;br&gt;
（例: &lt;code&gt;mitz17@mitz17.com&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;「宛先」に転送したい Gmail アドレスを入力&lt;/li&gt;
&lt;li&gt;「作成して続行」をクリック&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;すると Gmail に確認メールが届く。&lt;/p&gt;
&lt;p&gt;メール内の &lt;strong&gt;&amp;ldquo;Verify email address&amp;rdquo;&lt;/strong&gt; をクリックすれば認証完了。&lt;/p&gt;
&lt;p&gt;だいたい数分待てば設定が反映される。&lt;/p&gt;
&lt;p&gt;&lt;img src="./blog/receive-email/email-routing-setup.png"
	width="778"
	height="720"
	loading="lazy"
	
		alt="Email Routing のセットアップウィザード"
	
 
	
		class="gallery-image" 
		data-flex-grow="108"
		data-flex-basis="259px"
	
&gt;&lt;/p&gt;
&lt;h2 id="操作4-受信テスト"&gt;操作4: 受信テスト
&lt;/h2&gt;&lt;h3 id="手順-3"&gt;手順
&lt;/h3&gt;&lt;p&gt;外部アドレスからテストメールを送る。&lt;/p&gt;
&lt;p&gt;Gmailに届けば成功。&lt;/p&gt;
&lt;p&gt;届かない場合は：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MXレコードが間違っている&lt;/li&gt;
&lt;li&gt;まだDNSが反映されていない&lt;/li&gt;
&lt;li&gt;認証が終わっていない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このあたりをチェック。&lt;/p&gt;
&lt;p&gt;&lt;img src="./blog/receive-email/routing-checklist.png"
	width="1120"
	height="428"
	loading="lazy"
	
		alt="最終チェックリスト"
	
 
 title="届いたぜ！"
 data-title-escaped="届いたぜ！"
 
	
		class="gallery-image" 
		data-flex-grow="261"
		data-flex-basis="628px"
	
&gt;&lt;/p&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;p&gt;Cloudflare Email Routing と Gmail で自分のドメインのメールを受け取れるようになりました。&lt;/p&gt;
&lt;p&gt;受信は簡単。&lt;/p&gt;
&lt;p&gt;問題は「送信側」。(らしい)&lt;/p&gt;
&lt;p&gt;SendGrid の無料プランがサ終したそうなので、&lt;br&gt;
Mailgun か AWS SES あたりを検討中。&lt;/p&gt;
&lt;p&gt;次は送信環境を作る。&lt;/p&gt;</description></item><item><title>MP3の音量がバラバラな問題、ffmpegで一発解決｜Python製ツールの使い方と仕組み</title><link>https://mitz17.com/blog/mp3-normalizer-devlog/</link><pubDate>Mon, 02 Mar 2026 00:00:00 +0900</pubDate><guid>https://mitz17.com/blog/mp3-normalizer-devlog/</guid><description>&lt;p&gt;GitHub: &lt;a class="link" href="https://github.com/mitz17/mp3-normalizer" target="_blank" rel="noopener"
 &gt;mitz17/mp3-normalizer&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="mp3の音量がバラバラで困っていませんか"&gt;MP3の音量がバラバラで困っていませんか？
&lt;/h2&gt;&lt;p&gt;昔作った MP3 を久しぶりに再生したら「あれ、音ちいさくない？」と感じたことはありませんか。ファイル自体は壊れていないのに、曲ごとに音量がまちまちで、再生のたびにボリュームを手動調整している──そんな悩みを &lt;strong&gt;ffmpeg の &lt;code&gt;loudnorm&lt;/code&gt; フィルタ&lt;/strong&gt; と &lt;strong&gt;Python 製ツール「mp3-normalizer」&lt;/strong&gt; で丸ごと解決します。&lt;/p&gt;
&lt;p&gt;このツールでできることを先にまとめます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;フォルダ内の MP3 を &lt;strong&gt;まとめて -14 LUFS に正規化&lt;/strong&gt;（業界標準値）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GUI でも CLI でも&lt;/strong&gt; 同じエンジンで処理できる&lt;/li&gt;
&lt;li&gt;ID3 タグ・歌詞・アートワークを保ったまま再エンコード&lt;/li&gt;
&lt;li&gt;処理済み履歴を JSON で管理し、&lt;strong&gt;二重処理・上書きを自動防止&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="ffmpeg-の-loudnorm-とは"&gt;ffmpeg の &lt;code&gt;loudnorm&lt;/code&gt; とは？
&lt;/h2&gt;&lt;p&gt;&lt;code&gt;loudnorm&lt;/code&gt; は ffmpeg に組み込まれた音量正規化フィルタです。波形のピーク値ではなく &lt;strong&gt;LUFS（Loudness Units Full Scale）&lt;/strong&gt; という人間の聴感に近い指標を基準にするため、曲ごとの「うるさい・静かすぎる」問題を自然に解消できます。&lt;/p&gt;
&lt;p&gt;パラメータや 1pass / 2pass の仕組みを先に把握したい場合は、&lt;a class="link" href="./blog/ffmpeg-loudnorm-guide/" &gt;ffmpeg loudnorm 完全解説：LUFS正規化と2passノーマライズの仕組み&lt;/a&gt;にまとめています。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="対象読者前提環境"&gt;対象読者・前提環境
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;古い MP3 アーカイブを大切にしていて、&lt;strong&gt;音量だけさっと手直ししたい&lt;/strong&gt;人&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ffmpeg&lt;/code&gt; のコマンドを覚えていないけど、まとめて処理したい人&lt;/li&gt;
&lt;li&gt;Python が書ける人で、実装の中身まで確認したい人&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;動作には &lt;strong&gt;Python 3.11 以上&lt;/strong&gt; と &lt;strong&gt;ffmpeg 6.x&lt;/strong&gt; が必要です。ffmpeg の導入は &lt;a class="link" href="https://qiita.com/Tadataka_Takahashi/items/9dcb0cf308db6f5dc31b" target="_blank" rel="noopener"
 &gt;この Qiita 記事&lt;/a&gt; を OS ごとに手順をたどれば OK です。正規化したい &lt;code&gt;.mp3&lt;/code&gt; はフォルダにまとめておいてください（WAV など他形式は事前に変換するか、後述の拡張子設定で対応できます）。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="セットアップ"&gt;セットアップ
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 1. リポジトリを取得&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;git clone https://github.com/mitz17/mp3-normalizer
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 2. 仮想環境を作成・有効化&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;source .venv/bin/activate &lt;span style="color:#75715e"&gt;# Windows: .venv\Scripts\Activate.ps1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 3. 依存パッケージをインストール&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 4. 起動&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py &lt;span style="color:#75715e"&gt;# GUI モード&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli ... &lt;span style="color:#75715e"&gt;# CLI バッチ処理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="使い方gui-編"&gt;使い方：GUI 編
&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;入力・出力ディレクトリを指定&lt;/strong&gt;すると、対象 MP3 がリストに表示されます。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LUFS と True Peak を入力&lt;/strong&gt;。迷ったらデフォルト（-14 LUFS / -1 dBFS）のままで OK。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;再帰チェック&lt;/strong&gt;（サブフォルダを含むか）や&lt;strong&gt;強制再エンコード&lt;/strong&gt;のトグルで挙動を調整。&lt;/li&gt;
&lt;li&gt;「&lt;strong&gt;実行&lt;/strong&gt;」を押すと進捗ログがリアルタイムで流れ、完了後に結果を確認できます。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="./images/mp3-normalizer-gui.png" alt="mp3-normalizer GUI" loading="lazy" style="border-radius:16px; box-shadow:0 8px 24px rgba(15,23,42,0.18);"&gt;&lt;/p&gt;

 &lt;blockquote&gt;
 &lt;p&gt;入力/出力パス・ターゲット LUFS・対象ファイル一覧・ログを 1 画面で確認できます。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="使い方cli-編"&gt;使い方：CLI 編
&lt;/h2&gt;&lt;p&gt;GUI なしでバッチ処理したいときはこちら。スクリプトやタスクスケジューラと組み合わせやすいです。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 基本（入力・出力ディレクトリを指定）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli --input ./music_in --output ./music_out
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# LUFS と True Peak を変更する場合&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli --input ./music_in --output ./music_out --lufs -16 --tp -1.5
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 並列数を指定（デフォルトは CPU コア数）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli --input ./music_in --output ./music_out --workers &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 対象拡張子と出力形式を変更&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;python main.py --cli --input ./music_in --output ./music_out --ext flac --format aac
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;処理結果は &lt;code&gt;mp3_normalizer.log&lt;/code&gt; に記録され、実行した ffmpeg コマンドごとの LUFS 値も残ります。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="実装のポイント"&gt;実装のポイント
&lt;/h2&gt;&lt;h3 id="アーキテクチャ概要"&gt;アーキテクチャ概要
&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;main.py
├── gui.py ─ Tkinter + ttk。別スレッドで ffmpeg を呼び出し UI フリーズを回避
├── processor.py ─ GUI / CLI 共通エンジン。ファイル走査・重複回避・正規化を担当
└── utils.py ─ パス生成・ログ整形などの汎用ユーティリティ
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;処理済みファイルは &lt;code&gt;processed_history.json&lt;/code&gt; にサイズ・更新日時で記録され、再実行時に自動スキップされます。デフォルト（&lt;code&gt;force=False&lt;/code&gt;）では既存ファイルへの上書きも行いません。&lt;/p&gt;
&lt;h3 id="ファイル走査と重複回避"&gt;ファイル走査と重複回避
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;processor.py&lt;/code&gt; の &lt;code&gt;AudioProcessor.process_directory&lt;/code&gt; では、入力ディレクトリを走査して処理計画を立て、衝突しそうなファイル名に &lt;code&gt;_1&lt;/code&gt;, &lt;code&gt;_2&lt;/code&gt; のサフィックスを付加します（&lt;a class="link" href="https://github.com/mitz17/mp3-normalizer/blob/main/processor.py#L180-L232" target="_blank" rel="noopener"
 &gt;GitHub の該当コード&lt;/a&gt;）。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; index, entry &lt;span style="color:#f92672"&gt;in&lt;/span&gt; enumerate(plan&lt;span style="color:#f92672"&gt;.&lt;/span&gt;entries, start&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;):
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; destination &lt;span style="color:#f92672"&gt;=&lt;/span&gt; output_dir &lt;span style="color:#f92672"&gt;/&lt;/span&gt; entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;relative
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ensure_directory(destination&lt;span style="color:#f92672"&gt;.&lt;/span&gt;parent)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; destination &lt;span style="color:#f92672"&gt;=&lt;/span&gt; destination&lt;span style="color:#f92672"&gt;.&lt;/span&gt;with_suffix(&lt;span style="color:#e6db74"&gt;&amp;#34;.mp3&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; destination &lt;span style="color:#f92672"&gt;=&lt;/span&gt; generate_unique_output_path(destination)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; result &lt;span style="color:#f92672"&gt;=&lt;/span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;executor&lt;span style="color:#f92672"&gt;.&lt;/span&gt;normalize(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; input_file&lt;span style="color:#f92672"&gt;=&lt;/span&gt;entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;source,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; destination&lt;span style="color:#f92672"&gt;=&lt;/span&gt;destination,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; target_lufs&lt;span style="color:#f92672"&gt;=&lt;/span&gt;target_lufs,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; true_peak&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true_peak,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; results&lt;span style="color:#f92672"&gt;.&lt;/span&gt;append(result)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; result&lt;span style="color:#f92672"&gt;.&lt;/span&gt;success:
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;history_service&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mark_processed(entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;relative, entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;size, entry&lt;span style="color:#f92672"&gt;.&lt;/span&gt;mtime)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;history_service&lt;span style="color:#f92672"&gt;.&lt;/span&gt;save()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="ffmpeg-コマンドの組み立て"&gt;ffmpeg コマンドの組み立て
&lt;/h3&gt;&lt;p&gt;実際に ffmpeg を叩くのは &lt;code&gt;FfmpegExecutor.normalize&lt;/code&gt;（&lt;a class="link" href="https://github.com/mitz17/mp3-normalizer/blob/main/processor.py#L63-L111" target="_blank" rel="noopener"
 &gt;コードはこちら&lt;/a&gt;）。コマンド全体をログに残すため、GUI からでも「裏で何をしているか」が一目でわかります。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;command &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;ffmpeg_cmd, &lt;span style="color:#e6db74"&gt;&amp;#34;-hide_banner&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-y&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-i&amp;#34;&lt;/span&gt;, str(input_file),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-af&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;loudnorm=I=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;target_lufs&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:TP=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;true_peak&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;:LRA=11&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-c:a&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;libmp3lame&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;-q:a&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;2&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-map_metadata&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; str(destination),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;command_str &lt;span style="color:#f92672"&gt;=&lt;/span&gt; format_command(command)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;self&lt;span style="color:#f92672"&gt;.&lt;/span&gt;logger&lt;span style="color:#f92672"&gt;.&lt;/span&gt;info(&lt;span style="color:#e6db74"&gt;&amp;#34;ffmpeg コマンド: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;%s&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;, command_str)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;completed &lt;span style="color:#f92672"&gt;=&lt;/span&gt; subprocess&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(command, check&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;, capture_output&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;, text&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;-map_metadata 0&lt;/code&gt; で ID3 タグをそのまま引き継ぎ、mutagen ライブラリで歌詞タグを後から上書きコピーすることで、ffmpeg だけでは落ちてしまうタグも保持します。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="アップデート履歴"&gt;アップデート履歴
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;日付&lt;/th&gt;
 &lt;th&gt;内容&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-03-04&lt;/td&gt;
 &lt;td&gt;直列処理 → 並列処理対応。145 件の処理が 670 秒 → 207 秒（約 3.2 倍高速化）。Windows での文字コード処理を堅牢化&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-03-05&lt;/td&gt;
 &lt;td&gt;mutagen を用いた歌詞タグのコピー処理を追加。正規化後も歌詞を保持可能に&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-03-08&lt;/td&gt;
 &lt;td&gt;静かなイントロでの音量増大を抑制。入力ビットレートを検出して同等ビットレートで出力。入力拡張子・出力形式（mp3 / aac / flac / wav / ogg）を選択可能に。アートワーク保持を二段構えで強化&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2026-03-18&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;既知の不具合&lt;/strong&gt;：100 曲に 1 曲程度の割合でアーティストタグが消えるケースを確認。同一アルバム内でも再現条件が定まらず調査中。Issue を立てる予定。&lt;a class="link" href="https://github.com/mitz17/mp3-normalizer" target="_blank" rel="noopener"
 &gt;修正に協力いただける方はこちら&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="まとめ"&gt;まとめ
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Tkinter × ffmpeg という素朴な構成でも、MP3 音量の正規化はかなり快適にできる&lt;/li&gt;
&lt;li&gt;GUI で直感操作・CLI でバッチ自動化、どちらでも同じ品質に揃えられる&lt;/li&gt;
&lt;li&gt;古い MP3 をまだまだ活用したい人は、ぜひ &lt;code&gt;mp3-normalizer&lt;/code&gt; をクローンしてみてください&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="関連記事"&gt;関連記事
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/ffmpeg-loudnorm-guide/" &gt;【コマンド例あり】ffmpeg loudnormの使い方｜LUFS正規化と2pass設定を解説&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/ansys-version-selector/" &gt;Ansysのバージョン選択ツールを作った理由｜古い解析ファイルを別バージョンで開くリスクを減らす&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>【初投稿】技術ブログを始めました｜このブログで発信する内容</title><link>https://mitz17.com/blog/my-first-post/</link><pubDate>Sun, 01 Mar 2026 11:17:14 +0900</pubDate><guid>https://mitz17.com/blog/my-first-post/</guid><description>&lt;h2 id="意気込み"&gt;意気込み
&lt;/h2&gt;&lt;p&gt;はじめまして、mitz17です。
ブログ、ハジメマシタ。&lt;/p&gt;
&lt;p&gt;日々をちょっとだけ豊かにするためのプログラム開発とか、
思いついたライフハックとか、
作ってみたものの記録とか。&lt;/p&gt;
&lt;p&gt;そんな感じのことを、備忘録がてらゆるっと書いていきます。&lt;/p&gt;
&lt;h2 id="始めようと思ったきっかけ"&gt;始めようと思ったきっかけ
&lt;/h2&gt;&lt;p&gt;「自分のドメインを持つ」って、なんかちょっとカッコよくないですか？&lt;/p&gt;
&lt;p&gt;そんなノリで勢いよく取得しました。
で、取ったはいいものの、特に壮大なビジョンがあったわけでもなく。&lt;/p&gt;
&lt;p&gt;でも、ドメインって維持費(約￥1,500-)かかるんですよね。&lt;/p&gt;
&lt;p&gt;私は毎月・毎年じわじわ削られていくいわゆる固定費が死ぬほど苦手です。&lt;/p&gt;
&lt;p&gt;だったら――&lt;br&gt;
ブログの広告で維持費くらい回収できればよくない？&lt;/p&gt;
&lt;p&gt;途中で飽きる可能性はあります。
でも、それも含めてログとして残っていたら面白いかなと思っています。&lt;/p&gt;
&lt;h2 id="目標"&gt;目標
&lt;/h2&gt;&lt;p&gt;このブログの広告収益で、ドメインの維持費をまかなうこと。&lt;/p&gt;
&lt;p&gt;これが最初の10歩。&lt;/p&gt;
&lt;h2 id="関連記事"&gt;関連記事
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="./blog/ffmpeg-loudnorm-guide/" &gt;【コマンド例あり】ffmpeg loudnormの使い方｜LUFS正規化と2pass設定を解説&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/ansys-version-selector/" &gt;Ansysのバージョン選択ツールを作った理由｜古い解析ファイルを別バージョンで開くリスクを減らす&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="./blog/dev-pc-build-ryzen7600-rtx4070s/" &gt;Ryzen 7600＋RTX 4070 Superの自作PC構成例｜購入価格16.3万円の実例紹介&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>