読者です 読者をやめる 読者になる 読者になる

COBOL技術者の憂鬱

COBOLプログラマは不在にしています

RetroTubeをリニューアルしています

RetroTube
http://www.retro-tube.com/



5年ほど前に作ったRetroTubeを、去年の暮れからまた新たにいじり始めています。


最初に作った当初は、週に一度くらいの間隔で表示データを手動更新していたのですが、だんだん面倒くさくなってきて、ここ3年ほど全くやっていませんでした。
おかげで今ではひどい状態になっており、これはかわいそうだなーいつかなんとかしないとなーと、長い間思っていました。
なので、今回ようやく重い腰を上げて取り組むことにしたのです。


とりあえずは、今時のインフラの上で動くようにしようと考え、例によってGAE上でcronを回し、自動でデータ更新するようにしました。
このあたりはもうお手のものですね。


次に、今時のWebサービスのように、画面遷移をしないUIに変更してやろうと考えました。
こういうことをやろうと思うと、かつてはものすごく大変だったのに、今は色々ライブラリを組み合わせていくだけでできるようになっているんですよね。

まず、年代の指定にjquery ui sliderというライブラリを使い、スライダーで指定できるように変えました。これを使って動画の一覧が動的に更新されるようにしています。
これでひとつ画面遷移を減らすことができました。

次に、ユーザーの選択した動画を表示する際に、lightbox風のイフェクトをかけようと思い、prettyPhotoというライブラリを使うことにしました。
これでさらに画面遷移が減りましたね。っていうか全くなくすことができましたね。


その他の変更点としては、検索対象の年代を大幅に増やして、1960年から2009年までの半世紀を対象にしました。
なので、結構年配の方にも楽しんでいただけるようになったかなーと思っています。

また、検索ジャンルは邦楽のみに絞っています。これは、映画とかアニメについては、また別のサービスとして違った形で提供していこうと考えているからです。


今後の機能追加としては、現時点で年代単位の検索はできるのですが、歌手単位で横串検索できるようにしないと面白さが半減するので、それをやろうと思います。
まだUIがちょっとよくイメージできていないのですが…

ソースコード公開(その2)

昨日はバッチ部分を掲載したので、今日はオンライン部分ですね。
オンライン部分は、大きく以下の4つの部分に分かれています。

  • トップページを表示する部分(リスト6)
  • 検索結果一覧を表示する部分(リスト7)
  • 検索結果詳細を表示する部分(リスト8)
  • Rimoと連携する部分(リスト9、10)


さてと、これでRetroTube内で動いている全てのプログラムのソースコードを公開したことになりますが、ちょっとコードを読める方であれば、シンプルな仕組みで動いていることに驚かれたのではないでしょうか?
てっきりRailsを使ったDBアプリだと勘違いされていた方もいらっしゃると思いますが、そのような小難しいことは一切やっていないのです。
ひたすら移送と分岐と繰り返しの羅列なので、このままCOBOLでリメイクできそうな気がしますね。
では、何故Rubyで書いたのかと興味深く思われた方は、是非雑誌の方の記事を参照していただきたいです。


今後も暇を見ては(RetroTubeの)改良を続けていくつもりですが、既に別のサービスの開発に取り掛かっているところなので、徐々にそちらの方へ注力していくことになると思います。
そちらの方もご期待ください。




【リスト6】index.cgi

#!/usr/local/bin/ruby
print "Content-type: text/html\n\n"

#####################################
#【RetroTube】トップページ表示プログラム
#####################################

require "csv"

#ここからHTML生成
#HTMLヘッダ
print '<html><head>
          <title>RetroTube</title>
          <meta http-equiv="content-type" content="text/html; charset=Shift_JIS">
          <meta http-equiv="Content-Style-Type" content="text/css">
          <link rel="alternate" type="application/rss+xml" title="RetroTube RSS" href="http://www.retro-tube.com/rss.rdf">
          <link rel="stylesheet" type="text/css" href="title.css">
          </head><body>'

#開発者
print '<p class="header">This service is created by
          <a href="http://d.hatena.ne.jp/quill3/" target="_blank" class="cursor">quill3</a>.
          </p><br>'

#ロゴ
print '<div align=center>
          <img src="logo.gif" alt="RetroTube">
          <br><br>'

#検索フォーム
print '<form action="search.cgi" method="GET">
          <select name="year">'
for i in 1976..2004
  print '<option>' + i.to_s + '</option>'
end
print '</select>
          年の
            <select name="category">
            <option value="music">邦楽</option>
            <option value="anime">アニメ</option>
            <option value="movie_j">邦画</option>
            <option value="movie_a">洋画</option>
          </select>

          <input type="submit" value="YouTube検索">
          </form>'

print '</div><br><br>'

#新着動画
print '<h4>新着動画
          <a href="http://rssicon20.com/rss.php?u=http%3A%2F%2Fwww.retro-tube.com%2F&s=1">
          <img src="rsslogo.gif" alt="RSS" title="RSS" align="absmiddle" border="0" style="margin-left:5px"></a></h4>'

