perlでMP4やMOVの縦横サイズを取得する

2025/4/21 [17:44:28] (月) 天気

タイトルのまんまのエントリ。

動機としては、ウチのおひとり様ActivityPubサーバーに動画をアップロードしてみたい。

んで、ActivityPubサーバーは、フォロワーさんのところに投稿(ActivityのJSON)を配送するんだけど、この時に縦横のピクセルサイズが必要っぽい。


動画を扱う時に、鉄板の定番のffmpegでサイズ取得も当然、ほかに解像度変更やファイルサイズの圧縮とかいろいろできるんだけど、レンタルサーバーでffmpegを使えるという話を見たことがなく、当然わたしが利用しているロリポップも使えない。


となると、縦横のピクセルサイズは自力でなんとかする必要がある。

ぐーぐる先生に聞きまくってなんとかなったのでメモ。


「MP4のファイル構造を解説」

https://qiita.com/satken2/items/d14b4113fe3fb5f5597b

↑こちらのサイトで一発解答(多謝!)


MP3とMOVファイルは基本3つのブロックの塊らしい。

サイズタイプ名データ
4バイト4バイト可変

最初の4バイトに入ってるサイズでブロック全体の長さがわかる。

BOXの長さの情報が2^32バイト(=4294967296)を超える場合、最初の4バイトには0x00000001が格納され、その変わりに9バイト~16バイトに実際のBOXの長さが書かれます

ということだけど、今回わたしの場合は10秒もないようなSNSに上げるためだけの動画なのでスルーする。


そのブロックにもろもろの情報が入っているので、欲しいところを取得する。

このブロックのタイプはいったいなんなの?というのは

「QuickTime File Format」

https://developer.apple.com/documentation/quicktime-file-format#//apple_ref/doc/uid/TP40000939-CH204-SW1

↑こちらに全部ある。


ここまでわかればやることはわりと単純。

1)最初の4バイトをみてブロックサイズを取得

2)次の4バイトをみてブロックのタイプを取得

3)必要なタイプだったらそこで情報を取得して終了

 必要なタイプじゃなかったら次のブロックをみにいく


ちょっとハマったのが

各ブロックはデータを持たないものと持ってるものがあるということ。

しょうがないんで、次のブロックを先読みしてタイプ名があるかないかで判定。


以下で欲しい情報が取れた。

わたしが欲しいのはタイプ「tkhd」(たぶん、トラックヘッダーの略)のブロックのデータ部に入ってる幅と高さ。


my @parent = (
’cmov’, ’ctts’, ’edts’, ’esds’,
’free’, ’ftyp’, ’iods’, ’junk’,
’mdia’, ’minf’, ’moov’, ’mvhd’,
’pict’, ’pnot’, ’rmda’, ’rmra’,
’skip’, ’stbl’, ’trak’, ’uuid’,
’wide’);
my @child = (
’fiel’, ’mdat’, ’rdrf’, ’rmcd’,
’rmcs’, ’rmdr’, ’rmqu’, ’rmvc’,
’wfex’, ’cmvd’, ’co64’, ’dcom’,
’elst’, ’gmhd’, ’hdlr’, ’mdhd’,
’smhd’, ’stco’, ’stsc’, ’stsd’,
’stss’, ’stsz’, ’stts’, ’tkhd’,
’vmhd’);

open(IMG, $file) || die;
binmode IMG;
my $len; my $type; my $data;
my $width; my $height;
while(1){
    my $buf;
    read(IMG, $buf, 4);
    $len = unpack("N", $buf);
    read(IMG, $type, 4);
    last if ! $type;
    # check next type
    my $has_data;
    seek(IMG,4, 1) || last;
    read(IMG,$buf,4);
    last if ! $buf;
    $buf = quotemeta $buf;
    unless( grep(m!$buf!, @parent) || grep(m!$buf!, @child) ){ 
        $has_data = 1; 
    }
    seek(IMG, -8, 1);
    if( $has_data ){
        $len -= 8;
        last if $len < 0;
        my $begin = tell(IMG);
        read(IMG, $data, $len);
    }
    if($type =~ m!tkhd!){
        my @check =  unpack("x76 n x2 n", $data);
        if( $check[0] && $check[1] ){
            $width = $check[0]; $height = $check[1];
            last;
        }
    }
}


