Youtubeから音楽を持ってくると前後に無音部分が付いてくる問題

前回、Youtube動画から音楽を切り離してスマホ転送して聞くあれこれの話をしたのですが、
miz999.hatenablog.com
その続き。

動画として音楽を聞いている時には気づかなかったのですが、音楽単体にして音楽として聞いてると、音楽の前後に無音部分がやたら長いことに気づきました。リンク開いて、動画読み込み始めていきなり音が出始めるよりも、少し「ため」があったほうがユーザビリティがいいのでしょう、理解できます。
でもこれ、音楽としてiPod等のプレイヤーで聞くと前後の空白に違和感を感じるというか、再生できてないんじゃと思うくらいの間が空きます。

で、今回はその「間」を削ろう、という話です。

個人的希望としてbat化してコマンド一発(厳密にはエクスプローラの右クリックの「送る」から)で片付けたいので色々調べ回ると、soxというコマンドラインプログラム(CLI)がありました。GUIプログラムは他に結構あるようです。

SoX - Sound eXchange | HomePage


Windows上で試行錯誤のうえで出た試行錯誤は

  • パイプが上手く行ってくれなくて、結局テンポラリファイル作る
  • opus、mp3のDLLが組み込まれて無くて、拾ってくればいいようなこと書いてあるけど面倒なのでwavを通した。
  • 前回の結論を踏まえて、最終的にmp3を作る
  • wav化、mp3化するのにffmpegを使う
  • エクスプローラの「送る」からファイル名を渡す
  • mp3へのコンバート時の音質オプションは何も考えず

で、こんな感じのbatになりました。windowsの「送る」に関わる補足説明を少しすると、%1が送るファイル名、%~n1が送るファイル名の拡張子無し部分、です

ffmpeg.exe" -i %1 "%~n1.wav"
sox.exe" "%~n1.wav" output1.wav vad -t 0.01 -p 0.3 reverse vad -t 0.01 -p 0.5 reverse
ffmpeg.exe" -i output1.wav "%~n1.mp3"
del output1.wav
del "%~n1.wav"
pause

soxの難解なコマンドラインオプションと参考記事はこのあたりを参考にさせていただきました。ほぼわけも分からずコピペです。

音声の無音部分削除

qiita.com

あと、soxのmanの日本語訳のページがリンクは貼られてるのに元が消えてるパターンだったので、arcive.orgから見つけてきて参考にしました。意味はほとんどわかりませんでしたが。
web.archive.org

macとかlinuxでやればもっと綺麗にパイプ使ってやれるのかな、あるいはwindowsでもちゃんとやる方法はあるのかと思いましたが、中間ファイル作ってもSSDならまぁ速いしくらいに思って深追いしないことにしました。

gnupackは添付以外のpythonをインストールするとpythonが死ぬ

gnupackは長いこと便利に使わせてもらっていたのですが、ここ1年近く更新がありません。
以前にも長いこと更新が止まったあとに復活して、またそのパターンかなと思ったら、もう駄目かもしれません。
ja.osdn.net

後進の止まったものを使い続けてもトラブルが無ければいいのですが、ついにトラブルが発生しました。

デフォルトで入ってるpython2なのですが、python2がメインだった時代のもののようでpython2ではなく'
python'の命名規則です。どうやらその後、明確にpython2とpython3に分かれたようで、そのさい色々と変更があったのか、その新しいpython2をインストールすると、そのpythonはエラーで落ちます。
ざっと見た感じでは、gnupack添付のpythonは2.7.10でcygwinでインストールされる最新は2.7.13(くらい?)でやっぱりバージョン違いです。

cygwinリポジトリ自体がどんどん新しくなっているので、python関連のライブラリ追加すると依存関係でその問題の'python2'がインストールされ上書きされます。
つまり今後一切、python関連の追加一切が出来ないということになります。

何処かでバージョン固定してやればなんとかなるのかもしれませんが、雰囲気的に別のトラブルを呼びそうです。

というわけで、gnupackは完全に死んでると思うのです。
libcとか根幹に関わる部分もアップデートすればいいのかもしれませんが、setup.exeを使うようなら、もうそれはcygwin使ったほうが早いのでしょう。

というわけで、今後の対応としては

  1. gnupackを今の環境のまま使い続ける
  2. cygwin以降
  3. 他のみなさん同様、macbookにする
  4. win10でubuntu

などがありますが、お金のかからない方向だと2番かなと

更新してくれないかなー、gnupack

Youtubeの最近のオーディオコーデックがOPUSになっている件について

Youtubeの音楽をローカルに保存して聞いてる人は今時いるのでしょうか?
スマホで常時接続の時代になってローカル保存の概念が無くなると思いきや、動画というのはやはりサイズが大きく、大変なパケットを消費します(今風だと「ギガが減る」)。
定額音楽配信も増えてきたとは言え、無い曲ばかりです。

というわけで、Youtubeからのダウンロードは今でも必要なテクニックだと思います。

