class FlatironVideoUploader::Runner

Constants

GRANT_TYPE
THOR_SHELL

Public Class Methods

upload_video(file,batch) click to toggle source
# File lib/flatiron-video-uploader/runner.rb, line 42
def self.upload_video(file,batch)

  begin
    `ffmpeg > /dev/null 2>&1`
  rescue
    puts "Looks like you don't have ffmpeg. Install with brew install ffmpeg"
    exit
  end

  begin
    http = Net::HTTP.new("accounts.google.com",443)
    http.use_ssl = true
    req = Net::HTTP::Head.new('/')
    http.start {http.request(req)}
  rescue OpenSSL::SSL::SSLError => e
    puts "Looks like your version of ruby has messed up SSL Certs."
    puts "Please install openssl with brew install openssl"
    puts "Then re-install ruby without binaries with rvm reinstall #{RUBY_VERSION} --disable-binary"
    exit
  end
  FFMPEG.logger=Logger.new(STDOUT)
  FFMPEG.logger.level = Logger::ERROR
  begin
    token_response = YAML.load_file(File.expand_path('~/.flatiron-uploader'))
  rescue
    puts "We are going to need a few ID/Secret stuff."
    puts "\n\nGoogle"
    google_client_id = THOR_SHELL.ask("What is your Google Client ID?")
    google_client_secret = THOR_SHELL.ask("What is your Google Client Secret?")
    puts "Finished with Google.\n\n"
    puts "#"*30
    puts "\n\nAmazon S3"
    amazon_s3_key = THOR_SHELL.ask("What is your Amazon S3 Key?")
    amazon_s3_secret = THOR_SHELL.ask("What is your Amazon S3 Secret?")
    puts "#"*30
    response = HTTParty.post("https://accounts.google.com/o/oauth2/device/code",:query => {:scope => "https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtube", :client_id => "404828420863-l0nvu5ihjg7kji18d4pghrnetre28d5e.apps.googleusercontent.com"})

    puts "OK cool, go to #{response["verification_url"]} and type in the following code"
    `open #{response["verification_url"]}`
    puts "#"*30
    puts response["user_code"]
    puts "#"*30
    puts "polling"

    token_response = HTTParty.post("https://www.googleapis.com/oauth2/v3/token", :query => {:client_id => google_client_id, :client_secret => google_client_secret, :grant_type => GRANT_TYPE, :code => response["device_code"]})
    while(token_response.code==400)
      sleep(response["interval"])
      token_response = HTTParty.post("https://www.googleapis.com/oauth2/v3/token", :query => {:client_id => google_client_id, :client_secret => google_client_secret, :grant_type => GRANT_TYPE, :code => response["device_code"]})
    end
    puts "Found!"

    token_response["google_client_id"] = google_client_id
    token_response["google_client_secret"] = google_client_secret
    token_response["amazon_s3_key"] = amazon_s3_key
    token_response["amazon_s3_secret"] = amazon_s3_secret
    File.open(File.expand_path('~/.flatiron-uploader'), 'w') do |h|
      h.write token_response.to_yaml
    end
  end

  if !File.exist?(file) || !File.file?(file)
    puts "Not a file!"
    exit
  end
  progress_mutex = Mutex.new
  print_progress = false
  movie_transcode_thread = Thread.new do 
    FFMPEG::Transcoder.timeout=90
    movie = FFMPEG::Movie.new(file)
    movie.transcode("/tmp/transcoded.mp4","-vf 'scale=-2:720:flags=lanczos' -vcodec libx264 -profile:v main -level 3.1 -preset veryfast -crf 31 -x264-params ref=4 -acodec copy -movflags +faststart") do |prog|
      progress_mutex.synchronize do
        if print_progress 
          print "\r"
          # puts add \n to the end of string, use print instead
          print sprintf("%.2f",prog*100)

          # force the output to appear immediately when using print
          # by default when \n is printed to the standard output, the buffer is flushed.
          $stdout.flush
        end
      end
    end
    TerminalNotifier.notify("Finished Transcoding for iPad")
  end

  auth = Signet::OAuth2::Client.new(
    :authorization_uri => "https://accounts.google.com/o/oauth2/auth",
    :token_credential_uri => "https://accounts.google.com/o/oauth2/token",
    :client_id => token_response['google_client_id'],
    :client_secret => token_response['google_client_secret']
  )
  auth.refresh_token = token_response["refresh_token"]
  auth.refresh!

  client = Google::APIClient.new(
    :application_name => "flatiron_uploder",
    :application_version => "0.1")

  client.authorization = auth
  video_title= ""
  topics = []
  answer = "N"
  while (answer.upcase != "Y") do
    temp = THOR_SHELL.ask("What is the name of this video(#{video_title})?")
    video_title = temp unless temp.empty?
    if (batch.empty?)
      puts "#"*30
      puts "\n\nUploading Topic Video!\n\n"
      puts "#"*30
    end
    temp = THOR_SHELL.ask("Comma separated list of topics (#{topics})?")
    topics = temp.split(",") unless temp.empty?
    puts "#"*30
    puts "Summary"
    puts "Title: #{video_title}"
    puts "Batch: #{batch}" unless batch.empty?
    puts "Topics: #{topics}"
    answer=THOR_SHELL.ask("Is that correct (Y/N)?")

  end
  youtube = client.discovered_api("youtube","v3")

  s3 = Aws::S3::Client.new(
    # credentials: Aws::Credentials.new('AKIAJY4YSCUKNRWWASIA', 'lXp+rsDPaHcoaEay3XcjfAnE1l1W8BRWOrdLfsPH'),
     credentials: Aws::Credentials.new(token_response['amazon_s3_key'], token_response['amazon_s3_secret']),
    region: 'us-east-1'
  )

  topics << batch unless batch.empty?
  # begin
    body = {
      :snippet => {
        :title => video_title,
        :tags => topics
      },
      :status => {
        :privacyStatus => "unlisted"
      }
    }

    puts "\n\nOK, about to upload the video. This will take a while"
    begin
      video_to_upload = Google::APIClient::UploadIO.new(file, 'video/*')

      video_to_upload.chunk_size =10000000
      progressbar = ProgressBar.create(:title => "Youtube Upload", :total =>video_to_upload.length)
      videos_insert_response = client.execute(
        :api_method => youtube.videos.insert,
        :body_object => body,
        :media => video_to_upload,
        :retries => 0,
        :parameters => {
          :uploadType => 'resumable',
          :part => body.keys.join(','),
        }
      )
      while !videos_insert_response.resumable_upload.complete? do
        progressbar.progress += video_to_upload.chunk_size
        videos_insert_response = client.execute(videos_insert_response.resumable_upload)
      end
      progressbar.finish
      puts "Uploaded wooooooooo. The id is #{videos_insert_response.data.id}"
      puts "\n\n"
      puts "#"*30
      puts "Place this in todays schedule on github\n\n\n"
      command = "<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/#{videos_insert_response.data.id}?rel=0&modestbranding=1\" frameborder=\"0\" allowfullscreen></iframe><p><a href=\"https://www.youtube.com/watch?v=#{videos_insert_response.data.id}\">#{video_title}</a></p>"
      puts command
      puts "\n\n"
      puts "#"*30
    rescue Exception => e
      exit
    end
    TerminalNotifier.notify("Finished Uploading Video to Youtube. Click to copy embed ", :title => "Video Uploader", :execute => `echo '#{command}' | pbcopy`)
  # rescue Google::APIClient::TransmissionError => e
  #   binding.pry
  #   puts e.result.body
  # end

  puts "Waiting for the video to finish encoding to iPad format\n\n"
  raw_upload_thread = nil
  if batch.empty?
    raw_upload_thread = Thread.new do 
      raw_obj = s3.bucket('flatiron-school-learn-videos').object("topic-videos-full/#{videos_insert_response.data.id}.mp4")
      raw_obj.upload_file(file, acl:'public-read')
    end
  end
  print_progress = true
  movie_transcode_thread.join

  puts "\nDone transcoding. Let's upload to S3"
  bucket = 'flatiron-school-learn-videos'
  key = "lecture-videos/#{videos_insert_response.data.id}.mp4"
  lock_file = File.open("/tmp/flatiron-uploader.lock","a+")
  lock_file.sync=true
  exit unless lock_file.flock( File::LOCK_NB | File::LOCK_EX )

  file_name = "/tmp/transcoded.mp4"
  File.open('/tmp/transcoded.mp4', 'rb') do |file|
    if file.size > PART_SIZE
      puts "File size over #{PART_SIZE} bytes, using multipart upload..."
      input_opts = {
        bucket: bucket,
        key:    key,
      }  
      input_opts_with_acl = input_opts.merge({acl: "public-read"})
      lock_hash = {}
      total_parts = file.size.to_f / PART_SIZE
      current_part=1
      begin
        existing_file = lock_file.read
        lock_hash = JSON.parse existing_file
        if lock_hash["file_name"]==file_name && lock_hash["file_size"]==file.size && lock_hash["key"]==key && lock_hash["total_parts"] == total_parts
          binding.pry
          current_part = lock_hash["current_part"]
        else
          input_opts_with_upload = input_opts.merge({
            :upload_id   => lock_hash["upload_id"],
            :key => lock_hash["key"],
          })   
          s3.abort_multipart_upload(input_opts_with_upload)
          binding.pry
          lock_file.truncate(0)
          mpu_create_response = s3.create_multipart_upload(input_opts_with_acl)
          lock_hash = {"total_parts"=> total_parts,
                       "current_part" => current_part,
                       "file_name" =>file_name,
                       "file_size" => file.size,
                       "key" => key,
                       "upload_id" =>mpu_create_response.upload_id,
          }
          lock_file.puts lock_hash.to_json
        end
      rescue StandardError => e
        mpu_create_response = s3.create_multipart_upload(input_opts_with_acl)
        lock_hash = {"total_parts"=> total_parts,
                     "current_part" => current_part,
                     "file_name" =>file_name,
                     "file_size" => file.size,
                     "key" => key,
                     "upload_id" =>mpu_create_response.upload_id,
        }
        lock_file.puts lock_hash.to_json
      end
      file.each_part(current_part) do |part|

        begin
          tries ||=3
          part_response = s3.upload_part({
            body:        part,
            bucket:      bucket,
            key:         key,
            part_number: current_part,
            upload_id:   lock_hash["upload_id"],
          })  

          percent_complete = (current_part.to_f / total_parts.to_f) * 100 
          percent_complete = 100 if percent_complete > 100 
          percent_complete = sprintf('%.2f', percent_complete.to_f)
          puts "percent complete: #{percent_complete}"
          current_part = current_part + 1 
          lock_hash["current_part"]=current_part
          lock_file.truncate(0)
          lock_file.puts lock_hash.to_json
        rescue Exception => e
          if e.class == Interrupt
            puts "LEaving"
            lock_file.truncate(0)
            lock_file.puts lock_hash.to_json
            File.delete(lock_file.path)
            exit
          end
          puts "trying again"
          # input_opts = input_opts.merge({
          #   :upload_id   => mpu_create_response.upload_id,
          # })
          lock_file.truncate(0)
          lock_file.puts lock_hash.to_json

          retry unless (tries-=1).zero?
          puts "Couldn't get it to work, exiting"

          File.delete(lock_file.path)
          exit
        end
      end 

      mark_as_complete(s3,input_opts,lock_hash)
      File.delete(lock_file.path)
    else
      s3.put_object(bucket: bucket, key: key, body: file, acl: 'public-read')
    end
  end


  # obj = s3.bucket('flatiron-school-learn-videos').object("lecture-videos/#{videos_insert_response.data.id}.mp4")
  # obj.initiate_multipart_upload(options)
  # obj.upload_file('/tmp/transcoded.mp4', acl:'public-read')
  raw_upload_thread.join unless raw_upload_thread.nil?
  puts "All Done!"
  TerminalNotifier.notify("All Done!", :title =>"Video Uploader")
  `rm /tmp/transcoded.mp4`
end