ふぁメモ

主に技術系のメモをしたいけどやっぱり適当日記。たまにPHPコード載ってるけどメモ書き程度のスクリプトなのでそのまま使っちゃダメ。

内輪公開用3Dモデルアップローダーを作ろうとして頓挫した話

LiDAR搭載のiPhone13Proを手に入れた

昨年末のことであるが、iPhone11ProMax(256GB)のストレージが枯渇していたため、iPhone13ProMax(512GB)を購入した。 新しく3Dスキャナ(LiDAR)というオモチャを手に入れたのだ。

各種3Dスキャンアプリを試し、サブスクリプション課金のあるアプリを避けると残るのはまあ無難にScaniverseである。 身の回りのどうでもいい小物や建物、散らかった部屋などをスキャンして遊んでいる。

細かいところが潰れてしまうので精度はイマイチであるが、通常では難しい角度から写真が撮れるという観点では非常に面白いオモチャである。

3Dスキャンモデルの共有

で、当然作成したモデルを誰かに見せたくなるわけだ。が。これが案外敷居が高い、ような気がしている。

Scaniverseから直接アップロードできるSketchfabがメジャーどころであり機能が充実しており申し分ないのだが、限定公開は有料なのだ。

適当に撮った私的な3Dスキャンでありクォリティも微妙なものであり近所や自分の家や職場のモデルなんて恥ずかしくて全世界OPENな場所に公開したくはない。 URLを知っている人だけ閲覧できるアップローダーに置いて知り合いに見せて完結、そんなレベルのシェア機能でいいのだが、Sketchfabの限定公開機能はモデルを売買する際のプレビューに使うようなビジネス用途を想定した機能なのでやけに高額なのである。

STYLYとやらも同様に限定公開は有料、DOORは無料ではあるが3Dモデルを配置する作業がなかなか面倒である。 PolyというGoogleがやっていたらしいサービスは終了してしまった。 3DCGARTSみたいなクリエイティブなところにスキャンデータを上げるのは違う気がする。

え、先人たちはどこでモデルを公開しているの?、プライベートとか気にせずSketchfabなの???

もちろんモデルデータをそのままGoogleドライブとかDropboxにアップロードして知り合いに配布して各自でモデルビューアで見てもらえばそれで済む話ではある。ブラウザで見れるタイプのビューアーもある。 でもできればURLを渡したらそのままブラウザで手軽にモデルを見てもらいたい。

ないものは自分で作ろう

ちょっと調べると3DモデルをHTMLに埋め込むのに便利なライブラリはmodel-viewerとかa-frameとかThree.jsなどがあって、これらを利用すれば比較的簡単に3DモデルのWeb埋め込みは実現できるみたい。 じゃあアップローダと組み合わせて埋め込んで表示するサイトを自分で作ってみよう、と思い立った。

作ってみた

想定外の利用をされては困るのでメールアドレスが必須だったりなぜか有名どころの無料転送アドレスが使えなかったりするのは実験的な意味もある。

アップローダとしてはさておき。

実際にやってみるとなかなかうまくいかないものである。

問題点

ブラウザが落ちちゃう

ルームレベルでスキャンしたモデルは大きなものになりがち。Scaniverseが自動的に小さくしてくれてはいるが、そのままブラウザで表示させようとするとだいたいメモリ不足でブラウザが落ちる。落ちなくても光があたらなくなってモデルが真っ黒になる。視点を変えようとしても動かなかったりする。快適な閲覧環境を提供できているとはとても言えない。

何かサーバー側でモデルを小さくする方法はないか探ってみたが、圧縮方法を変えることができても大して効果はなかった。

Scaniverseが保存用の精密モデルと共有用の軽量モデルを両方生成できるようにしてくれればいいのだけど今のところそういった機能はないみたい。

Metascanではできたけどサブスクリプション課金が結構高いので微妙である。

サムネを作ることができない

自分で用意しなくても自動的にサムネを作りたいなと思っていたのだが、実現方法がない。assimpとかいうのでできるかと思ったができなかった。

ブラウザ上でモデルを表示してスクリーンショットを撮ることはできたのでblobをアップロードする手順にすればできないことはなさそうな気がしたけど公開手順が煩雑になるので諦めた。

PCとスマホで体験が異なる

同じライブラリを使っているのにスマホで見ると自動的にスマホ用のビューアになってしまってPCブラウザで使えるように用意したプラグインの意味がなくなる。

ジャイロを使うと許諾画面が出てきてユーザーに優しくない。初見殺し。

ジャイロを使わないと上下の視点移動ができない。でも時には視点移動できないほうが動きやすい場合もある。

カメラ操作が微妙

