2012/07/20

Redmineで番組予約、atで録画、Jenkinsで監視

番組データからredmineチケット作成まではなんとか自動化出来た。
次は、予約~録画~監視までの流れを構築する。

’予約’トラッカー
当初は、チケットステータスで、新規・予約・録画・・・ってやろうと思ったけれど
ヤメた。録画に必要な番組情報をカスタムフィールドで持つことにしたのだが、これらはトラッカーに紐付いているので、ステータス表現だと都合が悪い。トラッカーで表現する事にした。

録画予約したい番組チケットをredmine上でトラッカーを’予約’に変更すればよい。
という事にした。

予約専用カスタムフィールド :job_id
以前の記事:RedmineとActiveRecordで番組チケット作成

* 録画開始時間(数値)
* チャンネル(文字列)
* ジョブ番号(数値):録画ジョブ管理番号

って考えていたが、ジョブ番号は、予約トラッカーだけが持つものにした。
録画ジョブが実行されるのは予約したチケットだけなわけだから。

録画ジョブ
暫定ですが、’at’で行うようにしてみる。(思うところあって、暫定です)

atで録画スクリプト実行を登録するコマンド
echo 'CH=[チャンネル] LEN=[長さ] OUT=[ファイルパス] record.sh'|at -t [開始時間]
こんな感じ。atコマンドは実行内容をファイルで受け取るか標準入力から受け取るか、なので
ファイルにしたくなかったんで、echoとパイプで標準入力から入れるように書く。

こういう形になるように、録画予約スクリプトを記述すれば良い。
ただ、後でちょっと変更するはめになった(後述)。

そして、これから用意する予約スクリプトでは、予約チケットを検索して、番組時間に録画が開始されるようにatジョブを登録して、ジョブIDをチケットに記録しておく処理を行えばいい。

atのjob-id取得
予約スクリプト内でatを実行するわけだが、そのJOB番号をスクリプト内で簡単に取得する方法でちょっと悩んだ。`at`とかsystem("at")とかexec("at")じゃとれないし・・・欲しい情報は標準エラーに出力されているのだ。色々調べた結果、シンプルに取得することが出来た。
result,_s = Open3.capture2e( command )
result[/\d* at/].to_i
open3のcapture2eは標準出力と標準エラー出力を混ぜて返してくれる。
2行目で”・・・ [ID] at ・・・”な感じの文字列から"数値 at”にする。最初が数値ならそのまま.to_iすれば数値だけが得られる。"数値"な文字列にするところまで頑張らなくてもいい。

あっ。スクリプト言語はRubyです

Jenkinsで監視
atジョブの結果をJenkinsで管理したい。
Jenkinsで「外部ジョブの監視」っていうジョブタイプでジョブ名を「atjob」って事にして作成しておく。
で、外からJenkinsサーバーのこのatjobに対して結果をXMLでPOSTすればいいらしい。
それを簡単に行うためのコマンドが Jenkins-core だそうな。

えーと、jenkins-coreはどこかいな・・・
マニュアル読んでも、どこかの記事にも具体的なパスが書いてないぞ?
/var/lib/jenkins ?違うなぁ。
/usr/share/jenkins ? jenkins.warならここにあるけど、この中から引っこ抜くのか?

あったあった
$ ps ax|grep jenkins (プロセスを探すときによくやるフレーズ)
でみてみると
/usr/bin/java -jar /usr/share/jenkins/jenkins.war --webroot=/var/run/jenkins/war --httpPort=8080 --ajp13Port=-1 --preferredClassLoader=java.net.URLClassLoader --prefix=/jenkins --logfile=/var/log/jenkins/jenkins.log

ってなってる。--webroot=/var/run/jenkins/war だって。
/var/run/jenkins/war に、jenkins.war が展開されていた。
jenkinsをapt-getでインストールしたんで、不思議なところがJenkinsのwebrootになってた。

ということで、これですな。
/var/run/jenkins/war/WEB-INF/lib/jenkins-core-1.424.6.jar

java -jar /var/run/jenkins/war/WEB-INF/lib/jenkins-core-*.jar atjob [コマンド]
で、コマンドの結果がjenkinsのatjobで監視できるということか。
-*.jarにしてるのはバージョンが変わっても変更しないでいいように濁しておく。

さきほどの録画コマンドをatジョブに登録する処理が以下のようだとして
echo 'CH=%d LEN=%d OUT=%s record.sh'|at -t '%s'

jenkins-coreをかぶせる
echo 'java -jar /var/run/jenkins/war/WEB-INF/lib/jenkins-core-*.jar atjob "echo 'CH=%d LEN=%d OUT=%s record.sh'|at -t '%s'"

echo 'CH... そんなコマンドないって怒られる。echoはシェルコマンドだからか?
atならシェルコマンドとして解釈されていたが、jenkins-coreはjavaだからダメか?
これに更にシェルを被せるのはちょっと気が引けるな。どうしようか。
仕方ない。
録画コマンドシェルを環境変数じゃなくて引数で受け取るように変更だ。

echo 'java -jar /var/run/jenkins/war/WEB-INF/lib/jenkins-core-*.jar atjob record2.sh %d %d %s'|at -M -t '%s'

こうだね。record2.shが改良版ってことで。
%d %d %sは、スクリプト内で値を渡すところ。

実行・・・おっと、別の原因でだめだった。
jenkinsサーバーのURLをJENKINS_HOMEという環境変数で渡すらしい。

