Webpackその他

メモ記事とはいえなかなか書いてる時間がとれず一月以上の間が空いてしまった。FastAPIによるバックエンド実装からTypescriptとSASSによるフロントエンド実装の勉強に入った所、WebAssemblyとその有力言語のRustに興味が写ってしまい、そのあたりを勉強しながらフロントエンドに組み込む技術を調査していたら、Webpackという技術に突き当たった。

WebAssemblyとRustに関しては別の機会に書くとして、今回はWebpack。いままで何度か単語程度に見かけはしたが、なんとなくお腹一杯感でスルーしていたが、必要に応じて調べてみると目からウロコ的な技術だった。

従来の静的コンテンツ(ブラウザ側で処理をする所謂フロントエンド)は、HTMLベースでスタールシート(CSS)やJavascriptを必要に応じて読み込む形式だったが、Webpack化することで、大まかには一つのJavascriptにまとめられる。

例えば下記のようなindex.htmlでは

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <!-- 省略 -->
  <style type="text/css">
    @import "/static/css/bootstrap-custom.css";
  </style>
  <script type="module">
    import { Alert, Button, Carousel, Collapse, Dropdown, Modal, Offcanvas, Popover, ScrollSpy, Tab, Toast, Tooltip } from "/static/js/bootstrap.esm.js"
  </script>
</head>
<body class="base">
  <script type="module">
    import {init} from "/static/js/app.js"
    window.addEventListener("load", init());
  </script>
</body>
</html>

6行目〜でbootstrapのカスタムスタイルシート(これは別途SASSでコンパイルしている)を読み込んで、その下9行目〜でbootstrap用のスクリプトをモジュールとして読み込んでいる。Bodyタグを読み込んだあとで14行目〜でシングルページアプリケーション用スクリプトapp.jsを読み込んでページロード完了後にinitを実行(window.addEventListener)。app.jsもTypescriptのソースから別途コンパイルしておりアプリケーションの規模によっては多くのモジュールに分かれてロードされる。

こういった構成のものが、Webpack化することで下記のように出来る

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <!-- 省略 -->
    <script src="/static/dist/main.js"></script>
</head>
<body class="base">
</body>
</html>

6行目のmain.js を読み込むだけとなる。main.jsはWebpackによってコンパイルされたjavascriptで読み込むべきリソースが全てパック化された生成物となる。ソースファイルは一般にindex.jsとされ、下記のような内容となる

import '../scss/bootstrap-custom.scss';
import 'bootstrap/dist/js/bootstrap.esm.js';

import {init, build} from '../ts/app';

init();

window.addEventListener("load", () => {
  build();
});

一行目でbootstrapのカスタムスタイルシートを読み込むが、ここではscssのソースファイルを直接インポートして、WebpackのプラグインでSassコンパイラを起動する。生成したスタイルシートをインポートすると呼び出し側のHTMLのHEADタグ内にSTYLEタグを生成して直接コーディングするため、CSSファイルは生成されない。

2行目はbootstrap用のスクリプトをインポート。4行目でシングルページアプリケーションスクリプトを読み込むが、これもTypescriptのソースを直接していして内部でコンパイラを起動している。また、従来構成と違って読み込んだ時点ではBODYタグが生成されていないため、DOM関連の初期化があると失敗する。DOM関連の初期化はloadイベントをトリガとして始めるよう工夫が必要。

とりあえず、Webpackを導入するとこうなるというだけのメモで、Webpackの具体的な設定などはまた別の機会に。

FastAPIの導入

前回記事より一ヶ月ほど経過してしまいましたが、また思いつきで調査勉強の没頭していたという適当な感じで休んでいました。やっぱりこういうものを習慣化するのは難しい……。

現在の作業項目としてdjangoによるがWEBアプリテンプレートがあるが、シングルページアプリケーションへの移行を考慮するなど次第にバックエンド側の比重が軽くなってきた。このためもう少し軽いバックエンドフレームワークをと言うことで検討を行いFastAPIの採用を検討することにした。

まだ不勉強なので、間違った比較かも知れないが下記のように比較できると思う。