Scaniverseのビューアはよくできている。見たい場所をダブルタップするとぐいーんと入り込める。これをWebビューアでも実現したい。

基本的に3Dモデルのカメラの設定は予めビューポイントを決めておくものが多く、あとはWASDで上下左右に動かせるとか、スマホの加速度センサを使って見渡せるといったものが多い。 JavaScriptで如何様にもカスタマイズできるようなのだが、そんなスキルはない。 きっとUnityが使える環境ならば壁や床を認識させて無理のない動きで視点移動ができるんじゃないかと思うのだけど、JavaScriptでうまいことやってくれるライブラリを見つけることができなかった。

ダブルタップするとちょっと動くようにできたけど思い通りには動かない。

WASDはキーが足りない。マウスと組み合わせるのが前提なのだろうけどせめてX軸Y軸Z軸で6キー用意しておいてほしい。

タップしてると前に進むUIは導入できたけどカメラを動かすだけでも前に進んでしまうので操作感はいまいち。

上に視点移動するにはまず上を向いてからタップすることでなんとか移動できるけどわかりにくい。

画面にコントローラ的な操作ボタンを表示してそれを押すようなUIにしたいけどうまくいかない。実現してる人のソースを真似すればいいのだけどA-Frameの古いバージョンを使っていたりして互換性がない。ライブラリのバージョンを合わせると表示がうまくいかなくなる。

Oculus Questを使っている時はスティック操作で動かしたいけどコントローラのビジュアルが出るだけで操作はできない(自分で作る必要がある)。

Oculus Questで行きたい場所にビームを出してテレポートできるUIもあったけど新しいバージョンのA-Frameでは動作しない。

飽きた

そんなわけでもっと簡単に使える便利なライブラリを誰かが作ってくれたり便利な共有サイトを誰かが作ってくれるのを待つばかりとなった。

後日談

Scaniverseの3/22のアップデートでScaniverseのコミュニティにモデルをアップロードする機能がついた。無料でUnlisted(非公開)が選べる。ブラウザで見た時のカメラの動きは同じではないが、アプリで開ければぐいーんといける。パスワード付けたりはできないけど、これが求めていたものだよ…。

5/14のアップデートでスキャンデータRAW保存して精密モデルと軽量モデル別々に生成できるようになった。

PCIスロットのあるスリムPCを調達したい件

※与太話です。

PCを新調しようとしている件

今使ってるPCはCore第1世代、Lynnfieldの10年選手である。さすがにガタがきていてBSODとかも多い。 SSDは使ってるしマザーボードの電池を交換したらまだいけるのではないか?とも思えるが、そろそろパフォーマンス的にもワンランク上(つまりM.2 NvMEなストレージである)のモノが欲しくなってきた。

で。もうデカいPC(といってもミドルタワーなのでフルタワーほど大きくはないのだが)を選ぶ必要性はない気がしていて、ミニPCかノートPCにして、重いゲームがしたいときだけ外付けGPUボックスでも使えばいいのではなかろうか、なんて思っているのだった。外付けGPUボックスはけっこう高いのでまだまだ皮算用である。

しかしレガシーな機器にも未練がある。PCIのTVチューナーカードである。以前よく話題になったアレだ。 PCI Expressになったモノも持ってはいるのだが、電波障害時のドロップ耐性はPCI版のほうが良い気がしているのだ。 なのでPCIスロット付きのサブPCは1台持っておきたい。

別に今使ってるのをそのまま保存しておけばいいじゃあないかーという説もあるが、やっぱりでかい筐体は手放したいし?

あと電波障害が発生する地域なのが悪いのでCATVやフレッツテレビを契約すれば解決してしまう問題でもある。

まあなんだ、アレコレ言い訳をするまでもなく、物欲なのだ、今使ってるミニタワーPCを廃棄してミニPCとスリムPCの2台体制にしたいのだ。

ちなみに録画専用機にしているスリムPCは既にあるが、特にこだわらずに買ったセール品でPCI Expressしかついてない。

PCIスロットを備えるスリムPCは現存するのか

今どきPCIスロットを求めるのは特殊用途が過ぎる。 工場用のパソコンにはだいたい付いているが、特殊用途すぎて一般に手が出る価格ではない。

PCI ExpressからPCIに変換するライザーカードも販売されているが、ケースを工夫しないと設置が難しい。

できれば普通にPCIスロットを備えているスリムPCが欲しいのだ。

調べてみると、フルタワーPCの場合は案外まだPCIスロットを備えているモデルはある。しかしこれがスリムPCになると激減。PCI Express全盛である。

余談だが「PCI」と「PCI Express」は両方備えているものが理想的なのでとても検索しづらい。PCIスロットのあるPC、どうやってググればいいのだ。