perlはやっぱり凄くて、バイナリファイルも簡単に扱うことができる。

とはいえ、それを扱うスキルがない人間のせいでずいぶん時間がかかってしまった。


これでうちのActivityPubサーバーに動画をあげるための下準備はできた!

…と思ったんだけど、動画ファイルって5秒ぐらいのものでも1M〜2Mもあって慌てた(今さら

わたしはロリポップのビギナーコース。そんなでかいファイルを気楽に上げてたらあっという間に利用できるディスクスペースを食い尽くしてしまう。


なもんで、ここまで作ったけど諦め。きっとそのうち何かの約に立つ、こともある、かな。


image


今の時期、まじでタケノコ美味くて悶絶する。

刺身で美味いのはもちろん、焼きタケノコがもう絶品。

水分を飛ばすイメージでじっくり弱火のフライパン。水分が飛んできたかなと思ったらオリーブオイルを回しかけてタケノコに焼きめをつけて、塩をふたふり。

これだけでいくらでもご飯が食えるし酒が飲める。

マジでオススメ!!!

おひとり様ActivityPubサーバーの詳細

2025/4/3 [11:04:38] (木) 天気

先月、ウチのおひとり様ActivityPubサーバーの全体像、実際やってることはホームページに個別ページを作って掲載した。

あらためて、こっちのブログでもその告知。


ブログとホームページの棲み分けというか、以前、SNSに投げた通り

1)SNSで思いつきをちらほらメモ

2)ブログで自分のSNSを見ながら動機や経緯を整理

3)1と2を経て、ホームページに完結した個別ページとして掲載


各Activityを実装するにあたってのブログのエントリとそれを完結させた個別ページはあるけど、サーバー構成について完結させた個別ページはあるけどブログのエントリはなかった。


上記の棲み分けでいうと、2)→3)ではなく3)→2)が今回のエントリ。


おひとり様ActivityPubサーバーの構成::On Golden Pond

https://www.doncha.net/activitypub/activitypub013.html

↑ホームページ掲載はこちら


大雑把にやってることは

・htaccessでリクエストのリダイレクト設定

・リダイレクトされたリクエストをスクリプト1で処理

・ActivityPubの仕様通りにスクリプト2で対応


ウチの構成を丸裸…こういうページを作ることで、自分でも未確認だったり再確認することがいろいろ出てきてびっくり。

昔からよく言われるように「ひとに教えるのは自分に教えることになる」というやつか。


自作実装したのは「自分のわかる範囲で片付ける」ため。

仮にMastodonなど定評のあるActivityPubサーバーソフトウェアをめちゃくちゃ頑張って導入/設定したとしても、何か問題が出た時に、自分のスキル/知識では解決できるとは思えない。管理運用するサーバーの種類も多いし、それらを操作する言語もひとつじゃ済まないし。

おひとり様でサーバーを立ててFediverseに参加できても、すぐに退場することになるんじゃ困る・面白くない。


てことで

・ドメイン取得済み

・レンタルサーバー契約済み

・perlならどうにか手癖で書ける

・apacheなら少しわかる

・データベースも少しはわかる

・htmlとcssは手書きできる

という現状環境と自分のスキル/知識の範囲内。


ActivityPubの仕様、というか決められてる「約束事」は最低限のリクエストのやりとりだけだし、上記程度のスキルで対応できる。

仕様を実装した上で、気をつけたのはひと様の投稿の扱いだけで、それ以外はいろいろがっつり省いた。