djangoFastAPI
バックエンド主体の比較的規模の大きなWebアプリケーションに向いている
主にHTMLレスポンスで動的にWebページを表示できる
フロントエンド主体のWebアプリケーションに向いている
主にJSONレスポンスでWebAPIに特化。ブラウザに表示するためには別途テンプレートや静的HTML文書を用意し、API利用によるWebアプリを実現する
ユーザー認証、データベース利用など一般的なバックエンド機能が一通り揃っている状態。基本状態ではリクエストに応じてJSONオブジェクトを返すだけのAPIサーバー。
バックエンド機能はチュートリアルに沿って進めればある程度のものが出来る。
機能追加は容易ではあるが、ある程度の調査が必要なのと、複雑なライブラリ構造の理解がないと敷居が高い印象構造が比較的単純で分かりやすいため、機能追加は容易。ただしそれなりの調査も必要。
簡単な比較

例えば、Djangoではユーザー認証や管理画面が最初から用意されているが、それのデザインや振る舞いを変えたり認証方法をカスタムしたりするためには、フレームワークの約束事に沿ったカスタム方法を調査するなど、かなりの労力が必要になるが、FastAPIはそもそも準備されていないので、自分で作らなくてはならない。そのため後から機能追加などのカスタムは非常に容易という事になる(ただし出来上がりもそれなりのものになってしまうのは自己責任ということで)。また、フレームワーク的な約束もあまり細かくないので、作っていくうちにコードの構成が分かりにくくなってしまう危険もあるのではと思う。

実験用のサーバー構成は、色々解説があるが、こちらではもともとのDjangoテンプレートのDocker環境があるのでそちらをベースにDocker環境の構築を行う。

次回はFastAPI導入の準備としてDocker環境構築のメモを残しておきたいと思う。

FastAPIでDjangoのテンプレートアプリと同等のものを実現しようとすると、認証の仕組み、データベースアクセスの仕組みなどDjangoでは組み込まれていたものを個別に調査・勉強・実装を行わないといけない。
大変そうだけど、汎用的な仕組みの理解なので色々役に立ちそうと、前向きに考えておこう。

WEBアプリテンプレート(管理機能)

前回紹介したdjangoWEBアプリテンプレートに関して、個別の機能について気が向いたらメモを残しておきます。

ユーザー(www-data)の設定

djahgoのサービスはwww-dataというユーザー(debian-slimイメージに最初から居る)で起動するので、/code以下のファイル所有権もwww-dataとし、また実際のアプリ開発のために起動したコンテナにVScodeにアタッチして編集したりgitでソース管理出来るようにwww-dataユーザーでログインすることとし、念のためsudo権限を与えておく事とした。

# Dockerfile
RUN apt update; apt install -y git sudo
......
# usermod for www-data & sudo group
RUN usermod -d /code www-data; \
    usermod -s /bin/bash www-data; \
    echo "%www-data ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/www-data

# docker-compose.yml
services:
  django:
    build: .
    image: django_image:test
    user: www-data
    command: /code/startup
.......

こうすることで/code/startupはwww-dataの権限で実行されてテンプレートの構築が行われる。ただし、コンテナでマウントしたディレクトリの扱いがMacとそれ以外では異なるようで、Linuxなどの場合コンテナとホストのUIDが一致していない場合、ホストのユーザーで作成した/codeにwww-data権限で書込が出来ない問題がある。ここでは/code/startupの初期の段階で/codeの所有権をコンテナのwww-dataに取得させている(その為にパスワード無しのsudo権限を与えてしまった・・・・)

adminユーザーの設定

コンテナ側のユーザー管理とは別に、django WEBアプリにも認証があるため、少なくとも管理ユーザーの作成が必要(認証する気がなければ無くても良いかも知れない)。これもサイト構築毎に毎回は面倒なので、/code/startupのテンプレート作成時に自動生成させている。

ユーザーデータベースの更新は本来インタラクティブモードで動作するが、全自動で行うために下記のようにshellでコマンドを食わせている。

# startup
python manage.py migrate # データベースの初期化

cat <<CREATESUPER | sed -r "s/^\s*//g" | python manage.py shell
    from django.contrib.auth import get_user_model
    User = get_user_model()
    User.objects.create_superuser('admin' ,'', 'admin')
CREATESUPER