それでもなんとか調べているとエプソンダイレクトのPCが現行機種でもカスタマイズでPCIスロットを装着可能なことがわかった。しかし高い。おおよそ『サブPC』に投資する価格ではない。

きっと出来合いのPCではなく自作すべきなのだろうけど自作する気力はない…タワーならまだいいが小さいPCは内部を弄るのも面倒である。

そこでエプソンダイレクトの古い機種を中古で探すことにした。

Endeavor AT992e、AT993eあたりが非常に買いやすいお値段である。Core第4世代なので一応グレードアップにもなっている。

ひとつ要件を忘れていたが、スリムPCにはロープロファイル対応PCIカードのみが装着可能なモデルが多い。これではダメだ。フルサイズのPCIスロットが入れられることも必須条件である。

AT992e、AT993eはこのあたりも問題ない。これで解決であるか。

4Kディスプレイ対応

一応他のメーカーにも似たモデルがないか中古ショップを漁っていると、富士通のPC、Esprimo D586あたりは同様にフルサイズのPCIスロットが備わっていて、さらにCore第6世代であり、DisplayPortも備わっていることがわかった。

DisplayPortは4K対応である。

そういえば今使っているモニタは15インチではあるが4K解像度であった。

別にフルHDの映像を入力して使えないことはないが、せっかくだから4Kのまま使いたい。

Core第4世代もチップセットとしては4K対応である。PCIスロットにPCI対応のビデオカードを挿せば4K対応にできるかもしれないが、PCI対応のビデオカードを探すのも苦労しそうである。スリムPCなのでチューナーカードと干渉しないかも心配だ。

というわけで購入ターゲットが富士通D586となった。価格は若干高くなるがかなりのグレードアップである。

ところがさらに仕様詳細を調べていると、これらのモデルはカスタマイズでPCI Expressを選択できることがわかった。そうなるとPCIスロットは付いていない。(ちなみに最新モデルもエプソン同様カスタマイズでPCIスロット選択可能である)

中古PCショップの販売ページにはそこまで詳しいスペック情報は掲載されていない。エプソンのモデルも同様だ。これは困った。注文する前にショップに問い合わせて解決するだろうか?ショップの担当者はそこまで詳しくチェックできるだろうか?

再びライザーカード

さらに調べているとどうやら富士通のD586・D587はベースとしてはPCI Expressが1スロットあるだけで、PCIはライザーカードによって追加されているようなのだ。

ついさっきライザーカードはケースが云々書いたばかりだがこれは専用品なので問題ないだろう。(中古品だとケース内マウント用パーツが欠損しているという可能性はあるが)

そして比較的最近のモデルであるためライザーカードは単品入手が可能である。もし購入したモデルが理想と違ってもライザーカードだけ追加購入すれば目的の構成に変更できるのだ。これで一安心。購入候補が決まった。

あとはCPUとかメモリとかストレージとかを選ぶだけである。比較的最近のモデルなので、中古ショップも現行モデルと遜色ないレベルでバリエーションを取りそろえてくれている。 どれにしようかな…。安いのにして自分で増設してもいいし…面倒だから最初からSSDとか入ってるやつもいいし…。

余談

富士通のPCは型番がいっぱいあって何がどう違うのかさっぱりわからないものが多い。幸いカタログや説明書は充実しているのだが、読んでもわけがわからない。 D586/MとD586/Pはどうやら初期OSのWindows10のバージョンが違うだけっぽい。 中古ショップにはD586/MXとかいうモデルがあるのだが富士通のサイトでは見つけられない。D586/MWはあるがたぶんオフィスがついてたとかそんな感じだろう。 D587はデフォルトがPCI Expressなモデルであると思われる。 D586とD587は「多機能モデル」と位置づけられており、上位に「ハイエンドモデル」D956・D957が存在するが、違いはvProに対応しているかどうか、チップセットが上位、といったところで、PCIスロットはデフォルトでは存在しなくなっているので却下。逆に下方の「エントリーモデル」「スモールモデル」もPCI Expressすらないので却下である。

後日談(2021/8/31)

Yahoo!ショッピングSASストアというショップで富士通「ESPRIMO D587/R」を3万円弱で購入した。

標準ではPCI Express 16x&PCI Express 1xの拡張スロットであったため、PC-MAXというショップでPCIPCI Express 16xのライザーカード(D3454-A11 GS 1)を購入し交換した。 何の問題もなくPCIのチューナーカードが動作して満足である。

まあ環境構築を横着して旧PCで使っていたHDDをクローニングして無理矢理動かしたためBSODが発生しやすい環境になってしまったが、Windows10の上書きインストール で軽減した。

