この記事は「 PostgreSQL Advent Calendar 2025 」の18日目です。
TL; DR
メリークリスマス!!!
日々データベースと格闘し、走される皆さん!お元気ですか!
たまにはデータベースに普段しないことをさせてみるのもいいじゃない?ってことで、
簡単なクリスマスムービーを流してもらいました。
こんな感じ
使ったもの一覧
- AlmaLinux9(VirtualBox+Vagrant)
- PostgreSQL17.7
- plpython
- mpg123
- alsa-util
音声協力
音源はオトロジック(CC BY 4.0)様からお借りしました。
ムービーの実装
ムービーの実装は先人の知恵を大いにお借りしました。
具体的には、以下のようなテキストファイルを用意して、plpgsqlプロシージャで
1画面22行ごとにSELECTして、RAISEから標準出力。これをループするという方法です。
表定義
postgres=# \d+ joke
Table "public.joke"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
--------+---------+-----------+----------+----------------------------------+----------+-------------+--------------+-------------
id | integer | | not null | generated by default as identity | plain | | |
frame | text | | | | extended | | |
Indexes:
"joke_pkey" PRIMARY KEY, btree (id)
Access method: heap
※id列は正直おまけです。LIMIT/OFFSETでSELECT条件を書くので、frame列だけでOKです。が、テキストファイル(AA)で記述すると、特殊記号が悪さするので、デバッグ用途で入れてます。
テキスト例
+ ・ + + ・ + + ・ +
゚。:。゚ + ゚。:。゚ + ゚。:。゚ +
* ・‥☆‥・ * ・‥☆‥・ * ・‥☆‥・
+ 。゚▲゚。 * + 。゚▲゚。 * + 。゚▲゚。 *
+ ▲▲ + ▲▲ + ▲▲
▲▲☆ + ▲▲☆ + ▲▲☆ +
* ▲☆彡▲ * * ▲☆彡▲ * * ▲☆彡▲ *
十 ☆彡▲▲☆ 十 ☆彡▲▲☆ 十 ☆彡▲▲☆
彡▲▲☆∴▲ 十 彡▲▲☆∴▲ 十 彡▲▲☆∴▲ 十
▲▲☆∴▲▲☆ ▲▲☆∴▲▲☆ ▲▲☆∴▲▲☆
▲☆∴▲▲☆彡▲ ▲☆∴▲▲☆彡▲ ▲☆∴▲▲☆彡▲
`☆∴▲▲☆彡▲▲☆ `☆∴▲▲☆彡▲▲☆ `☆∴▲▲☆彡▲▲☆
▲▲▲☆彡▲▲☆∴▲ ▲▲▲☆彡▲▲☆∴▲ ▲▲▲☆彡▲▲☆∴▲
__|_|__ __|_|__ __|_|__
∥∥∥∥∥∥ ∥∥∥∥∥∥ ∥∥∥∥∥∥
 ̄ ̄ ̄ ̄ ̄  ̄ ̄ ̄ ̄ ̄  ̄ ̄ ̄ ̄ ̄
∩ ∩
い_cノ / ̄>O
`c/・ ・っ (ニニニ)△△
("●" ) (・ω・`)[∥]
O┳Oノ)=[ ̄てノ ̄ ̄]
◎┻し◎ ◎――◎=3
+ ・ + + ・ + + ・ +
゚。:。゚ + ゚。:。゚ + ゚。:。゚ +
* ・‥☆‥・ * ・‥☆‥・ * ・‥☆‥・
+ 。゚▲゚。 * + 。゚▲゚。 * + 。゚▲゚。 *
+ ▲▲ + ▲▲ + ▲▲
▲▲☆ + ▲▲☆ + ▲▲☆ +
* ▲☆彡▲ * * ▲☆彡▲ * * ▲☆彡▲ *
十 ☆彡▲▲☆ 十 ☆彡▲▲☆ 十 ☆彡▲▲☆
彡▲▲☆∴▲ 十 彡▲▲☆∴▲ 十 彡▲▲☆∴▲ 十
▲▲☆∴▲▲☆ ▲▲☆∴▲▲☆ ▲▲☆∴▲▲☆
▲☆∴▲▲☆彡▲ ▲☆∴▲▲☆彡▲ ▲☆∴▲▲☆彡▲
`☆∴▲▲☆彡▲▲☆ `☆∴▲▲☆彡▲▲☆ `☆∴▲▲☆彡▲▲☆
▲▲▲☆彡▲▲☆∴▲ ▲▲▲☆彡▲▲☆∴▲ ▲▲▲☆彡▲▲☆∴▲
__|_|__ __|_|__ __|_|__
∥∥∥∥∥∥ ∥∥∥∥∥∥ ∥∥∥∥∥∥
 ̄ ̄ ̄ ̄ ̄  ̄ ̄ ̄ ̄ ̄  ̄ ̄ ̄ ̄ ̄
∩ ∩
い_cノ / ̄>O
`c/・ ・っ (ニニニ)△△
("●" ) (・ω・`)[∥]=+
O┳Oノ)=[ ̄てノ ̄ ̄]
◎┻し◎ ◎――◎=3
・・・以下、繰り返し(今回は全部で858行のテキストファイルになりました)
プロシージャの定義
CREATE OR REPLACE PROCEDURE play_movie()
AS $$
DECLARE
var_start integer :=0;
var_cnt integer;
var_end integer;
var_height integer :=22;
var_out text;
var_interval integer :=1;
scr_i integer := 0;
BEGIN
SELECT count(*) INTO var_cnt FROM joke;
var_end:= var_cnt/22 -1 ;
PERFORM '\pset format unaligned';
FOR var_i IN var_start..var_end LOOP
-- fetch data
SELECT array_to_string(array(SELECT frame FROM joke LIMIT var_height OFFSET scr_i),CHR(10)) INTO var_out;
-- clear display and write frame
RAISE INFO '%', E'\x\033[H\033[2J' || var_out || chr(10);
-- wait
PERFORM pg_sleep(var_interval);
scr_i := scr_i + var_height;
END LOOP;
PERFORM '\pset format aligned';
END;
$$ LANGUAGE plpgsql;
BGMの実装
plpythonを使ったファンクションを作成し、関数内でsubprocess.Popenを使ってOSコマンドであるmpg123経由で音声ファイルを鳴らします。なお、今回はmp3ファイルのためmpg123を使いますが、音声ファイルの形式に合わせてOSのツールを選んでください。
また、VirtualBoxを利用する場合は、仮想マシンの設定のところであらかじめオーディオ設定を正しくいじっておく必要があります。
OS上の音声カードの確認
$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: Intel [HDA Intel], device 0: STAC9221 A1 Analog [STAC9221 A1 Analog] Green Star 今回はコイツ
Subdevices: 1/1
Subdevice #0: subdevice #0
card 0: Intel [HDA Intel], device 1: STAC9221 A1 Digital [STAC9221 A1 Digital]
Subdevices: 1/1
Subdevice #0: subdevice #0
plpythonの有効化
$ dnf -y install plostgresql17-plpython3-17.7-1PGDG.rhel9.x86_64.rpm
postgres=# CREATE EXTENSION plpython3u;
CREATE EXTENSION
ファンクションの実装
CREATE OR REPLACE FUNCTION play_mp3_direct(file_path text)
RETURNS text
AS $$
import subprocess
import os
try:
subprocess.Popen(['mpg123', '-a', 'hw:0,0', '-q', file_path]) --# ここでデバイスを指定
return f"Attempting to play on hw:0,0: {file_path}"
except Exception as e:
return str(e)
$$ LANGUAGE plpython3u;
ここまで出来たら、あとは各プログラムをpsqlで呼ぶだけです。
良いクリスマスを!