本テンプレートでは認証画面への遷移や管理画面へのリンクはnavbarに配置しており、テンプレートのcommon.tsでDOM構築している。

テンプレートのDOM構造は先日投稿のものをTypescript対応にしたもので、少し冗長になる感じだけど上手く動いています。これも気が向いたらメモを残しておこうかと・・・・

WEBアプリテンプレート(django+javascript)

code
code
nginx
nginx
Dockerfile
Dockerfile
docker-compose.yml
docker-compose.yml
conf.d
conf.d
uwsgi_params
uwsgi_params
project.conf
project.conf
project.conf
project.conf
/
/
[PROJECT]
[PROJECT]
docker
マウントポイント
docker…
サイト構築
ポイント
サイト構築 ポイント
プロジェクト構成
プロジェクト構成
Text is not SVG – cannot display
# 通常起動
$docker-compose up -d
# 再構築
CLEARSITE=1 docker-compose up -d
テンプレート表示状態

WEBアプリの勉強を始めた所、djangoを用いて同じ構成のサイトを何度も構築するのが面倒でテンプレートを作ることにした。しかしコレばっかりに夢中で肝心の勉強の方がおろそかになってきているのは、いつものお約束。

テンプレート構成

  • docker image 仕様(Dockerfile)
    • debianベースのpythonスリムイメージをベースとする
    • pythonライブラリインストールステージ:dbアクセス用ライブラリの為に一時的に開発ツール一式をインストール(後にスリム化処理)
    • nodejsは一旦aptパッケージインストールした後、npmで最新のバージョンをインストール
    • npmでtypescript、sass、bootstrapをインストール
    • その他チョットしたツール:gitやwget、アプリ実行用ユーザー(user)の作成とsudo権限設定
    • 実行コマンドは/code/startupシェルスクリプト:イメージにはダミーのシェルスクリプトを格納しているが、実際はサイト構築テンプレートとwusgi起動を含むシェルスクリプトを/code/startupとして作成しマウントして実行(後述)。
  • 起動:docker-compose構成
    • djangoは/codeをマウントしてサービスを実行
    • 環境変数CLEARSITE=1とすることでサイトを削除してテンプレートで再構築する(危険コマンド)
    • 構築用のstartupスクリプトが長いため、djangoサービスが始める前にnginxからのサービス要求がありロックすることがあったので、ヘルスチェックを行うようにしている(uwsgiのプロセスチェック)。
  • startupスクリプト(テンプレート構築&サービス起動)
    • 環境変数でサイト名アプリ名を定義し、サイト名のディレクトリが存在しない場合、またはCLEARSITEでアプリを削除している場合、テンプレートを用いてアプリケーション雛形を構築する。
    • Django-admin startupprojectでサイトを構築、settings.pyの必要な部分を順次編集。
    • python manage.py startappで最初のアプリケーションを作成(これを用いてシングルページアプリケーションの雛形とする)
    • アプリケーションの設定部分をsettings.pyに追記編集。bootstrapなどのミドルウェアの設定、LOGGING設定、環境変数読み込み設定。
    • ルート用urls.py、フロント側にデフォルト変数を送る機構context_processors.pyの雛形作成
    • アプリケーション定義用urls.py、views.pyの雛形作成、indexページとajax待ち受けの最低限構成、およびテンプレートHTMLのindex.html、base.html雛形作成。
    • python manage.py migrate :sqliteデータベースの初期化、管理者ユーザーの作成
    • スタティック領域の構成
      • bootstrapはnpmのグローバルエリアからローカルコピー(これは最新版に対応するため常に行う)
      • サイト用カスタムscssのテンプレート作成(再構築時)
      • sass常駐コンパイラの起動(上記scssファイルの更新と同時に自動コンパイル)
      • bootstrap関連その他js、およびtinymce設定
      • DOM構築用javascriptモジュールテンプレート(TSソース)設置、およびTypeScript初期化と設定ファイル初期編集(再構築時)
      • tsc常駐コンパイラ起動

テンプレートに凝りすぎてほぼほぼ自由度が下がってしまっている件。前回のDOM構文もTypescriptに移植して使える状態にして用意してます。細かい解説は折りを見て追記します。

続:ES6とDOMの件