面白いことにこのモデルはM.2 SSDスロットを搭載している。カタログには掲載されてないのでWEBカスタマイズモデルというやつだろう。SATAだけでなくNVMe(PCIE3.0)も認識する素晴らしいモデルであったので1TBのNVMe SSDも買ってしまった。マニュアルには2つしか載っていないSATAポートも3つある。ただメモリスロットに関して、

注1: 次の場合は、最大8GBまでのメモリ容量でお使いください。
・「フラッシュメモリディスク128GB」搭載機種
・「フラッシュメモリディスク128GB+500GB」搭載機種

という謎の但し書きがあるのが気になっている。無視して8GBx4=32GB積んでしまったが問題なく動作しているように見える…が、もしかしたらこれもBSODの発生要因になってしまっているかもしれない。 CPUが第7世代Core i3であるためWindows11の動作対象からは外れてしまったのがやや惜しい。

Amazonに投稿したレビューをバックアップしようの巻

Amazonレビューが消されてた話

ブログを使ってガジェットレビューとかやっていこうかなとか思った時期もあるのだけど、だいたいAmazonレビューに投稿してしまうのでほぼ転載するだけになるし別にいいだろうと思っていたのだけど、最近ふと消えているレビューがあることに気付いた。

f:id:fashi:20210504163037p:plain
キャッシュの日付からすると最近

f:id:fashi:20210504163108p:plain
消されていたレビュー内容

Googleのキャッシュにはまだ残っていたので消されたのは最近のようだ。 HDCP云々がまずかったのか他のメーカーを揶揄しているのが悪かったのか原因はわからない。 同じメーカーの新モデルからはHDCPの文字は消えていたりするが、他の同一製品のレビューにある似たようなレビューは消されていない。

あるいは、悪評が多い中の賞賛レビューなのでサクラを疑われたのかもしれない。

f:id:fashi:20210504163241p:plain
似た内容の消えてないレビュー

自分としては気になる機器があるけど18,800円は手が出ないなーと思っていたら見た目が全く同じのが6,480円で売られていたのでダメ元で買ったら普通に使えたぜヒャッホウと思って★5レビューを投稿したのだが、その後まともに動かない・音ズレするという★1レビューが増えたのでサクラに見えるのは致し方ない。こんなことならダメなところを書いて★4にしておけばよかった。

f:id:fashi:20210504165401p:plain
気になるけどやけに高い機器

ちなみにインプレスのAVWatchを読んでいたら小寺信良氏も同じ製品を買っていた。

https://av.watch.impress.co.jp/docs/series/zooma/1306593.html

記事内にもある通り実にありふれた製品である。

f:id:fashi:20210504163135p:plain
AVWatch記事

中華製品はかなり品質にバラつきがあるので個体不良は多いだろうし、あるロットやある代理店の販売したものが全て不良品だったり中身の違う偽物なんてこともありうる。★1レビューをしている人はそういった不良品を掴んでしまったのかもしれない。 Monoqlo誌やthe360.lifeで絶賛されていたから買ったのにハズレだった、なんてこともままあるのだ。

競合他社が売れ筋商品を貶めようとして悪意あるレビューを投稿していたなんて事例もあるくらいなので、Amazonのレビューというのは実際はほとんどアテにならないのだ。

消えた自分のレビューももしかしたら競合他社が競合製品の評価を高くしているレビューを狙い打ちして通報している可能性だってあるやもしれない。

自分としては「手元にある製品はまともに動いています。Bandicamで動かしています。二時間くらいキャプチャした限り音ズレは確認出来ません。OBSは使ってないので知らないです。繋ぎっぱなしにしていますがまだ壊れてはいません。」ということしか言えない。これから購入する人が同じ性能の製品を手にできるかすらわからない。

今も残っているレビューで以前から気になっていたもの

そんな低評価のレビューの中で一際気になるレビューがある。

f:id:fashi:20210504163211p:plain
おかしなレビュー

使わずに返品したってw なんでこれでレビューとして成立するんだよ。

よく製品自体には触れずに配送が遅かったからとか梱包が悪いからとか(他の商品の)サポートの態度が悪かったとかで評価を下げようとしているひどいレビューがあるけど、これも相当だ。悪評を投稿するためだけに購入して返品しているじゃないか。

そして気になる一文が。

レビュー☆5にしたら2000円分ギフト券プレゼントと書かれた紙も同梱されておりました

え、そんなの入ってなかったぞ…。

ただまあこれは販売代理店(販売元)が入れるのだろうから、自分が買った時と違う販売元の商品には入っているのかもしれない。

気になるのはこのレビューが普通に長期間掲載されているということだ。

レビューしてくれたら金券あげます的な紙が入っているのはよくあるので自分もレビューに書いたことがあるが、その時はAmazonからこのレビューは掲載できませんと言われてしまった。

