raspberry pi picoみたいな限られたリソースで動画を再生したいなら
動画を小さく圧縮してみよう
大まかな流れ
main.py
動画ファイルを読み込んで各フレームを240*135のビットマップとして保存
↓
main.cpp(dxt1_encorder.exe)
ビットマップを圧縮(dxt1)してバイナリファイルとして出力
↓
main.py
全フレームのバイナリ情報を1つのバイナリファイルに統合
※ DXT1形式は4×4ピクセル単位で圧縮するため、画像サイズは必ず4の倍数にする必要があります。
僕の手持ちのLCDが 240×135だったため、240×136にリサイズし、最後の1行を描画時に捨てています。
ソース
フォルダ構成、必要なファイルはこんな感じ

自分で用意するファイルは、
main.py, main.cpp, dxt1_encoder.exeだけ
でもdxt1_encoder.exeはmain.cppをビルドして作るから
実質main.pyとmain.cppの2つだけでOK
※ dxt1_encoder.exe は main.cpp を g++ や clang などのC++コンパイラでビルドして作成します(Windows環境ならMSYS2やMinGWがおすすめ)。
各フォルダは以下の役割
- all_frames_bin
最終的な成果物を入れるフォルダ - frames_temp
1フレームずつに分けたビットマップとそれを圧縮したファイルを入れるフォルダ - source_videos
圧縮したい動画ファイルを入れておくフォルダ
main.py
必要なpythonのモジュールをインストール
pip install "imageio[ffmpeg,pyav]" pillow
import imageio.v3 as iio
from PIL import Image
import os
import subprocess
frames_temp_dir = "frames_temp"
os.makedirs(frames_temp_dir, exist_ok=True)
source_videos_dir = "source_videos"
anime_titles = os.listdir(source_videos_dir)
output_dir = "all_frames_bin"
for title in anime_titles:
title_path = os.path.join(source_videos_dir, title)
if os.path.isdir(title_path):
video_files = [f for f in os.listdir(title_path) if f.endswith(".mp4")]
for video_file in video_files:
base_name = os.path.splitext(video_file)[0]
video_path = os.path.join(title_path, video_file)
reader = iio.imiter(video_path, plugin="pyav")
frame_count = 0
# 動画の1フレームずつ読み込み
for i, frame in enumerate(reader):
img = Image.fromarray(frame).resize((240, 136)).convert("RGB")
bmp_path = os.path.join(frames_temp_dir, f"{title}_{base_name}_frame{i:04d}.bmp")
dxt1_path = os.path.join(frames_temp_dir, f"{title}_{base_name}_frame{i:04d}.dxt1")
# iフレーム目をビットマップ形式で保存
img.save(bmp_path)
# ビットマップをDXT1形式に変換
subprocess.run(["./dxt1_encoder.exe", bmp_path, dxt1_path], check=True)
frame_count += 1
# 動画別にbinファイルを作成
output_bin_path = os.path.join(output_dir, f"{title}_{base_name}_allframes_dxt1.bin")
# 1つのbinファイルに全フレームのDXT1データを書き足していく
with open(output_bin_path, "wb") as out:
for i in range(frame_count):
dxt1_path = os.path.join(frames_temp_dir, f"{title}_{base_name}_frame{i:04d}.dxt1")
with open(dxt1_path, "rb") as f:
out.write(f.read())
# フレームファイルの削除
for i in range(frame_count):
os.remove(os.path.join(frames_temp_dir, f"{title}_{base_name}_frame{i:04d}.bmp"))
os.remove(os.path.join(frames_temp_dir, f"{title}_{base_name}_frame{i:04d}.dxt1"))
コメントを多めに書いたから
なんとなく処理の流れはわかりやすいと思う
main.cpp(dxt1_encorder.exe)
// main.cpp
// 使用: ./dxt1_encoder input.bmp output.dxt1
#define STB_IMAGE_IMPLEMENTATION
#define STB_DXT_IMPLEMENTATION
#include "stb_image.h"
#include "stb_dxt.h"
#include <fstream>
#include <cstdint>
#include <iostream>
int main(int argc, char** argv) {
std::cout << "Arguments: " << argv[1] << " " << argv[2] << std::endl; // デバッグ用
int w, h, c;
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " input.bmp output.dxt1\n";
return 1;
}
// 3チャンネルでRGBとして読み込み(BGRが来ることに注意)
unsigned char* img = stbi_load(argv[1], &w, &h, &c, 3);
if (!img) {
std::cerr << "Failed to load image: " << argv[1] << std::endl;
return 1;
}
std::ofstream out(argv[2], std::ios::binary);
for (int y = 0; y < h; y += 4) {
for (int x = 0; x < w; x += 4) {
unsigned char block[64];
for (int dy = 0; dy < 4; dy++) {
for (int dx = 0; dx < 4; dx++) {
int px = (x + dx < w) ? x + dx : w - 1;
int py = (y + dy < h) ? y + dy : h - 1;
block[(dy * 4 + dx) * 4 + 0] = img[(py * w + px) * 3 + 0]; // B
block[(dy * 4 + dx) * 4 + 1] = img[(py * w + px) * 3 + 1]; // G
block[(dy * 4 + dx) * 4 + 2] = img[(py * w + px) * 3 + 2]; // R
block[(dy * 4 + dx) * 4 + 3] = 255; // A
}
}
uint8_t dxt1[8];
stb_compress_dxt_block(dxt1, block, 0, STB_DXT_HIGHQUAL);
out.write((char*)dxt1, 8);
}
}
out.close();
stbi_image_free(img);
return 0;
}
コンパイルしてexeファイルにしてmain.pyと同じ階層に置いてね。
それ以外のファイル

libgcc_s_seh-1.dllとかstd_dxt.hとかはインターネットで探せばあるよ
一応リンクを
- stb_image.h
https://github.com/nothings/stb/blob/master/stb_image.h - stb_dxt.h
https://github.com/nothings/stb/blob/master/stb_dxt.h - libgcc_s_seh-1.dll, libstdc++-6.dll, libwinpthread-1.dll
↓このページを参考にしたら手に入るかな??
windows10でのC++のプログラミング環境設定(mingw-w64+VSCode) – yokobuttonの不定期で競技プログラミングをするブログ
結果
デコードしたものがこれ

ちっちぇ~