前回はなるべく素のjavascriptでDOMっぽい構文を考えてみようということで、下記のような即時関数を使用して子要素を生成して内容を定義しつつ親要素にアペンドするような形で、文書構造に則したコーディングが出来るのではないかという感じで投稿しました。

((body) => {                            // bodyを引数とした矢印関数①
  body.className='hogehoge';
  body.innerHTML = '';
  body.appendChild(((title) => {        // bodyタグへのappendChildを同様に矢印関数で記述②
    title.innerHtml = "page title";
    return title;
  })(document.createElement('h1')));
  body.appendChild(((content) => {      // 同様に本文ダグも矢印関数で生成と同時にappendChild
    content.innerHtml = "page content";
    return content;
  })(document.createElement('p')));
  return body;                          // ①関数の戻り値(これが無いと結果が空になる)
})(document.querySelector('body'))      // ①関数の引数として body = document.querySelector('body')

結果↓

<body>
<!--
   空のHTML文書
-->
</body>

=>

<body class="hogehoge">
  <h1>page title</h1>
  <p>page content</p>
</body>

慣れてしまえばサクサク書けてしまうが、やっぱり文書構造が大きくなると即時関数の入れ子だらけになって見にくくなってくるのと、即時関数のretrun文を入れ忘れて文書が真っ白になりがちという欠点も見えてくる。

というか、そもそもappendChildなどのメソッドが自身を返してくれればメソッドチェインでもっと便利に使えるのに・・・ということで、あまり大規模にならない程度に下記の拡張メソッドをHTMLElementに追加してみた。

/**
 * ① 自身を引数とする関数を引数に取り、処理を行った後、自身を返す
 */
HTMLElement.prototype.editElement = function(func) {
  func(this);
  return this;
}

/**
 * ② appendChild(文字列引数の場合はappend)処理を行った後、自身を返す
 */
HTMLElement.prototype.appendChain = function(child) {
  if(child instanceof HTMLElement){
    this.appendChild(child);
  } else if (typeof(child) === "string") {
    this.append(child);
  }
  return this;
}

/**
 * ③ 子要素をクリアしたのち自身を返す
 */
HTMLElement.prototype.clearElement = function() {
  this.innerHTML = "";
  return this;
}

これらを定義することで、先の構文は下記の様に書き換えられる(思った以上にスッキリ)

document.querySelector("body")
  .clearElement()
  .editElement(body => body.className = "hogehoge")
  .appendChain(document.createElement("h1").appendChain("page title"))
  .appendChain(document.createElement("p").appendChain("page content"));

良い感じになってきたー。と思ったけど、良く考えたら最近Web製作に関してはTypescriptに移行しつつあり、こんな型の曖昧な仕様を実装するのとても面倒な気がする・・・かといってanyの多用は避けたい。。。ということで次のネタに続く…と。

室内灯自動点灯システム

システムなんて言うほど大した物ではないが、先日室内灯の蛍光灯が切れたのを機会に、LEDシーリングライトに交換した(日立LEC-AH08U)。お安い割に明るさ調整や色も白から電球色まで変えられるです。

付属のリモコンでON/OFF出来るのだが、リモコンが小さく、さらにボタンも小さくて押しにくい感じ、暗い部屋に来ていざ点けようとするとマゴマゴ感が否めない。そこでせっかくのリモコン受信付きの室内灯ということで人感センサーで自動で点灯する仕組みを作ることにした。

計画内容

  • 基本的にリモコンは使用せず、光量や色合い設定の為だけとする。
  • Arduinoなどのワンボードマイコンを使用して学習リモコンの要領でIR LEDを発光させる
  • 人感センサーでONさせるがOFFはしない(トイレの人感センサーライトで散々経験しているが、人がじっとしていると消灯してしまうので)。OFFの仕組みは別に用意する(フォトレジスタに手をかざしてOFFにする仕様)。
  • センサとLEDを仕込んだ適当なケースを3Dプリンタで製作

学習リモコン仕様

  • Arduino Nano(安い互換品)を使用
  • 人感センサー:‎ARCELI00595
  • IR LED:uxcell IRLED
  • IR受光素子:HX1838(リモコンコード取得用)
  • フォトレジスタ uxcell GL5528