f:id:fashi:20210504163305p:plain
掲載拒否されたレビュー

コミュニティを混乱させる誤解を招く行為になるんだそうである。 確かに★5レビューの並んでいる商品のレビューにあいつらはみんな金をもらっているサクラだという書き込みがあれば一気に評判は落ちるだろう。実際にはそんな事実がないとしても悪魔の証明だし確かめようがない。(逆に実際に紙きれの写真を提示したところで捏造を疑われる可能性もある)

以前買った毛布のレビューには「毛が抜ける」という低評価が散見されたし、以前使っていたパソコン用ユーティリティ製品には「ユーザー登録すると迷惑メールがいっぱい来る」みたいなレビューが付けられていた。もちろん自分の購入した毛布に抜け毛は見られないし、登録したメールアドレスに迷惑メールが来ることもなかった。こういうのは昔からある嫌がらせの手法であろう。それはわかる。

じゃあなんでさっきのレビューはずっと掲載されているのだ。

Amazonレビューも担当者ガチャ次第ということなのかもしれない。 通報しようにも昔はあった理由を記入する欄がなくなってしまったので数が集まらない限りチェックしない気がしてならない。

Amazonに投稿したレビューをバックアップする

で、本題。

Amazonにレビューを投稿したらそれで満足していたが、消される可能性があるなら念のため手元に控えを取っておいたほうがいいだろう。 さすがにいちいち書いた文章のコピーを保存しているほどマメではなかった。消されているものは仕方ないが今見られるものだけでも保存しておこうと思い立ったわけだ。

自分が投稿したレビューの一覧はどこにあるのか。

投稿したレビューは、公開プロフィールより編集または削除できます。

公開プロフィールを見てみると、書き込み履歴という欄があって、自分の投稿したレビューが並んでいる。 しかし「全文を表示する」をクリックしないと全文がでてこないし、商品名も途中で切られてるし、販売終了した製品に至っては何の商品のレビューかもわからない。DOMからはASINと個別ページへのURLしか取得できない。

ええい、もっと再利用しやすい一覧はないのかっ

Chromeの開発者ツールでネットワークタブのXHR見てみると、スクロールして読み込みが発生する度に

https://www.amazon.co.jp/profilewidget/timeline/owner?...

というエンドポイントへのアクセスがある。 ここで取得しているJSONの中身を見るとレビューが全文書いてある上に商品名もフルで入っている。販売終了した商品名も入っていたり(入っていないのもある)する。

f:id:fashi:20210504162906p:plain
プロフィールページをスクロールしながら開発者ツールを確認する

「owner?」で抽出できることを確認して過去の分が全部表示されるようにスクロールしながらこれを右クリックしてHARを保存するとかオブジェクトをコピーしてテキストエディタに貼り付けると目的は果たせそうである。

Previewタブの▼contributionsをcopy valueしてテキストエディタに貼り付けて自分でカンマを入れて次のをくっつけていくのが使いやすいだろうか。あるいはその左上の▼{marketplaceId~}をCopy objectしてcontributionsだけ連結するのがわかりやすいかな。あるいは左ペインの行ごとに右クリックしてCopy→Copy responseしてテキストエディタに貼り付けて改行して1行1レスポンスで貼り付けていくのがいいだろうか。一括で保存できないのがちょっともどかしい(Save all as Har with contentsだとフィルタした結果以外も入る)。

あとはこのJSONを…PHPとかで処理すれば…と思ったけどJSONとして保存したところで目的が果たせたので整形はしなくていいや。

おしまい。

きっと本来はAmazonのdeveloper登録したら適切なAPIが利用できるのだろうけど、普通にブラウズしながら開発者ツールを眺めるだけでも結果は得られるというわけである。

いやーChromeの開発者ツール便利だなー(そこか?)

余談

開発者ツールを眺めているとイロイロ解決する話は数多い。

たとえばInstagramなんか、Facebookに買収されてからAPIの利用申請がとても面倒になってしまって、likeした画像を保存するだけみたいな個人的な趣味ではAPIを利用することができなくなってしまったのだが、ログインしない状態で個別URLを開いて開発者ツールを見るとOGPと共有ボタンのところに必要な情報は全て書いてあったりするのだ。(※Mediaタブで拾えるのは縮小版であってオリジナルではない)

あとTogetterでマンガが公開されてるやつ、普通に見るとページをめくるのが面倒なんだけど、画像クリックすると専用ビューアが開いて全ページ分の画像リストがAjaxで読み込まれるのを拾えたりする。

規約で禁止されてたりするスクレイピング行為をしたりスクレイピングするツールを公開するといろいろ問題がありそうだけど、普通にブラウジングしながら裏で取得してる情報を保存しておくだけなら何も問題はないよね…?