で完成したのが
echo 'JENKINS_HOME=http://localhost/jenkins java -jar /var/run/jenkins/war/WEB-INF/lib/jenkins-core-*.jar atjob record2.sh %d %d %s'|at -M -t '%s'

こんな感じでatジョブに登録すると、at内で

JENKINS_HOME=http://localhost/jenkins java -jar /var/run/jenkins/war/WEB-INF/lib/jenkins-core-*.jar atjob record2.sh %d %d %s

が実行される。
record2.shの出力結果がjenkins-coreを通してjenkinsの'atjob'ジョブにポストされる。

やってみると、録画からエンコードまでの出力がすべてログされる感じになった。
だけど、失敗してもjenkinsジョブには失敗として伝わらなかった・・・うーん。
atジョブ自体がどうかなぁと思っているんで、今はこれ以上の追求はやめて、jenkinsでログが取れただけでもよしとしよう。

最終的に、redmineの予約チケットから情報を取得して、atジョブがまだ登録されていなければ、上記のコマンドで登録して、atのジョブIDをチケットに記録するスクリプトがこちら

resserve.rb
#!/usr/bin/ruby
# -*- coding: utf-8 -*-
#
require 'rubygems'
require 'active_record'
require 'active_support/core_ext'
require 'open3'

ActiveRecord::Base.establish_connection(
 :adapter => 'mysql2',
 :host => 'localhost',
 :username => 'redmine',
 :password => 'redmine',
 :database => 'redmine'
)

class Issue < ActiveRecord::Base
end

class CustomValues < ActiveRecord::Base
end

# ジョブ登録
def entry_job info

  # コマンド
  command = "echo 'JENKINS_HOME=http://localhost/jenkins java -jar /var/run/jenkins/war/WEB-INF/lib/jenkins-core-*.jar atjob record2.sh %d %d %s'|at -M -t '%s'" %
    #command = "echo 'CH=%d LEN=%d OUT=%s record.sh'|at -t '%s'" %
    [ info[:channel],
      info[:duration],
      info[:output]+info[:datetime],
      info[:datetime]
    ]

  # 登録実行
  result,_s = Open3.capture2e( command )
  p command, result

  # ジョブID
  result[/\d* at/].to_i
end

# 予約チケット
issues = Issue.find( :all, :conditions => {:tracker_id => 2})

issues.each {|issue|
  # job_id
  job_id = CustomValues.first( :conditions => {
                                 :customized_id => issue.id,
                                 :custom_field_id => 3})

  # job_idアトリビュートが存在しない(queryから登録された)
  if !job_id
    job_id = CustomValues.create( :customized_type => "Issue",
                                  :customized_id => issue.id,
                                  :custom_field_id => 3, # job_id
                                  :value => "" )
  end

  # 未登録?
  if job_id.value.to_i == 0
    time = CustomValues.first( :conditions => {
                                 :customized_id => issue.id,
                                 :custom_field_id => 1}).value.to_s
    channel = CustomValues.first( :conditions => {
                                    :customized_id => issue.id,
                                    :custom_field_id => 2}).value.to_i

    info = {
      # 録画開始日時
      :datetime => issue.start_date.strftime("%Y%m%d") + format("%04d",time),
      # 録画時間
      :duration => issue.estimated_hours.to_i * 60,
      # チャンネル
      :channel => channel,
      # 保存場所
      :output => "出力パス"
    }

    # ジョブ 記録
    job_id.value = entry_job info
    job_id.save if job_id.value.to_i > 0

    # ステータス更新
    issue.status_id = 2
    issue.save
  end
}

色々ハードコードだったり、record2.shへのパスとか出力パスとかを濁してあるんで、ご参考までに。

とにかくこれを、jenkinsで毎時*時50分に実行するようにした。
大体録画したいやつは〇〇時00分とかが多いので、10分前までに予約すれば
録画ジョブ登録のタイミングに間に合うって感じだ。
この辺もちょっとなぁと思っているけど、今はこれでいい。

録画シェルコマンドがこちら
record2.sh [channel] [録画時間(分間)] [出力ファイルパス]
#!/bin/sh

CH=$1
LEN=$2
OUT=$3

echo "CHANNEL : $CH"
echo "LENGTH  : $LEN"
echo "OUTPUT  : $OUT"

TMP=$(tempfile)

# record
recfsusb2n --b25 $CH $LEN ${TMP}.ts

# encode
ffmpeg -y -i ${TMP}.ts -c:v libx264 -c:a libfaac -preset superfast -b:v 1800k -s 960x540 -aspect 16:9 -f mp4 -threads 0 ${TMP}.mp4

# corresponding to iPod
MP4Box -ipod -inter 500 ${TMP}.mp4 -out ${OUT}.mp4

# thumbnail
ffmpeg -i ${OUT}.mp4 -ss 12 -vframes 1 -an -s 320x180 ${OUT}.jpg

#FILE=`echo $OUT|sed s/^.*[/]//`
#ffmpeg -y -i ${OUT}.mp4 -threads 0 -s 480x272 -acodec copy -vcodec mpeg4 -b:v 900k -qmin 3 -qmax 5 -f mp4 ${OUT}-s.mp4

# Cleanup first!
rm ${TMP}*
これは、以前の記事:ひとまず録画できるところまで構築 から若干手を加えた感じのもの。
それほど変わってないけど。

ふぅ。何とか録画までたどり着いた。だけど、色々問題を残してるな。
その辺は追々じっくりと解決して行きたい。

次回は、録画した後処理についてかな。

0 件のコメント:

コメントを投稿