右が配線図。受光素子はブレッドボード環境で実験的にリモコンコードを読み込むために使用することとし、実際の装置には組み込まない(装置としての学習機能は無し)

学習リモコン配線図

リモコン信号の学習

Arduinoのライブラリとして、ライブラリマネージャから赤外線リモコンのためのIRremoteをインストールすると、サンプルで付いてくる「ReceiveDump」を使用すると、使用したいリモコン信号を取得することが出来る。Arduino開発環境からシリアルモニタを起動しておくと下記のようにリモコン受信データの詳細がレポートされる。下記の例は今回ターゲットとしているシーリングライトのリモコン信号(ON)の詳細。

Protocol=PULSE_DISTANCE Address=0x0 Command=0x0 Raw-Data=0x3DC2FB 88 bits LSB first

Raw result in internal ticks (50 us) - with leading gap
rawData[180]: 
・・・・・
Raw result in microseconds - with leading gap
rawData[180]: 
・・・・・
Result as internal ticks (50 us) array - compensated with MARK_EXCESS_MICROS=20
uint8_t rawTicks[179] = {65,33, 10,24, 9,7, 9,8, 8,9, 8,9, 8,8, 8,9, 8,9, 8,9, 8,8, 8,9, 8,9, 8,25, 8,9, 8,8, 8,9, 8,9, 8,9, 8,8, 9,8, 9,24, 9,24, 9,8, 9,8, 9,8, 8,8, 9,8, 9,8, 8,25, 9,8, 8,25, 8,9, 8,25, 9,24, 9,24, 9,25, 8,8, 9,24, 9,8, 9,25, 8,8, 9,8, 9,8, 8,9, 8,8, 9,8, 8,9, 8,9, 8,25, 8,25, 8,25, 9,24, 9,24, 9,25, 8,25, 8,25, 9,8, 9,8, 8,25, 8,9, 8,8, 9,8, 9,8, 8,9, 8,25, 8,25, 9,8, 8,25, 9,25, 8,25, 8,25, 8,25, 9,8, 8,25, 9,8, 8,9, 8,9, 8,8, 8,25, 9,25, 8,25, 8,9, 8,25, 8,25, 8,25, 8,25, 9,8, 8,9, 8};  // Protocol=PULSE_DISTANCE Address=0x0 Command=0x0 Raw-Data=0x3DC2FB 88 bits LSB first

Result as microseconds array - compensated with MARK_EXCESS_MICROS=20
uint16_t rawData[179] = {3230,1670, 480,1220, 430,370, 430,420, 380,470, 380,470, 380,420, 380,470, 380,470, 380,470, 380,420, 380,470, 380,470, 380,1270, 380,470, 380,420, 380,470, 380,470, 380,470, 380,420, 430,420, 430,1220, 430,1220, 430,420, 430,420, 430,420, 380,420, 430,420, 430,420, 380,1270, 430,420, 380,1270, 380,470, 380,1270, 430,1220, 430,1220, 430,1270, 380,420, 430,1220, 430,420, 430,1270, 380,420, 430,420, 430,420, 380,470, 380,420, 430,420, 380,470, 380,470, 380,1270, 380,1270, 380,1270, 430,1220, 430,1220, 430,1270, 380,1270, 380,1270, 430,420, 430,420, 380,1270, 380,470, 380,420, 430,420, 430,420, 380,470, 380,1270, 380,1270, 430,420, 380,1270, 430,1270, 380,1270, 380,1270, 380,1270, 430,420, 380,1270, 430,420, 380,470, 380,470, 380,420, 380,1270, 430,1270, 380,1270, 380,470, 380,1270, 380,1270, 380,1270, 380,1270, 430,420, 380,470, 380};  // Protocol=PULSE_DISTANCE Address=0x0 Command=0x0 Raw-Data=0x3DC2FB 88 bits LSB first

uint16_t address = 0x0;
uint16_t command = 0x0;
uint32_t data = 0x3DC2FB;
・・・・・

この例ではプロトコルがPULSE_DISTANCEとなっており、サポートしているメーカーフォーマットでは無いっぽいため、rawDataとして直接送信する必要がある。ターミナルに出力された詳細データのrawDataはそのままCのソースに貼り付けられる形式となっている。