だいたい、フロント側はアクセスしたらタイムラインを表示するだけ。もちろん、ページ更新は手動で再読み込みというチープな作りだ。

それでも十分面白いし、自分のタイムラインにどっぷりハマってる。


現状環境、スキル/知識の範囲内でいうとphpやrubyを使えるひとが多いだろうし、phpやrubyでwebというひとはデータベースも使えるだろう。ドメインやレンタルサーバーなんかは外食2回分ぐらい。


やってみると、思っていたよりもFediverse参加のハードルは低い。

おひとり様サーバーを作って参加してみませんか!?


image

お散歩デジカメ日記2025年3月号

2025/3/31 [13:58:43] (月) 天気

個人ホームページのお散歩デジカメ日記に月刊デジカメ徘徊老人日記3月号追加した。


今年はウォーキングを頑張るのがひとつの目標。

還暦すぎると健康第一。その健康維持とボケ防止のため。年初から3ヶ月続いていて、ウォーキングがほぼ習慣化してる。デジカメでパシャパシャ撮るものを探して歩いて、同じコースでもちょっとした発見があるので飽きない。


そうして撮りためた写真をホームページに掲載する、という成果物を得られるんで、一石二鳥。


お散歩デジカメ日記::On Golden Pond

https://www.doncha.net/toydigi.html


1月2月は撮ったものを、なんの加工もせずそのまんま。

今月、3月は縦横比こそそのままだけど、少しトリミングをしてみた。

KODAK PIXPRO FZ45はデフォルトのワイドの画角は50mm換算27mmとかなり広い。ズームすればいいんだろうけど、ぱっと撮りたい時にいちいちズームして〜とかやってられない。

そもそもウォーキングの記録、みたいなもんだしあまりこだわってもなあ、と。


とはいえ、ホームページに掲載するにあたって、写真を選んでると、ここもっと寄りたいよなあ、こっちに詰めてこっちは広がりを残したよなあ、とか欲が出てくる。


色味の調整とかフォトショでやりだすとキリがない。そもそもFZ45で写真の保存形式はjpgなのでそれをいじると劣化することになるし。


ただ、構図については、少しいじってみようと、今回はいくつかの写真でやってみた。

昔、新卒で入った底辺エロ出版社で写真集のために写真のトリミングはさんざんやってたことを思い出す。見せたいものを見せたいように見せるのも編集者の仕事、ということだったなあ。


まあ、還暦すぎの趣味、続かないと意味がないんで気楽にやっていこう。


image


個人ホームページ 『On Golden Pond』 はオレオレMovableTypeが生成する静的ページ。それってのは、表示速度最優先のため。

なので、1ページに表示させる写真は1枚限定。

ページ遷移してもらう=クリックしてもらう、というのはかなりコストが高くてほとんどのひとはクリックしないもの、というのは理解してるけど、それでも、それ以上に表示速度はだいじだ。

ActivityPubサーバーのバグ修正メモ

2025/3/29 [10:02:19] (土) 天気

ここんとこ時間もあるので、おひとり様サーバーの機能追加やバグ修正。

忘れないようにメモしておこう。


・仏語アクサンや独語ウムラウトまじりの投稿で文字化けを起こしてJSONのdecodeで失敗していた。


JSON.pmは文字コードというかutf8フラグの扱いにクセがあって。

decode_jsonに渡すJSONはutf8フラグがついていてはいけない/事前にutf8フラグを外す必要がある。


なので、事前にEncode::is_utf8でutf8フラグの有無を調べて、utf8フラグがついてたらEncode::encode(’utf8’,JSON)などと、utf8フラグを外して渡してた。


アクサンやウムラウトは見た目1文字

「â」「ë」

だけど、コードポイントは2バイト使う(日本語などは3バイト)ので、スクリプトではutf8フラグをつけて扱う(検索などにそのまま使えるから)

これをEncode::is_utf8でうまく検知できてなかったっぽい…「ぽい」というのは、まだしっかり特定できてないから。

