fix
This commit is contained in:
parent
dabb1b89f7
commit
0e53f2a54a
@ -21,7 +21,7 @@ slug = "private"
|
||||
|
||||
どうしても重ね着がしたいという方は下のようなノースリーブがおすすめかもしれません。
|
||||
|
||||
ノースリーブ : [https://www.amazon.co.jp/dp/B00RGQJSTA/](https://www.amazon.co.jp/dp/B00RGQJSTA/)
|
||||
ノースリーブ : [https://www.amazon.co.jp/dp/B00RGQA/](https://www.amazon.co.jp/dp/B00RGQA/)
|
||||
|
||||
色は基本的に下が黒。上が白とかがいいんじゃないでしょうか。あと、Tシャツはちょっと大きめのサイズを選択すると良いと思ってます。すごく大きめのサイズのものもおすすめで家では下をはかなくて良くなりますよ(なにそれ)。
|
||||
|
||||
|
@ -5,9 +5,9 @@ title = "sierra-jis-keyboard"
|
||||
slug = "mac"
|
||||
+++
|
||||
|
||||
## KarabinerをインストールしていてSierraにアップグレードした場合、JSTキーボードがUSとして認識され、設定を変更しても再起動と同時にもとに戻ってしまう問題の解決法
|
||||
## KarabinerをインストールしていてSierraにアップグレードした場合、キーボードがUSとして認識され、設定を変更しても再起動と同時にもとに戻ってしまう問題の解決法
|
||||
|
||||
- システム設定、キーボード、キーボードの種類の変更に進み、JSTに変更した後、Karabiner-Elementsをインストールして、Deviceのキーボード項目についているチェックを外す、再起動
|
||||
- システム設定、キーボード、キーボードの種類の変更に進み、に変更した後、Karabiner-Elementsをインストールして、Deviceのキーボード項目についているチェックを外す、再起動
|
||||
|
||||
その後、Karabiner系は全部消しても大丈夫。
|
||||
|
||||
|
@ -9,7 +9,7 @@ slug = "keyboard"
|
||||
|
||||
https://github.com/syui/tsuki-layout
|
||||
|
||||
かな入力は今までやったことなかったし、ローマ字より速いっていうし、汎用性あるので覚えておいて損はないと思うので。また、JSTなら確認がすぐできるのもポイント高かった気がする。
|
||||
かな入力は今までやったことなかったし、ローマ字より速いっていうし、汎用性あるので覚えておいて損はないと思うので。また、なら確認がすぐできるのもポイント高かった気がする。
|
||||
|
||||
練習はしてないけどしてる感じです。どういうことかというと、これを書いているのは月配列で書いているのだけど、しかし、こういった実践ではなくタイピングアプリとか使ってやった方が上達早そうに思います。
|
||||
|
||||
|
@ -5,7 +5,7 @@ title = "キーボードを買った"
|
||||
slug = "private"
|
||||
+++
|
||||
|
||||
買ったのはlogicoolのやつ。JSTです。私は、JSTしか使ったことないのでJSTです。
|
||||
買ったのはlogicoolのやつ。です。私は、しか使ったことないのでです。
|
||||
|
||||
今までノートパソコンのキーボードしか使ってこなかったこともあって、やばいほど慣れません。
|
||||
|
||||
|
@ -13,6 +13,6 @@ $ which date
|
||||
$ date +"%Y-%m-%d" -d "1 day"
|
||||
|
||||
# 代わり
|
||||
$ env TZ=JST-33 date +"%Y-%m-%d"
|
||||
$ env TZ=-33 date +"%Y-%m-%d"
|
||||
```
|
||||
|
||||
|
@ -65,7 +65,7 @@ MacBook Airのキーボードは基本的に脆いもので、数年使用して
|
||||
|
||||
## 写真
|
||||
|
||||
### 壊れたJST
|
||||
### 壊れた
|
||||
|
||||
![](https://syui.gitlab.io/blog/img/post/macbook-air-keyboard-01.png)
|
||||
|
||||
@ -87,5 +87,5 @@ MacBook Airを分解して、組み立ててみた感想ですが、MacBook Air
|
||||
|
||||
とは言え、正直、それほど難しい感じではないと思うので、MacBook Airが故障した人とかは、自分で直してみても面白いかもしれないなーと思います。
|
||||
|
||||
そういえば、キーボードがJSTからUSになったので、USにも慣れていかないと....。
|
||||
そういえば、キーボードがからUSになったので、USにも慣れていかないと....。
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2019-12-09T08:00:00JST"
|
||||
date = "2019-12-09T08:00:00"
|
||||
tags = ["illust"]
|
||||
title = "イラスト1時間チャレンジ"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2019-12-11T08:00:00JST"
|
||||
date = "2019-12-11T08:00:00"
|
||||
tags = ["hugo"]
|
||||
title = "hugoでその日の記事がビルドされない問題を回避する"
|
||||
slug = "hugo"
|
||||
@ -15,7 +15,7 @@ date = [":filename", ":default"]
|
||||
ファイルは`content/post/2019-12-09-test.md`(その日の日付)とします。
|
||||
|
||||
```md:content/post/2019-12-09-test.md
|
||||
date = "2019-12-09T08:00:00JST"
|
||||
date = "2019-12-09T08:00:00"
|
||||
```
|
||||
|
||||
### hugoでその日の記事がビルドされない問題
|
||||
@ -23,9 +23,9 @@ date = "2019-12-09T08:00:00JST"
|
||||
これは、UTCがデフォルトになっているためだと思いますが、hugoでは、通常、`.Date.Local`を使っても、その日の記事がビルドされない問題があります。
|
||||
|
||||
```html:layout/_default/list.html
|
||||
{{ dateFormat "2006-01-02T15:04:05JST" .Date.Local }}
|
||||
{{ dateFormat "2006-01-02T15:04:05" .Date.Local }}
|
||||
```
|
||||
|
||||
そこで、dateをファイル名から取得する方法に切り替えると、回避できます。
|
||||
|
||||
追記 : `date = "2019-12-09JST"`と書けばいける?
|
||||
追記 : `date = "2019-12-09"`と書けばいける?
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2019-12-15JST"
|
||||
date = "2019-12-15"
|
||||
tags = ["gscript"]
|
||||
title = "Google Apps Scriptで翻訳してみた"
|
||||
slug = "gscript"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2019-12-18JST"
|
||||
date = "2019-12-18"
|
||||
tags = ["xq"]
|
||||
title = "golangのcli toolをurfave/cli/v2に移行する"
|
||||
slug = "xq"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2019-12-22JST"
|
||||
date = "2019-12-22"
|
||||
tags = ["heroku","sns"]
|
||||
title = "Herokuで立てる分散SNS"
|
||||
slug = "heroku-sns"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2019-12-23JST"
|
||||
date = "2019-12-23"
|
||||
tags = ["dotfiles","archlinux"]
|
||||
title = "Packer + Vagrant + Ansibleで構築するdotfiles"
|
||||
slug = "dotfiles"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2019-12-24JST"
|
||||
date = "2019-12-24"
|
||||
tags = ["music"]
|
||||
title = "作曲してみた8"
|
||||
slug = "music"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2019-12-30JST"
|
||||
date = "2019-12-30"
|
||||
tags = ["illust"]
|
||||
title = "絵を描く機会を増やしてみる"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-01-17JST"
|
||||
date = "2020-01-17"
|
||||
tags = ["game"]
|
||||
title = "ゲーム2を作ってみた"
|
||||
slug = "game"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-01-25JST"
|
||||
date = "2020-01-25"
|
||||
tags = ["dotfiles"]
|
||||
title = "開発環境を作り直してみる3"
|
||||
slug = "dotfiles"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-01-25JST"
|
||||
date = "2020-01-25"
|
||||
tags = ["illust"]
|
||||
title = "イラストを描いてみた17"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-01-25JST"
|
||||
date = "2020-01-25"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGoで最高の相棒にしてみる"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-01-26JST"
|
||||
date = "2020-01-26"
|
||||
tags = ["ios"]
|
||||
title = "iosのgoodreaderでsftpのprivate-keyが読み込めない問題"
|
||||
slug = "goodreader"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-02-11JST"
|
||||
date = "2020-02-11"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGoにリーグが実装されたのでやってみる6"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-02-15JST"
|
||||
date = "2020-02-15"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGoにリーグが実装されたのでやってみる7"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-02-23JST"
|
||||
date = "2020-02-23"
|
||||
tags = ["private"]
|
||||
title = "夫婦別姓について個人的な考え"
|
||||
slug = "private"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-03-01JST"
|
||||
date = "2020-03-01"
|
||||
tags = ["pokemon","pokemas","game"]
|
||||
title = "ポケマスをプレイしていて思ったこと"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-03-15JST"
|
||||
date = "2020-03-15"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGOバトルリーグのシーズン1が始まった"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-03-16JST"
|
||||
date = "2020-03-16"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGO、バトルリーグの改善案ついて説明してみる"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-03-24JST"
|
||||
date = "2020-03-24"
|
||||
tags = ["manga"]
|
||||
title = "再びマンガを描きはじめた話"
|
||||
slug = "manga"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-03-25JST"
|
||||
date = "2020-03-25"
|
||||
tags = ["vue2x"]
|
||||
title = "vue 2.x + webpack 4.xでenvを使う"
|
||||
slug = "vue2x"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-03-28JST"
|
||||
date = "2020-03-28"
|
||||
tags = ["blog"]
|
||||
title = "ブログをはじめるならGitHub Pagesがおすすめ"
|
||||
slug = "blog"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-02JST"
|
||||
date = "2020-04-02"
|
||||
tags = ["manga"]
|
||||
title = "マンガを1話だけ完成させてみる"
|
||||
slug = "manga"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-11JST"
|
||||
date = "2020-04-11"
|
||||
tags = ["vue"]
|
||||
title = "vueのhooperでスライドを実装する"
|
||||
slug = "vue"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-12JST"
|
||||
date = "2020-04-12"
|
||||
tags = ["vue"]
|
||||
title = "vueでapexchartsを使いチャートを作ってみた"
|
||||
slug = "vue"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-20JST"
|
||||
date = "2020-04-20"
|
||||
tags = ["ios"]
|
||||
title = "phonegapを使ってhtml5で開発したwebアプリをbuildする方法"
|
||||
slug = "ios"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-23JST"
|
||||
date = "2020-04-23"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンハートゴールドをプレイしてみた"
|
||||
slug = "pokemon-heart-gold"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-25JST"
|
||||
date = "2020-04-25"
|
||||
tags = ["novel"]
|
||||
title = "短編小説を書いてみた1"
|
||||
slug = "novel-01"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-26JST"
|
||||
date = "2020-04-26"
|
||||
tags = ["novel"]
|
||||
title = "短編小説を書いてみた2"
|
||||
slug = "novel-02"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-27JST"
|
||||
date = "2020-04-27"
|
||||
tags = ["novel"]
|
||||
title = "短編小説を書いてみた3"
|
||||
slug = "novel-03"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-04-29JST"
|
||||
date = "2020-04-29"
|
||||
tags = ["game"]
|
||||
title = "ノベルゲームの続きを作ってみた57"
|
||||
slug = "game"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-01JST"
|
||||
date = "2020-05-01"
|
||||
tags = ["vocaloid"]
|
||||
title = "作った曲を整理してみた"
|
||||
slug = "vocaloid"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-02JST"
|
||||
date = "2020-05-02"
|
||||
tags = ["illust"]
|
||||
title = "イラストを描いてみた24"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-03JST"
|
||||
date = "2020-05-03"
|
||||
tags = ["pokemon","pokemas","game"]
|
||||
title = "ポケマスをプレイしてみた3"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-0 4JST"
|
||||
date = "2020-05-04"
|
||||
tags = ["pokemon","pokemas","game"]
|
||||
title = "ポケマスをプレイしてみた4"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-06JST"
|
||||
date = "2020-05-06"
|
||||
tags = ["illust"]
|
||||
title = "イラストを描いてみた25"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-07JST"
|
||||
date = "2020-05-07"
|
||||
tags = ["pokemon","pokemas","game"]
|
||||
title = "ポケマスをプレイしてみた6"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-08JST"
|
||||
date = "2020-05-08"
|
||||
tags = ["illust"]
|
||||
title = "お絵かき講座、修了編"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-08JST"
|
||||
date = "2020-05-08"
|
||||
tags = ["vocaloid"]
|
||||
title = "5曲目の修正に苦戦した話"
|
||||
slug = "vocaloid"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-10JST"
|
||||
date = "2020-05-10"
|
||||
tags = ["rust"]
|
||||
title = "rustで作るcli toolに入門してみる"
|
||||
slug = "rust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-12JST"
|
||||
date = "2020-05-12"
|
||||
tags = ["rust"]
|
||||
title = "rustで作るcli tool"
|
||||
slug = "rust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-14JST"
|
||||
date = "2020-05-14"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGo、最近の進捗"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-15JST"
|
||||
date = "2020-05-15"
|
||||
tags = ["pokemon","pokemas","game"]
|
||||
title = "ポケマスのことがわかってきたので解説してみる"
|
||||
slug = "pokemon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-16JST"
|
||||
date = "2020-05-16"
|
||||
tags = ["github"]
|
||||
title = "pull-reqが来たときgithub-actionsを実行してhtml,sqlをreviewする"
|
||||
slug = "arch"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-17JST"
|
||||
date = "2020-05-17"
|
||||
tags = ["golang"]
|
||||
title = "xqというcli toolにtxtをjson{body:}に出力するオプションを追加してみた"
|
||||
slug = "golang"
|
||||
@ -9,7 +9,7 @@ shellの`cat`では、github-apiのpostでjson errorが出る場合があった
|
||||
|
||||
```sh
|
||||
$ xq j ./index.md
|
||||
{"body":"+++\ndate = \"2020-05-17JST\"\ntags = [\"golang\"]\ntitle = \"\"\nslug = \"golang\"\n+++\n\n\n[xq](https://github.com/syui/xq)にtxt, mdをjsonのbodyに入れるコマンドを追加した。\n\n```sh\n$ xq j ./index.md\n```\n"}
|
||||
{"body":"+++\ndate = \"2020-05-17\"\ntags = [\"golang\"]\ntitle = \"\"\nslug = \"golang\"\n+++\n\n\n[xq](https://github.com/syui/xq)にtxt, mdをjsonのbodyに入れるコマンドを追加した。\n\n```sh\n$ xq j ./index.md\n```\n"}
|
||||
```
|
||||
|
||||
これでgh-actionsに以下のような書き方ができます。
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-29JST"
|
||||
date = "2020-05-29"
|
||||
tags = ["illust"]
|
||||
title = "お絵かき講座、応用編"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-30JST"
|
||||
date = "2020-05-30"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGo、GBLの真実"
|
||||
slug = "pogo"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-05-31JST"
|
||||
date = "2020-05-31"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGo、スポットレイトアワーなど"
|
||||
slug = "pogo"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-06-01JST"
|
||||
date = "2020-06-01"
|
||||
tags = ["windows"]
|
||||
title = "windowsの必須ツールをupdateしてみる"
|
||||
slug = "win"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-06-03JST"
|
||||
date = "2020-06-03"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGo、GBL S2ハイパーリーグがはじまった"
|
||||
slug = "pogo"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-06-06JST"
|
||||
date = "2020-06-06"
|
||||
tags = ["game"]
|
||||
title = "ノベルゲームを更新してみた60"
|
||||
slug = "game"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-06-17JST"
|
||||
date = "2020-06-17"
|
||||
tags = ["game"]
|
||||
title = "ノベルゲームを更新してみた69"
|
||||
slug = "game"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-06-18JST"
|
||||
date = "2020-06-18"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGoは早いうちからやっておいたほうがいい"
|
||||
slug = "pogo"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-06-19JST"
|
||||
date = "2020-06-19"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGoのメガシンカはどうなるのか"
|
||||
slug = "pogo"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-06-22JST"
|
||||
date = "2020-06-22"
|
||||
tags = ["illust"]
|
||||
title = "ヘッダー画像を変えてみた"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-06-23JST"
|
||||
date = "2020-06-23"
|
||||
tags = ["icon"]
|
||||
title = "アイコンをいくつか作ってみた"
|
||||
slug = "icon"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-01JST"
|
||||
date = "2020-07-01"
|
||||
tags = ["anime"]
|
||||
title = "七星のスバルが面白かった"
|
||||
slug = "anime"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-02JST"
|
||||
date = "2020-07-02"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGoのGBLをやめた話"
|
||||
slug = "pogo"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-03JST"
|
||||
date = "2020-07-03"
|
||||
tags = ["illust"]
|
||||
title = "新しいキャラ描いた"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-04JST"
|
||||
date = "2020-07-04"
|
||||
tags = ["curl"]
|
||||
title = "コマンドラインからjavascriptでレンダリングされたHTMLソースを取得する方法"
|
||||
slug = "curl"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-05JST"
|
||||
date = "2020-07-05"
|
||||
tags = ["vocaloid"]
|
||||
title = "2020年上半期ボカロ曲1位の発表"
|
||||
slug = "vocaloid"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-06JST"
|
||||
date = "2020-07-06"
|
||||
tags = ["game"]
|
||||
title = "ノベルゲームの週刊連載はじめました"
|
||||
slug = "game"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-08JST"
|
||||
date = "2020-07-08"
|
||||
tags = ["pokemon"]
|
||||
title = "ポケモンGo、4周年"
|
||||
slug = "pogo"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-19JST"
|
||||
date = "2020-07-19"
|
||||
tags = ["illust"]
|
||||
title = "イラスト描いてみた29"
|
||||
slug = "illust"
|
||||
|
@ -1,5 +1,5 @@
|
||||
+++
|
||||
date = "2020-07-21JST"
|
||||
date = "2020-07-21"
|
||||
tags = ["illust"]
|
||||
title = "イラストを修正してみた"
|
||||
slug = "illust"
|
||||
|
@ -37,5 +37,5 @@ $ curl -sL ipinfo.io
|
||||
|
||||
どうやらicloud private relayのipv4はcloudflareを使ってるみたいです。
|
||||
|
||||
最近、icloud private relayが使いやすいのでbrowserをsafariに置き換えようかなと思っています。もうすぐchromeの広告ブロック排除がくるみたいだし。
|
||||
最近、icloud private relayが使いやすいのでbrowser(mac)をsafariに置き換えようかなと思っています。もうすぐchromeの広告ブロック排除がくるみたいだし。
|
||||
|
||||
|
39
content/blog/2024-08-11-safari.md
Normal file
39
content/blog/2024-08-11-safari.md
Normal file
@ -0,0 +1,39 @@
|
||||
+++
|
||||
date = "2024-08-11"
|
||||
tags = ["mac"]
|
||||
title = "macのbrowserをsafariにした"
|
||||
+++
|
||||
|
||||
chromeからsafariに切り替えました。
|
||||
|
||||
理由としては、以下の4つです。
|
||||
|
||||
1. ios(mobile)で使っているのがsafariなので統一したかった
|
||||
2. safariだとicloud private relayが使える
|
||||
3. chromeだとprivate windowでもcacheが効いて開発に支障が出る
|
||||
4. chromeがadblockなどを排除予定
|
||||
|
||||
### chromeだと最新のpreviewができない
|
||||
|
||||
主にweb開発ですがchromeだと最新のsrc previewが反映されません。cookieかcacheかわかりませんが、それが残っているのだと思います。
|
||||
|
||||
safariだとprivate windowは普通に機能します。localhostでpreviewするときも支障がありません。
|
||||
|
||||
ちなみに、chromeのprivate windowは完全にprivateではありません。
|
||||
|
||||
### google翻訳やgoogle検索を使わなくなった
|
||||
|
||||
これまでchromeを使っていた理由は`google翻訳`があるからです。
|
||||
|
||||
私はbrowserをあまりカスタマイズせず、拡張機能も入れず、ほとんどの初期機能を無効にし、基本設定のまま使います。ですからchromeについているgoogle翻訳は便利でした。
|
||||
|
||||
しかし、近年、`chatgpt`, `perplexity.ai`などのほうがよく使うようになり、逆にgoogle検索やgoogle翻訳をあまり使わないようになっていました。
|
||||
|
||||
これだとchromeを使うメリットは薄れ、デメリットが目立ちます。
|
||||
|
||||
### icloud private relayが使える
|
||||
|
||||
ほとんどのアプリはicloud relayが有効になっていますが、browserはsafariくらいしか通しません。
|
||||
|
||||
もともとスマホはsafariしか使いませんし、パソコンも統一したほうがいいだろうということで、しばらくsafariを使ってみることにします。
|
||||
|
107
content/blog/2024-08-15-vtuber.md
Normal file
107
content/blog/2024-08-15-vtuber.md
Normal file
@ -0,0 +1,107 @@
|
||||
+++
|
||||
date = "2024-08-15"
|
||||
tags = ["vtuber", "fuwamoco"]
|
||||
title = "vtuberのfuwamocoを見始めた"
|
||||
+++
|
||||
|
||||
数日前からvtuberを見はじめて、最初はさくらみこを見て「へえこんな人がいるのか面白い」と思って、次に古石ビジューを見て、そこからふわもこ(fuwamoco)を見てる。
|
||||
|
||||
今回はそこで考えた色々なことを話していきたい。
|
||||
|
||||
### fuwamocoのすごさ
|
||||
|
||||
私はfuwawaが特に気に入ってて、好き。fuwawaが怒ってるところは見たことないし、想像つかないところがいい。
|
||||
|
||||
なんだけど、今回は主に技術的な側面からfuwamocoを考察します。
|
||||
|
||||
fuwamocoを見てて思ったのは、この子達はプロだなってこと。あらゆる面でそのことを読み取れます。
|
||||
|
||||
例えば、彼女たちの口癖であるbaubauです。繰り返されることで定着して、今やbaubauって聞くと「あ、fuwamocoだ」ってわかる。
|
||||
|
||||
彼女たちはおそらくいくつかの決め事があり、それを実行していると思う。しかも日々配信がどうすれば面白くなるか考えてupdateしていく姿勢もある。
|
||||
|
||||
この両方を持ち合わせることは難しい。例えば、jpは精神論や昔ながらのやり方を重要視する傾向にあるように感じたのに対し、enのfuwamocoは決まりを大切にしながらも、updateを大切にしているように感じた。とはいえ精神論が重要じゃないわけじゃない。最後まで立ってるやつが勝ちみたいな考えは好き。
|
||||
|
||||
また、fuwamocoが特徴的なのが二人組みであること。
|
||||
|
||||
私は最初はソロでやっている人を見てるんだけど、最終的には二人組でやっている人を見るようになる。
|
||||
|
||||
なぜかというと、多分、飽きるからだと思う。二人組の場合、やり取りの幅は広い。
|
||||
|
||||
そして、vtuberを見ていて最も楽しいと感じるのがadventでやっているとき。adventというのはfuwamocoの同期の人達が集まってわちゃわちゃやってることがあって、それが一番おもしろいと思う。
|
||||
|
||||
2番目がfuwamocoで3番目がソロのメンバーという感じ。
|
||||
|
||||
やっぱり二人組が強いと思う。お笑いも最終的には二人組に行き着く事が多い。
|
||||
|
||||
次にすごいのはfuwamocoがオタクであるということ。そして、ただのオタクじゃないことはわかる。
|
||||
|
||||
その他にもネーミングの凄さやfuwamoco morning、冒頭の「ふわわじゃないよ、もここだよ」、オープニングのセンスの良さ、鳥のマスコットなどなどすごいところはたくさんあるけど、一番はやっぱり視聴者が嫌な気分になることがないってところ。見ていて楽しいところ。
|
||||
|
||||
配信ってどうしても我が出てしまって、それはいいことでもあるんだけど、悪いことでもあると思う。
|
||||
|
||||
特に悪いのは個人的資質(性格の悪さ)による我が出ること。そのへんは内面がいい人を選ぶしかない。
|
||||
|
||||
vtuberに内面なんてあるのと思われるかもしれないけど、毎日配信だから個人的な性質は隠しきれない。
|
||||
|
||||
だからそういうのは必ず出ると思ったほうがいい。
|
||||
|
||||
おそらく、fuwamocoは個人的資質が良いこと(性格が良いこと)と、いくつかの決め事でプロに徹すること。この2点がすごいと思った。
|
||||
|
||||
ちなみに、みこちゃんもbibooも好きだしかわいい。
|
||||
|
||||
### vtuberをおすすめしない
|
||||
|
||||
こういうのを見て、やってみたいなあって思う。でもそういうのはおすすめしない。
|
||||
|
||||
理由としては、何かが流行っているとき、先行者利益はすでに失われている。
|
||||
|
||||
vtuberになるには、vtuberがまだ知られていないし、誰もやっていない、流行ってもいない。そういうとき勇気を出してやるのが一番いい。
|
||||
|
||||
逆に言うと、そこで勇気を出し努力をしてきた人たちが最もvtuberで活躍すると思う。それ以外は厳しいと思う。
|
||||
|
||||
「誰でもvtuberになれます」、「今、vtuberが大流行」みたいな状況でそういうのに参入するのはおすすめしない。
|
||||
|
||||
個人的には誰もやってないこと、かつ自分が好きで続けられて得意なことをやるのが良いように思います。
|
||||
|
||||
### vtuberの技術が気になったのでやってみた
|
||||
|
||||
実はvtuberにハマってから数日、自分ならどういった形で実現できそうかやってみました。
|
||||
|
||||
途中でモーションキャプチャに切り替えて動かしてます。
|
||||
|
||||
<video controls style="width:100%;"><source src="/m/post/ue/ue5_2024-08-15_01.mp4"></video>
|
||||
|
||||
vみたいなことやるならアイのモデルを貸してもらうしかないな。名前はyoutubeで@syuiが取れなかったので@syaiなんだけどこれになる。シャイ?かな。んでその際はアイじゃなく自分(シャイ)であることを説明しないと。作者とキャラって全く関係ない別人だから。
|
||||
|
||||
ただ、やるにしてもvtuberでは厳しいと思う。何かを新しいものを組み合わせないとダメそう。
|
||||
|
||||
みんなを楽しませるもので、かつ気軽に楽しめるもので、今までにないもの。
|
||||
|
||||
どういった形で実現できそうかな。
|
||||
|
||||
### ゲーム動画の注釈
|
||||
|
||||
今回実装したもの
|
||||
|
||||
#### room(home)
|
||||
|
||||
vではみんな背景にroomを表示しているので作ることにした。
|
||||
|
||||
twinmotion+collisionで実装してる。meshを全選択して、右クリックで`アセットアクション -> プロパティマトリクスで選択内容を... -> collision complexity(use complex collision as simple...)`を選択します。
|
||||
|
||||
#### motion caps
|
||||
|
||||
モーションキャプチャ
|
||||
|
||||
vrmvmc+abpの切り替えで実装してる。blueprintはこんな感じ。
|
||||
|
||||
```sh
|
||||
cast to CBP_SandboxCharacter_ai -> target:ai, target:sk_ai -> set anim instance class -> ABP_GenericRetarget_2
|
||||
```
|
||||
|
||||
#### loading screen
|
||||
|
||||
ローディング画面
|
||||
|
||||
バグあり、build後に一時背景が映り込んでしまう。editor上では再現しないし、コード的には映り込まないはず。
|
14
content/blog/2024-08-24-ue.md
Normal file
14
content/blog/2024-08-24-ue.md
Normal file
@ -0,0 +1,14 @@
|
||||
+++
|
||||
date = "2024-08-24"
|
||||
tags = ["ue","ue5"]
|
||||
title = "gpt-4o-miniに乗り換え"
|
||||
+++
|
||||
|
||||
この前、[elevenlabs](https://elevenlabs.io/)を使って、ゲーム内で音声で聞くと音声で返してくれるようにした。
|
||||
|
||||
その時、`gpt-3.5-turbo`より`gpt-4o-mini`のほうが安いことに気づいたので乗り換えた。
|
||||
|
||||
https://openai.com/index/gpt-4o-mini-advancing-cost-efficient-intelligence/
|
||||
|
||||
ただ、elevenlabsは、かわいい声が少なかったのと、日本語の発音がちょっと微妙なことがあるので、これよりおすすめのものはないかなと思ってる。
|
||||
|
98
content/blog/2024-08-28-three.md
Normal file
98
content/blog/2024-08-28-three.md
Normal file
@ -0,0 +1,98 @@
|
||||
+++
|
||||
date = "2024-08-28"
|
||||
tags = ["vrm","three"]
|
||||
title = "theatre.jsを使ってみる"
|
||||
+++
|
||||
|
||||
three.jsをGUIで調整するためのlibです。reactのexampleがあるのでreactで書きます。
|
||||
|
||||
ちなみに、最近は色々なprojectがreactばかりになってきたのでreactを使っています。ただ、vueのほうがわかりやすいのでvueをおすすめしておきます。jsに近いほどよいですね。最初はjs+html+cssが一番ですけど。
|
||||
|
||||
```sh
|
||||
$ nvm use 21
|
||||
|
||||
# https://www.theatrejs.com/docs/latest/getting-started/with-react-three-fiber
|
||||
$ npx create-react-app theatre --template typescript
|
||||
$ npm install --save react three @react-three/fiber @theatre/core @theatre/studio @theatre/r3f
|
||||
$ npm install --save-dev @types/three
|
||||
```
|
||||
|
||||
```json:package.json
|
||||
{
|
||||
"name": "vite-react-typescript-starter",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@theatre/core": "^0.6.1",
|
||||
"@theatre/r3f": "^0.7.2",
|
||||
"@theatre/studio": "^0.6.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@vitejs/plugin-react": "^4.3.1",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.11",
|
||||
"globals": "^15.9.0",
|
||||
"typescript": "^5.5.3",
|
||||
"typescript-eslint": "^8.3.0",
|
||||
"vite": "^5.4.2"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```ts:src/main.tsx
|
||||
import './index.css'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import React, { useEffect } from 'react'
|
||||
import { Canvas } from '@react-three/fiber'
|
||||
import studio from '@theatre/studio'
|
||||
import extension from '@theatre/r3f/dist/extension'
|
||||
import { SheetProvider, editable as e, PerspectiveCamera } from '@theatre/r3f'
|
||||
import { getProject } from '@theatre/core'
|
||||
import demoProjectState from './state.json'
|
||||
|
||||
studio.initialize()
|
||||
studio.extend(extension)
|
||||
|
||||
//const demoSheet = getProject('Demo Project', { state: demoProjectState }).sheet('Demo Sheet')
|
||||
const demoSheet = getProject('Demo Project').sheet('Demo Sheet')
|
||||
const App = () => {
|
||||
useEffect(() => {
|
||||
demoSheet.project.ready.then(() => demoSheet.sequence.play({ iterationCount: Infinity, range: [0, 1] }))
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Canvas>
|
||||
<SheetProvider sheet={demoSheet}>
|
||||
<PerspectiveCamera theatreKey="Camera" makeDefault position={[0, 0, 0]} fov={75} />
|
||||
<ambientLight />
|
||||
<e.pointLight theatreKey="Light" position={[1, 1, 1]} />
|
||||
<e.mesh theatreKey="Cube">
|
||||
<boxGeometry args={[1, 1, 1]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
</e.mesh>
|
||||
</SheetProvider>
|
||||
</Canvas>
|
||||
)
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(<App />)
|
||||
```
|
||||
|
||||
```sh
|
||||
$ npm run dev
|
||||
```
|
||||
|
||||
https://github.com/AndrewPrifer/CodropsCameraFlyThroughTutorial/
|
145
content/blog/2024-09-01-voice.md
Normal file
145
content/blog/2024-09-01-voice.md
Normal file
@ -0,0 +1,145 @@
|
||||
+++
|
||||
date = "2024-09-01"
|
||||
tags = ["mac","ue", "obs"]
|
||||
title = "obs+discord+beatriceを使う"
|
||||
+++
|
||||
|
||||
obsは配信のためのアプリですが、windowsで使えるマイクがないので、iphoneかmacを使って音声変換してdiscordから取ってみた。このやり方がすごく良かった。あと後述する[ちやは神社](https://chihaya369.booth.pm/)が配布しているrvc modelがすごかった。
|
||||
|
||||
- https://github.com/w-okada/voice-changer
|
||||
- https://huggingface.co/wok000/vcclient000/tree/main
|
||||
|
||||
```sh
|
||||
./start_http.command
|
||||
```
|
||||
|
||||
- https://booth.pm/ja/items/4701666
|
||||
|
||||
まずマイクに近づかないと音を取れないのがきつい。なので離れていても音を取るのがいい。最初はiphone(discord)からwindowsに繋いだらかなり離れててもしっかりと音を取れてよかった。
|
||||
|
||||
ただ、音声変換があまりうまくいかなくて、obsのVST Pluginだったかを使うのは厳しいと感じた。なので、方向を変えてmac(discord)から接続することにした。
|
||||
|
||||
macのgaragebandによる音声変換も使えるけど、beatriceがいいらしいのでw-okada/voice-changerから使うことにした。
|
||||
|
||||
## webcam motion capture
|
||||
|
||||
- https://webcammotioncapture.info/ja/
|
||||
|
||||
まずvrm4u(vmc)ではwebcam motion captureが動きません。ue5.4.4のvrm4u(202408)の環境ですが動かない。しかし、一旦、vseefaceなどを挟むと使用できます。ただし、`webcam motion capture`と`w-okada/voice-changer`を同時に使用するとアプリが落ちます。時間経過でwebcamのcaptureが動かなくなります。
|
||||
|
||||
## discordを使う理由
|
||||
|
||||
discordからでないと音量が大きくなりません。小さい声でもちゃんと拾って変換してくれるやり方として、discordを使うとうまくいきました。ただし、高価なマイクがある場合はそちらの方が良いでしょう。
|
||||
|
||||
## 配信環境
|
||||
|
||||
[mac]
|
||||
1. VB-Cableで仮想オーディオデバイス(output)を起動
|
||||
2. discordのinputに指定
|
||||
3. w-okada/voice-changerを起動してoutputにいれる
|
||||
|
||||
[windows]
|
||||
|
||||
4. discordでボイスチャットに入る(別アカウント)、ここで音声が聴こえるはず
|
||||
5. obsでdiscordの音声キャプチャ
|
||||
6. webcam motion capture -> vseefaceでカメラからvrmを動かせるようにする
|
||||
7. ue5を起動して、obsでウィンドウキャプチャ。youtubeアカウントに接続し、配信管理から予約、開始する。開始したあとは準備できるまで音声キャプチャをミュート。ゲームを調整できれば開始してミュートを切る
|
||||
|
||||
配信中にやること。操作がちょっと大変です。カメラ操作や移動操作など。
|
||||
|
||||
また、英語音声に変換しながら配信する予定。自動音声変換は精度が悪かったので文字列にしました。文字を打ち込むかあらかじめ用意しておいた文字を変換します。これをmacで流すとdiscordを通じてかなりはっきり英語を喋ってくれます。たまってきたら音声ファイルをpecoとかfzfで検索できるようにしておくと良さそう。
|
||||
|
||||
```sh:voice.zsh
|
||||
#!/bin/zsh
|
||||
|
||||
d=${0:a:h}
|
||||
f=$d/voice.json
|
||||
vdir=$d/voice_dir
|
||||
cfg=~/.config/ai/voice.json
|
||||
|
||||
if [ ! -d $vdir ];then
|
||||
mkdir -p $vdir
|
||||
fi
|
||||
|
||||
if [ -z "$1" ];then
|
||||
ep=`cat $f|jq length`
|
||||
ep=$((ep - 1))
|
||||
else
|
||||
ep=$1
|
||||
fi
|
||||
|
||||
j=`cat $f|jq ".[$ep].body"`
|
||||
n=`echo $j|jq length`
|
||||
n=$((n - 1))
|
||||
|
||||
fnction voice_chat() {
|
||||
#echo chat : https://openai.com
|
||||
#echo voice : https://elevenlabs.io
|
||||
|
||||
vfile=$vdir/${ep}_${i}.mp3
|
||||
if [ -f $vfile ];then
|
||||
mpv $vfile
|
||||
echo voice ok
|
||||
read
|
||||
continue
|
||||
fi
|
||||
|
||||
echo "[$i]"
|
||||
t=`echo $j|jq -r ".[$i].text"`
|
||||
echo $t
|
||||
echo ---
|
||||
read chat_text
|
||||
if [ -z "$chat_text" ] && [ -n "$t" ];then
|
||||
chat_text=$t
|
||||
fi
|
||||
echo $chat_text
|
||||
echo ---
|
||||
|
||||
chat_api=`cat $cfg|jq -r .chat_api`
|
||||
voice_text=`curl -sL https://api.openai.com/v1/chat/completions -H "Content-Type: application/json" -H "Authorization: Bearer $chat_api" -d "{ \"model\": \"gpt-4o-mini\", \"messages\": [{\"role\": \"user\", \"content\": \"次の文章を英語に訳して\n\n$chat_text\"}], \"temperature\": 0.7 }"|jq ".choices.[].message.content"`
|
||||
|
||||
echo $voice_text
|
||||
# like-model
|
||||
voice_id=zrHiDhphv9ZnVXBqCLjz
|
||||
# alice-model
|
||||
voice_id=Xb7hH8MSUJpSbSDYk0k2
|
||||
voice_api=`cat $cfg|jq -r .voice_api`
|
||||
curl -sL --request POST \
|
||||
--url https://api.elevenlabs.io/v1/text-to-speech/$voice_id \
|
||||
--header "xi-api-key: $voice_api" \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data "{
|
||||
\"text\": $voice_text,
|
||||
\"model_id\": \"eleven_multilingual_v2\",
|
||||
\"voice_settings\": {
|
||||
\"stability\": 0.5,
|
||||
\"similarity_boost\": 0.5
|
||||
}
|
||||
}" --output $vfile && mpv $vfile
|
||||
}
|
||||
|
||||
for ((i=0;i<=$n;i++))
|
||||
do
|
||||
voice_chat
|
||||
done
|
||||
```
|
||||
|
||||
```sh:voice.json
|
||||
[
|
||||
{
|
||||
"id":1,
|
||||
"body" :[
|
||||
{ "text":"こんにちは、みんな"},
|
||||
{ "text":"配信を見てくれてありがとう。またね。"}
|
||||
]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
### なぜue5(editor)で起動するのか?
|
||||
|
||||
editorは相当重いので本来はbuildしたpackageで実行する方が良いです。しかし、buildするとvrm4u(vmc)の表情が動かなくなります。またstandaloneはもっと重くなりますので選択しません。PIEでwindowを作りません。windowを作ると新しい問題が発生しますし、動作も重くなります。
|
||||
|
||||
## rvc model
|
||||
|
||||
boothでいくつかrvc modelを販売しているけどおすすめしません。freeのものも含めて使用できるレベルのものは現状少ないと感じています。
|
37
content/blog/2024-09-02-ws-sixel.md
Normal file
37
content/blog/2024-09-02-ws-sixel.md
Normal file
@ -0,0 +1,37 @@
|
||||
+++
|
||||
date = "2024-09-02"
|
||||
tags = ["windows"]
|
||||
title = "windows terminalでsixelを使う"
|
||||
+++
|
||||
|
||||
現時点ではwindows terminalの`preview 1.22`で使用できるようになりました。
|
||||
|
||||
https://github.com/microsoft/terminal/releases
|
||||
|
||||
![](/img/windows_terminal_sixel.png)
|
||||
|
||||
最も手っ取り早くlibsixelをbuildする方法はmsys2を使うことです。
|
||||
|
||||
```sh
|
||||
$ scoop install msys2
|
||||
# ここでmsys2を起動
|
||||
$ msys2
|
||||
|
||||
$ pacman -S git make gcc
|
||||
$ git clone https://github.com/saitoha/libsixel
|
||||
$ cd libsixel
|
||||
$ ./configure
|
||||
$ make
|
||||
$ make install
|
||||
|
||||
$ ls ./converters/img2sixel.exe
|
||||
$ which img2sixel.exe
|
||||
|
||||
# 画像を表示
|
||||
$ img2sixel.exe syui.png
|
||||
|
||||
$ exit
|
||||
```
|
||||
|
||||
ref : https://devblogs.microsoft.com/commandline/windows-terminal-preview-1-22-release/
|
||||
|
222
content/blog/2024-10-01-holo.md
Normal file
222
content/blog/2024-10-01-holo.md
Normal file
@ -0,0 +1,222 @@
|
||||
+++
|
||||
date = "2024-10-01"
|
||||
tags = ["vrm"]
|
||||
title = "3Dホログラムを作った"
|
||||
+++
|
||||
|
||||
この前、ホログラムを作ってみようと思い10分くらいで作ってみた。必要なものは透明板だけ。それを斜めに設置してスマホで黒背景の3Dモデルを表示する。
|
||||
|
||||
もともと写真立てが余っててそれに付いてる透明板を使った。
|
||||
|
||||
設置にはスマホの箱が便利だった。どちらかに切り込みを入れればいいと思う。
|
||||
|
||||
ここまではっきり映るとは思ってなくて驚いた。余ってるスマホを何に使おうと思ってたので、こういうのを表示させておくといいかも。ただし、表記は反転させないといけないみたい。
|
||||
|
||||
<video loop autoplay muted controls style="width:100%;"><source src="/img/holo.mov"></video>
|
||||
|
||||
## react + tsx + three-vrm
|
||||
|
||||
一応、最小限のコードを載せておきます。反転は対応しています。
|
||||
|
||||
```json:package.json
|
||||
{
|
||||
"name": "holoai",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@pixiv/three-vrm": "^3.1.1",
|
||||
"@pixiv/three-vrm-animation": "^3.1.1",
|
||||
"@react-three/drei": "^9.114.0",
|
||||
"@react-three/fiber": "^8.17.9",
|
||||
"@react-three/postprocessing": "^2.16.3",
|
||||
"@testing-library/jest-dom": "^5.17.0",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.112",
|
||||
"@types/react": "^18.3.10",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"@types/three": "^0.167.2",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"three": "^0.167.1",
|
||||
"three-stdlib": "^2.30.5",
|
||||
"typescript": "^4.9.5",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```css:src/index.css
|
||||
.time {
|
||||
transform:scale(-1,1);
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
padding: 10px;
|
||||
z-index: 100;
|
||||
color: #e9ff00;
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
```
|
||||
|
||||
```ts:src/pages/time.tsx
|
||||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
//function reverseString(str: string): string {
|
||||
// return str.split('').reverse().join('');
|
||||
//}
|
||||
|
||||
const ScreenTimeCanvas: React.FC = () => {
|
||||
const [currentDateTime, setCurrentDateTime] = useState<string>('');
|
||||
//const [reversedDateTime, setReversedDateTime] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
const updateDateTime = () => {
|
||||
const now = new Date();
|
||||
const formatted = now.toLocaleString("ja-JP", {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
setCurrentDateTime(formatted);
|
||||
//setReversedDateTime(reverseString(formatted));
|
||||
};
|
||||
updateDateTime();
|
||||
const timer = setInterval(updateDateTime, 1000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="time">
|
||||
<p>{currentDateTime}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScreenTimeCanvas;
|
||||
```
|
||||
|
||||
```ts:src/pages/vrm.tsx
|
||||
import * as THREE from 'three'
|
||||
import React, { useState, useEffect, useRef } from 'react';
|
||||
import { OrbitControls } from '@react-three/drei'
|
||||
import { useFrame, Canvas } from '@react-three/fiber';
|
||||
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
|
||||
import { VRM, VRMUtils, VRMLoaderPlugin } from '@pixiv/three-vrm';
|
||||
import { VRMAnimationLoaderPlugin, VRMAnimation, createVRMAnimationClip } from "@pixiv/three-vrm-animation";
|
||||
|
||||
interface ModelProps {
|
||||
url: string
|
||||
url_anim: string
|
||||
scale: [number, number, number]
|
||||
position: [number, number, number]
|
||||
rotation: [number, number, number]
|
||||
}
|
||||
|
||||
const VRMModel: React.FC<ModelProps> = ({ url, url_anim, position, rotation, scale }) => {
|
||||
|
||||
const [vrm, setVrm] = useState<VRM | null>(null);
|
||||
const mixerRef = useRef<THREE.AnimationMixer | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const loader = new GLTFLoader();
|
||||
loader.register((parser) => new VRMLoaderPlugin(parser));
|
||||
loader.register((parser) => new VRMAnimationLoaderPlugin(parser));
|
||||
loader.load(url, (gltf) => {
|
||||
const vrmModel = gltf.userData.vrm as VRM;
|
||||
VRMUtils.removeUnnecessaryJoints(vrmModel.scene);
|
||||
setVrm(vrmModel);
|
||||
const mixer = new THREE.AnimationMixer(vrmModel.scene);
|
||||
mixerRef.current = mixer;
|
||||
loader.load(url_anim, (animGltf) => {
|
||||
const vrmAnimations = animGltf.userData.vrmAnimations as VRMAnimation[];
|
||||
if (vrmAnimations && vrmAnimations.length > 0) {
|
||||
const clip = createVRMAnimationClip(vrmAnimations[0], vrmModel);
|
||||
mixer.clipAction(clip).play();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [url, url_anim]);
|
||||
|
||||
useFrame((state, delta) => {
|
||||
if (mixerRef.current) mixerRef.current.update(delta);
|
||||
if (vrm) vrm.update(delta);
|
||||
});
|
||||
|
||||
return vrm ? <primitive object={vrm.scene} position={position} rotation={rotation} scale={scale}/> : null;
|
||||
};
|
||||
|
||||
export const VRMModelCanvas = () => {
|
||||
return (
|
||||
<div style={{ height: '100vh', width: '100vw' }}>
|
||||
<Canvas
|
||||
shadows
|
||||
gl={{
|
||||
//toneMapping: THREE.ACESFilmicToneMapping,
|
||||
//toneMapping: THREE.ReinhardToneMapping,
|
||||
toneMapping: THREE.NeutralToneMapping,
|
||||
toneMappingExposure: 1,
|
||||
alpha: true,
|
||||
powerPreference: "high-performance",
|
||||
antialias: true,
|
||||
//stencil: false,
|
||||
//depth: false
|
||||
}}
|
||||
camera={{ position: [1.2, 0, 0] }}>
|
||||
<color attach="background" args={["#000"]} /> {/* Light gray background */}
|
||||
<OrbitControls />
|
||||
<ambientLight intensity={10} />
|
||||
<pointLight position={[10, 10, 10]} />
|
||||
<VRMModel url="./models/t.vrm" url_anim="./models/default.vrma" position={[0, -0.6, 0]} rotation={[0, 0, 0]} scale={[1, 1, 1]} />
|
||||
</Canvas>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default VRMModelCanvas;
|
||||
```
|
||||
|
||||
```ts:src/App.tsx
|
||||
import React from 'react'
|
||||
import VRMModelCanvas from './pages/vrm'
|
||||
import ScreenTimeCanvas from './pages/time'
|
||||
|
||||
const App = () => {
|
||||
return (
|
||||
<>
|
||||
<VRMModelCanvas/>
|
||||
<ScreenTimeCanvas/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
192
content/blog/2024-10-02-vrm4u.md
Normal file
192
content/blog/2024-10-02-vrm4u.md
Normal file
@ -0,0 +1,192 @@
|
||||
+++
|
||||
date = "2024-10-02"
|
||||
lastmod = "2024-10-03"
|
||||
tags = ["vrm","ue"]
|
||||
title = "ue5.5と最新のvmc事情"
|
||||
+++
|
||||
|
||||
unreal engine 5.5.0 preview(ue5.5p)がインストールできるようになっています。今回は最新環境のvmc事情を解説します。
|
||||
|
||||
## vmcとは
|
||||
|
||||
webカメラから表情や動きをキャラクターに反映させるためのものです。vmcはprotocolとclientがあります。大抵はprotocolを指します。webカメラからの読み取りをcaptureといいます。つまり、captureとprotocolとclientを組みわせて動作します。ueで使うには更にvmcを受信してキャラクターに反映させるpluginが必要です。わけがわからないと思いますが、そんな感じです。
|
||||
|
||||
- https://github.com/sh-akira/VirtualMotionCapture
|
||||
- https://github.com/ruyo/VRM4U
|
||||
- https://github.com/HAL9HARUKU/VMC4UE
|
||||
- https://github.com/HAL9HARUKU/ueOSC
|
||||
- https://github.com/HAL9HARUKU/VRMMapExporter
|
||||
- https://github.com/vrm-c/UniVRM
|
||||
|
||||
## vmc4ueをue5.5でbuildしてみよう
|
||||
|
||||
> この手順は興味がある人以外は読み飛ばすことを推奨します。表情を動かしたい人はこの方法で問題を解決することができません。
|
||||
|
||||
`vmc4ue`はue5.1までしかbuildされていません。そこでue5.5でbuildして使えるようにしてみます。
|
||||
|
||||
まずc++でprojectを作成し、patchをsrcに当てて`$project/Plugins/`に入れます。projectをueで開くとbuildされます。正常に終了するとeditorが開きます。
|
||||
|
||||
> c++で作成したprojectには$project.slnが作成されますので、それを開いてrebuildしてもいいです。
|
||||
|
||||
なお、patchはbuildが通るよう適当に作ったものです。vmcは動きますが、表情は動きませんでした。
|
||||
|
||||
```cpp:VMC4UEBlueprintFunctionLibrary.cpp.patch
|
||||
--- ./VMC4UE/VMC4UE/Source/VMC4UE/Source/VMC4UEBlueprintFunctionLibrary.cpp
|
||||
+++ ./VMC4UEBlueprintFunctionLibrary.cpp
|
||||
@@ -119,27 +119,29 @@ UVMC4UEStreamingSkeletalMeshTransform* UVMC4UEBlueprin
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
-
|
||||
+
|
||||
+ UVMC4UEStreamingSkeletalMeshTransform* StreamingSkeletalMeshTransform = nullptr;
|
||||
+
|
||||
+ // Try to get existing transform
|
||||
{
|
||||
- // Get
|
||||
FRWScopeLock RWScopeLock(OSCManager->RWLock, FRWScopeLockType::SLT_ReadOnly);
|
||||
- auto StreamingSkeletalMeshTransform = OSCManager->StreamingSkeletalMeshTransformMap.Find(Port);
|
||||
- if (StreamingSkeletalMeshTransform != nullptr)
|
||||
+ auto FoundTransform = OSCManager->StreamingSkeletalMeshTransformMap.Find(Port);
|
||||
+ if (FoundTransform != nullptr)
|
||||
{
|
||||
- return *StreamingSkeletalMeshTransform;
|
||||
+ return *FoundTransform;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ // Create new transform if not found
|
||||
{
|
||||
- // Create
|
||||
FRWScopeLock RWScopeLock(OSCManager->RWLock, FRWScopeLockType::SLT_Write);
|
||||
- auto StreamingSkeletalMeshTransform = OSCManager->StreamingSkeletalMeshTransformMap.Find(Port);
|
||||
- if (StreamingSkeletalMeshTransform != nullptr)
|
||||
+ auto FoundTransform = OSCManager->StreamingSkeletalMeshTransformMap.Find(Port);
|
||||
+ if (FoundTransform != nullptr)
|
||||
{
|
||||
- return *StreamingSkeletalMeshTransform;
|
||||
+ return *FoundTransform;
|
||||
}
|
||||
- UVMC4UEStreamingSkeletalMeshTransform* NewStreamingSkeletalMeshTransform = NewObject<UVMC4UEStreamingSkeletalMeshTransform>();
|
||||
|
||||
- //FRWScopeLock RWScopeLock2(NewStreamingSkeletalMeshTransform->RWLock, FRWScopeLockType::SLT_Write);
|
||||
+ UVMC4UEStreamingSkeletalMeshTransform* NewStreamingSkeletalMeshTransform = NewObject<UVMC4UEStreamingSkeletalMeshTransform>();
|
||||
OSCManager->StreamingSkeletalMeshTransformMap.Emplace(Port, NewStreamingSkeletalMeshTransform);
|
||||
|
||||
// Bind Port
|
||||
@@ -149,9 +151,10 @@ UVMC4UEStreamingSkeletalMeshTransform* UVMC4UEBlueprin
|
||||
|
||||
OSCManager->OscReceivers.Emplace(OscReceiver);
|
||||
|
||||
- return NewStreamingSkeletalMeshTransform;
|
||||
+ StreamingSkeletalMeshTransform = NewStreamingSkeletalMeshTransform;
|
||||
}
|
||||
- return nullptr;
|
||||
+
|
||||
+ return StreamingSkeletalMeshTransform;
|
||||
}
|
||||
|
||||
void UVMC4UEBlueprintFunctionLibrary::RefreshConnection(float Seconds)
|
||||
```
|
||||
|
||||
```cpp:VMC4UEBoneMappingAssetFactory.cpp.patch
|
||||
--- ./VMC4UE/Source/VMC4UEEd/Source/VMC4UEBoneMappingAssetFactory.cpp
|
||||
+++ ./VMC4UEBoneMappingAssetFactory.cpp
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "../../VMC4UE/Include/VMC4UEStreamingData.h"
|
||||
#include "Dom/JsonObject.h"
|
||||
#include "JsonObjectConverter.h"
|
||||
+#include "UObject/ConstructorHelpers.h"
|
||||
+#include "UObject/UObjectGlobals.h"
|
||||
|
||||
UVMC4UEBoneMappingAssetFactory::UVMC4UEBoneMappingAssetFactory(const FObjectInitializer &ObjectInitializer)
|
||||
: Super(ObjectInitializer)
|
||||
@@ -26,11 +28,12 @@
|
||||
return UVMC4UEVRMMapping::StaticClass();
|
||||
}
|
||||
|
||||
+
|
||||
UObject *UVMC4UEBoneMappingAssetFactory::FactoryCreateText(UClass *InClass, UObject *InParent, FName InName, EObjectFlags Flags, UObject *Context, const TCHAR *Type, const TCHAR *&Buffer, const TCHAR *BuferEnd, FFeedbackContext *Warn)
|
||||
{
|
||||
FString TextData = FString(Buffer);
|
||||
|
||||
- UVMC4UEVRMMapping *NewAsset = CastChecked<UVMC4UEVRMMapping>(StaticConstructObject_Internal(InClass, InParent, InName, Flags));
|
||||
+ UVMC4UEVRMMapping* NewAsset = NewObject<UVMC4UEVRMMapping>(InParent, InClass, InName, Flags);
|
||||
if (!IsValid(NewAsset))
|
||||
{
|
||||
return nullptr;
|
||||
```
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/HAL9HARUKU/VMC4UE
|
||||
|
||||
$ patch -u ./VMC4UE/VMC4UE/Source/VMC4UE/Source/VMC4UEBlueprintFunctionLibrary.cpp < VMC4UEBlueprintFunctionLibrary.cpp.patch
|
||||
$ patch -u ./VMC4UE/VMC4UE/Source/VMC4UEEd/Source/VMC4UEBoneMappingAssetFactory.cpp < VMC4UEBoneMappingAssetFactory.cpp.patch
|
||||
```
|
||||
|
||||
## vrm4uを5.5向けにbuildしてみる
|
||||
|
||||
> この手順は興味がある人以外は読み飛ばすことを推奨します。表情を動かしたい人はこの方法で問題を解決することができません。
|
||||
|
||||
vrm4uは既に5.5のbuildを[releases](https://github.com/ruyo/VRM4U/releases/tag/20241002)しています。
|
||||
|
||||
ただいくつかの処理が5,4,0向けのようです。それを書き直してbuildしてみます。
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/ruyo/VRM4U
|
||||
$ cd VRM4U
|
||||
$ git reset --hard a261860872936c8654e1705a91cff6f8224dbee5
|
||||
$ grep -R 5,4,0 ./Source/*|cut -d : -f 1|xargs sed -i '' 's/5,4,0/5,5,0/g'
|
||||
$ grep -R 5,5,0 .
|
||||
```
|
||||
|
||||
これを先程と同じ手順でue5.5で開いてbuildします。
|
||||
|
||||
## vrm4u(vmc)はbuild後にも表情を動かせるのだろうか
|
||||
|
||||
わかりません。私の環境下では動きませんでした。他の人も動かない可能性がありますが、issueを読む限り動くようにも思えます。
|
||||
|
||||
> この問題は`vrm4u 20241007`で修正されました
|
||||
|
||||
issue : https://git.syui.ai/ai/ue/issues/9
|
||||
|
||||
それではどうするのか。livelink(face)を使います。ここからは少しめんどくさいことになりますが、かなり多くのアプリが必要です。
|
||||
|
||||
- 動き : webcam motion capture(vmc送信) + vseeface(vmc受信/送信) + vrm4u(vmc受信)
|
||||
- 表情 : iphone + livelink face
|
||||
|
||||
まず、`vrm4u`は`webcam motion capture`のvmcを直接受信できません。なので、一旦、vseefaceなどのclientで受信する必要があります。それをvrm4uで受信するportに送信します。なお、`vmc4ue`では直接受信できて動きます。また、xr-animator, vseeface, vmc(client)も受信できて動いています。不思議な現象です。
|
||||
|
||||
vrm4u(vmc)はbuild後は表情が動かないので、表情はlivelinkを使用します。これはiphoneに`livelink face`というappがあります。ueでいくつかのpluginを有効にします。
|
||||
|
||||
- live link
|
||||
- apple arkit
|
||||
- apple arkit face support
|
||||
|
||||
`/VRM4U/Util/Actor/latest/BP_LiveLinkFace`をmapにおいて、`live link subject -> iphone`, `target actor sk -> SK_$name`を設定します。
|
||||
|
||||
![](/img/ue-2024-10-01-7.36.38.png)
|
||||
|
||||
これでbuildすると表情を動かすことができます。
|
||||
|
||||
## character(player)にlivelinkを当てるには
|
||||
|
||||
character(player)にlivelinkを当てるにはblueprintを編集する必要があります。characterのdirにでも`BP_LiveLinkFace`をcopyして`BP_Player(CBP_Character)`に追加します。
|
||||
|
||||
![](/img/ue-2024-10-01-7.36.39.png)
|
||||
|
||||
ここで`BP_LiveLinkFace -> livelink subject -> iphone`をセットしておきます。
|
||||
|
||||
次に`BP_LiveLinkFace`を以下のような形で`sk_$name`に置き換えます。場所はコメントを参考にしてください。私の場合は見た目をカスタマイズしているので少し複雑です。
|
||||
|
||||
<iframe src="https://blueprintue.com/render/pu_xl52s/" scrolling="no" allowfullscreen width="100%" height="400px"></iframe>
|
||||
|
||||
https://blueprintue.com/blueprint/pu_xl52s/
|
||||
|
||||
## vmcで行くべきか
|
||||
|
||||
基本的には依存関係が少なく使うアプリが少ないほうがいいですね。vmcの更新頻度やclient, vrm1のsupport状況を見るとueはlivelink路線のほうがいいかもしれません。
|
||||
|
||||
仮に`webcam motion capture -> vrm4u`で表情も体も動かせるならvmcで問題ないですが、表情にlivelinkを使うなら全部統一するのがいいですね。
|
||||
|
||||
[mocopi](https://apps.apple.com/jp/app/mocopi/id6444393701)や[iclone](https://www.unrealengine.com/marketplace/ja/product/iclone-unreal-live-link)はlivelinkなのでそちらが使えると思います。
|
29
content/blog/2024-10-12-wasmer.md
Normal file
29
content/blog/2024-10-12-wasmer.md
Normal file
@ -0,0 +1,29 @@
|
||||
+++
|
||||
date = "2024-10-12"
|
||||
tags = ["wasm"]
|
||||
title = "wasmでwasmer-shを動かしてみる"
|
||||
+++
|
||||
|
||||
https://docs.wasmer.io/install
|
||||
|
||||
```sh
|
||||
$ wasmer run wasmer/wasmer-sh -- --port 4480 --host 127.0.0.1
|
||||
```
|
||||
|
||||
```sh
|
||||
$ wasmer login
|
||||
$ wasmer deploy
|
||||
wasmer/wasmer-sh
|
||||
|
||||
# custom domain
|
||||
https://docs.wasmer.io/edge/configuration/custom-domains
|
||||
```
|
||||
|
||||
https://wterm-syui.wasmer.app/
|
||||
|
||||
```sh
|
||||
# https://wterm-syui.wasmer.app/
|
||||
$ wasmer run clang/clang example.c -o example.wasm
|
||||
$ ls
|
||||
$ ./example.wasm
|
||||
```
|
80
content/blog/2024-10-20-stream.md
Normal file
80
content/blog/2024-10-20-stream.md
Normal file
@ -0,0 +1,80 @@
|
||||
+++
|
||||
date = "2024-10-20"
|
||||
tags = ["cloudflare"]
|
||||
title = "restreamerでobsを配信する"
|
||||
+++
|
||||
|
||||
live配信するには様々な方法があり、色々と検討していますが、やはりobsを使うのが一番いいという結論になりました。今回は配信環境を構築する方法を紹介します。
|
||||
|
||||
https://github.com/obsproject/obs-studio
|
||||
|
||||
## ue5を使うのはリスクが大きく拡張性がない
|
||||
|
||||
pixel streamingを使った配信を考えました。操作機能を無効にして配信する方法です。
|
||||
|
||||
しかし、これには問題が多かった。
|
||||
|
||||
例えば、すべての機能をueで実装する必要があります。
|
||||
|
||||
一応実装してみましたが大変な上に不便が多かった。例えば、ゲームがクラッシュすると音声などが途切れますよね。
|
||||
|
||||
ue5だけで完結することだけが魅力ですが、拡張性がありませんし、リスクが大きいのです。
|
||||
|
||||
## restreamerを使う
|
||||
|
||||
次に検討したのは`restreamer`を使う方法です。これは配信serverのようなもので、簡易ですがpageもカスタマイズできます。
|
||||
|
||||
https://github.com/datarhei/restreamer
|
||||
|
||||
```yml:compose.yaml
|
||||
services:
|
||||
|
||||
restreamer:
|
||||
image: datarhei/restreamer
|
||||
#image: datarhei/restreamer:cuda-latest
|
||||
ports:
|
||||
- 8080:8080
|
||||
- 1935:1935
|
||||
- 6000:6000/udp
|
||||
restart: always
|
||||
volumes:
|
||||
- ./data/config:/core/config
|
||||
- ./data/data:/core/data
|
||||
```
|
||||
|
||||
`8080`がweb, `1935`がrtmp, `6000`がsrtです。rtmpとsrtではsrtのほうが高品質で遅延が少なくなると思います。使わないものはportを閉じてもokです。基本的にはバッティングなどもありますから以下のようにlocahostのportを変えて使うのがいいですね。
|
||||
|
||||
```yml:compose.yaml
|
||||
services:
|
||||
restreamer:
|
||||
ports:
|
||||
- 8980:8080
|
||||
- 1835:1935
|
||||
- 6700:6000/udp
|
||||
```
|
||||
|
||||
使い方は簡単で最初にwebにアクセスしてadminを作ります。設定を行い、domain(ip)を`127.0.0.1`にします。ここではsrt protocolを使います。obsで配信をカスタムにしてrestreamerで発行されたurlを使用すればokです。
|
||||
|
||||
なお、同じserverでない場合はobsに設定するurlはipv4に変換します。
|
||||
|
||||
```sh
|
||||
- srt://127.0.0.1:6700
|
||||
+ srt://192.168.1.99:6700
|
||||
```
|
||||
|
||||
systemから`expert mode`を選択しましょう。
|
||||
|
||||
現時点で録画機能はありません。つまり、配信終了時にffmpegでconvertしてdocker volumeに保存し、再視聴が可能になる機能があると嬉しいですね。
|
||||
|
||||
https://github.com/datarhei/restreamer/issues/692
|
||||
|
||||
## mediacmsに保存する
|
||||
|
||||
https://github.com/mediacms-io/mediacms
|
||||
|
||||
`mediacms`を使用することで録画をuploadすることは可能です。obsで配信、録画を行い、終了時にmediacmsにuploadする方法です。
|
||||
|
||||
しかし、別のpageに移動しなければなりませんし、管理システムが異なるので良い方法とは言えません。
|
||||
|
||||
一番いいのは`youtube`を利用することですが、すべて自前で構築する場合は`restreamer` + `mediacms`が良さそう。
|
||||
|
279
content/blog/2024-10-21-bbs.md
Normal file
279
content/blog/2024-10-21-bbs.md
Normal file
@ -0,0 +1,279 @@
|
||||
+++
|
||||
date = "2024-10-21"
|
||||
tags = ["cloudflare", "bluesky"]
|
||||
title = "blueskyのoauthとbbsをlivestream serviceに実装する"
|
||||
+++
|
||||
|
||||
youtubeのlivestreamはchatのような書き込みができます。それを再現します。
|
||||
|
||||
まずはblueskyのoauthが動作するかの確認です。これは[bluesky-social/cookbook](https://github.com/bluesky-social/cookbook/tree/main/python-oauth-web-app)を使います。
|
||||
|
||||
```sh
|
||||
# https://github.com/bluesky-social/cookbook/tree/main/python-oauth-web-app
|
||||
$ cd ./repos/cookbook/python-oauth-web-app
|
||||
$ rye sync
|
||||
$ rye run python3 -c 'import secrets; print(secrets.token_hex())'|xargs echo FLASK_SECRET_KEY|tr -d ' ' >> .env
|
||||
$ rye run python3 generate_jwk.py |xargs echo FLASK_CLIENT_SECRET_JWK|tr -d ' ' >> .env
|
||||
$ cat .env
|
||||
$ rye run flask run
|
||||
```
|
||||
|
||||
oauthはlocalhostでは動作しません。したがって、`ngrok`, `tailscale`, `cloudflare`などを使用します。個人的にはcloudflareがおすすめです。
|
||||
|
||||
```sh
|
||||
$ cloudflared tunnel --url http://localhost:5000
|
||||
```
|
||||
|
||||
表示されるurlにアクセスするとoauthでloginすることができました。oauthの情報はserverに保存されており以後は承認なしにlogin可能となります。
|
||||
|
||||
## bbsと連携する
|
||||
|
||||
今回はloginしている場合にbbsの書き込みシステムを表示します。
|
||||
|
||||
<video src="https://raw.githubusercontent.com/syui/img/master/movie/bluesky_oauth_livestream_gameplay.mp4" width="100%" height="500px" controls> </video>
|
||||
|
||||
```html:template/home.html
|
||||
{% block content %}
|
||||
{% if g.user %}
|
||||
<p>@{{ g.user['handle'] }}</p>
|
||||
<iframe src="example.com?handle={{ g.user['handle'] }}"></iframe>
|
||||
{% endif %}
|
||||
```
|
||||
|
||||
bbsを作ります。
|
||||
|
||||
```toml:Cargo.toml
|
||||
[package]
|
||||
name = "rust-bbs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4.0"
|
||||
rusqlite = { version = "0.28", features = ["bundled"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
askama = "0.11"
|
||||
```
|
||||
|
||||
```rust:src/main.rs
|
||||
use actix_web::{web, App, HttpServer, HttpResponse, Responder};
|
||||
use rusqlite::{Connection, Result as SqliteResult};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use askama::Template;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Post {
|
||||
id: i32,
|
||||
content: String,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "index.html")]
|
||||
struct IndexTemplate {
|
||||
posts: Vec<Post>,
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "post.html")]
|
||||
struct PostTemplate {}
|
||||
|
||||
fn init_db() -> SqliteResult<()> {
|
||||
let conn = Connection::open("sqlite.db")?;
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS posts (
|
||||
id INTEGER PRIMARY KEY,
|
||||
content TEXT NOT NULL
|
||||
)",
|
||||
[],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn index() -> impl Responder {
|
||||
let conn = Connection::open("sqlite.db").unwrap();
|
||||
let mut stmt = conn.prepare("SELECT id, content FROM posts ORDER BY id DESC").unwrap();
|
||||
let posts = stmt.query_map([], |row| {
|
||||
Ok(Post {
|
||||
id: row.get(0)?,
|
||||
content: row.get(1)?,
|
||||
})
|
||||
}).unwrap().filter_map(Result::ok).collect::<Vec<Post>>();
|
||||
|
||||
let template = IndexTemplate { posts };
|
||||
HttpResponse::Ok().body(template.render().unwrap())
|
||||
}
|
||||
|
||||
async fn post_form() -> impl Responder {
|
||||
let template = PostTemplate {};
|
||||
HttpResponse::Ok().body(template.render().unwrap())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct FormData {
|
||||
content: String,
|
||||
}
|
||||
|
||||
async fn submit_post(form: web::Form<FormData>) -> impl Responder {
|
||||
let conn = Connection::open("sqlite.db").unwrap();
|
||||
conn.execute(
|
||||
"INSERT INTO posts (content) VALUES (?1)",
|
||||
[&form.content],
|
||||
).unwrap();
|
||||
|
||||
web::Redirect::to("/").see_other()
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
init_db().unwrap();
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.route("/", web::get().to(index))
|
||||
.route("/post", web::get().to(post_form))
|
||||
.route("/submit", web::post().to(submit_post))
|
||||
})
|
||||
.bind("0.0.0.0:8080")?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
```
|
||||
|
||||
```html:template/index.html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Simple BBS</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Simple BBS</h1>
|
||||
<a href="/post">New Post</a>
|
||||
<ul>
|
||||
{% for post in posts %}
|
||||
<li>{{ post.content }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```html:template/post.html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>New Post</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>New Post</h1>
|
||||
<form action="/submit" method="post">
|
||||
<textarea name="content" required></textarea>
|
||||
<br>
|
||||
<input type="submit" value="Post">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```yml:compose.yml
|
||||
services:
|
||||
web:
|
||||
build: .
|
||||
ports:
|
||||
- "8080:8080"
|
||||
volumes:
|
||||
- ./sqlite.db:/sqlite.db
|
||||
```
|
||||
|
||||
```sh:Dockerfile.txt
|
||||
FROM syui/aios
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY . .
|
||||
RUN cargo build --release
|
||||
COPY ./templates /templates
|
||||
CMD ["/usr/src/app/target/release/rust-bbs"]
|
||||
```
|
||||
|
||||
```sh
|
||||
$ cargo build
|
||||
$ ./target/debug/rust-bbs
|
||||
|
||||
$ docker compose up
|
||||
$ curl -sL localhost:8080
|
||||
```
|
||||
|
||||
あとは、iframeからparamでhandleを取得するので、それを使用するようにしたり、cssで見栄えを整えたら完成です。
|
||||
|
||||
要点だけまとめたので適時修正してください。redirectしたときにurlを変更しないとiframeで呼び出したとき2回目からはhandleが使えません。
|
||||
|
||||
```rust:src/main.rs
|
||||
async fn submit_post(
|
||||
req: HttpRequest,
|
||||
form: web::Form<FormData>
|
||||
) -> Result<impl Responder, Error> {
|
||||
let query = web::Query::<QueryParams>::from_query(req.query_string())
|
||||
.unwrap_or_else(|_| web::Query(QueryParams { handle: None }));
|
||||
//let handle = query.handle.clone().filter(|h| !h.is_empty());
|
||||
//println!("Debug: Extracted handle: {:?}", handle);
|
||||
let handle = if !form.handle.is_empty() {
|
||||
form.handle.clone()
|
||||
} else {
|
||||
query.handle.clone().unwrap_or_default()
|
||||
};
|
||||
|
||||
println!("Debug: Using handle: {:?}", handle);
|
||||
|
||||
let conn = Connection::open("sqlite.db")
|
||||
.map_err(|_| ErrorInternalServerError("Database connection failed"))?;
|
||||
let result = conn.execute(
|
||||
"INSERT INTO posts (handle, content) VALUES (?1, ?2)",
|
||||
&[&form.handle, &form.content],
|
||||
);
|
||||
match result {
|
||||
Ok(_) => {
|
||||
let redirect_url = if !handle.is_empty() {
|
||||
format!("/?handle={}", handle)
|
||||
} else {
|
||||
"/".to_string()
|
||||
};
|
||||
Ok(HttpResponse::SeeOther()
|
||||
.append_header(("Location",
|
||||
redirect_url))
|
||||
.finish())
|
||||
},
|
||||
|
||||
//Ok(_) => Ok(web::Redirect::to("/").see_other()),
|
||||
Err(_) => Err(ErrorInternalServerError("Failed to insert post")),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```html:templates/index.html
|
||||
<form action="/submit" method="post">
|
||||
<input type="hidden" name="handle" id="handleInput">
|
||||
<textarea name="content" required></textarea>
|
||||
<input type="submit" value="Post">
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function getHandleFromUrl() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
return urlParams.get('handle');
|
||||
}
|
||||
window.onload = function() {
|
||||
const handle = getHandleFromUrl();
|
||||
if (handle) {
|
||||
document.getElementById('handleInput').value = handle;
|
||||
} else {
|
||||
document.getElementById('handleInput').value = "anonymous";
|
||||
}
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
```html:templates/home.htmll
|
||||
{% if g.user %}
|
||||
@{{ g.user['handle'] }}
|
||||
<iframe id="livechat" title="bskychat" width="100%" height="500" src="example.com/?handle={{ session['user_handle'] }}"></iframe>
|
||||
{% endif %}
|
||||
```
|
261
content/blog/2024-10-26-frontpage.md
Normal file
261
content/blog/2024-10-26-frontpage.md
Normal file
@ -0,0 +1,261 @@
|
||||
+++
|
||||
date = "2024-10-26"
|
||||
tags = ["cloudflare", "bluesky", "atproto"]
|
||||
title = "atprotoのfrontpageを触ってみる"
|
||||
[params]
|
||||
comment = "3l7nuqahc7q27"
|
||||
+++
|
||||
|
||||
前回、live配信にatprotoでoauth loginして掲示板(bbs)に書き込めるサイトを作成し、bbsは簡単にrustで自作したものを使っていました。
|
||||
|
||||
しかし、やはり機能的に不足していたのと、公式のoauth exampleがpythonで書かれていたため、python + rustでやっていました。
|
||||
|
||||
そこに[likeandscribe/frontpage](https://github.com/likeandscribe/frontpage)というものを見つけて、これはいいものだと思ったので触っていきます。
|
||||
|
||||
|
||||
詳しくはこちらを見てください。
|
||||
|
||||
- https://frontpage.fyi/post/tom.frontpage.team/3l6nbjyjmcg2v
|
||||
|
||||
これがどういったものかというと、おそらく、bsky.socialとは別サービスですがoauthでlogin(signin)でき、投稿情報は自身のpdsに保存されるのでしょう。また、`drainpipe`はこう書かれています。
|
||||
|
||||
> Drainpipe is a atproto firehose consumer written in rust. It knows how to reliably* take messages from the firehose, filter them, and forward them over HTTPs to a webhook receiver some place else on the internet.
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/likeandscribe/frontpage
|
||||
$ cd !$:t
|
||||
|
||||
$ nvm use 20
|
||||
$ pnpm i
|
||||
$ cat turbo.json
|
||||
$ pnpm exec turbo run --affected type-check
|
||||
```
|
||||
|
||||
turboを見て分かる通り、dbはtursoを使用するようです。また、`drainpipe`は`fly.io`ですね。
|
||||
|
||||
```sh
|
||||
TURSO_CONNECTION_URL
|
||||
TURSO_AUTH_TOKEN
|
||||
```
|
||||
|
||||
```sh
|
||||
$ cd packages-rs/drainpipe
|
||||
$ cargo install diesel_cli --no-default-features --features sqlite
|
||||
$ diesel setup
|
||||
$ diesel migration run
|
||||
|
||||
$ cp .env .env.local
|
||||
FRONTPAGE_CONSUMER_SECRET
|
||||
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
なお、ubuntuなどrustcのversionが古い場合は[rustup](https://rustup.rs/)を使ってpathを設定してください。
|
||||
|
||||
```sh
|
||||
$ rustup update
|
||||
|
||||
# ~/.zshrc
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
. $HOME/.cargo/env
|
||||
```
|
||||
|
||||
```sh
|
||||
$ cd packages/frontpage
|
||||
$ pnpm exec tsx ./scripts/generate-jwk.mts
|
||||
|
||||
# https://docs.turso.tech/quickstart
|
||||
$ turso db create
|
||||
TURSO_CONNECTION_URL
|
||||
TURSO_AUTH_TOKEN
|
||||
DRAINPIPE_CONSUMER_SECRET
|
||||
|
||||
$ pnpm run db:generate
|
||||
$ pnpm run db:migrate
|
||||
|
||||
$ cloudflared tunnel --url http://localhost:3000
|
||||
|
||||
$ pnpm run dev
|
||||
```
|
||||
|
||||
![](https://raw.githubusercontent.com/syui/img/master/other/atproto_frontpage_preview_0001.png)
|
||||
|
||||
基本的に`drainpipe`を裏で動かします。これがpostを取得したり投稿したりします。
|
||||
|
||||
```sh:packages-rs/drainpipe/.env
|
||||
# .env.local
|
||||
- FRONTPAGE_CONSUMER_URL="http://localhost:3000/api/receive_hook"
|
||||
+ FRONTPAGE_CONSUMER_URL="http://example.com/api/receive_hook"
|
||||
```
|
||||
|
||||
## rewrite
|
||||
|
||||
```sh
|
||||
$ cd packages/frontpage
|
||||
$ PUBLIC_URL=example.com
|
||||
$ grep -R frontpage.fyi .|cut -d : -f 1|sed -i "s/frontpage.fyi/$PUBLIC_URL/g"
|
||||
```
|
||||
|
||||
`pnpm run start`と`pnpm run dev`では`client_id`が異なります。これは`/oauth/client-metadata.json`を見てください。
|
||||
|
||||
```.env
|
||||
# .env.local
|
||||
# packages/frontpage/lib/auth.ts
|
||||
VERCEL_PROJECT_PRODUCTION_URL=example.com
|
||||
VERCEL_BRANCH_URL=example.com
|
||||
```
|
||||
|
||||
## local-infra
|
||||
|
||||
self-hostするのに必要なserver構成だと思います。
|
||||
|
||||
https://github.com/likeandscribe/frontpage/tree/main/packages/frontpage/local-infra
|
||||
|
||||
```sh
|
||||
$ cd local-infra
|
||||
$ cat README.md
|
||||
docker-compose up
|
||||
Create a test account with ./scripts/create-account.sh <email> <handle>
|
||||
DRAINPIPE_CONSUMER_SECRET=secret
|
||||
TURSO_CONNECTION_URL=libsql://turso.dev.unravel.fyi
|
||||
PLC_DIRECTORY_URL=https://plc.dev.unravel.fyi
|
||||
```
|
||||
|
||||
これらのnameserverはcaddyを見てください。
|
||||
|
||||
plcはerrorが出るので、以下のようにします。おそらく、postgresのdatabaseが必要なのでしょう。portsは開けなくて大丈夫です。
|
||||
|
||||
```yml:docker-compose.yml
|
||||
plc:
|
||||
image: ghcr.io/bluesky-social/did-method-plc:plc-f2ab7516bac5bc0f3f86842fa94e996bd1b3815b
|
||||
container_name: plc
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- '4000:8080'
|
||||
depends_on:
|
||||
- plc_db
|
||||
env_file:
|
||||
- ./plc.env
|
||||
|
||||
plc_db:
|
||||
image: postgres:16-alpine
|
||||
restart: always
|
||||
env_file:
|
||||
- ./postgres.env
|
||||
volumes:
|
||||
- ./configs/postgres/init/:/docker-entrypoint-initdb.d/
|
||||
- ./data/postgres/:/var/lib/postgresql/data/
|
||||
healthcheck:
|
||||
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
|
||||
interval: 5s
|
||||
retries: 20
|
||||
```
|
||||
|
||||
```sh:.env
|
||||
# plc.env
|
||||
DEBUG_MODE=1
|
||||
LOG_ENABLED=true
|
||||
LOG_LEVEL=debug
|
||||
LOG_DESTINATION=1
|
||||
PORT=8080
|
||||
DATABASE_URL=postgres://postgres:postgres@plc_db/plc
|
||||
DB_CREDS_JSON='{"username":"postgres","password":"postgres","host":"plc_db","port":"5432","database":"plc"}'
|
||||
ENABLE_MIGRATIONS=true
|
||||
DB_MIGRATE_CREDS_JSON='{"username":"postgres","password":"postgres","host":"plc_db","port":"5432","database":"plc"}'
|
||||
```
|
||||
|
||||
## pds
|
||||
|
||||
大体の原理が理解できてきたので、わかっていることをまとめます。
|
||||
|
||||
まずoauth(session)でpdsUrlをgetする感じなのかなと思います。sessionがあれば投稿などは操作できます。
|
||||
|
||||
```sh
|
||||
$ grep -R pdsUrl .
|
||||
./lib/data/user.ts: const pdsUrl = await getPdsUrl(session.user.did);
|
||||
```
|
||||
|
||||
あるいは`ws://pds:3000`を使用する可能性も考えられますが、基本は`bsky.network`を使うのだと思います。
|
||||
|
||||
```sh:packages-rs/drainpipe/.env
|
||||
RELAY_URL=wss://bsky.network
|
||||
```
|
||||
|
||||
次に`unravel.frontpage`についてです。これは主に`collection`に書き込まれているようです。この場合、`frontpage.fyi`と投稿は共通します。
|
||||
|
||||
```sh
|
||||
$ grep -R unravel.frontpage ./app ./lib
|
||||
./app/api/receive_hook/route.ts: if (collection === "fyi.unravel.frontpage.vote") {
|
||||
./lib/data/atproto/comment.ts:export const CommentCollection = "fyi.unravel.frontpage.comment";
|
||||
./lib/data/atproto/vote.ts: collection: "fyi.unravel.frontpage.vote",
|
||||
./lib/data/atproto/vote.ts: collection: "fyi.unravel.frontpage.vote",
|
||||
./lib/data/atproto/event.ts: z.literal("fyi.unravel.frontpage.vote"),
|
||||
./lib/data/atproto/post.ts:export const PostCollection = "fyi.unravel.frontpage.post";
|
||||
|
||||
# HOST_REVERT=com.unravel.example
|
||||
# grep -R unravel.frontpage ./app ./lib |cut -d : -f 1|xargs sed -i "s/fyi.unravel.frontpage/${HOST_REVERT}/g"
|
||||
```
|
||||
|
||||
```js
|
||||
// https://atproto.com/ja/guides/applications
|
||||
// レコードの時間ベースのキーを生成します
|
||||
const rkey = TID.nextStr()
|
||||
|
||||
// 書き込み
|
||||
await agent.com.atproto.repo.putRecord({
|
||||
repo: agent.assertDid, // ユーザー
|
||||
collection: 'xyz.statusphere.status', // コレクション
|
||||
rkey, // レコード キー
|
||||
record: { // レコード値
|
||||
status: "👍",
|
||||
createdAt: new Date().toISOString()
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
`drainpipe`はpdsの`fyi.unravel.frontpage(collection)`を検索してfirehoseの`subscribeRepos`にcommitするようです。この2つの部分を変更すると`frontpage.fyi`と連動しません。
|
||||
|
||||
```rust:packages-rs/drainpipe/src/main.rs
|
||||
let mut ws_request = format!(
|
||||
"{}/xrpc/com.atproto.sync.subscribeRepos{}",
|
||||
relay_url, query_string
|
||||
)
|
||||
|
||||
// https://github.com/likeandscribe/frontpage/blob/e7444ec6c19f0ccef3776f04702c3bb033ed3bfc/packages-rs/drainpipe/src/main.rs#L66-L97
|
||||
/// Process a message from the firehose. Returns the sequence number of the message or an error.
|
||||
async fn process(message: Vec<u8>, ctx: &mut Context) -> Result<i64, ProcessError> {
|
||||
let (_header, data) = firehose::read(&message).map_err(|e| ProcessError {
|
||||
inner: e.into(),
|
||||
seq: -1,
|
||||
source: message.clone().into(),
|
||||
kind: ProcessErrorKind::DecodeError,
|
||||
})?;
|
||||
let sequence = match data {
|
||||
firehose::SubscribeRepos::Commit(commit) => {
|
||||
let frontpage_ops = commit
|
||||
.operations
|
||||
.iter()
|
||||
.filter(|op| op.path.starts_with("com.unravel.example."))
|
||||
//.filter(|op| op.path.starts_with("fyi.unravel.frontpage."))
|
||||
.collect::<Vec<_>>();
|
||||
if !frontpage_ops.is_empty() {
|
||||
process_frontpage_ops(&frontpage_ops, &commit, &ctx)
|
||||
.map_err(|e| ProcessError {
|
||||
seq: commit.sequence,
|
||||
inner: e,
|
||||
source: message.clone().into(),
|
||||
kind: ProcessErrorKind::ProcessError,
|
||||
})
|
||||
.await?;
|
||||
}
|
||||
commit.sequence
|
||||
}
|
||||
msg => msg.sequence(),
|
||||
};
|
||||
|
||||
Ok(sequence)
|
||||
}
|
||||
```
|
||||
|
||||
ただ、infraのpdsは`pds.dev.unravel.fyi`となっていて、中の人の話を聞くと`frontpage.fyi`のpdsにpostされるように感じました。
|
||||
|
@ -57,6 +57,7 @@ header_image = "/games/genshin.jpg"
|
||||
|2023|04|`ナヒーダ`を完凸|初めての完凸|
|
||||
|2023|11|`フリーナ`を2凸|スカークが実装されたら完凸したい|
|
||||
|2024|04|`ヌヴィレット`を1凸|螺旋単騎やってみた|
|
||||
|2024|10|`シロネン`を確保|炎神と氷神を完凸予定なので1人につき原石が90,000必要|
|
||||
|
||||
[![](https://raw.githubusercontent.com/syui/img/master/other/genshin_20220701_0001.png)](https://raw.githubusercontent.com/syui/img/master/other/genshin_20220701_0001.png)
|
||||
|
||||
@ -73,19 +74,17 @@ header_image = "/games/genshin.jpg"
|
||||
|3| ヌヴィレット|
|
||||
|4| 雷電 / 鍾離|
|
||||
|
||||
|元素|キャラ|特徴|
|
||||
|---|---|---|
|
||||
|雷|忍|スキルでの回復及び継続的な元素反応|
|
||||
|炎|ベネット|爆発で回復とバフ、短時間スキル|
|
||||
|炎|香菱|強力な爆発|
|
||||
|水|行秋|強力な爆発、スキルの攻撃力とエネルギー回復力|
|
||||
まずは各元素の強キャラを1人確保して、余裕が出てくれば2人確保しましょう。
|
||||
|
||||
補足として以下のキャラは育成しておいて損はないと思います。
|
||||
|
||||
|元素|キャラ|特徴|
|
||||
|---|---|---|
|
||||
|草|蛍|元素別では最強の主人公|
|
||||
|氷|レイラ|氷シールド|
|
||||
|元素|キャラ1|キャラ2|凸|
|
||||
|---|---|---|---|
|
||||
|風|ウェンティ|万葉|無凸/無凸|
|
||||
|岩|鍾離|シロネン|無凸/無凸|
|
||||
|雷|雷電|忍|無凸/完凸|
|
||||
|草|ナヒーダ|蛍|完凸/5凸|
|
||||
|水|フリーナ(ヌヴィレット)|行秋|2凸/完凸|
|
||||
|炎|---|香菱/ベネット|完凸/完凸|
|
||||
|氷|---|レイラ|---/完凸|
|
||||
|
||||
#### 元素
|
||||
|
||||
@ -128,6 +127,7 @@ header_image = "/games/genshin.jpg"
|
||||
|2023|04|40,000|-40,000|0|ナヒーダ(完凸)|
|
||||
|2023|11|44,000|-22,000|22,000|フリーナ(2凸)|
|
||||
|2024|04|55,000|-30,000|25,000|ヌヴィレット(1凸)|
|
||||
|2024|10|75,000|-8,000|65,000|シロネン|
|
||||
|
||||
- スターダスト変換した運命も合算しています
|
||||
|
||||
|
245
content/m/ue.md
245
content/m/ue.md
@ -8,7 +8,7 @@ slug = "ue"
|
||||
|
||||
ここでは[epic games](https://www.epicgames.com/)の[unreal engine 5](https://www.unrealengine.com/ja/unreal-engine-5)の使い方をまとめます。現在はversionの`5.4.2`に追従しています。
|
||||
|
||||
- [ue 5.4.2](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/unreal-engine-5.4-release-notes)
|
||||
- [ue 5.4.4](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/unreal-engine-5.4-release-notes)
|
||||
- [ue 5.3.2](https://dev.epicgames.com/documentation/ja-jp/unreal-engine/unreal-engine-5.3-release-notes)
|
||||
|
||||
ブログで説明しづらい部分が多いので[blueprintue](https://blueprintue.com/profile/ai/)を参考にしてください。
|
||||
@ -730,6 +730,53 @@ https://logicalbeat.jp/blog/11044/
|
||||
5.3から5.4にシーケンサを持ってきて使用していましたが、一度でも編集するとおかしくなります。例えば、BP_Playerを置いたとして、mesh(skeltal)も追加しなければならなくなりました。なぜならanimを追加できないからです。meshを追加したあとanimを追加できます。しかし、これでもまだ正常ではありません。buildが進まなくなり、編集するとanimが機能しなくなります。つまり、meshを追加、animを追加、meshを削除という手順を踏まなければいけません。BP_Playerの直下にanimを置くことでようやく正常になります。
|
||||
|
||||
これは5.4.3にしたら治りました。基本的にはskeltal meshがSKM_UEFN_Mannequinのもの`CharacterMesh0`を置いて、その下にanimを置きます。この際、Mannequinのanimが必要です。リターゲットで作成します。そして、transformを0にしておいてください。animは右クリックで`ルートコンポーネントを交換`にしておくといいかもしれません。
|
||||
今もbuild後の画質問題に悩まされています。私はcity sampleをベースにprojectを作成し、package buildしていますが、build後は画質が悪くなります。editor上では問題ありません。`高DPIを許可`なども試してみましたが効果がありませんでした。
|
||||
|
||||
以下のようなscalability(エンジン拡張機能設定)をしています。DeviceProfilesでも設定できます。ツール -> プラットフォーム -> デバイスプロファイル -> Windows, rはレンダリングで、sgはscalability group
|
||||
|
||||
```
|
||||
#DefaultEngine.ini
|
||||
[ConsoleVariables]
|
||||
sg.AntiAliasingQuality=4
|
||||
sg.EffectsQuality=4
|
||||
sg.FoliageQuality=4
|
||||
sg.GlobalIlluminationQuality=4
|
||||
sg.LandscapeQuality=4
|
||||
sg.ReflectionQuality=4
|
||||
sg.ResolutionQuality=100
|
||||
sg.ShadingQuality=4
|
||||
sg.ShadowQuality=4
|
||||
sg.PostProcessQuality=4
|
||||
sg.TextureQuality=4
|
||||
sg.ViewDistanceQuality=4
|
||||
r.MaterialQualityLevel=3
|
||||
|
||||
#DefaultScalability.ini
|
||||
[ScalabilitySettings]
|
||||
sg.AntiAliasingQuality=4
|
||||
sg.EffectsQuality=4
|
||||
sg.FoliageQuality=4
|
||||
sg.GlobalIlluminationQuality=4
|
||||
sg.LandscapeQuality=4
|
||||
sg.ReflectionQuality=4
|
||||
sg.ResolutionQuality=100
|
||||
sg.ShadingQuality=4
|
||||
sg.ShadowQuality=4
|
||||
sg.PostProcessQuality=4
|
||||
sg.TextureQuality=4
|
||||
sg.ViewDistanceQuality=4
|
||||
```
|
||||
|
||||
また、blueprintで`r.SetRes 1920x1080f`や`Set Screen Resolution`+`Apply Settings`を実行し、build後のwidgetから確認済みです。`Get Game User Settings`から`Get Screen Resolution`して`1920x1080`が表示されています。ウィンドウ形式なども`Set Fullscreen Mode`で変更できているようです。念の為`.ini`に以下の項目なども追加しています。
|
||||
|
||||
```
|
||||
[ConsoleVariables]
|
||||
r.SetRes=1920x1080f
|
||||
```
|
||||
|
||||
他にはゲーム中にscalabilityやscreen size(Screen Resolution)を変更できるようにしていて、これを変更すると画質や表示が切り替わっているように感じます。ウィンドウ形式は確実に切り替えられます。
|
||||
|
||||
ただ、肝心のタイトル文字はぼやけていて、ゲーム中の雲の画質が解像度でいうと`1280x720`相当になってしまいます。雲はdynamic volumetric skyを使用しており、editor上の画質に問題はありません。
|
||||
|
||||
## [tips] vrm4uの見た目の調整
|
||||
|
||||
@ -1035,3 +1082,199 @@ modelにつけるアクセサリをblenderで統合させ、three-vrmで表示
|
||||
|
||||
`unity + vrm 1.0`でアクセサリを付けて、exportしましょう。
|
||||
|
||||
## [issue] build後の画質問題
|
||||
|
||||
今もbuild後の画質問題に悩まされています。私はcity sampleをベースにprojectを作成し、package buildしていますが、build後は画質が悪くなります。editor上では問題ありません。`高DPIを許可`なども試してみましたが効果がありませんでした。
|
||||
|
||||
![](/m/post/ue/ue5_2024-08-12_01.png)
|
||||
|
||||
左:build / 右:editor
|
||||
|
||||
以下のようなscalability(エンジン拡張機能設定)をしています。DeviceProfilesでも設定できます。ツール -> プラットフォーム -> デバイスプロファイル -> Windows, rはレンダリングで、sgはscalability group
|
||||
|
||||
```
|
||||
#DefaultEngine.ini
|
||||
[ConsoleVariables]
|
||||
sg.AntiAliasingQuality=4
|
||||
sg.EffectsQuality=4
|
||||
sg.FoliageQuality=4
|
||||
sg.GlobalIlluminationQuality=4
|
||||
sg.LandscapeQuality=4
|
||||
sg.ReflectionQuality=4
|
||||
sg.ResolutionQuality=100
|
||||
sg.ShadingQuality=4
|
||||
sg.ShadowQuality=4
|
||||
sg.PostProcessQuality=4
|
||||
sg.TextureQuality=4
|
||||
sg.ViewDistanceQuality=4
|
||||
r.MaterialQualityLevel=3
|
||||
|
||||
#DefaultScalability.ini
|
||||
[ScalabilitySettings]
|
||||
sg.AntiAliasingQuality=4
|
||||
sg.EffectsQuality=4
|
||||
sg.FoliageQuality=4
|
||||
sg.GlobalIlluminationQuality=4
|
||||
sg.LandscapeQuality=4
|
||||
sg.ReflectionQuality=4
|
||||
sg.ResolutionQuality=100
|
||||
sg.ShadingQuality=4
|
||||
sg.ShadowQuality=4
|
||||
sg.PostProcessQuality=4
|
||||
sg.TextureQuality=4
|
||||
sg.ViewDistanceQuality=4
|
||||
```
|
||||
|
||||
また、blueprintで`r.SetRes 1920x1080f`や`Set Screen Resolution`+`Apply Settings`を実行し、build後のwidgetから確認済みです。`Get Game User Settings`から`Get Screen Resolution`して`1920x1080`が表示されています。ウィンドウ形式なども`Set Fullscreen Mode`で変更できているようです。念の為`.ini`に以下の項目なども追加しています。fはfull, wはwindowed
|
||||
|
||||
```
|
||||
[ConsoleVariables]
|
||||
r.SetRes=1920x1080f
|
||||
```
|
||||
|
||||
> Unreal Engineのr.SetResコマンドは、画面やウィンドウの解像度を変更するために使用されます。このコマンドは、解像度を指定した後にオプションでウィンドウモードを示すことで構成されます。例えば、r.SetRes 1920x1080wは1920x1080のウィンドウモードに設定し、r.SetRes 1920x1080fはフルスクリーンモードに設定します。ただし、特定のバージョンのUnreal Engine(例えば4.5.1)では、r.SetResコマンドが信頼性を持って動作しないという報告があります。特にウィンドウモードからフルスクリーンモードに切り替える際に、解像度が正しく更新されないという問題が発生しています。この不具合はハードウェアやドライバの特性に関連している可能性があり、ウィンドウモードの方がフルスクリーンよりも信頼性が高いと指摘されています。Unreal Engine 5.1では、解像度やその他の画面設定は一般的にUGameUserSettingsクラスを通じて管理され、特定のプロジェクトニーズに合わせてカスタマイズすることができます。
|
||||
|
||||
他にはゲーム中にscalabilityやscreen size(Screen Resolution)を変更できるようにしていて、これを変更すると画質や表示が切り替わっているように感じます。ウィンドウ形式は確実に切り替えられます。
|
||||
|
||||
ただ、肝心のタイトル文字はぼやけていて、ゲーム中の雲の画質が解像度でいうと`1280x720`相当になってしまいます。雲はdynamic volumetric skyを使用しており、editor上の画質に問題はありません。
|
||||
|
||||
その後、dynamic volumetric skyの雲や時間が動いていない事に気づきました。editorでは動いていますが、buildは動いていません。他のassetやmap(level)は動いています。これが雲の画質に関係しているのかもしれません。ただ、そう考えるとタイトル文字のぼやけが奇妙です。
|
||||
|
||||
## [close] build後の画質問題
|
||||
|
||||
特にdynamic volumetric skyの画質問題は`BP_DynamicVolumetricSky`のSingle Player Fps Lockを`Free`にしていると発生するようです。これを`60 FPS`に変更すると雲が動くようになり、画質も改善されました。
|
||||
|
||||
ただ、タイトル文字の画質は改善されていません。
|
||||
|
||||
> nvidiaの`グラフィック -> グローバル設定`が原因で、アプリが新しくなったからとインストール通知が来てて、そこから低画質が始まってたっぽい。イメージスケーリング(maxが解像度85%)をオフにすると治った。この設定は一体何なのだろう。windowsとゲームで設定できるから解像度はそれでいいと思ってた。でも違った。windows + ue + nvidiaの全部で解像度をうまく設定しないとおかしくなる。
|
||||
|
||||
## [issue] loading widget
|
||||
|
||||
ue5はloading画面を作るのにも苦労します。個人的にはwidgetがそもそも使いづらいのと、open levelの扱いがおかしいのです。例えば、widgetは`all remove`しか用意されていません。
|
||||
|
||||
私はprojectのconfigを用意して、変数にwidget blueprint(loading)のobjectを作成し、BP_Playerのevent beginでcreate widgetしてから、cast configからset loadingしています。
|
||||
|
||||
ocean waveは少し特殊で読み込みが遅いので、ここで待たなければなりません。ocean waveの読み込みが終わったときconfigから取ってきたloading(object)を`remove parent`します。
|
||||
|
||||
```sh
|
||||
title -> create loading -> open level -> bp_palyer -> create loading, set config -> ocean wave(loadend) -> cast config -> remove parent
|
||||
```
|
||||
|
||||
### [issue] 家を置くとexeが落ちる
|
||||
|
||||
> 建物の上に浮かんでいる輪っかみたいなactorを消したら正常に動くようになった。なんか変なのが浮かんでいると思っていた。`WeatherOccluder`
|
||||
|
||||
ビデオメモリが足りないと言われ落ちます。editorでは落ちません。
|
||||
|
||||
円形の家でライトは周りを取り囲むように設置されてて、それが重くなる一つの要因だとは思ってた。ちょっとおかしいけど、でもライトを少なくすると落ちなくなったのでそれが原因だったぽい。
|
||||
|
||||
ちなみに、ビデオメモリは足りてる。全然使っていない。最初の読み込みのところでmapが起動しない。小さな家の中にあるライトが原因でゲームが起動しないなんてこと普通ないと思うけど。
|
||||
|
||||
その後、また落ちるようになり、今度はライトを消しても落ちるようになったみたいだ。試しにコードをうまく行ったときの状態に戻してbuildしてみると落ちる。
|
||||
|
||||
### [issue] city sampleで少しでも遠ざかると追加したactorが消える
|
||||
|
||||
これはcity sampleに持ってくるといつの間にかインスタンスの親子関係がバラバラに解除されるからです。
|
||||
|
||||
最初はうまく構成されていますが、いつの間にかおかしいフォルダ構成になっていたり、親子付が解除されていたりします。
|
||||
|
||||
一度アプリを落とすと次に起動したときにはおかしくなっているのでしょう。
|
||||
|
||||
つまり、保存したものがいつの間にか勝手に変更されているのです。ですから場合によっては気づかないことがあります。
|
||||
|
||||
actorをまたもとのインスタンス下に置くと遠ざかっても建物が消えないようになります。
|
||||
|
||||
これはあらゆるものをcity sampleに追加するときに見られる挙動でもあります。
|
||||
|
||||
追加するとそのときは問題ないのですが、アプリを落とした以降におかしな挙動や構成になり再設定しないと適切に描写されず、距離に応じて消えてしまうactorになります。
|
||||
|
||||
また、再度設定して親子付けしたはずのものがいつの間にか解除されてたりもします。描写されないのでおかしいなと見てみると勝手に変更されています。定期的にあるので、毎回チェックするのが大変です。
|
||||
|
||||
色々な距離設定やプロジェクト設定、ワールド設定を見てみましたが、どれも効果を発揮しませんでした。
|
||||
|
||||
> 目の前のactorしか表示しないのは負荷軽減になります。正常な描写に戻すよう努力することでビデオメモリが足りないと落ちる可能性が高まります。ただ、ビデオメモリが足りなくなるようなものは追加していないし、負荷を監視していますがメモリは足りているので、ue(もしくはcity sample)が壊れているのかもしれません。その証拠にeditorからの起動では落ちません。あるいはdynamic volumetric skyとocean wavesを追加したことで相当な負荷がかかっており、少しactorを追加するだけでも落ちるようになっている可能性もあります。
|
||||
|
||||
### [tips] twinmotionがすり抜け
|
||||
|
||||
twinmotionで実装するものはcollisionが設定されていないのですり抜けます。
|
||||
|
||||
これを解消するにはmeshを全選択して、右クリックで`アセットアクション -> プロパティマトリクスで選択内容を... -> collision complexity(use complex collision as simple...)`を選択します。
|
||||
|
||||
これはtwinmotionに限らず`AutomotiveBridgeScene`にも見られる挙動でした。とはいえ設置したい場所にもよります。
|
||||
|
||||
### [issue] nvidiaの画面がちらつく
|
||||
|
||||
特にue5でひどかった画面のチラ付きに対処するには、nvidiaコントロールパネルを開いて解像度の変更からリフレッシュレートをディスプレイに合わせたあと`nvidiaのカラー設定を使用`を選択して再起動します。
|
||||
|
||||
### [tips] 音声を読み取り、chatgptで変換する
|
||||
|
||||
https://github.com/gtreshchev/RuntimeSpeechRecognizer/wiki/1.-How-to-use-the-plugin
|
||||
|
||||
https://www.youtube.com/watch?v=xBs-nXzXwoM&list=LL&index=1
|
||||
|
||||
- https://github.com/gtreshchev/RuntimeSpeechRecognizer
|
||||
- https://github.com/gtreshchev/RuntimeAudioImporter
|
||||
- https://blueprintue.com/blueprint/et6u52bm/
|
||||
|
||||
最後の`Get Sample Rate -> Process Audio Data(Sample Rate)`のところだけコピーでは対応できないので手動でつなげること。
|
||||
|
||||
### [tips] city sampleのbgmをcustomする
|
||||
|
||||
[suno](https://suno.com/@syui)を使用して作ると良いでしょう。
|
||||
|
||||
https://www.youtube.com/watch?v=99uP2WuU2Jo
|
||||
|
||||
`Audio/MetaSounds/Music/music_leavebehind_New_mix_Meta`を編集して、引用されているbaseの音楽を変更します。ただし、補助楽器を鳴らしているものは変更しません。
|
||||
|
||||
基本的にはそれぞれのパターンに合わせて、baseとなるbgmを改変して複数用意して入れていきます。そこまで面倒な手間をかけていられない場合は全部同じものを入れます。
|
||||
|
||||
### [issue] build後にcity sampleの風の音が消える
|
||||
|
||||
```sh
|
||||
# [close]
|
||||
この問題は最初にPlayStartする場所が鍵でした。私は上空の家でスタートさせているのですが、これを地上でスタートさせるようにしなければなりません。
|
||||
あるいは、再度スタートさせる場所を変更、移動して読み込みましょう。
|
||||
```
|
||||
|
||||
build後にcity sampleの風の音が消えていることに気づいた。
|
||||
|
||||
いつ頃から消えていたかはよくわからないけど、多分、音声認識のpluginを入れてからだと思う。
|
||||
|
||||
しかし、おかしなところはopen levelでまずはtitle画面を読み込んでそこからmapに移行するんだけど、title画面では風の音が聞こえている。mapに移行してから消える。
|
||||
|
||||
それもただ消えるだけじゃなく、他の音は聞こえる。他の音もcity sampleの同じシステムで作成、管理されている。
|
||||
|
||||
でも風の音だけ消えているように思う。なお、これらの問題はeditorやpreviewでは確認できず、あくまでbuild後のpackage(exe)で発生している。
|
||||
|
||||
参照ビューアで辿っていると、`Script/CitySampleWorldAudioDataScript`に行き当たり、そこで`MetaSounds/Ambint/sfx_amb_Pawn_Wind_lp_meta`を追加したがbuild後に再生されていない。
|
||||
|
||||
### [tips] ultra dynamic skyの雲がきれいになった
|
||||
|
||||
updateが来てから雲の質が上がったので、`dynamic volumetric sky`と比べてもそこまで差がないです。したがって、天候もあるultra skyを使用することにしました。ちなみに、未だ雲の質はdynamic skyのほうが少し上です。
|
||||
|
||||
oceanと同時に使うには`BP_EarthSizedSphericalMesh`のtransform-location-zを`-636000100`にすると波紋が軽減される。ただし、この問題を完全に解決するには、sky-atmosphereを惑星の中心にするしかなく、ultra skyはそれだと問題が発生します。
|
||||
|
||||
### [issue] 画面がチカチカする2
|
||||
|
||||
nvidiaのスケーリングをONにしたら治った。でもこれをONにするとbuildしたpackageで雲の画質が悪くなったのを思い出した。
|
||||
|
||||
### [tips] 音声で操作する
|
||||
|
||||
これは前からやろうか迷ってたけど、簡単に実装できる。
|
||||
|
||||
configで音声を保存してstatus画面でも表示する。
|
||||
|
||||
### [tips] 音声でNPCを喋らせる
|
||||
|
||||
まず音声認識からchatgpt,elevenlabsを使うのは前回まででやっているけど、これを利用するとNPCを喋らせることができる。
|
||||
|
||||
この場合、会話のバリエーションは無限大だが制御はできない。
|
||||
|
||||
NPC(collision)にあたったとき、configにNPCのnumを入れてボタンが表示されるようにする。キーを押すとchatgptにNPCの設定をいれる。
|
||||
|
||||
### [issue] ビデオメモリ不足で落ちる2
|
||||
|
||||
これはまず重くないmapを開くことで次に開くmapをクラッシュを防ぐことができます。起動後にすぐ重いmapを開くと大体はクラッシュします。
|
||||
|
||||
ちなみに、原因はわかりませんし、メモリは不足していません。おそらく、GPUの性能にかかわらずクラッシュすると思われます。
|
||||
|
||||
|
BIN
content/m/ue/ue5_2024-08-12_01.png
Normal file
BIN
content/m/ue/ue5_2024-08-12_01.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
BIN
content/m/ue/ue5_2024-08-15_01.mp4
Normal file
BIN
content/m/ue/ue5_2024-08-15_01.mp4
Normal file
Binary file not shown.
@ -4,7 +4,7 @@
|
||||
<article>
|
||||
<div class="content">
|
||||
{{ if ne .Lastmod .Date }}<div class="post-time-date">update : {{ .Lastmod.Format "2006-01-02" }}</div>{{ end }}
|
||||
<div class="post-time-date">{{ .Date.Local.Format "2006-01-02" }} / <a href="https://bsky.syui.ai">@{{ .Site.Author.name }}</a>
|
||||
<div class="post-time-date">{{ .Date.Local.Format "2006-01-02" }} / <a href="https://bsky.syui.ai">@{{ .Site.Params.Author.name }}</a>
|
||||
|
||||
{{ $taxo := "tags" }}
|
||||
{{ with .Param $taxo }}
|
||||
@ -50,9 +50,16 @@
|
||||
-->
|
||||
|
||||
</div>
|
||||
|
||||
</article>
|
||||
|
||||
{{ partial "comment.html" . }}
|
||||
{{ if .Param "comment" }}
|
||||
<div class="comment">
|
||||
<p><a href="https://o.syui.ai/post/syui.ai/{{ .Params.comment }}" class="frontpage-button"> @comment </a></p>
|
||||
<iframe class="frontpage" src="https://o.syui.ai/post/syui.ai/{{ .Params.comment }}" frameBorder="0" SRC=""></iframe>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
|
||||
</div>
|
||||
{{ partial "footer.html" . }}
|
||||
|
@ -1,6 +1,9 @@
|
||||
|
||||
<!--
|
||||
<link href="/js/comment/app.js" rel="preload" as="script">
|
||||
<link href="/js/comment/chunk-vendors.js" rel="preload" as="script">
|
||||
<div id="comment"></div>
|
||||
<script async src="https://embed.bsky.app/static/embed.js" charset="utf-8"></script>
|
||||
<script src="/js/comment/chunk-vendors.js"></script>
|
||||
<script src="/js/comment/app.js"></script>
|
||||
-->
|
||||
|
@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<title>{{ .Title }} | {{ .Site.Title }}</title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="author" content="{{ .Site.Author.name }}" />
|
||||
<meta name="copyright" content="© {{ .Site.Author.name }}" />
|
||||
<meta name="author" content="{{ .Site.Params.Author.name }}" />
|
||||
<meta name="copyright" content="© {{ .Site.Params.Author.name }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<meta name="description" content="syui blog" />
|
||||
<meta name="keywords" content="syui, blog" />
|
||||
|
@ -2,8 +2,8 @@
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="author" content="{{ .Site.Author.name }}" />
|
||||
<meta name="copyright" content="© {{ .Site.Author.name }}" />
|
||||
<meta name="author" content="{{ .Site.Params.Author.name }}" />
|
||||
<meta name="copyright" content="© {{ .Site.Params.Author.name }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<meta name="description" content="syui nyancat" />
|
||||
<meta name="keywords" content="syui" />
|
||||
|
@ -2,8 +2,8 @@
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="author" content="{{ .Site.Author.name }}" />
|
||||
<meta name="copyright" content="© {{ .Site.Author.name }}" />
|
||||
<meta name="author" content="{{ .Site.Params.Author.name }}" />
|
||||
<meta name="copyright" content="© {{ .Site.Params.Author.name }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<meta name="description" content="syui nyancat" />
|
||||
<meta name="keywords" content="syui" />
|
||||
|
@ -3,8 +3,8 @@
|
||||
<head>
|
||||
<title>{{ .Title }}</title>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="author" content="{{ .Site.Author.name }}" />
|
||||
<meta name="copyright" content="© {{ .Site.Author.name }}" />
|
||||
<meta name="author" content="{{ .Site.Params.Author.name }}" />
|
||||
<meta name="copyright" content="© {{ .Site.Params.Author.name }}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<meta name="description" content="syui" />
|
||||
<meta name="keywords" content="syui" />
|
||||
|
@ -126,10 +126,6 @@ button.comment_open:hover {
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
.comment {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
article p img {
|
||||
width: 100%;
|
||||
padding:0;
|
||||
|
@ -3,6 +3,7 @@ body {
|
||||
background-color: #f1f1f1;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
width:100%;
|
||||
|
||||
margin:0;
|
||||
padding: 0;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
@ -354,9 +355,7 @@ button.comment_open:hover {
|
||||
margin: 0px auto;
|
||||
}
|
||||
|
||||
.comment {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.loading {
|
||||
background:#f1f1f1;
|
||||
@ -398,3 +397,31 @@ span.icon-syui {
|
||||
}
|
||||
}
|
||||
|
||||
iframe.frontpage {
|
||||
width:100%;
|
||||
height: 1000px;
|
||||
pointer-events:none;
|
||||
border: 2px solid #fff;
|
||||
}
|
||||
|
||||
a.frontpage-button {
|
||||
width:100%;
|
||||
font-size: 15px;
|
||||
background-color: #e80063;
|
||||
/* background-color: #007fff; */
|
||||
color: #fff;
|
||||
border: 2px solid #fff;
|
||||
padding: 10px 30px;
|
||||
border-radius: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.frontpage-button:hover {
|
||||
/* background-color: #0077ff; */
|
||||
background-color: #e80073;
|
||||
}
|
||||
|
||||
.comment {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
|
BIN
static/img/holo.mov
Normal file
BIN
static/img/holo.mov
Normal file
Binary file not shown.
BIN
static/img/ue-2024-10-01-7.36.38.png
Normal file
BIN
static/img/ue-2024-10-01-7.36.38.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
BIN
static/img/ue-2024-10-01-7.36.39.png
Executable file
BIN
static/img/ue-2024-10-01-7.36.39.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 72 KiB |
BIN
static/img/windows_terminal_sixel.png
Normal file
BIN
static/img/windows_terminal_sixel.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 168 KiB |
Loading…
Reference in New Issue
Block a user