// 初期化
IrSender.begin(IR_SEND_PIN);
// 送信部
uint16_t rawData[179] = {
  3230,1720, 380,1270, 380,470, 380,420, 480,370, 430,420, 430,420,
  430,420, 380,420, 430,420, 430,420, 430,420, 380,470, 380,1270,
  380,470, 380,420, 430,420, 430,420, 430,420, 380,470, 380,420,
  430,1270, 380,1270, 430,420, 380,470, 380,420, 430,420, 430,420,
  430,420, 380,1270, 430,420, 380,1270, 430,420, 430,1220, 430,1270,
  380,1270, 380,1270, 430,420, 380,1270, 430,420, 430,1220, 430,420,
  430,420, 430,420, 380,470, 380,420, 430,420, 430,420, 380,470, 380,1270,
  430,1220, 430,1270, 380,1270, 380,1320, 380,1270, 380,1270, 380,1270,
  430,420, 430,420, 380,1320, 380,420, 380,470, 380,420, 430,420, 430,420じ,
  430,1270, 380,1270, 380,470, 380,1270, 380,1270, 430,1270, 380,1270,
  380,1320, 380,420, 430,1270, 380,420, 430,420, 380,470, 380,470, 380,1270,
  380,1270, 430,1270, 380,470, 380,1270, 380,1270, 380,1270, 430,1270,
  380,420, 430,420, 380
};
IrSender.sendRaw(rawData, sizeof(rawData) / sizeof(rawData[0]), 38);

Arduinoのプログラムでは人感センサーの出力に連動してON信号を送信、フォトレジスタのアナログ入力レベルが一定以下になったとき(手をかざすなど)にOFF信号を送信する仕組みとした。

フローチャート(draw.io)
START
START
初期化
初期化
Yes
Yes
No
No
readmode?
readmode?
受信モード初期化
受信モード初期化
送信モード初期化
送信モード初期化
Loop
Loop
Yes
Yes
No
No
readmode?
readmode?
Yes
Yes
受信データあり
受信データあり
受信データ
ターミナル出力
受信データ ターミナル出力
光センサ
光センサ
On
On
Off
Off
LIGHT Mode
LIGHT Mode
LIGHT OFF CMD
LIGHT Mode Off
LIGHT OFF CMD…
Off
Off
Off
Off
LIGHT Mode
LIGHT Mode
5000ms
5000ms
Yes
Yes
No
No
人感センサ
人感センサ
LED インジケータON
LED インジケータON
1500ms
1500ms
Yes
Yes
人感センサ
人感センサ
LIGHT ON CMD
LIGHT ON CMD
1000ms
1000ms
500ms
500ms
LED インジケータOFF
LED インジケータOFF
100ms
100ms
LIGHT Mode On
LIGHT Mode On
Text is not SVG – cannot display

←ソースコードと簡単な資料はこちら

完成した装置全貌

3Dプリンタでザックリ作ったボックスと内部配線。

ボックスの上部にリモコン発信用赤外線LEDとOFF感知用フォトレジストを埋め込んでます。

リモコンが小さすぎて操作しにくいなら、従来通り壁スイッチで良いじゃん!というのはごもっともなお話ですが、それに気づいたのが一通り完成して運用し始めてからでしたw
あと、最近の半導体不足でArduinoが純正品しか手に入らず比較的高価なため、安価なRaspberryPi Picoに移植するべく開発環境など準備中・・・てか次の投稿ネタに保存中。。

FFmpeg on Docker

久々の投稿

dockerイメージでFFmpegをフルコンパイルしてみました。第1の理由は興味本位ですが、あとSVTAV1のライブラリを使用したいのと、tarボールからコンパイル&インストールして通常使用の環境が汚れるのをある程度防ぎたかったというのもあり、実験環境としてのDockerで行うこととした。副次的な効果として、Windows環境やMac環境、ひいてはRaspberryPi環境のどれか空いてるマシンで動画変換作業をさせることが出来る。

Dockerfileの解説

ベースとするイメージはpython:3.10.3-slim-bullseyeでDebianスリムイメージを利用しました。