evalでdecode_jsonを括って$@でエラーを捕捉してるところ、エラーが出たらもう一度同じ処理を入れることにした。原因追求をさぼって結果オーライ、というのは昔からの得意技(技?



・お相手サーバーの生存確認を追加した。


応答の確認に時間がかかるサーバーがあった。セッション切れを起こしてしまうのは致命的。


「LWPでtimeout指定が効かない」

https://t2aki.doncha.net/?id=1731205503

↑この対応で解決したはずなのに、このtimeoutの処理が効いていないサーバーがあった。

同じ500番代なのになんでだろ…。


てことで、投稿を配送する前にお相手サーバーが応答してるかどうかの確認することにした。

lwpでリクエストを投げてもたぶん同じことだろうし、Net::Pingを使うことにした。

今のところ意図通りに捕捉できてる。



・アナウンスした時にフォロワーさん以外に通知が飛ばない。


Activityの配送先は自分のフォロワーさん。なので「自分のフォロワーさんじゃないアカウント」の投稿をAnnounce(ブースト、リポスト、リノート)したら、そのアカウントにも配送する必要がある、ということをすっかり忘れてた。


AnnounceするActivityの投稿者をフォロワーリストで確認。

フォロワーリストに入ってなかったら、配送先に追加。



細々というか、2年近く使ってるというのに、いろいろ出てくるもんだ。


image

けっこうなスピードで疾走感があった。楽しそうでいいよねえ。

自作ActivityPubサーバーにリプライを実装

2025/3/16 [13:44:34] (日) 天気

当初からリプライを実装するつもりはなかったんだけど、今日とりあえずリプライを実装した。


どうしてリプライを実装しなかったのか。

タイムラインの投稿の返信(リプライ)ボタンをポチッとクリックして、画面の向こうのたぶん面識のないひとに安直に話しかけることの距離感があやういから(わたしの場合)


返信するケースというのは、ほとんどの場合がマウント合戦だと思っていて、

「それ、おれ/わたしも知ってるー!」とか

「おれ/わたしはもうやってきたことだ」とか

「おれ/わたしの方が詳しい/当事者だよ!」とか

そこまでの関係性のない、あかの他人に話しかける動機というのはやっぱり、親近感とか共感だけではなくて、なにかが少し混じってビミョーに違ってると思う。


なので、SNSでやらかすのはこんな局面だろうと思ってるし、わたしもやらかしの返信をしてきたこともあり、リプライは危ないから実装をしない、とActivityPubサーバーを作り始めた時点で決めていた。


なんだけど。

Mentionをいただいて返事をする時に、ウチはリプライに未対応なのでリプライ要素のないJSONを返していた。投稿に対するリプライ要素があればツリー表示となるけど、リプライ要素がないJSONだと単品のMentionとして表示されるだけ(だと思う)


お相手にしてみると「あれ?なんだっけ?これ」ということになるだろうしなあ。

てことで、リプライ要素を付与したActivity(JSON)を返すように実装した。


ただし、返信(リプライ)ボタンを表示するのはMentionをいただいた投稿限定。

ホームタイムラインに流す投稿には表示しない。(わたしの場合)やらかす危険しかないから。


昨日実装した「転送」と今日実装した「リプライ」について、別サーバーの別アカを使って確認したところ、「意図通り」反映してるっぽい。

転送もリプライも、いろいろちょっと危ういんで、様子を見てながら運用する、ということで。


昨日、今日と2日続けてActivityPubサーバーをいじっててしみじみ。

やっぱ、ActivityPubも、それを実装するperlもめっちゃ面白い!


還暦過ぎの趣味、ボケ防止の趣味としては文句のつけようがないよなあ。


image

通り抜けた向こう側に光がある、という絵面。定番だし、やっぱ好きだわ。

<<2026/2>>
       
1234567
891011121314
15161718192021
22232425262728
検索:

【最近の20件】