投稿のタイトルをそのまま動画の保存名にすると、スラッシュや制御文字、長すぎる名前が混ざります。今回は表示用タイトルから、短い ASCII のファイル名を作る関数を実装します。任意の入力をファイルシステムのパスとして使わず、アプリが決めた形式に落とす方針です。
私は Twitter Viewer を開発しています。公開ポストの MP4 を保存する twitter video downloader のような道具では、投稿に由来する文字列とファイル名を分けて考える必要があります。以下は独立したサンプルで、本番実装の転載ではありません。
保存名に必要な情報を限定する
ファイル名には、短い説明と投稿 ID があれば区別できます。投稿全文、閲覧者名、取得用 URL のクエリなどを詰め込む必要はありません。特に署名付き URL は一時的な認証情報を含む可能性があるため、名前や公開ログへコピーしません。
この例では、日本語を無理にローマ字へ変換しません。ASCII の説明が作れない場合は video に戻し、ID で識別します。これは多言語表示の一般解ではなく、互換性を優先した限定的な保存名の規則です。
export function mp4Filename(title, postId) {
if (!/^[0-9]{1,20}$/.test(postId)) {
throw new Error('postId must be a decimal string');
}
const label = String(title).normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 48)
.replace(/-+$/g, '') || 'video';
return `x-video-${label}-${postId}.mp4`;
}
ID は数値に変換しません。大きな整数を JavaScript の Number にすると、正確な桁を保持できない場合があります。末尾へ数字を付けるだけでも、変換の段階で値を失っていたら区別に使えません。
先頭を固定の x-video- にしたため、入力が CON のような予約名でも出力全体はその予約名になりません。拡張子も入力から受け取らず、MP4 用の関数として固定します。この関数で名前を変更しても、ファイルの内容が MP4 に変換されるわけではありません。
危険な文字列と空の説明をテストする
関数を filename.mjs に保存し、次のテストを filename.test.mjs に保存します。
import test from 'node:test';
import assert from 'node:assert/strict';
import { mp4Filename } from './filename.mjs';
test('入力をパスとして残さない', () => {
assert.equal(mp4Filename('../../CON\n demo', '123'),
'x-video-con-demo-123.mp4');
});
test('空や非ASCIIの説明でもIDを残す', () => {
assert.equal(mp4Filename('動画', '18446744073709551615'),
'x-video-video-18446744073709551615.mp4');
});
test('出力の長さと文字集合を限定する', () => {
const name = mp4Filename('a'.repeat(1000), '123');
assert.ok(name.length < 90);
assert.match(name, /^x-video-[a-z0-9-]+-[0-9]+\.mp4$/);
assert.throws(() => mp4Filename('demo', '../123'));
});
node --test filename.test.mjs で実行できます。実ファイルを作らないテストなので、OS のフォルダー構成や権限とは独立して名前の規則を確認できます。
HTTPヘッダーとパスの安全性は別に確認する
ブラウザーへ Content-Disposition を返すサーバーでは、ファイル名をヘッダーに埋め込む処理も必要です。任意の投稿タイトルを文字列連結でヘッダーへ入れる方法は避けます。この関数の出力は限定した文字だけですが、ヘッダー生成にはフレームワークの既存機能を使い、レスポンスをテストします。
サーバー側へ保存する場合は、保存先ディレクトリをアプリが決め、同名ファイルを上書きするかどうかも別に定義します。ファイル名の清掃だけで、競合やシンボリックリンク、アクセス権限まで解決したことにはなりません。同じ投稿の別解像度を保存する設計なら、アプリが管理する variant ID を追加するなど、重複の規則を先に決めます。