それに内部APIなんて突然仕様が変わって使えなくなるのがオチ…

追記

同じ商品に内容を調整したレビューを再度投稿したところすんなり掲載された。日付は新しいものなのになぜか前のレビューに付いていた「9人のお客様がこれが役に立ったと考えています」という評価がそのまま新しいレビューに付いてきてしまった。はてブでも内容編集して前と中身違うのにスターが付いたままなのなんか申し訳ないなってのあるけど同じ気分になったよ。

mixi_exportの件追記

mixi_exportの件追記

昨日の件の続き。

mixi_exportが途中で落ちたので

mixi側の仕様変更に追従できなかったのではないか?とか、最悪自分で改造して対応…とか思ったけど

#---------------------------------------------------------------------
# ●ログデータを取得
#---------------------------------------------------------------------
my $get_border = 0;
my %years;
@yyyymm = sort { $b <=> $a } @yyyymm;
foreach my $yyyymm (@yyyymm) {
    my $year = substr($yyyymm, 0, 4);
    my $mon  = substr($yyyymm, 4, 2);
    my $int_mon = int($mon);
    if (($yyyymm*100+99) < $get_border) { last; }
    &myprint("■$year$mon月を処理\n");

ここのforeachの下に

foreach my $yyyymm (@yyyymm) {
    # 2010.7より新しい月はスキップ
    if ($yyyymm > 201007){ next; }
    my $year = substr($yyyymm, 0, 4);

とかやって取得できたところはスキップして試したら普通に続きが取得できたので単にネットワークエラーかメモリエラーで落ちたということだろう。ソース付いてて助かるね。 なお既存のファイルが存在すると更新モードになって1ヶ月ぶんしか取得してくれなくなるので落ちた月のファイルは消しておいたほうがよさげ。

追記

EXE版ではなくPL版を動かしていたのでわかったが、

Error : Error response from 'mixi.jp' (status 502)

しばらくアクセスが続くと502で落ちるっぽい。また↑の日付変えれば済む話だけど、

mixi_exportの画像をどうにかするの巻

mixi_export

今更ながらmixiプレミアムに課金してるの無意味っぽいから解約するかなあって思い立ったんですが、昔書いた日記は容量オーバーで消される前に退避しておきたいですね。

以前退会祭になった時分に日記をエクスポートするツールがいろいろあったと思ったのですが、その後の仕様変更(これが多いのでツールがなかなか出ない)で使えなくなってしまったものばかり。

かろうじてmixi_exportはまだ動作するっぽい…。

Proxyでhttps不可だけどサービス一覧とかからログイン可能。 てかCookie取得するだけなら管理者ツールからコピペとかでいい気もします。

しかし…

しかし動いたけど2010年以前の日記が取得できない…これもmixiの仕様変更が悪いのかたまたまそこでメモリ不足になってしまうのか…。ひとまず2010年以前の日記は諦める。

あと画像も回収してくれない。URLを絶対パスに置き換える処理だけしてくれて、保存されたHTML開けばサーバーに残ってる画像は表示されるので、退会する前にPDF化とかしておくのが一般的みたいです。

うーん。画像。。。ChromeのSavePageWEとか使えばページ単位では保存できるが、面倒。 SwiftProxyみたいなProxyでアクセスしたURL全部保存みたいなツール最近はないのかな(これもhttpsだとダメか)。

じゃあ自分で作るしかないかなあ…

というわけで作りました。単純にURLリストアップしてダウンロードすればいい気もしたけど、SavePageWE同様にIMGタグで埋め込まれてる画像をDataURIに置き換えることにします。

本当は全ての画像をDataURIに置き換えるのがベストなんだけど、そうすると同じファイルを読みに行かないようにキャッシュを導入しないといけなくなって面倒なので日記のフォトの画像だけにします。

本当はAタグのリンク先の元画像を拾うほうが高品質なのだけど、雰囲気がわかればいいのでサムネイルだけ拾うことにします。 元画像はそれこそDataURIで埋め込むには大きすぎるので普通にダウンロードするコード書いたほうがいいかもしれないし。

なお写真が今のサーバー(photoserviceなんちゃら)に移動したのは最近の仕様変更なので、昔作ったファイルに対しては動作しない可能性が高いです。

成果物

<?php
/***
 * mixi_exportの生成したHTML内のフォト(imgタグの一部)をDataURIに変換
 *
 *   2021.04.10 fa
 */

// mixi_exportで出力されたログHTMLのあるフォルダ
$path = './log2/';

$mime_types = array('png'=>'image/png', 'jpg'=>'image/jpeg', 'gif'=>'image/gif');

$context = stream_context_create(
    array(
        'http' => array(
            "protocol_version" => "1.1",
            'follow_location' => 1,
            'max_redirects' => 5,
            'timeout' => 60.0,
            'user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36',
        ),
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false,
        ),
    )
);

if (!is_dir($path)){
    _dlog('log path '.$path.' is not found',0);
    exit;
}

// サブディレクトリ走査
$iterator = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator(
        $path,
        FilesystemIterator::SKIP_DOTS
        |FilesystemIterator::KEY_AS_PATHNAME
        |FilesystemIterator::CURRENT_AS_FILEINFO
    ), RecursiveIteratorIterator::LEAVES_ONLY
);