ブラウザのアドオン、例えばyoutube-dlとか使うのは以前と変わらないのですが、最近になってオーディオコーデックがopusという聞きなれないものになりました。
どうやらその流れは2016年頃からのものらしい。
yuichiro-s.hatenablog.com

youtubeからダウンロードしてるような人達は手慣れたものだと思いますが、まず動画をダウンロードします。
ダウンロードするとwebmやmkv何かの拡張子がついた動画ファイルになります。

大まかな流れはこんな感じ
f:id:miz999:20170812084041p:plain

今回、問題なのは、音声ファイルを再エンコードしないで取り出すパターン。再エンコードのたびに音質が劣化するのでできるだけ再エンコードはしたくない、その結果、youtubeの動画ファイルに使われてる(使われだした)opusという形式の音声ファイルが取り出される。

Opus (音声圧縮) - Wikipedia

で、このopus、新しいファイル形式なのか対応環境が少ない。ウチは普段iTunes(windows)を常用してるのですが、iTunesはダメ(そもそもiTunesはファイル形式にうるさいのだが)、androidiPhoneも対応していることは対応しているという話もあるのだが、何か曖昧。
つまり、使いづらいフォーマットなのである。ちなみにウチではvlcでテスト再生しています。vlcはライブラリ管理としてイマイチなので、あくまでテストのみ。

まぁ、mp3なりaacなりに再エンコードしてやれば、どんな環境でも再生できるのですが、上述のとおりできるだけ再エンコードしたくないのです。
もうiPodも使ってないし、iPhoneは持っていないのでiTunesを捨てる時期なのかもしれません。

windows用の環境を色々探し回って、MusicBeeというのがopusも再生できるのですが、乗り換えに関してはおいおい考えるとして、opusを使う上でのもう一つの問題を。
ここ数ヶ月でGoogle Play Musicを使うようになったのですが、スマホで音楽を聴く環境としてはすこぶる便利であり、最早これなしでは生きていけないほどです。
そんな現在の状況を踏まえて上記の図に追加したのがこれ
f:id:miz999:20170812091237p:plain
これを最終型としてできるだけ再エンコードしないようにしたいのです。
Google側の音声フォーマット対応状況としてはこれ
support.google.com
これを読む限り、どんなファイルもスマホに行き着くまでにmp3になってしまうようです。

ということは、youtubeから手持ちのスマホまで全てopusで通過することは不可能であり、どこかでopus -> mp3の再エンコードを一度に限り入れてやるのが現状のベストのようです。

というわけで、最終的にはこんな感じの流れになりました
f:id:miz999:20170812094123p:plain

youtubegoogle傘下なんだから、Google PlayMusicもopusに対応してほしいものです。

Google App Engine のdatastore readコストを極力まで減らす

だいたいgaeのdatastore関連の問題ってのはwriteのコストに起因することで、そこをイカに減らすかがノウハウ(あるいはバッドノウハウ)なんですが、今作ってるサービスでは珍しくreadのコストが問題になってきました。

で、readのコストを減らす定番が「keyを活用する」ということで、key読み込みだけならばコストは発生しない、ということ。

qiita.comこのあたりを参考にすると、なるほどcountでも1read単位で済むらしい。

q = act_db.query()
logging.info("actress_db count:%s" % q.count())
for a in q.fetch(keys_only=True):
logging.error("id -> %s" % a.id())

このコードのコストは管理コンソールの割当ページで見るところの「Datastore読み取り操作」で1件分である(もしかしたらcountでもう1件で2件かも)。forループのfetchもkeys_onlyなら0件である。当然ループの中でa.get()してしまうとこのループの件数だけreadが発生して大爆発になる。a.id()だけならkey(あるいはIDと言い方もある)を読むだけなので0コスト。

そこでdatastore writeの時にid(key)に意味を持たせてやれば最小コストに近づくというわけである。

keyに意味をもたせるノウハウはこの辺から↓

Google App Engine / Python 上での開発で最初から知ってればよかった、ってことをいくつか - Masatomo Nakano Blog

writeはこれで

act_db(id=nanika_uniq, name=nanika_name).put()

readはこんな

q = act_db.get_by_id(url)

これはentityを返すのでget()も兼ねている。

gqlを使うなら

gql_q = ndb.gql("select __key__ from act_db where __key__ = KEY('act_db', :1)", nanika_uniq)

これはkeyだけ返す。どちらもread1件である。keyだけを返すgqlもreadを食うのが謎であるが、そういうものなんだろう。

で、ここまでが調べればすぐに分かる話である。

今回ハマったのが「readの1件すら惜しい」ケース。keyだけで済むケース、例えば存在確認なんかの場合。

url = "yahoo.co.jp"
q = act_db.query()
for a in q.fetch(keys_only=True):
q = url_db.get_by_id(url) # q is entity
# or q = ndb.gql("select __key__ from url_db where __key__ = KEY('url_db', :1)", url).fetch(keys_only=True) #q is key
# or q = url_db.query(url_db.url == url).fetch(keys_only=True) #q is key
# 上記全てread1件であるのでどれを使っても同じ。keyかentityかの違いはあるが今回はkeyだけ欲しい状況
if q:
shori()