csv = CSV.open('compare.csv', 'r')
csv.each { |line|
  print '<div class="video" ><p><a href="detail.cgi?movieid=' + line[3] + '&category=' + line[6] + '" target="_blank"><img src="' + line[4] + 
           '" alt="' + line[2] + ' - ' + line[1] + '" title="' + line[2] + ' - ' + line[1] + '" class="video-thumbnail"/></a></p>
           <p><a href="detail.cgi?movieid=' + line[3] + '&category=' + line[6] + '" target="_blank" class="cursor">' + line[2] + '</a></p>
           <p><a href="detail.cgi?movieid=' + line[3] + '&category=' + line[6] + '" target="_blank" class="cursor">' + line[1] + '</a></p>
           </div>'
  }

#Google Analytics
print '<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>
          <script type="text/javascript">_uacct = "UA-568420-2";urchinTracker();</script>'

print '</body></html>'


【リスト7】search.cgi

#!/usr/local/bin/ruby
print "Content-type: text/html\n\n"

#####################################
#【RetroTube】検索結果一覧表示プログラム
#####################################

require "cgi-lib"
require "kconv"
require "csv"

#他ページからの入力パラメータ(年代とカテゴリ)
input = CGI.new
inputyear = input["year"]
inputcategory = input["category"]

#カテゴリ表示用クラス
class ShowCategory
  def initialize(parm_category)
    @categorytable = [["music","邦楽"],
                              ["anime","アニメ"],
                              ["movie_j","邦画"],
                              ["movie_a","洋画"]]
    @categorytable.each { |elem|
      if parm_category == elem[0]
        @category = elem[1]
        break
      end
    }
  end  
  def show
    print @category
  end
  def select
    @categorytable.each { |elem|
      print '<option'
      if @category == elem[1]
        print ' selected="selected"'
      end
      print ' value="' + elem[0] + '">' + elem[1] + '</option>'
    }
  end
end

show_category = ShowCategory.new(inputcategory) 

#ここからHTML生成

#HTMLヘッダ
print '<html><head>
          <title>RetroTube : ' + inputyear + '年の'
show_category.show
print '</title>
         <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
         <meta http-equiv="Content-Style-Type" content="text/css">
         <link rel="stylesheet" type="text/css" href="basic.css">
         </head><body>'

#ロゴ
print '<a href="http://www.retro-tube.com/">
          <img src="logo.gif" alt="RetroTube" width=250 border="0"></a>'

#検索フォーム
print '<form action="search.cgi" method="GET">
          <select name="year">'
for i in 1976..2004
  if i.to_s == inputyear
    print '<option selected="selected">' + inputyear + '</option>'
  else
    print '<option>' + i.to_s + '</option>'
  end
end
print '</select>
          年の
          <select name="category">'

show_category.select

print '</select>

          <input type="submit" value="YouTube検索">
          </form>'

#前後の年代へのリンク
print '<div align=right>'
beforeyear = inputyear.to_i - 1
nextyear = inputyear.to_i + 1
if beforeyear >= 1976
  print '<a href="search.cgi?year=' + beforeyear.to_s + '&category=' + inputcategory + '">'
  print '<' + beforeyear.to_s + '年</a>&nbsp;&nbsp;'
end
if nextyear <= 2004
  print '<a href="search.cgi?year=' + nextyear.to_s + '&category=' + inputcategory + '">'
  print nextyear.to_s + '年></a>'
end
print '</div>'

#検索結果動画一覧
print '<h3>' + inputyear + '年の'
show_category.show
print '<a href="remo.cgi?year=' + inputyear + '&category=' + inputcategory + '" target="_blank"><img src="show_rimo.gif"'
print ' alt="Rimo" title="Rimo" align="absmiddle" height="25" border="0" style="margin-left:10px"></a>'
print '</h3>'

csv = CSV.open(inputcategory + '.csv', 'r')
csv.each { |line|
  if line[0] == inputyear
    print '<div class="video" ><p><a href="detail.cgi?movieid=' + line[3] + '&category=' + inputcategory + '" target="_blank"><img src="' + line[4] +
             '" alt="' + line[2] + ' - ' + line[1] + '" title="' + line[2] + ' - ' + line[1] +'" class="video-thumbnail"/></a></p>'
    print '<p><a href="detail.cgi?movieid=' + line[3] + '&category=' + inputcategory + '" target="_blank" class="cursor">' + line [2]
#アニメだけはOPとEDの区別を表示する(特殊ロジック)
    if inputcategory == "anime"
      if line[6] != "NA"
        print ' ' + line[6]
      end
    end
#ここまで特殊ロジック
    print '</a></p>'
    print '<p><a href="detail.cgi?movieid=' + line[3] + '&category=' + inputcategory + '" target="_blank" class="cursor">' + line [1] + '</a></p>'
    print '</div>'
  end
  }