// htmlファイルだけチェック
$iterator = new RegexIterator($iterator, "/\.html$/", RecursiveRegexIterator::MATCH);

$i = 1;
foreach($iterator as $pathname => $f){
    $name = $f->getFilename();
    $dir = $f->getPath();
    $path = $f->getPathname();
    $fullpath = realpath($path);

    // 目次ファイルはスキップ
    if ($name == 'index.html'){
        continue;
    }

    // 処理済ファイルスキップ用
    //if (preg_match("/".preg_quote(DIRECTORY_SEPARATOR,'/')."(2010|2011)/",$path)){
    // _dlog("skip ".$path);
    // continue;
    //}

    _dlog($path);

    // 念のため2000マイクロ秒待つ
    usleep(2000);

    // ローカルのHTML読込
    $contents = file_get_contents($path);

    // 置換処理
    $contents2 = preg_replace_callback(
        '/\<img ([^\>]*)src="(https?:\/\/(?:photoservice|classic-imagecluster)[^"]+\.(' . implode('|', array_keys($mime_types)) . '))"/im',
        function ($matches) use ($mime_types,$context) {

            $url = trim($matches[2], '\'\"');
            _dlog($url);

            // サーバアクセス前に2000マイクロ秒待つ
            usleep(2000);

            // リモートファイル取得
            $http_response_header = null;
            $image = file_get_contents($url,false,$context);
            if ($image){
                // DataURIに置換
                return '<img '.$matches[1].'src="data:' . $mime_types[strtolower($matches[3])] . ';base64,' . base64_encode($image).'"';
            }elseif (!$http_response_header){
                // サーバの反応がなかった時、1秒置いて一度だけ再試行
                _dlog("retry",0);
                sleep(1);
                $image = file_get_contents($url,false,$context);
                if ($image) { return '<img '.$matches[1].'src="data:' . $mime_types[strtolower($matches[3])] . ';base64,' . base64_encode($image).'"'; }
            }
            _dlog("ERROR! ".$url,2);
            return $matches[0];
        },
        $contents
    );

    // 置き換えたHTMLだけ保存
    if (strlen($contents) != strlen($contents2)){
        $i++;
        // 元ファイルをリネームしてから保存
        rename($path,$path.'.bak');
        file_put_contents($path,$contents2);

        // 10ファイル処理毎に少し待つ
        if (!($i % 10)){
            _dlog('wait');
            sleep(20);
        }
    }
}
function _dlog($msg,$lv=0){
    echo $msg.PHP_EOL;
    if ($lv) {
        file_put_contents('./error.log',date('Y/m/d-H:i:s ').$msg.PHP_EOL,FILE_APPEND);
    }
    //TODO エラー多い時は中断する処理
}

img2datauri_mixi.php

