radikoのタイムフリー&エリアフリーを保存する(ruby版)
以前にradikoのタイムフリー&エアフリーを保存するする手順を書きましたが、今回はRuby版です。
環境
事前準備
myplayer-release.swfのダウンロード
認証のための事前準備としてswfファイルをダウンロードする
$ curl -O http://radiko.jp/apps/js/flash/myplayer-release.swf
swfファイルから画像ファイルを取り出す
認証にキーとなる画像ファイルをswfextractで取り出す
$ swfextract myplayer-release.swf -b 12 -o authkey.png $ ls authkey.png authkey.png
実行方法
タイムフリー(エリア内)
$ ruby rec_radiko.rb --sid=LFR --ft=201812190000 --to=20181219010000
エリアフリー(mailとpassにプレミア登録している情報を設定する)
$ ruby rec_radiko.rb --sid=MBS --ft=201812190000 --to=20181219010000 --mail=hoge@example.com --pass=password
rec_radiko.rb
require 'optparse' require 'date' require 'httpclient' M4A_FILE = DateTime.now.strftime('%Y%m%d%H%M%S') + ".m4a" LOGIN_URL = 'https://radiko.jp/ap/member/login/login' LOGIN_CHECK_URL = 'https://radiko.jp/ap/member/webapi/member/login/check' AUTH1_URL = 'https://radiko.jp/v2/api/auth1_fms' AUTH2_URL = 'https://radiko.jp/v2/api/auth2_fms' params = ARGV.getopts("", "sid:", "ft:", "to:", "mail:", "pass:") if params["sid"] == nil || params["ft"] == nil || params["to"] == nil puts "Usage: ruby rec_radiko.rb --sid=<station id> --ft=<start time> --to=<end time> --mail=<mail address --pass=<password>" exit end sid = params["sid"] ft = params["ft"] to = params["to"] PLAYLIST_URL = "https://radiko.jp/v2/api/ts/playlist.m3u8?station_id=#{sid}&l=15&ft=#{ft}&to=#{to}" mail = params["mail"] pass = params["pass"] client = HTTPClient.new header = { \ 'Content-Type' => 'application/x-www-form-urlencoded', \ 'Referer' => 'http://radiko.jp/', \ 'Pragma' => 'no-cache', \ 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30', \ 'X-Radiko-Device' => 'pc', \ 'X-Radiko-App-Version' => '4.0.0', \ 'X-Radiko-User' => 'test-stream', \ 'X-Radiko-App' => 'pc_ts' \ } # premium login if mail != nil && pass != nil res = client.post(LOGIN_URL, {'mail' => mail, 'pass' => pass}, header) res = client.get(LOGIN_CHECK_URL, nil, header) if HTTP::Status.successful?(res.code) != true puts "Login failed" exit 1 end puts "Login Succeed" end # Authentication 1 res = client.post(AUTH1_URL, nil, header) if HTTP::Status.successful?(res.code) != true puts "Auth1 failed" exit 1 end puts "Auth1 Succeed" # X-Radiko-AuthToken が大文字の場合と小文字の場合があるため両方に対応 if res.headers['X-Radiko-AuthToken'] != nil header['X-Radiko-Authtoken'] = res.headers['X-Radiko-AuthToken'] elsif res.headers['X-RADIKO-AUTHTOKEN'] != nil header['X-Radiko-Authtoken'] = res.headers['X-RADIKO-AUTHTOKEN'] else puts "X-Radiko-AuthToken not found" exit 1 end header['X-Radiko-Partialkey'] = `dd if=authkey.png ibs=1 skip=#{res.headers['X-Radiko-KeyOffset']} count=#{res.headers['X-Radiko-KeyLength']} 2>/dev/null | base64`.chomp # Authentication 2 res = client.post(AUTH2_URL, nil, header) if HTTP::Status.successful?(res.code) != true puts "Auth2 failed" exit 1 end puts "Auth2 Succeed" # ffmpegで保存 ffmpeg = "ffmpeg \ -content_type 'application/x-www-form-urlencoded' \ -headers 'Referer: http://radiko.jp/' \ -headers 'Pragma: no-cache' \ -headers 'X-Radiko-AuthToken: #{header['X-Radiko-Authtoken']}' \ -user_agent 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_4) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.1 Safari/603.1.30' \ -i '#{PLAYLIST_URL}' \ -vn -acodec copy -bsf aac_adtstoasc #{M4A_FILE}" `#{ffmpeg}`
CentOS7でApache+PassengerのRails本番環境を構築する手順
Apache + PassengerでのRails本番環境を作成する手順についてまとめました。
前提
- Vagrant のCentOS7.5イメージを利用する
- Rails のアプリケションはgithub等のリモートリポジトリから取得する
- Vagrant + Virtual Boxはインストール済みとする
環境
手順
Vagrant環境構築
ホストOSの任意のディレクトリでCentOS7.5の仮想環境を構築します
$ vagrant init centos/7
Vagrantfileの以下の部分のコメントアウトを外し、ホストOSの8080番ポートをゲストOSの80番ポートにフォワードするようにします。
config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
仮想環境を起動してログインします。
$ vagrant up $ vagrant ssh
CentOS7.5の構成
OSを更新します。
$ sudo yum -y update
SELinuxを無効化します。
/etc/selinux/configのSELINUXをdisabledにします。
SELINUX=disabled
OSを再起動します。
$ sudo reboot
rbenv で Ruby をインストール
まずはrbenvのインストールに必要な各種パッケージをインストールします。
$ sudo yum -y install git-all openssl-devel readline-devel sqlite gcc gcc-c++
続いて、rbenvをインストールします。
$ git clone git://github.com/sstephenson/rbenv.git ~/.rbenv $ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build $ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc $ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bashrc $ echo 'eval "$(rbenv init -)"' >> ~/.bashrc $ echo 'gem: --no-ri --no-rdoc' > ~/.gemrc $ source ~/.bashrc $ rbenv --version rbenv 1.1.1-39-g59785f6
rbenvがインストールできたら、Rubyをインストールします。
バージョンは、公開しようとしているRailsアプリケーションが指定しているバージョン(2.5.3)に合わせます。
$ rbenv install 2.5.3 $ rbenv global 2.5.3 $ ruby -v ruby 2.5.3p105 (2018-10-18 revision 65156) [x86_64-linux]
bundlerもあわせてインストールします。
$ gem install bundler $ bundle version Bundler version 1.17.2 (2018-12-11 commit 43e950846)
Apacheのインストール
$ sudo yum -y install httpd httpd-devel curl-devel apr-devel apr-util-devel $ sudo systemctl enable httpd
Passengerのインストール
Passengerをインストールします
$ sudo yum install -y epel-release pygpgme curl $ sudo curl --fail -sSLo /etc/yum.repos.d/passenger.repo https://oss-binaries.phusionpassenger.com/yum/definitions/el-passenger.repo $ sudo yum install -y mod_passenger
node.jsのインストール
Rails に必要なJavaScriptランタイムとしてnode.jsをインストールします。
なお、node.jsはEPELリポジトリから取得するため、Passengerのインストールの最初に実施しているepel-releaseのインストールが前提となっていますので注意してください。
$ sudo yum install -y nodejs
Mysqlのインストール
Mysqlをインストールします。
$ sudo yum install -y mariadb-server mariadb-devel $ sudo systemctl enable mariadb $ sudo systemctl start mariadb $ sudo mysql_secure_installation
本番DBの作成
本番環境用のデータベースと接続ユーザを作成します。
以下の情報はRailsアプリケションのconf/database.yamlの内容と合わせます。
- データベース名: prodution_db
- 接続ユーザ: prod_user
- パスワード: Password
$ mysql -u root -p MariaDB [(none)]> CREATE DATABASE production_db DEFAULT CHARACTER SET utf8; MariaDB [(none)]> GRANT ALL PRIVILEGES ON production_db.* TO prod_user@localhost IDENTIFIED BY 'Password' WITH GRANT OPTION;
Rails環境を構築
いよいよ、Railsの環境を構築します。
ここでは、github等のリモートリポジトリからファイルを取得する前提としています。 (SSH接続の設定については割愛します)
$ git clone <リモートリポジトリ>
以降、アプリケーションのディレクトリを rails_app とします。
アプリケーションのディレクトリを/var/www配下に移動します。
$ sudo mv rails_app /var/www
bundle install でRailsと必要なgemをインストールします。
なお、ここではpathを指定して、gemをローカルにインストールすることにします。
$ cd /var/www/rails_app $ bundle install --path vendor/bundler $ bundle exec rails -v Rails 5.2.2
Credential情報の管理に使うキー情報をconfig/master.keyに設定します。
Rails5.2が生成する.gitignoreでは/config/master.keyがデフォルトで指定されているため master keyはリモートリポジトリに含まれません。
そのため、rails ini した環境のmaster.keyの内容を設定します
$ vi config/master.key
config/database.yamlの内容を先ほど作成したmysqlの環境と合わせます。
production: adapter: mysql2 encoding: utf8 database: production_db pool: 5 username: prod_user password: Password
マイグレーションを行い、本番環境のデータベースを作成します。(必要に応じてdb:seedで初期データの登録も実施します)
$ bundle exec rails db:migrate RAILS_ENV=production $ bundle exec rails db:seed RAILS_ENV=production
アセットのプリコンパイルを行います。
$ bundle exec rails assets:precompile
Apacheの設定
/etc/httpd/conf.d/passenger.conf を修正します。
- PassengerRuby をrbenvのパスに変更
- ServerName をlocalhostに変更
- DocumentRoot と Directory をRailsアプリケーションのpublic フォルダに設定
<IfModule mod_passenger.c> PassengerRoot /usr/share/ruby/vendor_ruby/phusion_passenger/locations.ini PassengerRuby /home/vagrant/.rbenv/shims/ruby PassengerInstanceRegistryDir /var/run/passenger-instreg </IfModule> <VirtualHost *:80> ServerName localhost # Be sure to point to 'public'! DocumentRoot /var/www/rails_app/public <Directory /var/www/rails_app/public> # Relax Apache security settings AllowOverride all Require all granted # MultiViews must be turned off Options -MultiViews </Directory> </VirtualHost>
設定を反映させるために、httpdを再起動します。
$ sudo systemctl restart httpd
動作確認
ホストOSのブラウザから http://localhost:8080 にアクセスし、railsアプリケーションにアクセスし正常に動作すれば成功です。
なお、エラーが発生した場合は、Apacheのエラーログ(/var/log/httpd/error_log)等を確認して切り分けていくことになります。
AtCoder BC074 D: Restoring Road Network
問題
https://beta.atcoder.jp/contests/abc074/tasks/arc083_b
解法
ワーシャルフロイド法をベースにしてその考え方を応用することで解けます。
問題を「道路の構造が存在するかどうか」と「存在する道路の長さの和が最小となるようなもの」の二つに分けて考えます。
- 道路の構造が存在するかどうか
表Aが「都市間の道路に沿った最短距離を表した表」なので、表Aに対してワーシャルフロイド法を適用し、表内の値が更新されることがあった場合に表Aは矛盾することになります。
例えば、(iからjへの距離)> (iからkへの距離)+(kからjへの距離)のような場合に、表Aはそもそも矛盾することになります。
よってそのような組み合わせが一つでもあれば、'-1'を出力して終了すれば良いです。
- 存在する道路の長さの和が最小となるようなもの
どのような場合に道路が存在しないといけないかを考えます。
表Aは各都市間の最短経路を表しているので、iからjへの経路を考える場合、
(iからjへの距離)= (iからkへの距離)+(kからjへの距離)
のようなkが存在した場合、k経由で迂回しても距離は変わらないので、iからjへの道路は不要ということになります。
よって、表のサイズと同じ表を用意しておき、道路が必要か不要かをtrue/falseなどで記憶しておき、最後にtrueな経路の距離だけ加算すればよいです。
実装
#include<iostream> #define FOR(i,a,b) for(int i=(a);i<(b);++i) #define REP(i,n) FOR(i,0,n) #define MOD 1000000007 using namespace std; typedef long long int ll; const ll INF=(ll)1e18; int main(){ int N; cin >> N; ll a[N][N]; bool f[N][N]; REP(i,N)REP(j,N)cin >> a[i][j]; REP(i,N)REP(j,N)f[i][j] = true; REP(k,N)REP(i,N)REP(j,N){ if(a[i][k] + a[k][j] < a[i][j]){ cout << -1 << endl; return 0; }else if(a[i][k] + a[k][j] == a[i][j] && i != k && k != j){ f[i][j] = false; } } ll ans = 0; REP(i,N)REP(j,N){ if(f[i][j])ans+=a[i][j]; } cout << ans / 2 << endl; }
AtCoder BC074 C: Sugar Water
問題
https://beta.atcoder.jp/contests/abc074/tasks/arc083_a
解法
質量の合計の最大値が3000gに対して、水は100g単位、砂糖は1g単位です。
なので、全ての組み合わせを試したとしても間に合います。
よって、4重ループで全探索すれば解けます。
実装
A, B, C, D, E, F = gets.chomp.split(" ").map(&:to_i) limit = E.to_f / (100 + E) tmp = 0 max_s =0 max_sw = 0 for a in 0..(F/100/A) for b in 0..((F-100*a*A)/100/B) next if a + b == 0 for c in 0..((F-100*a*A-100*b*B)/C) for d in 0..((F-100*a*A-100*b*B-c*C)/D) sw = c*C+d*D+a*A*100+b*A*100 s = c*C+d*D n = s.to_f / sw break if n > limit if tmp < n tmp = n max_s = s max_sw = sw end end end end end if max_s > 0 puts "#{max_sw} #{max_s}" else puts "#{100*A} #{max_s}" end
AtCoder Beginner Contest 113 参戦記
今度こそは水色へ、と意気込んだABC113。
C完でDも解法はある程度わかったのですが、実装で手間取って例題を通す前に時間切れとなりました。
うーん、水色目前で停滞中です。
A - Discount Fare
問題
https://beta.atcoder.jp/contests/abc113/tasks/abc113_a
解法
X + (Y/2) を出力するだけ
実装
x,y=gets.chomp.split.map(&:to_i) puts x+y/2
B - Palace
問題
https://beta.atcoder.jp/contests/abc113/tasks/abc113_b
解法
各Hi毎に平均気温とA度との差の絶対値を計算し、それが最小の時のiを出力すればよい。
実装
n=gets.to_i t,a=gets.chomp.split.map(&:to_i) h=gets.chomp.split.map(&:to_i) m=Float::INFINITY ans=0 h.each_with_index do |v, i| mm = (a-(t-v*0.006)).abs if mm < m ans = i+1 m = mm end end puts ans
C - ID
問題
https://beta.atcoder.jp/contests/abc113/tasks/abc113_c
解法
各市を読み込みながら、各県に誕生年を振り分けていく。
全て読み込んだら、各県毎の誕生年をソートして各市の順番(x番目)を決定する。
最後に各市毎に認識番号を出力していく。
ということで、考え方自体はそんなに難しくないが、誕生年の順番をどのように知るかにちょっと工夫が必要。
実装
実装としては、配列とハッシュを使って以下のようにした。
まず、各県ごとに誕生年を配列に格納してから最後にソートする。
その後、配列を先頭から見ていき、誕生年をキー、順番を値とするハッシュに格納する。
認識番号を決める際はこのハッシュを使って誕生年から順番を取得するようにする。
n,m=gets.chomp.split.map(&:to_i) p = [] y = [] py = Array.new(n+1).map{Array.new()} m.times do |i| pp, yy = gets.chomp.split.map(&:to_i) p << pp y << yy py[pp] << yy end ph = Array.new(n+1).map{Hash.new()} py.each_with_index do |v, i| v.sort! v.each_with_index do |vv, j| ph[i][vv] = j+1 end end 0.upto(m-1) do |i| printf("%06d%06d\n", p[i], ph[p[i]][y[i]]) end
D - Number of Amidakuji
問題
https://beta.atcoder.jp/contests/abc113/tasks/abc113_d
解法
あと一歩で解けなかったD問題。
取りかかってから10分ぐらいで動的計画法を使うっていうのは分かったんだけど、実装に手間取って例5を通すことが出来なかった。
この問題のポイントは、動的計画法の漸化式をどうたてるか、と、各縦線の数に対して有効な横線の引き方が1段について何通りあるか、です。
- 漸化式の考え方
i番目の行まで横線が引き終わっている時にj列目に存在するときの組み合わせをDP[i][j]とします。
すると、i+1番目でj列にいく組み合わせは
DP[i+1][j] = DP[i][j-1] x (j-1とjの間に線を引くときの組み合わせ)+ DP[i][j] x (j-1とjとj+1の間に線を引かない組み合わせ)+ DP[i][j+1] x (jとj+1の間に線を引くときの組み合わせ)
で求められます。
例を使って説明します。
i+1番目で4列目にいけるのは、i番目で3列目、4列目、5列目にいる場合です。
3列目にいる場合は、3列目と4列目の間に横線を引く必要があります。この場合、それ以外の場所については、左側は1、2列目の間、右側は5、6列目の間の横線の引き方の組み合わせとなります。
4列目にいる場合は、3列目と4列目、及び、4列目と5列目の間に横線を引いてはいけません。この場合、それ以外の場所については、左側は1、3列目の間、右側は5、6列目の間の横線の引き方の組み合わせとなります。
5列目にいる場合は、4列目と5列目の間に横線を引く必要があります。この場合、それ以外の場所については、左側は1、3列目の間、右側はこれ以上横線は引けません。
- 有効な横線の引き方
では、あみだくじとして有効な横線の引き方をどう算出するかです。
これも漸化式の考え方で求めることが出来ます。
縦棒の数がi本の時の一行分の線の引き方をSiとします。
このとき、漸化式は以下のようになります。
これは、i列目とi+1列目に横棒を引く場合はi-1列目とi列目に横棒は引けないのでそれより左側の組み合わせはSi-1通りとなり、i列目とi+1列目に横棒を引かない場合はそれより左側の組み合わせはSi通りになるためです。
ちなみにこれはフィボナッチ数列になります。なお、今回の問題は縦棒は8本までしかないので、数列はハードコードしてしまってます。
実装
以下の実装では、左端と右端は場合分けしていますが、両端に0の列を追加すればもう少し綺麗なるかも。
h,w,k=gets.chomp.split.map(&:to_i) dp=Array.new(h+1).map{Array.new(w)} MOD = 1000000007 nn = [1,2,3,5,8,13,21,34] dp[0][0] = 1 1.upto(w-1) do |i| dp[0][i] = 0 end if w == 1 puts 1 exit end 1.upto(h) do |i| 0.upto(w-1) do |j| dp[i][j] = 0 if(j==0) dp[i][j] += dp[i-1][j] * nn[w-2] dp[i][j] += dp[i-1][j+1] * nn[[0,w-3].max] elsif(j==w-1) dp[i][j] += dp[i-1][j] * nn[w-2] dp[i][j] += dp[i-1][j-1] * nn[[0,w-3].max] else n = nn[[0,j-2].max] n *= nn[[0,w-2-j].max] dp[i][j] += dp[i-1][j-1] * n n = nn[j-1] n *= nn[[0,w-2-j].max] dp[i][j] += dp[i-1][j] * n n = nn[j-1] n *= nn[[0,w-3-j].max] dp[i][j] += dp[i-1][j+1] * n end dp[i][j] = dp[i][j] % MOD end end puts dp[h][k-1] % MOD
結果
Cまでは順調だったんですが、Dで躓いてしまいました。
しかも、Dは動的計画法の漸化式まではある程度思いついたんですが、結局実装しきれませんでした。
とはいえ、動的計画法は苦手分野だったので、途中まで書けただけでも成長したかなという感じです。
いずれにせよ、水色手前でなかなか上がれません。
やはり400点問題は確実に解けるようにしたいところですね。
AtCoder BC079 D: Wall
問題
https://beta.atcoder.jp/contests/abc079/tasks/abc079_d
解法
各数字を1に書き換えるコストが最小になるようにし、その合計を出せば良い。
「1に書き換える最小コスト」=「1への最短距離」と考えることが出来るので、事前にワーシャルフロイド法などで最短距離を求めておけば、あとは各Aijについて加算していけばよい。
実装
#include<iostream> #include<vector> #include<map> #include<algorithm> #include<cmath> #include<string> #include<cstdlib> #define FOR(i,a,b) for(int i=(a);i<(b);++i) #define REP(i,n) FOR(i,0,n) #define MOD 1000000007 using namespace std; typedef long long int ll; const ll INF=(ll)1e18; int main(){ int H,W; cin >> H >> W; int c[10][10]; REP(i,10)REP(j,10){ int tmp; cin >> tmp; c[i][j] = tmp; } REP(i,10)REP(j,10)REP(k,10){ c[j][k] = min(c[j][k], c[j][i] + c[i][k]); } int ans = 0; REP(i,H)REP(j,W){ int a; cin >> a; if(a != -1 && a != 1){ ans += c[a][1]; } } cout << ans << endl; }
AtCoder BC079 C: Train Ticket
問題
https://beta.atcoder.jp/contests/abc079/tasks/abc079_c
解法
演算子の場所は3カ所で+-の2通りしかない。そのため、全パターン試しても 通りで十分に速い。
全パターン試す実装はビット演算で実施。
実装
#include<iostream> #include<vector> #include<map> #include<algorithm> #include<cmath> #include<string> #define FOR(i,a,b) for(int i=(a);i<(b);++i) #define REP(i,n) FOR(i,0,n) #define MOD 1000000007 using namespace std; typedef long long int ll; const ll INF=(ll)1e18; int main(){ string str; int A,B,C,D; cin >> str; A = stoi(str) / 1000; B = stoi(str) / 100 % 10; C = stoi(str) / 10 % 10; D = stoi(str) % 10; REP(i,8){ int tmp = A; char op1,op2,op3; if(i & 1){ tmp += B; op1 = '+'; }else{ tmp -= B; op1 = '-'; } if(i & 2){ tmp += C; op2 = '+'; }else{ tmp -= C; op2 = '-'; } if(i & 4){ tmp += D; op3 = '+'; }else{ tmp -= D; op3 = '-'; } if(tmp == 7){ cout << A << op1 << B << op2 << C << op3 << D << "=7" << endl; return 0; } } }