module Niconico class Video < Fabric include Niconico::Helper def initialize(ptr, agent=nil) super(agent) @thumb = nil @flv = nil @id = normalize(ptr) # pvid | thumb_cached end def download(output=".") escaped_title = escape_string(thumb.title) escaped_output = escape_string(output) filename = sprintf OUTPUT_NAME, {:title => escaped_title, :id => thumb.video_id, :extension => thumb.extension} filepath = File.join(escaped_output, filename) filepath_nvdownload = "#{filepath}.nvdownload" # Return when video file is already exist if File.exist? filepath File.delete filepath_nvdownload if File.exist? filepath_nvdownload return end # Create output dir Dir.mkdir escaped_output unless Dir.exist? escaped_output # Define request headers options = { 'Cookie' => flv.history_cookies } if File.exist? filepath_nvdownload options['Range'] = "bytes=#{File.size(filepath_nvdownload)}-" end # Download video progress_bar = nil url = URI.parse(flv.url) begin Net::HTTP.start(url.host, url.port) do |http| header = http.request_head("#{url.path}?#{url.query}", options) progress_bar = ProgressBar.create(:total => header['content-length'].to_i) transferred_bytes = 0 request = Net::HTTP::Get.new(url, options) http.request request do |response| open(filepath_nvdownload, 'ab') do |io| response.read_body do |chunk| transferred_bytes += chunk.size if progress_bar progress_bar.progress = transferred_bytes else puts "#{transferred_bytes} / Total size is unknown" end io.write chunk end end end end rescue => e puts "Failed download: #{e}" return end # Rename .nvdownload to real file File.rename(filepath_nvdownload, filepath) end # GET http://flapi.nicovideo.jp/api/getwaybackkey?thread=1345476375 # => waybackkey=1417346808.E9d0LUF9gvFvt3Rrf5TP91Pa0LA # POST http://msg.nicovideo.jp/53/api/ # 0-14:100,1000 # # # 0-14:100,1000 # # 白いレースのハンカチかな? # くっそwwww def download_comments(output=".") escaped_title = escape_string(thumb.title) escaped_output = escape_string(output) filename = sprintf OUTPUT_NAME, {:title => escaped_title, :id => thumb.video_id, :extension => 'comments'} filepath = File.join(escaped_output, filename) Dir.mkdir(escaped_output) unless Dir.exist? escaped_output url = URI.parse(flv.ms) thread_id = flv.thread_id length = (flv.l.to_i / 60).round res = Net::HTTP.new(url.host).start do |http| req = Net::HTTP::Post.new(url.path, {'Cookie' => flv.history_cookies}) req.body = %|0-#{length}:10| http.request(req) end open(filepath, 'w') do |f| f.write res.body end # TODO: Comment parser # doc = REXML::Document.new(res.body) # chats = doc.elements.to_a('/packet/chat') # chats.each do |chat| # puts chat.attribute('vpos') # puts chat.text # end end # Combined parameter fetcher def method_missing(method, *args) thumb[method] || flv[method] || raise(NoMethodError, method) end private def strip_id(url) url.match(/(?:watch\/)?(\w{2}?\d+)/)[1] end def normalize(ptr) vid = strip_id(ptr) thumb(vid).perm_video_id unless vid =~ /\A\d+\z/ end def id @id end def thumb(id=@id) return @thumb if @thumb doc = REXML::Document.new(open("http://ext.nicovideo.jp/api/getthumbinfo/#{id}")) watch_url = doc.elements['nicovideo_thumb_response/thumb/watch_url'].text.to_s perm_video_id = strip_id(watch_url) @thumb = OpenStruct.new({ :video_id => doc.elements['nicovideo_thumb_response/thumb/video_id'].text.to_s, :perm_video_id => perm_video_id, :watch_url => watch_url, :extension => doc.elements['nicovideo_thumb_response/thumb/movie_type'].text.to_s, :title => doc.elements['nicovideo_thumb_response/thumb/title'].text.to_s, :description => doc.elements['nicovideo_thumb_response/thumb/description'].text.to_s }) end def flv(id=@id) return @flv if @flv @agent.get("http://www.nicovideo.jp/watch/#{id}") history_cookie = @agent.cookies.map(&:to_s).join('; ') flv_str = @agent.get("http://www.nicovideo.jp/api/getflv?v=#{id}").body flv_hash = Hash[flv_str.split("&").map{|e| i=e.split('=');i[1]=CGI.unescape(i[1]);i }] flv_hash.update(:history_cookies => history_cookie) @flv = OpenStruct.new(flv_hash) end end end