pythonのビルドと同様に

  1. 現在のaptパッケージを保存(python環境など)
  2. aptで開発環境をドヤ−−−−っとインストール
  3. debianのライブラリにはfdk-aacがパッケージとして存在しないので、sourceforgeからソースコードをダウンロードしてビルド&インストール
  4. 開発環境が一通り完成したところで、SVT-AV1をgithubからcloneしてビルド&インストール
  5. ライブラリが揃ったので、ひとまずldconfigしてから、目的のffmpegをこれもgithubからcloneしてビルド&インストール
  6. このままではDockerイメージサイズが大変なことになっているので、不必要なパッケージを削除します。
  7. apt-markコマンドを使用して、2番以降でインストールしたパッケージをauto-remove対象にします(1番以前のパッケージは対象としない)。
  8. /usr/local 以下にある実行ファイルに関してlddコマンドを使用して関連するダイナミックライブラリをリストアップ、さらにそれらを含むaptパッケージを検索してリストアップしてauto-remove対象外にする。
  9. aptコマンドを使用しauto-removeで不必要なパッケージをゴッソリ削除
  10. イメージのスリム化を行った所でステージを分けて、Python用の必要パッケージをインストールしてffmpeg-pythonなどが利用できるようにする

イメージのスリム化のための仕組みはベースとしたpython:3.10.3-slim-bullseyeを参考に・・・というか殆どパクりで使用しました。1〜9までを一つのRUN文で行うのがミソ。RUN文で分けた方が見やすいけど、ビルドのステージが分かれてしまうのでステージが変わってからパッケージを削除してもイメージサイズが変わらないというの何とも。。

ビルドファイルの開発中などは複数のステージに分かれていた方がデバッグしやすくて良いが、一通り完成したら一つのRUN文にまとめてしまう感じですかね。

Windows11リモート環境構築メモ

プログラミング等の作業用としては現在はM1 MacBookを使用しており、WindowsデスクトップPCは主にゲームなどエンタメ用に使用している。その為置き場所はリビングとなり、作業場と少しばかり離れていたりする。

それに加えて最近WindowsPCの動作が不安定になってきたため、暫くメンテナンスを繰り返すこととなり。最終的にはOS再インストールという結論。そのついでに今まで放置していた問題点など諸々対策することとした。

デスクトップPCは今時少なくなった自作PCで、筐体に至っては15年以上使っている。OSに至ってはVistaから7にアップグレードした際に一度クリーンインストールして以来、全てアップグレードインストールで、M/B&CPU等のハードウェアも2回ほどそのままアップグレード、ハードディスクの交換やSSDへの換装の際もクローンコピーツールを用いて環境を変えずに運用してきた経緯がある。
M/B買ったときにも店員さんに「クリンインストールした方が良い」と散々言われたのに、動くから〜とそのまま結果オーライで運用してきたので、とうとう破綻が来たのかなーくらいの印象。

Windows11をクリーンインストールする際、同じPC(ハードウェア)であればそのまま認証された。またユーザー設定をする際にマイクロソフトアカウントを使用してしまうと、ユーザーフォルダ名が適当な物にされてしまい後で変更するハメとなった。最初はローカルユーザーでセットアップした方が良いかもしれない。

ツールやゲームなどアプリを一通りインストールし直して環境を再現したところで無事終了。

ここからがリモート環境構築

  • Macからのリモートデスクトップ
    • これはMac用のMicrosoft Remote Desktopがあるので使用
  • Macからssh接続
    • Windows側でOpenSSHサーバーをインストール(設定→機能の追加)
    • Windows側でOpenSSHサーバーを起動する(サービスの管理)
    • SSHデフォルトシェルをPowershellにする
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Program Files\PowerShell\7\pwsh.exe" -PropertyType String -Force
  • リモートで電源ON/OFF
    • 電源OFFはリモートデスクトップもしくはSSHからシャットダウンコマンド(PSTOOLS)
    • 電源ONはWindowsPCのBIOS設定でWakeup On LANを有効にする
    • 電源設定で高速起動をOFF
    • デバイスマネージャでネットワークカードの電源管理項目でmagicpacketでの起動を有効にする。また省電力機能をOFFにする
    • magicpacketを発行するスクリプト(Powershell)を作成し、Macから起動することでWindowsPCの電源が入るのを確認(シャットダウン状態、休止状態、スリープ状態それぞれから起動出来る事を確認)