#開発者
print '<p class="footer"><br>This service is created by
          <a href="http://d.hatena.ne.jp/quill3/"  target="_blank" class="cursor">quill3</a>.</p>'

#Google Analytics
print '<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>
          <script type="text/javascript">_uacct = "UA-568420-2";urchinTracker();</script>'

print '</body></html>'


【リスト8】detail.cgi

#!/usr/local/bin/ruby
print "Content-type: text/html\n\n"

#####################################
#【RetroTube】検索結果詳細表示プログラム
#####################################

require "cgi-lib"
require "kconv"
require "csv"
require "net/http"

##AmazonAPIアクセス用ライブラリ
require "amazon/search"
include Amazon::Search
assoc_id = "retr-22"
dev_token = "0Z2TJZWAYA1T2ZNW4482"

#他ページからの入力パラメータ(動画IDとカテゴリ)
input = CGI.new
inputmovieid = input["movieid"]
inputcategory = input["category"]

#動画IDとカテゴリを使用して、検索結果一覧から対象動画を引き当てる
csv = CSV.open(inputcategory + '.csv', 'r')
csv.each { |line|
  if line[3] == inputmovieid
    $tyear = line[0]
    $tartist = line[1]
    $ttitle = line[2]
    $tmovieid = line[3]
    if inputcategory == "anime"
      if line[6] == "NA"
        $show_title = line[2]
      else
        $show_title = line[2] + " " + line[6]
      end
    else
      $show_title = line[2]
    end
    break
  end
  }

#ここからHTML生成