forループの中のget_by_id()はread1件ではあるが、ループの回数だけ塵も積もれば山となる。何しろ無料でreadできるのは5万件しか無い。これを十分と取るか少ないと取るか。

最終的にどうしたかというと、やっぱりmemcacheである。

#全部リードでread1件
q = url_db.query()
for a in q.fetch(keys_only=True):
memcache.set(a.id(), True)
 
url = "yahoo.co.jp"
 
#ここでもread1件
q = act_db.query()
for a in q.fetch(keys_only=True):
if memcache.get(url):
shori()

全部でread2件で済む。

いざ書いてみると、当たり前の話だが、key利用だけではコストが安くならない場合もあるということで、もうひと工夫必要なこともあるということで。

 

#追記

記事公開したら、ソース部分のインデントと改行がおかしくなった。if、forは適宜対応でお願いします。

node.jsのhttpモジュールでutf-8以外のデータをもってくる場合

それくらい標準でやってくれよと思うところもあるのですが外部モジュールでやるもののようです

qiita.com

info-i.net

今回は後者の iconv-liteを使いました。liteのほうが他の依存モジュールが無いので楽。

var iconvLite = require('iconv-lite')
var title = iconvLite.decode(actress_name, "EUC-jp")
console.log(title)

ソースはこんなものですが上手く行きません。文字化けしたままです。

いろいろ調べてみるとどうやらサンプル持ってきたそのままで、今まではutf-8のページばっかり扱ったので上手く行ってただけのsetEncodingがutf8決め打ちがまずいらしい。

var client = http.get(url, function (res) {
var data = ''
if (res.statusCode === 200) {
res.setEncoding('utf8');
res.on('data', function (chunk) {
data += chunk
})
res.on('end', function () {
f(url, data)
})
}

 

 ここを元データの文字コードであるEUC-JPにすると詳しいことはメモらなかったが「知らない文字コード」という類のエラーがでる。

 最終的に上手く行ったのはバイナリを扱うように生データで持ってくるのがいいらしい

res.setEncoding('binary');

その辺の経緯はここのページが参考になる。やっぱり勝手なことしてくれてるのね

qiita.com

それにしても、はてなブログになってから初めてソースコード貼り付けしたが使いにくいことこの上ないな、この「見たまま編集」。貼り付けたソースは色付けとかタブとか修正しようがないみたいなので参考程度に。変な改行とか入ってるし。

便利なようで勝手なことやってくれてるというのは、今回のトラブルと同じか。

markdown記法でも覚えるかな。

 

 

Google Apps Scriptで久々にハマったら、以前のところと同じところでハマってた

ウェブアプリケーションとして公開する場合、修正するたびにバージョンを新規作成しなければ修正が反映されないというのをすっかり忘れてて数時間無駄にした。

気付くきっかけになったのがこのページ

eye4brain.sakura.ne.jp

コードを変更した場合、execのURLに反映させる為には、プロジェクトバージョンを変更し新規作成にしなければコードが反映しません。

 

今後の戒めとしてメモしておく。

 

しかし上記の参考サイトの「嵌まるポイント」の多さからも分かる、Googleサービスの謎の多さというか初見殺しポイントの多さよ。

この辺は日本語ドキュメントや利用者が少ないのか参考ページの少なさよ。

Google App Scriptの保存可能なプロパティ的なもの

Google App Script (以下GAS)で困るのがデータ保存をどうするかということ。

スプレッドシートならデータ用のシートを別に作成して、そこをデータベース代わりに使うらしい。ちなみにログも保存されないので、ログを残したければそういう形式を取るしか無い、不便。せめてログくらい何とかならないものかと思う。スクリプトの手動実行や、トリガー起動でもそのスクリプトのページ開いてて、実行してすぐなら見られるが。

で、使える保存領域としてあるのが、PropertiesService。これを使えばユーザー名何かのちょっとしたものは保存できる。

developabout0309.blogspot.jp

で、このページに書いてあるように

  • getDocumentProperties()
  • getScriptProperties()
  • getUserProperties()

の3つを使い分けるらしい。

自分だけしか見ないし、1番データ漏洩が少なそうなUserProperiesを使いたかったのだが、何故かこれがスクリプト編集画面のファイル->プロパティから編集できなくて不便。

ググっても対処法は見つからず、ダメだよーって情報しか出てこない。それも3年前の。

old-horizon.hateblo.jp

しかしPropertiesService.getUserProperties()で取得できるユーザープロパティはスクリプトエディタのUIからは編集できない、一方でスクリプトプロパティは編集できるという仕様 

 

で、結局、getScriptProperties()を使うことにした。

ちなみに、プロパティに数字(整数)を保存すると浮動小数点つき(小数点以下.0が付く)になるので、それをそのまま取り出して文字列と結合すると.0が残ってしまう。整数と演算すると消える。

この辺はjavascript的に仕方ないのだろうがね。

 

しかしGoogle関連のサービスはほんと日本語の情報が少ない。ただで使えて便利なのに。