#! /usr/bin/env pwsh
# Wakeup On Lan

# Unicorn
$hostmac="XX-XX-XX-XX-XX-XX"

$mac_addr=@($hostmac)
$header=[byte[]](@(0xff)*6)

foreach ($item in $mac_addr){
    Write-Host "Send magic packet to : " $item
    $addr=[byte[]]($item.split("-") | %{ [Convert]::ToInt32($_,16) });
    $magicpacket = $header + $addr * 16;
    $target = [System.Net.IPAddress]::Broadcast;
    $client = New-Object System.Net.Sockets.UdpClient;
    $client.Connect($target, 2304);
    $client.Send($magicpacket, $magicpacket.Length) | Out-Null
    $client.Close()
    Write-Host "Send magic paket to : " $item -ForegroundColor Green
}

2013長良川花火大会

花火の写真は毎年うまく行かないので今年は動画で撮ってみることにした。ところが、これが凄いことに結構よく撮れてた。Androidスマホで人力保持での撮影。。。静止画よりもよく撮れるってなんだこりゃって感じ。しかし、所詮スマホってことで、夜景撮影でピントが合わせられない。どこかでピントを合わせて、そのまま目標に向けるのがいいっぽいか。

そんなこんなで、比較的ピントが合ってる動画を編集して貼り付け~っと思ったら。何かおかしい。。。

WordPressのメディアライブラリから貼り付けると、リンクのみでブラウザの再生環境に依存するようだ。

2013nagara-fw1

埋め込もうと思ったら、Youtubeか何かにアップロードしないといけないらしい。

こういうときは、Youtubeにアップするとき「限定公開」とすると良いそうだ。リンクを知っている人以外には存在が分からないので、他サイトへの貼り付け専用動画に出来そう。

ただし、この辺はYoutubeでも但し書きがあって、、いわゆる「きれいなアカウント」でないと、限定公開は出来ないそうです。動画消されたり、著作権関連で怒られたことある人は「きれいなアカウント」ではないそうですw

 

メールサーバー顛末(SMTP-Auth)

今回のメールサーバーの目的は、現在使っているプロバイダ系のEメールをプロバイダからPOPして保存、IMAPサーバーとしてクライアントに公開するという物で、PCとか携帯、タブレットどれからでもメールが読み書き出来るようななるというWEBメールに近い環境が実現できるという物です。家サーバーの時はfetchmailコマンドを使って、プロバイダからPOPで落として来たが、専用サーバーということで、プロバイダからメール転送をかけることにした。これによりタイムラグ無くメールを読むことが出来るようになった(fetchmailだとポーリング間隔だけタイムラグが出来てしまう)。

imap環境は家サーバーのubuntuの時と同様にpostfixとdovecotの設定のみ。旧メールフォルダを家サーバーからtarしてscpしてtarしてVPSに転送、ついでにバックアップ。こちらを参考に取得したSSL証明書などもバッチリ反映させておけばSSLでアクセス可能。

SMTPでメール送信機能を持たせるのは家サーバーではやっていなかった事なので、後回しにします(一発ぶっつけでうまく動かなかった)。

せっかくなので、Webmail機能を持たせようと、SquirrelMailを導入したが、うまく動かない。ubuntuの時は何の問題も無くあっさり動いた物だが、パスワード認証のあと「このページにアクセスするにはアカウントが必要です。」というエラーメッセージが、httpdのログには何も載っていなかったので、不思議と思いググってみた所、どうも /var/lib/php/session の書き込み権限がないとか、、、ああ、思い当たった!家サーバーの時と条件を合わせるため、apacheの実行ユーザーをデフォルトと変えていたのでした。ubuntu と CentOSでは実行ユーザーが異なるため、同じ設定ではアクセスが出来ないということ。ということで、該当ディレクトリの所有者を変更して、書き込み可能にすることで無事解決。

SMTP Authの設定は基本的にはimapの設定の時と同じページを参考に薦めればOKでした。最後の最後までうまく動かず悩ませた理由は単純なtypoでした。。。これは気をつけないといけない。

メールサーバーの機能はこれで必要な分一通り完了。メールアカウント一杯作れます。悪用禁止w