何も考えずに手書きしたので使われてないコードやクォートのバラつきがありますが手仕事なんてこんなものだよね(汗

追記

途中で落ちた件は仕様変更対応とかではなかった。追記

エラーメッセージに特化した翻訳サイトがあればいいと思う件

(※この記事は思ったことをダラダラ呟いてるだけで何の知見も得られないし何も解決していません。増田でもよかったけどすぐ特定されそうなのでこちらに。)

ライブラリの紹介文でよく見かけるけどいまいち意味が分からない英単語 - Qiita

プログラミングに特化した翻訳サイト欲しいな

2020/12/13 16:00
b.hatena.ne.jp

これで思い出したんだけど、エラーメッセージの翻訳サイトってけっこう重宝されそうな気がするんだ。

というのも、仕事柄よく他のコーダー(※おおむね初級者)からPHPのエラーについて相談を受けることがあるのだけど、誰も彼も英語が嫌いすぎてエラーメッセージすら読んでないみたいなのだ。

相談を受けたものの、原因はエラーメッセージに書かれている通りなので、一体全体どこで詰まって相談してきているのかわからない。 エラーメッセージの内容を日本語で言い直してあげればそれで解決してしまうことがほとんどなのだ。

プログラム言語やPC用ソフトウェアが吐き出す英語なんてもう英語の文法の要素なんか全然なくて、ほぼほぼコンピュータ用語なので、文法とかは中学生レベルの知識で十分なのだけど、彼/彼女ら(以下彼ら)は英文のメッセージを見ただけで拒否反応を起こして見なかったことにしてしまう。

自分の観測範囲が狭いだけ(※サンプル数10くらい)と思いたいのだけど、初級プログラマから抜け出せないコーダーには英語嫌いが多い気がする。

彼らはエラーメッセージを確認した時、何をするか。

(全く読まずに相談してくる輩もいるが)

まずそのままググってしまう。

でもだいたいのエラーは汎用的なエラーメッセージだし、メッセージにはそのプログラムで使われている独自の単語が引用されてしまう場合も多い。

そんな単語の羅列でGoogle検索したところで出てくるのは関係ないプログラムの関係ないエラーか、もしくは何もヒットしない、ひどい時には「-」がついたままコピペして検索するせいでNOT検索になってしまっている場合もある。

ググるにしても重要そうなキーワードだけを拾って検索するでしょうよ普通は…、と思うのだけど、彼らにはどれが重要そうなキーワードなのかがわからない。

コーディング上のエラーではなくそのライブラリを使った時によくあるFAQだったりした場合には同じところで引っかかった人のブログが出てくるので、これでも解決は期待出来るのだけど…。

ググっても解決できない、もしくは大量にヒットした検索結果をつまんで解決しないと、彼らは次に翻訳サイトを利用する。

(そういえば彼らは検索結果のつまみ方も間違ってしまう場合が多い。1番目に公式リファレンスが出てきたならそれを見れば解決しそうなもの(特にPHPのマニュアルは親切だ)なのだが、検索結果画面に表示される公式リファレンスのスニペットはだいたい英語っぽい文字列になっているので、避けてしまうのだ。そして誰が書いたかわからない日本語のブログや翻訳された知恵袋系のサイトを見てしまう。質問を投げかけて解決せず止まっているものや、古いバージョンでしか起こらない記述、説明は正しいのにサンプルコードが間違っているもの等、罠は尽きない。)

最近の機械翻訳は優秀なので、特に詳細なリファレンスページなんかはChromeの翻訳機能でかなり読みやすくなった。

でもエラーメッセージは通常かなり短いし、あまり親切な英文ではないことが多く、機械翻訳ではうまく翻訳できないことが多い。

それで詰まってしまうようだった。

たとえばこんなのだ

Warning: fputcsv() expects parameter 1 to be resource, string given

警告。fputcsv()は1番目のパラメータにリソースを期待していますが、文字列が与えられています。

この時点でリソース(ここではファイルハンドル)が何かわからなくても、引数が間違ってることは想像が付くだろう。あとは関数リファレンスを見ればいいだけだ。

これを自分で読み解かずにGoogleの翻訳に掛けるとこうなる。

警告:fputcsv()は、パラメーター1がリソースであり、文字列が指定されていることを想定しています。

1番目のパラメータとしてリソースを文字列で指定してください、という意味になってしまう。

指定しているよ? 何が間違ってるの? という解釈になってしまっても仕方が無い。

DeepL翻訳でも試してみよう。

警告: fputcsv() はパラメータ 1 がリソース、与えられた文字列であることを期待しています。

あまり変わらない。PHPが吐き出しているエラーメッセージの文法がおかしいのではないか?という気さえしてくる。 ちなみにhoweverとかbutとかを付け足してやれば期待通りの日本語になる。

いつしか彼らもベテランになり、似たようなエラーには何度も遭遇することになるので、英語はわからないけどそのエラーの意味するところはわかります、という状態になることは十分に期待できる。 しかしもっと適切な、システムが吐き出したエラーメッセージをわかりやすい日本語に変換してくれるWebサービスがあったら彼らもすこぶる捗るに違いない気がするのだ。

あるいはエラーメッセージの文例集があるといいのだが、スクリプトエンジンが肥大化していることもありエラーメッセージも多岐に渡ることもあり、なかなか良いサイトが見つけられない(特に日本語となると…)

そもそもPHPが多言語対応ついでにエラーを日本語出力してくれてもいいようなものだが、そういったリソースやプロジェクトは聞き及んだことがない。(jperlくらいかな)

(もっとも、PHPだけの問題でもない。彼らはgitの吐いたAbortという文字列を見逃して手順が正常終了したと思い込んで罠に陥ってしまうこともしばしばあるのだ。gitは正常終了でもログがいくらか出るので普段から読み飛ばす癖がついてしまっているらしい…)

Google翻訳は日々進化しているのでそのうち改善されることを期待したほうが早いだろうか…。