#HTMLヘッダ
print '<html><head>
        <title>RetroTube : ['
case inputcategory
when "music"
  print '邦楽'
when "anime"
  print 'アニメ'
when "movie_j"
  print '邦画'
when "movie_a"
  print '洋画'
end
print '] ' + $show_title + ' - ' + $tartist + ' (' + $tyear +')'
print '</title>
         <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
         <meta http-equiv="Content-Style-Type" content="text/css">
         <link rel="stylesheet" type="text/css" href="basic.css">
         </head><body>'

#ロゴ
print '<a href="http://www.retro-tube.com/">
         <img src="logo.gif" alt="RetroTube" width=250 border="0"></a>'

#大見出し
print'<h3>' + $show_title + ' - ' + $tartist + ' (' + $tyear + ')</h3>'

#対象動画を埋め込みで表示
print'<div class="movieinfo">
        <object width="425" height="350">
        <param name="movie" value="http://www.youtube.com/v/' + $tmovieid + '"></param>
        <param name="wmode" value="transparent"></param>
        <embed src="http://www.youtube.com/v/' + $tmovieid + '" type="application/x-shockwave-flash" 
          wmode="transparent" width="425" height="350"></embed>
        </object>
       </div>'

#Wikipedia情報の表示(simpleAPIを使用して情報取得)
address = "wikipedia.simpleapi.net"
case inputcategory
when "music"
  path = "/api?keyword=" + Kconv.toutf8($tartist) + "&output=html"
when "anime","movie_j","movie_a"
  path = "/api?keyword=" + Kconv.toutf8($ttitle) + "&output=html"
end
convpath = path.gsub(' ','%20')
body = Net::HTTP.get( address , convpath ) 
sjisbody = Kconv.tosjis(body)
convbody = sjisbody.gsub('Wikipedia:<a href=','Wikipedia:<a target="_blank" href=')
print '<div class="wikiinfo">' + convbody + '</div>'

#Amazon広告の表示(AmazonAPIアクセス用ライブラリ使用)
request = Request.new(dev_token, assoc_id, "jp", false)
print '<br><br><div class="amazonad">'
case inputcategory
when "music"
  k = 1
  request.artist_search(Kconv.toutf8($tartist),"music",HEAVY) do |product|
    j = 1
    product.artists.each { |temp|
      if temp.upcase == Kconv.toutf8($tartist).upcase
        print '<div class="adspace">'
        print '<a href="' + product.url + '" target="_blank">
                <img src="' + product.image_url_medium + '" alt="thumbnail" style="float:left; margin: 0 15px 10px 10px; padding: 0;border:none;">
                </a>'
        print '<dl style="margin-bottom:0.5em; text-align:left; min-height: 168px;font-size:12px;line-height:16px;">'
        print '<dt><a href="' + product.url + '" target="_blank">' + Kconv.tosjis(product.product_name) + '</a></dt>'
        print '<dd>'
        i = 1
        product.artists.each { |temp2|
          print Kconv.tosjis(temp2) + '  ' 
          i += 1
          if i > 3 #表示するアーティストの数
            break
          end
        }
        print '</dd>'
        print '<dd>' + Kconv.tosjis(product.manufacturer) + '  ' + Kconv.tosjis(product.release_date) + '</dd><br>'
        if product.our_price != nil
          print '<dd>価格 : <strong>' + Kconv.tosjis(product.our_price) + '</strong></dd>'
        end
        print '<dd>(' + Kconv.tosjis(product.availability) + ')</dd><br>'
        print '<dd><a href="' + product.url + '" target="_blank">Ads by Amazon.co.jp</a></dd>'
        print '</dl></div>'
        k += 1
        break
      end
      j += 1
      if j > 3 #検索精度(この数字が少ないほど精度が高い)
        break
      end
    }
    if k > 2 #表示する商品の数
      break
    end
  end
when "anime","movie_j","movie_a"
begin
  j = 1
  if inputcategory == "anime"
    searchword = Kconv.toutf8($ttitle)
  else
    searchword = Kconv.toutf8($ttitle) + " " + Kconv.toutf8($tartist)
  end
  request.keyword_search(searchword,"dvd",HEAVY) do |product|
    print '<div class="adspace">'
    if product.image_url_medium != nil
      print '<a href="' + product.url + '" target="_blank">
              <img src="' + product.image_url_medium + '" alt="thumbnail" style="float:left; margin: 0 15px 10px 10px; padding: 0;border:none;">
              </a>'
    end
    print '<dl style="margin-bottom:0.5em; text-align:left; min-height: 168px;font-size:12px;line-height:16px;">'
    print '<dt><a href="' + product.url + '" target="_blank">' + Kconv.tosjis(product.product_name) + '</a></dt>'
    print '<dd>'
    i = 1
    if product.starring != nil
      product.starring.each { |temp2|
        print Kconv.tosjis(temp2) + '  ' 
        i += 1
        if i > 3 #表示する出演者の数
          break
        end
        }
    end
    print '</dd>'
    print '<dd>' + Kconv.tosjis(product.manufacturer) + '  ' + Kconv.tosjis(product.release_date) + '</dd><br>'
    if product.our_price != nil
      print '<dd>価格 : <strong>' + Kconv.tosjis(product.our_price) + '</strong></dd>'
    end
    print '<dd>(' + Kconv.tosjis(product.availability) + ')</dd><br>'
    print '<dd><a href="' + product.url + '" target="_blank">Ads by Amazon.co.jp</a></dd>'
    print '</dl></div>'
    j += 1
    if j > 2 #表示する商品の数
      break
    end
  end
rescue
end
end

print '</div>'

#関連動画表示
case inputcategory
when "music"
  print '<h4>' + $tartist + 'の曲一覧</h4>'
when "anime"
  print '<h4>' + $tartist + ' 製作アニメ一覧</h4>'
when "movie_j","movie_a"
  print '<h4>' + $tartist + ' 監督作品一覧</h4>'
end
scsv = CSV.open(inputcategory + '.csv', 'r')
scsv.each { |sline|
  if $tartist == sline[1]
    print '<div class="video" >'
    print '<p><a href="detail.cgi?movieid=' + sline[3] + '&category=' + inputcategory + '" >
            <img src="' + sline[4] + '" alt="' + sline[1] + ' - ' + sline[2] + 
            '" title="' + sline[1] + ' - ' + sline[2] +'" class="video-thumbnail"/></a></p>'
    print '<p><a href="detail.cgi?movieid=' + sline[3] + '&category=' + inputcategory + '" class="cursor">' + sline[2]
    if inputcategory == "anime"
      if sline[6] != "NA"
        print ' ' + sline[6]
      end
    end
    print '</a></p>'
    print '<p><a href="detail.cgi?movieid=' + sline[3] + '&category=' + inputcategory + '" class="cursor">' + sline[0] + '</a></p>'
    print '</div>'
  end
  }

#開発者
print '<p class="footer"><br>This service is created by
         <a href="http://d.hatena.ne.jp/quill3/"  target="_blank" class="cursor">quill3</a>.</p>'

#Google Analytics
print '<script src="http://www.google-analytics.com/urchin.js" type="text/javascript"></script>
         <script type="text/javascript">_uacct = "UA-568420-2";urchinTracker();</script>'

print '</body></html>'


【リスト9】remo.cgi

#!/usr/local/bin/ruby

#####################################
#【RetroTube】Rimo動画連続再生リクエスト用プログラム
#####################################

require "cgi-lib"
require "net/http"

#他ページからの入力パラメータ(年代とカテゴリ)
input = CGI.new
inputyear = input["year"] 
inputcategory = input["category"] 

#RimoAPIコール(Rimoへ動画一覧を渡し、連続再生をリクエストする)
remoch_url = 'http%3A%2F%2Fwww.retro-tube.com%2Fremoch.cgi%3Fyear%3D' + inputyear + '%26category%3D' + inputcategory
address = "rimo.tv"
path = "/channel/xml?url=" + remoch_url
body = Net::HTTP.get( address , path )

#Rimoの動画連続再生ページへリダイレクト
remoch_url = "http://www.retro-tube.com/remoch.cgi?year=" + inputyear + "&category=" + inputcategory
print "Location: http://rimo.tv/#/channel?url=" + remoch_url + "\n\n"


【リスト10】remoch.cgi

#!/usr/local/bin/ruby
print "Content-type: text/html\n\n"

#####################################
#【RetroTube】Rimoリクエスト用動画一覧生成プログラム
#####################################

require "cgi-lib"
require "csv"

#他ページからの入力パラメータ(年代とカテゴリ)
input = CGI.new
inputyear = input["year"] 
inputcategory = input["category"] 

#ここからHTML生成

#HTMLヘッダ
print '<html><head><title>RetroTube : ' + inputyear + '年の'
case inputcategory
when "music"
  print '邦楽'
when "anime"
  print 'アニメ'
when "movie_j"
  print '邦画'
when "movie_a"
  print '洋画'
end
print '</title><meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS"></head><body>'

#ロゴ
print '<a href="http://www.retro-tube.com/"><img src="logo.gif" alt="RetroTube" width=250 border="0"></a>'

#連続再生対象動画の一覧を生成
csv = CSV.open(inputcategory + '.csv', 'r')
csv.each { |line|
                if line[0] == inputyear
                  print '<br><a href="http://www.youtube.com/watch?v=' + line[3] + '">' + line[2] + '</a>'
                end
              }
print '</body></html>'

ソースコード公開


今年初めにリリースしましたRetroTubeですが、ソースコード公開すると宣言しつつ、お化粧直しする余裕が取れなくてこれまでズルズルときてしまいました。


気が付けばもう6月…これではいかんということで、一念発起してなんとか皆様のお目に触れても恥ずかしくない程度にまで整形することができました。
ですので、本日より数日間に渡って公開していきたいと思います。GPLで公開します。

  • RetroTubeのシステムは、バッチ部分とオンライン部分の二つに大きく分かれているので、まずはバッチ部分から掲載します。
  • リスト1〜4は、それぞれ音楽・アニメ・邦画・洋画の各カテゴリについて、YouTubeから一括検索して結果を出力するプログラムです。
  • リスト5は、上記検索結果から新着情報を出力するものです。


あと、ここからは超重要なお知らせになってしまうのですが、日経ソフトウエア8月号(6/23発売)から、短期集中連載という形で「RetroTube開発記」を執筆させていただくことになっています。
(写真は8月号の予告で、「COBOLプログラマによるRubyプログラミング挑戦記」とありますが、これ実は、私のことです。)


単にソースコードの解説に留まらず、開発のきっかけとなった出来事から、開発中に考えていた事、公開後の世間からの反応など、様々な角度から楽しんでいただける内容になっていますので、是非大勢の方に読んでいただきたいと思っています。
よろしくお願いします。


それでは以下より、ソースコード垂れ流していきます。




【リスト1】search_music.rb

#####################################
#【RetroTube】一括検索プログラム(邦楽)
#####################################

require "youtube"
require "kconv"
require "fastercsv"

#YouTubeAPIアクセス用ライブラリ初期化
youtube = YouTube.new '(DeveloperID)'

#前回検索時の結果を退避(newdataフォルダからolddataフォルダへ)
File.rename("newdata/music.csv","olddata/music.old.csv")

#ここから検索処理
musicout = open("newdata/music.csv",'w')
beforeartist = nil
FasterCSV.foreach("source/source_music.csv") { |line|
#アーティスト名がキーブレイクした時、タグ検索(最大300件)を行い、$getdataに格納する
  if line[3] != beforeartist
    $getdata = Array.new
    i = 1
    loop {
      begin
        videos = youtube.videos_by_tag(Kconv.toutf8(line[3]),i,100)
        if videos == []
          break
        end
        videos.each { |video|
          targettitle = Kconv.tosjis(video.title)
          convtitle = targettitle.gsub(',',' ')
          $getdata.push([convtitle,video.id,video.thumbnail_url,video.url])
          }
      rescue
        break
      end
      i += 1
      if i > 3
        break
      end
      }
  end
#アーティスト名でタグ検索した結果($getdataに格納されている)から、
#さらに曲タイトルを含むものを検索し、結果を出力
  $getdata.each { |getline|
    compare = Kconv.toutf8(line[4])
    pattern = Regexp.new(compare.upcase)
    compare2 = Kconv.toutf8(getline[0])
    if pattern =~ compare2.upcase
      musicout.puts line[0] + ',' + line[1] + ',' + line[2] + ',' + 
                           getline[1] + ',' + getline[2] + ',' + getline[3]
      break
    end
    }
  beforeartist = line[3]
  }
musicout.close


【リスト2】search_anime.rb

#####################################
#【RetroTube】一括検索プログラム(アニメ)
#####################################

require "kconv"
require "net/http"
require "fastercsv"

#前回検索時の結果を退避(newdataフォルダからolddataフォルダへ)
File.rename("newdata/anime.csv","olddata/anime.old.csv")

#ここから検索処理
address = "www.youtube.com"
path = "/api2_rest?method=youtube.videos.list_by_category_and_tag&dev_id=6BQYABg1UFM&category_id=1&page=1&per_page=20&tag="
newdataout = open("newdata/anime.csv",'w')
FasterCSV.foreach("source/source_anime.csv") { |line|
  p line
  begin
    searchword_utf8 = line[4].kconv(Kconv::UTF8,  Kconv::SJIS)
    searchpath = path + searchword_utf8
    body = Net::HTTP.get( address , searchpath ) 
    re = %r|<id>(.*?)</id>|u
    searchresult_id = body.scan(re)
    re = %r|<title>(.*?)</title>|u
    searchresult_title = body.scan(re)
    re = %r|<thumbnail_url>(.*?)</thumbnail_url>|u
    searchresult_thumbnail_url = body.scan(re)
    re = %r|<url>(.*?)</url>|u
    searchresult_url = body.scan(re)
    opflag = 0
    edflag = 0
    re_op = %r|op|u
    re_ed = %r|ed|u
    re_end = %r|end|u
    searchresult_id.zip(searchresult_title,searchresult_thumbnail_url,searchresult_url){|zip_id,zip_title,zip_thumbnail_url,zip_url|
      if opflag == 0
        if zip_title[0].downcase =~ re_op
          newdataout.print line[0] + "," + line[1] + "," + line[2] + "," + zip_id[0] + "," + zip_thumbnail_url[0] + "," + zip_url[0] + ",OP" + "\n"
          opflag = 1
          if zip_title[0].downcase =~ re_ed || zip_title[0].downcase =~ re_end
            edflag = 1
          end
        end
      end
      if edflag == 0
        if zip_title[0].downcase =~ re_ed || zip_title[0].downcase =~ re_end
          newdataout.print line[0] + "," + line[1] + "," + line[2] + "," + zip_id[0] + "," + zip_thumbnail_url[0] + "," + zip_url[0] + ",ED" + "\n"
          edflag = 1
        end
      end
      if opflag == 1 && edflag == 1
        break
      end
      }
    if opflag == 0 && edflag == 0
      if searchresult_id != []
        newdataout.print line[0] + "," + line[1] + "," + line[2] + "," + searchresult_id[0][0] + "," + searchresult_thumbnail_url[0][0] + "," + searchresult_url[0][0] + ",NA" + "\n"
      end
    end
  rescue
    p 'timeout err'
  end
  }
newdataout.close


【リスト3】search_movie_j.rb

#####################################
#【RetroTube】一括検索プログラム(邦画)
#####################################

require "kconv"
require "net/http"
require "fastercsv"

#前回検索時の結果を退避(newdataフォルダからolddataフォルダへ)
File.rename("newdata/movie_j.csv","olddata/movie_j.old.csv")

#ここから検索処理
address = "www.youtube.com"
path = "/api2_rest?method=youtube.videos.list_by_category_and_tag&dev_id=6BQYABg1UFM&category_id=1&page=1&per_page=1&tag="
newdataout = open("newdata/movie_j.csv",'w')
FasterCSV.foreach("source/source_movie.csv") { |line|
  begin
    re_japan = %r|日本|s
    if re_japan =~ line[6]
      p line[0] + " " + line[1] + " " + line[5]
      if line[5].split(//s).length > 10
        searchword = line[5]
      else
        searchword = line[5] + " " + line[4]
      end
      searchword_utf8 = searchword.kconv(Kconv::UTF8,  Kconv::SJIS)
      searchpath = path + searchword_utf8
      convpath = searchpath.gsub(' ','%20')
      body = Net::HTTP.get( address , convpath ) 
      re = %r|<id>(.*?)</id>|u
      result_id = body.scan(re)
      re = %r|<thumbnail_url>(.*?)</thumbnail_url>|u
      result_thumbnail_url = body.scan(re)
      re = %r|<url>(.*?)</url>|u
      result_url = body.scan(re)
      result_id.zip(result_thumbnail_url,result_url) { |zip_id,zip_thumbnail_url,zip_url|
        newdataout.print line[0] + ',"' + line[2].gsub('"','""') + '","' + line[3].gsub('"','""') + '","' + 
        zip_id[0] + '","' + zip_thumbnail_url[0] + '","' + zip_url[0] + '"' + "\n"
      }
    end
  rescue
    p 'err'
  end
  }
newdataout.close


【リスト4】search_movie_a.rb

#####################################
#【RetroTube】一括検索プログラム(洋画)
#####################################

require "kconv"
require "net/http"
require "fastercsv"

#前回検索時の結果を退避(newdataフォルダからolddataフォルダへ)
File.rename("newdata/movie_a.csv","olddata/movie_a.old.csv")

#ここから検索処理
address = "www.youtube.com"
path = "/api2_rest?method=youtube.videos.list_by_category_and_tag&dev_id=6BQYABg1UFM&category_id=1&page=1&per_page=1&tag="
newdataout = open("newdata/movie_a.csv",'w')
FasterCSV.foreach("source/source_movie.csv") { |line|
  begin
    re_japan = %r|日本|s
    if re_japan =~ line[6]
    else
      p line[0] + " " + line[1] + " " + line[5]
      searchword = line[5] + " " + line[4]
      searchword_utf8 = searchword.kconv(Kconv::UTF8,  Kconv::SJIS)
      searchpath = path + searchword_utf8
      convpath = searchpath.gsub(' ','%20')
      body = Net::HTTP.get( address , convpath ) 
      re = %r|<id>(.*?)</id>|u
      result_id = body.scan(re)
      re = %r|<thumbnail_url>(.*?)</thumbnail_url>|u
      result_thumbnail_url = body.scan(re)
      re = %r|<url>(.*?)</url>|u
      result_url = body.scan(re)
      result_id.zip(result_thumbnail_url,result_url) { |zip_id,zip_thumbnail_url,zip_url|
        newdataout.print line[0] + ',"' + line[2].gsub('"','""') + '","' + line[3].gsub('"','""') + '","' + 
        zip_id[0] + '","' + zip_thumbnail_url[0] + '","' + zip_url[0] + '"' + "\n"
        }
    end
  rescue
    p 'err'
  end
  }
newdataout.close


【リスト5】compare.rb

#####################################
#【RetroTube】新着情報出力プログラム
#####################################

require "kconv"
require "fastercsv"

#前回検索時の結果を退避(newdataフォルダからolddataフォルダへ)
File.rename("newdata/compare.csv","olddata/compare.old.csv")
File.rename("newdata/rss.rdf","olddata/rss.old.rdf")

#前回検索結果と今回検索結果を比較し、差分を新着情報として出力(邦楽)
compareout = open("newdata/compare.csv",'w')
$olddata = Array.new
FasterCSV.foreach("olddata/music.old.csv") { |line|
  $olddata.push(line)
  }
FasterCSV.foreach("newdata/music.csv") { |line|
  $hitsw = 0
  $olddata.each { |oldline|
  if oldline[1] == line[1] &&
     oldline[2] == line[2]
    $hitsw = 1
    break
  end
  }
  if $hitsw == 0
    compareout.puts line[0] + ',' + line[1] + ',' + line[2] + ',' + 
                             line[3] + ',' + line[4] + ',' + line[5] + ',music'
  end
  }

#前回検索結果と今回検索結果を比較し、差分を新着情報として出力(アニメ)
$olddata = Array.new
FasterCSV.foreach("olddata/anime.old.csv") { |line|
  $olddata.push(line)
  }
FasterCSV.foreach("newdata/anime.csv") { |line|
  $hitsw = 0
  $olddata.each { |oldline|
    if oldline[1] == line[1] &&
       oldline[2] == line[2] &&
       oldline[6] == line[6]
      $hitsw = 1
      break
    end
    }
  if $hitsw == 0
    if line[6] == "NA"
      compareout.puts line[0] + ',' + line[1] + ',' + line[2] + ',' + 
                               line[3] + ',' + line[4] + ',' + line[5] + ',anime'
    else
      compareout.puts line[0] + ',' + line[1] + ',' + line[2] + ' ' + line[6] + ',' + 
                               line[3] + ',' + line[4] + ',' + line[5] + ',anime'
    end
  end
  }

#前回検索結果と今回検索結果を比較し、差分を新着情報として出力(邦画)
$olddata = Array.new
FasterCSV.foreach("olddata/movie_j.old.csv") { |line|
  $olddata.push(line)
  }
FasterCSV.foreach("newdata/movie_j.csv") { |line|
  $hitsw = 0
  $olddata.each { |oldline|
  if oldline[1] == line[1] &&
     oldline[2] == line[2]
    $hitsw = 1
    break
  end
  }
  if $hitsw == 0
    compareout.puts line[0] + ',' + line[1] + ',' + line[2] + ',' + 
                             line[3] + ',' + line[4] + ',' + line[5] + ',movie_j'
  end
  }

#前回検索結果と今回検索結果を比較し、差分を新着情報として出力(洋画)
$olddata = Array.new
FasterCSV.foreach("olddata/movie_a.old.csv") { |line|
  $olddata.push(line)
  }
FasterCSV.foreach("newdata/movie_a.csv") { |line|
  $hitsw = 0
  $olddata.each { |oldline|
  if oldline[1] == line[1] &&
     oldline[2] == line[2]
    $hitsw = 1
    break
  end
  }
  if $hitsw == 0
    compareout.puts line[0] + ',' + line[1] + ',' + line[2] + ',' + 
                             line[3] + ',' + line[4] + ',' + line[5] + ',movie_a'
  end
  }
compareout.close

#新着情報(compare.csv)からRSS出力
rssout = open("newdata/rss.rdf",'w')
rssout.puts '<?xml version="1.0" encoding="utf-8" ?>
  <rdf:RDF
    xmlns="http://purl.org/rss/1.0/"
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns:dc="http://purl.org/dc/elements/1.1/"  
    xml:lang="ja">

    <channel rdf:about="http://www.retro-tube.com/rss.rdf">
      <title>RetroTube</title>
      <link>http://www.retro-tube.com/</link>
      <description>RetroTube</description>
      <items>
      <rdf:Seq>'
FasterCSV.foreach("newdata/compare.csv") { |rssline|
  rssout.puts '        <rdf:li rdf:resource="http://www.retro-tube.com/detail.cgi?movieid=' + 
                   Kconv.toutf8(rssline[3]) + '&amp;category=' + rssline[6] + '"/>'
  }
rssout.puts '      </rdf:Seq>
      </items>
    </channel>'
FasterCSV.foreach("newdata/compare.csv") { |rssline|
  rssout.puts '  <item rdf:about="http://www.retro-tube.com/detail.cgi?movieid=' + 
                   Kconv.toutf8(rssline[3]) + '&amp;category=' + rssline[6] + '">'
  case rssline[6]
  when "music"
    categoryname = Kconv.toutf8('邦楽')
  when "anime"
    categoryname = Kconv.toutf8('アニメ')
  when "movie_j"
    categoryname = Kconv.toutf8('邦画')
  when "movie_a"
    categoryname = Kconv.toutf8('洋画')
  end
  rssout.puts '    <title>[' + categoryname + '] ' + Kconv.toutf8(rssline[2].gsub('&','&amp;')) + 
                   ' - ' + Kconv.toutf8(rssline[1].gsub('&','&amp;')) + ' (' + rssline[0] +')</title>'
  rssout.puts '    <link>http://www.retro-tube.com/detail.cgi?movieid=' + Kconv.toutf8(rssline[3]) + 
                   '&amp;category=' + rssline[6] + '</link>'
  rssout.puts '  </item>'
  }
rssout.print '</rdf:RDF>'
rssout.close

RemoroTube

先日から、Rimoでユーザーチャンネルの追加ができるようになっていたので、RetroTube内の動画一覧ページを登録すればそのまま連続再生できるのかと思い、試してみました。
ところが私のサイトでは、個別のYouTube動画へのリンクを貼っているわけではないので、今の状態では使うことができませんでした。
これについては、そのうちチャンネル登録用のAPIが公開されるはずなので、それを待ってから対応を考えてみようと思っていたのですが、もう既に発見して利用されている方がいたので、早速自分もやってみることにしました。


現在、動画一覧ページの見出し横のRimoボタンを押すと、Rimoが起動して、そのページ内の動画の連続再生が始まるようになっています。
どうぞご利用ください。


同様の機能は、RetroTubeFeedで既に実現されているので、あえて対応する必要はなかったんですが、WebAPIを見かけたら叩かずにおれない性分なもので、つい・・


ちなみに仕組みとしては、

  1. ユーザーのブラウザがRetroTubeへ、Rimoの起動指示を行う。
  2. RetroTubeがRimoへ、動画一覧を生成するURLを渡す。
  3. RimoがRetroTubeへ、動画一覧の生成リクエストを送る。
  4. RetroTubeがRimoへ、動画一覧を生成して渡す。
  5. 動画一覧を受け取ったRimoが、そこからYouTubeのデータベースを参照しつつXMLを生成し、自分のデータベースに登録すると同時に、RetroTubeへその結果を返す。
  6. Rimoからの結果を受け、RetroTubeがユーザーのブラウザへ、Rimoでの動画再生用のURLを送りつける。
  7. ブラウザ側で、Rimoでの動画再生が始まる。

もう、わけがわからないですね。
↓動画で表すと、こんな感じでしょうか。それにしても小人さん達、頑張ってくれてますね。

RetroTubeFeed

最近は、RimoDarao、Oresegなど、特定のテーマに沿った動画を連続再生できるサイトが登場してきたので、RetroTubeでも同様のことをできるようにすることを今後の案として考えていたのですが、こちらで、それが既にできるようになっていました。

今まで自分のサイトを作るにあたって、他のWebサービスを利用する立場にいたわけですが、そうではなくて、逆に自分のサービスを他の方に利用していただく側に回ることも実はできるんだということに気づいてしまったのが衝撃でした。RetroTubeはマッシュアップサイトであって、この場所で情報の流れはストップするんだと思い込んでいただけだったんですね・・
で、ちょっと思ったんですが、RetroTubeが持っている動画情報を、WebAPIとして公開すること自体は簡単にできるので、これはすぐにやってみようと思います。

今後の開発予定としては、カテゴリの追加と、ユーザーインターフェースの追加を考えています。
カテゴリについては、あと2〜3件追加することになると思います。UIについては、見た目に派手で、わかりやすいものを検討していて、具体的なイメージは頭の中でできあがっているのですが、自分の力で実現できるのかどうかがまだよくわかっていません。