Initial import from our internal version of the Panda HQ application
This commit is contained in:
Родитель
90b613c2ae
Коммит
7ae041ee22
|
@ -0,0 +1,6 @@
|
|||
*.log
|
||||
*.pid
|
||||
uuid.state
|
||||
.DS_Store
|
||||
database.yml
|
||||
schema.rb
|
9
README
9
README
|
@ -0,0 +1,9 @@
|
|||
Panda is a Merb app which runs on a special EC2 instance to encode videos for you. Uploaded videos are stored on S3 with a small amount of info kept in SimpleDB. The REST API makes it easy to integrate user video uploading into your web application.
|
||||
|
||||
How does Panda work?
|
||||
|
||||
1. Video is uploaded to panda
|
||||
2. Panda checks the video's metadata, uploads the raw file to S3 and adds it to the encoding queue
|
||||
3. The encoder application picks the encoding job off the queue when it's free and encodes the video to all possible formats
|
||||
4. Panda sends a callback to your web application notifying you the video has been encoded
|
||||
5. You use the S3 url of the encoding you want to show to your users
|
|
@ -0,0 +1,124 @@
|
|||
require 'rubygems'
|
||||
Gem.clear_paths
|
||||
Gem.path.unshift(File.join(File.dirname(__FILE__), "gems"))
|
||||
|
||||
require 'rake'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/testtask'
|
||||
require 'spec/rake/spectask'
|
||||
require 'fileutils'
|
||||
|
||||
require File.dirname(__FILE__)+'/config/boot.rb'
|
||||
require Merb::framework_root+'/tasks'
|
||||
include FileUtils
|
||||
|
||||
# Set these before any dependencies load
|
||||
# otherwise the ORM may connect to the wrong env
|
||||
Merb.root = File.dirname(__FILE__)
|
||||
Merb.environment = ENV['MERB_ENV'] if ENV['MERB_ENV']
|
||||
|
||||
# Get Merb plugins and dependencies
|
||||
require File.dirname(__FILE__)+'/config/dependencies.rb'
|
||||
Merb::Plugins.rakefiles.each {|r| require r }
|
||||
|
||||
#desc "Packages up Merb."
|
||||
#task :default => [:package]
|
||||
|
||||
desc "load merb_init.rb"
|
||||
task :merb_init do
|
||||
# deprecated - here for BC
|
||||
Rake::Task['merb_env'].invoke
|
||||
end
|
||||
|
||||
task :uninstall => [:clean] do
|
||||
sh %{sudo gem uninstall #{NAME}}
|
||||
end
|
||||
|
||||
desc 'Run unit tests'
|
||||
Rake::TestTask.new('test_unit') do |t|
|
||||
t.libs << 'test'
|
||||
t.pattern = 'test/unit/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Run functional tests'
|
||||
Rake::TestTask.new('test_functional') do |t|
|
||||
t.libs << 'test'
|
||||
t.pattern = 'test/functional/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Run all tests'
|
||||
Rake::TestTask.new('test') do |t|
|
||||
t.libs << 'test'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc "Run all specs"
|
||||
Spec::Rake::SpecTask.new('specs') do |t|
|
||||
t.spec_opts = ["--format", "specdoc", "--colour"]
|
||||
t.spec_files = Dir['spec/**/*_spec.rb'].sort
|
||||
end
|
||||
|
||||
desc "Run all model specs"
|
||||
Spec::Rake::SpecTask.new('model_specs') do |t|
|
||||
t.spec_opts = ["--format", "specdoc", "--colour"]
|
||||
t.spec_files = Dir['spec/models/**/*_spec.rb'].sort
|
||||
end
|
||||
|
||||
desc "Run all controller specs"
|
||||
Spec::Rake::SpecTask.new('controller_specs') do |t|
|
||||
t.spec_opts = ["--format", "specdoc", "--colour"]
|
||||
t.spec_files = Dir['spec/controllers/**/*_spec.rb'].sort
|
||||
end
|
||||
|
||||
desc "Run a specific spec with TASK=xxxx"
|
||||
Spec::Rake::SpecTask.new('spec') do |t|
|
||||
t.spec_opts = ["--format", "specdoc", "--colour"]
|
||||
t.libs = ['lib', 'server/lib' ]
|
||||
t.spec_files = ["spec/merb/#{ENV['TASK']}_spec.rb"]
|
||||
end
|
||||
|
||||
desc "Run all specs output html"
|
||||
Spec::Rake::SpecTask.new('specs_html') do |t|
|
||||
t.spec_opts = ["--format", "html"]
|
||||
t.libs = ['lib', 'server/lib' ]
|
||||
t.spec_files = Dir['spec/**/*_spec.rb'].sort
|
||||
end
|
||||
|
||||
desc "RCov"
|
||||
Spec::Rake::SpecTask.new('rcov') do |t|
|
||||
t.spec_opts = ["--format", "specdoc", "--colour"]
|
||||
t.spec_files = Dir['spec/**/*_spec.rb'].sort
|
||||
t.libs = ['lib', 'server/lib' ]
|
||||
t.rcov = true
|
||||
end
|
||||
|
||||
desc 'Run all tests, specs and finish with rcov'
|
||||
task :aok do
|
||||
sh %{rake rcov}
|
||||
sh %{rake spec}
|
||||
end
|
||||
|
||||
unless Gem.cache.search("haml").empty?
|
||||
namespace :haml do
|
||||
desc "Compiles all sass files into CSS"
|
||||
task :compile_sass do
|
||||
gem 'haml'
|
||||
require 'sass'
|
||||
puts "*** Updating stylesheets"
|
||||
Sass::Plugin.update_stylesheets
|
||||
puts "*** Done"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##############################################################################
|
||||
# SVN
|
||||
##############################################################################
|
||||
|
||||
desc "Add new files to subversion"
|
||||
task :svn_add do
|
||||
system "svn status | grep '^\?' | sed -e 's/? *//' | sed -e 's/ /\ /g' | xargs svn add"
|
||||
end
|
|
@ -0,0 +1,53 @@
|
|||
class Accounts < Application
|
||||
provides :html
|
||||
before :require_login, :only => [:dashboard, :show, :edit, :update]
|
||||
|
||||
def new
|
||||
@account = Account.new
|
||||
@account.email = params[:email] if params[:email]
|
||||
|
||||
if request.post?
|
||||
if invite = Invite.find(:first, :conditions => ["email = ? and account_id IS NULL and approved IS NOT NULL",params[:account][:email]])
|
||||
@account = Account.new(params[:account])
|
||||
if @account.save!
|
||||
invite.update_attribute(:account_id, @account.id)
|
||||
session[:account_id] = @account.id
|
||||
redirect "/"
|
||||
end
|
||||
else
|
||||
render :text => "No invite for that email address."
|
||||
end
|
||||
else
|
||||
render :layout => "auth"
|
||||
end
|
||||
end
|
||||
|
||||
def dashboard
|
||||
@queued_videos = @account.queued_videos
|
||||
@recently_completed_videos = @account.recently_completed_videos
|
||||
render
|
||||
end
|
||||
|
||||
def show
|
||||
render
|
||||
end
|
||||
|
||||
def edit
|
||||
render
|
||||
end
|
||||
|
||||
def update
|
||||
@account.update_attribute(:name, params[:account][:name])
|
||||
@account.update_attribute(:email, params[:account][:email])
|
||||
@account.update_attribute(:upload_redirect_url, params[:account][:upload_redirect_url])
|
||||
@account.update_attribute(:state_update_url, params[:account][:state_update_url])
|
||||
|
||||
unless params[:account][:password].blank?
|
||||
@account.password = params[:account][:password]
|
||||
@account.password_confirmation = params[:account][:password_confirmation]
|
||||
@account.save!
|
||||
end
|
||||
|
||||
redirect "/accounts"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
# all your other controllers should inherit from this one to share code.
|
||||
class Application < Merb::Controller
|
||||
provides :html
|
||||
# Auth plugin
|
||||
attr_accessor :account
|
||||
|
||||
def index
|
||||
redirect "/dashboard"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_login
|
||||
case (params[:format] || "html")
|
||||
when "html"
|
||||
Merb.logger.info("AUTH DEBUG: session[:account_id]: #{session[:account_id]}")
|
||||
@account = Account.find(session[:account_id]) if session[:account_id]
|
||||
Merb.logger.info("AUTH DEBUG: @account:")
|
||||
Merb.logger.info(@account.to_yaml)
|
||||
throw :halt, redirect("/login") unless @account
|
||||
when "xml", "yaml"
|
||||
@account = Account.find_by_token(params[:account_key])
|
||||
throw :halt, render('', :status => 401) unless @account
|
||||
else
|
||||
throw :halt, render('', :status => 401)
|
||||
end
|
||||
end
|
||||
|
||||
# Ensure the request is coming from one of our ec2 instances
|
||||
def require_internal_auth
|
||||
# if Merb.environment == "development"
|
||||
# @ec2_instance = Ec2.find(:first, :conditions => {:amazon_id => "test"})
|
||||
# end
|
||||
unless @ec2_instance = Ec2.find(:first, :conditions => {:address => request.remote_ip})
|
||||
throw :halt, render('', :status => 401)
|
||||
end
|
||||
# Rog.log :info, "Identified Encoder##{@ec2_instance.id} with IP #{request.remote_ip}"
|
||||
end
|
||||
|
||||
def set_video
|
||||
unless @video = Video.find_by_token(params[:id])
|
||||
throw :halt, render('', :status => 404)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,31 @@
|
|||
class Auth < Application
|
||||
provides :html
|
||||
# Auth plugin
|
||||
|
||||
def login
|
||||
@account = Account.new
|
||||
|
||||
if request.post?
|
||||
Merb.logger.info("AUTH DEBUG: Got post")
|
||||
Merb.logger.info(params[:account].to_yaml)
|
||||
|
||||
if @account = Account.authenticate(params[:account])
|
||||
Merb.logger.info("AUTH DEBUG: Account.authenticate returned valid account")
|
||||
session[:account_id] = @account.id
|
||||
Merb.logger.info("AUTH DEBUG: session[:account_id] = #{@account.id}")
|
||||
redirect "/"
|
||||
else
|
||||
@account = Account.new(:login => params[:account][:login])
|
||||
@notice = "Your username or password was incorrect."
|
||||
Merb.logger.info("AUTH DEBUG: Invalid auth")
|
||||
end
|
||||
end
|
||||
|
||||
render
|
||||
end
|
||||
|
||||
def logout
|
||||
session[:account_id] = nil
|
||||
redirect "/"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
class Exceptions < Application
|
||||
|
||||
# handle NotFound exceptions (404)
|
||||
def not_found
|
||||
render :format => :html
|
||||
end
|
||||
|
||||
# handle NotAcceptable exceptions (406)
|
||||
def not_acceptable
|
||||
render :format => :html
|
||||
end
|
||||
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
class Invites < Application
|
||||
def create
|
||||
@invite = Invite.new(:email => params[:invite][:email])
|
||||
|
||||
return render :text => "Email already invited" if Invite.find_by_email(@invite.email)
|
||||
|
||||
if @invite.save!
|
||||
send_mail(InviteMailer, :notification, {
|
||||
:from => EMAIL_SENDER,
|
||||
:to => @invite.email,
|
||||
:subject => "Thanks for signing up for the Panda beta"
|
||||
})
|
||||
redirect "http://pandastream.com/thanks"
|
||||
else
|
||||
return render :text => "Invalid email"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,49 @@
|
|||
class Jobs < Application
|
||||
provides :yaml
|
||||
before :require_internal_auth
|
||||
before :set_job, :only => [:done]
|
||||
|
||||
# Called whenever an encoding is complete
|
||||
def done
|
||||
Rog.log :info, "Encoder##{@ec2_instance.id} sent internal request for completed job #{@job.id}"
|
||||
@job.result = params[:result] # Save the raw result for furture debugging or some such
|
||||
job_data = YAML.load(params[:result])
|
||||
@job.status = "done"
|
||||
@job.encoding_time = job_data[:encoding_time]
|
||||
@job.save
|
||||
|
||||
# Update status of video and its encodings
|
||||
job_data[:video][:encodings].each {|e| Encoding.find(e[:id]).change_status(e[:status]) }
|
||||
@job.video.change_status(:done)
|
||||
@job.video.send_status
|
||||
end
|
||||
|
||||
def next
|
||||
# Rog.log :info, "Encoder##{@ec2_instance.id}: Internal request for job from #{request.remote_ip}"
|
||||
# Rog.log :info, "EC2##{@ec2_instance.id}: Internal request for job"
|
||||
# Find the next job in the queue
|
||||
job = Job.find_next_job
|
||||
|
||||
# Tell the instance to call again later if there's nothing todo
|
||||
unless job
|
||||
# Rog.log :info, "EC2##{@ec2_instance.id}: No jobs, telling the instance to call again later"
|
||||
return {:command => :wait}.to_yaml
|
||||
end
|
||||
|
||||
# TODO: Shutdown or start a new instance depending on the size of the queue
|
||||
# return {:command => :shutdown} if it's the right thing to do
|
||||
|
||||
# Assign to the ec2 instance that requested it and change state to assigned
|
||||
job.assign_to_ec2(@ec2_instance)
|
||||
Rog.log :info, "Assigning job#{job.id} to encoder Encoder##{@ec2_instance.id}"
|
||||
return {:job => job.job_response}.to_yaml
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_job
|
||||
unless @job = Job.find(params[:id])
|
||||
throw :halt, render('', :status => 404)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,105 @@
|
|||
class Videos < Application
|
||||
provides :html, :xml, :yaml # Allow before filters to accept all formats, which are then futher refined in each action
|
||||
before :require_login, :only => [:index, :create]
|
||||
# before :require_internal_auth, :only => [:valid,:uploaded]
|
||||
before :set_video, :only => [:show, :valid, :uploaded]
|
||||
|
||||
def index
|
||||
provides :html, :xml, :yaml
|
||||
# @videos = AWS::S3::Bucket.find('pandavision').objects
|
||||
@videos = @account.videos.find(:all, :order => "created_at desc")
|
||||
|
||||
case content_type
|
||||
when :html
|
||||
render :layout => :accounts
|
||||
when :xml
|
||||
{:videos => @videos.map {|v| v.show_response }}.to_simple_xml
|
||||
when :yaml
|
||||
{:videos => @videos.map {|v| v.show_response }}.to_yaml
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
provides :html, :xml, :yaml
|
||||
|
||||
case content_type
|
||||
when :html
|
||||
@account = Account.find(session[:account_id]) if session[:account_id]
|
||||
if @account
|
||||
render :layout => :accounts
|
||||
else
|
||||
redirect("/login")
|
||||
end
|
||||
when :xml
|
||||
@video.show_response.to_simple_xml
|
||||
when :yaml
|
||||
@video.show_response.to_yaml
|
||||
end
|
||||
end
|
||||
|
||||
# Just for our testing
|
||||
def new
|
||||
provides :html
|
||||
render :layout => "simple"
|
||||
end
|
||||
|
||||
def create
|
||||
provides :html, :xml, :yaml
|
||||
@video = @account.videos.create
|
||||
Rog.log :info, "#{@video.token}: Created video"
|
||||
|
||||
case content_type
|
||||
when :html
|
||||
redirect @video.upload_form_url
|
||||
when :xml
|
||||
headers.merge!({'Location'=> "/videos/#{@video.token}"})
|
||||
@video.create_response.to_simple_xml
|
||||
when :yaml
|
||||
headers.merge!({'Location'=> "/videos/#{@video.token}"})
|
||||
puts @video.create_response.to_yaml
|
||||
@video.create_response.to_yaml
|
||||
end
|
||||
end
|
||||
|
||||
# Internal API
|
||||
|
||||
def valid
|
||||
provides :yaml
|
||||
Rog.log :info, "#{params[:id]}: Internal request for validity of video"
|
||||
if @video.empty?
|
||||
Rog.log :info, "#{params[:id]}: Response: 200"
|
||||
render('', :status => 200)
|
||||
else
|
||||
Rog.log :info, "#{params[:id]}: Response: 404"
|
||||
render('', :status => 404)
|
||||
end
|
||||
end
|
||||
|
||||
# Called when an uploader instance receives a new file
|
||||
# First we check that there is a video id with no previously uploaded file
|
||||
# If we were expecting this file, we create the jobs to encode it to varoius formats
|
||||
# Then we reply with the url that the customer wants user's to be redirected to after uploading a video
|
||||
def uploaded
|
||||
provides :yaml
|
||||
Rog.log :info, "#{params[:id]}: Internal request to confirm upload for video"
|
||||
# Don't allow files to be uploaded to a video id more than once
|
||||
unless @video.empty?
|
||||
Rog.log :info, "#{params[:id]}: Whoops, this doesn't look like a valid upload"
|
||||
Rog.log :info, "#{params[:id]}: Response: 404"
|
||||
render('', :status => 404) and return
|
||||
end
|
||||
|
||||
@video.filename = params[:filename]
|
||||
@video.save_metadata(YAML.load(params[:metadata]))
|
||||
@video.save
|
||||
|
||||
@video.add_encodings
|
||||
job = @video.add_to_queue
|
||||
Rog.log :info, "#{params[:id]}: Video added to queue (job id: #{job.id})"
|
||||
|
||||
# Tell the uploader where to redirect the client to
|
||||
headers.merge!({'Location'=> @video.account.upload_redirect_url_or_default})
|
||||
Rog.log :info, "#{params[:id]}: Response: 200"
|
||||
render('', :status => 200)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
module Merb
|
||||
module GlobalHelpers
|
||||
def nav_item(name, url=nil)
|
||||
if url.class == Hash
|
||||
url_str = '/'+url[:controller].to_s
|
||||
url_str += '/'+url[:action].to_s if url.include?(:action)
|
||||
url_str += '/'+url[:actions].first.to_s if url.include?(:actions) and url[:actions].first.to_s != "index"
|
||||
elsif url.class == String
|
||||
url_str = url
|
||||
end
|
||||
%(<li><a href="#{url ? url_str : '/'+name}">#{name.humanize}</a></li>)
|
||||
end
|
||||
|
||||
def notice
|
||||
%(<div class="notice">#{@notice}</div>) if @notice
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
class InviteMailer < Merb::MailController
|
||||
def notification
|
||||
render_mail
|
||||
end
|
||||
def approved
|
||||
render_mail
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
Hi,
|
||||
|
||||
Welcome to the Panda beta!
|
||||
|
||||
You can sign up for your account at: http://hq.pandastream.com/signup (make sure you use the same email that this invite was sent to)
|
||||
|
||||
Once you have your login details you'll probably want to have a read through the Getting started (using Ruby on Rails) guide here: http://pandastream.com/docs/getting_started
|
||||
|
||||
If you're interested in getting to know the API in more depth, the docs are here: http://pandastream.com/docs
|
||||
|
||||
During the beta period we won't be charging for use, however videos will be cut to 3 minutes and removed after 1 week. If you would like to use Panda in production please get in contact (panda@new-bamboo.co.uk) with us!
|
||||
|
||||
Thanks,
|
||||
Team Panda.
|
|
@ -0,0 +1,12 @@
|
|||
Hi!
|
||||
|
||||
Thanks for signing up for the Panda beta. We will be sending out invites every few days so sit tight.
|
||||
|
||||
In the meantime don't forget to have a read through the Getting started guide here: http://pandastream.com/docs/getting_started
|
||||
|
||||
If you're interested in getting to know the API in more depth, the docs are here: http://pandastream.com/docs
|
||||
|
||||
Let is know if you have any questions or suggestions!
|
||||
|
||||
Thanks,
|
||||
Team Panda.
|
|
@ -0,0 +1 @@
|
|||
<%= catch_content :layout %>
|
|
@ -0,0 +1 @@
|
|||
<%= catch_content :layout %>
|
|
@ -0,0 +1,68 @@
|
|||
class Account < ActiveRecord::Base
|
||||
attr_accessor :password # Virtual attribute for the unencrypted password
|
||||
attr_accessor :password_confirmation
|
||||
|
||||
validates_presence_of :login, :email
|
||||
validates_presence_of :password #,:if => :password_required?
|
||||
validates_presence_of :password_confirmation #,:if => :password_required?
|
||||
validates_length_of :password, :within => 4..40 #,:if => :password_required?
|
||||
validates_confirmation_of :password #,:if => :password_required?
|
||||
validates_length_of :login, :within => 3..40
|
||||
validates_length_of :email, :within => 3..100
|
||||
validates_uniqueness_of :login, :email, :case_sensitive => false
|
||||
validates_format_of(:email,
|
||||
:with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
|
||||
:message=>"is invalid")
|
||||
|
||||
before_save :encrypt_password
|
||||
before_create :set_token
|
||||
|
||||
has_many :videos
|
||||
has_many :jobs, :through => :videos
|
||||
|
||||
belongs_to :format
|
||||
|
||||
def set_token
|
||||
self.token = UUID.new
|
||||
end
|
||||
|
||||
def upload_redirect_url_or_default
|
||||
self.upload_redirect_url.blank? ? "http://#{PANDA_UPLOAD_DOMAIN}/videos/done" : self.upload_redirect_url
|
||||
end
|
||||
|
||||
def recent_videos
|
||||
self.videos.find(:all, :order => "created_at desc", :limit => 25)
|
||||
end
|
||||
|
||||
# def all_completed_videos
|
||||
# self.videos.find(:all, :conditions => "status = 'done'", :order => "created_at desc")
|
||||
# end
|
||||
|
||||
def recently_completed_videos
|
||||
self.videos.find(:all, :conditions => "status = 'done'", :order => "created_at desc", :limit => 5)
|
||||
end
|
||||
|
||||
def queued_videos
|
||||
self.videos.find(:all, :conditions => "status = 'queued' or status = 'processing'", :order => "created_at asc")
|
||||
end
|
||||
|
||||
# Auth plugin
|
||||
|
||||
def self.authenticate(params)
|
||||
return nil unless u = find_by_login(params[:login]) # need to get the salt
|
||||
puts "#{u.crypted_password} | #{encrypt(params[:password], u.salt)}"
|
||||
u && (u.crypted_password == encrypt(params[:password], u.salt)) ? u : nil
|
||||
end
|
||||
|
||||
def self.encrypt(password, salt)
|
||||
Digest::SHA1.hexdigest("--#{salt}--#{password}--")
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def encrypt_password
|
||||
return if password.blank?
|
||||
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{login}--") if new_record?
|
||||
self.crypted_password = self.class.encrypt(password, self.salt)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
class Ec2 < ActiveRecord::Base
|
||||
has_many :jobs
|
||||
end
|
|
@ -0,0 +1,76 @@
|
|||
class Encoding < ActiveRecord::Base
|
||||
belongs_to :video
|
||||
belongs_to :quality
|
||||
|
||||
has_many :notifications
|
||||
|
||||
before_create :copy_metadata
|
||||
before_create :set_pending_status
|
||||
|
||||
def filename
|
||||
"#{self.token}.#{self.container}"
|
||||
end
|
||||
|
||||
def full_path
|
||||
File.join(ENCODED_DIR,self.filename)
|
||||
end
|
||||
|
||||
def url
|
||||
# FIXME: only return the flv encoding
|
||||
"http://#{PANDA_VIDEOS_DOMAIN}/#{self.filename}"
|
||||
end
|
||||
|
||||
def embed_html
|
||||
%(<embed src="http://#{PANDA_VIDEOS_DOMAIN}/flvplayer.swf" width="#{self.width}" height="#{self.height}" allowfullscreen="true" allowscriptaccess="always" flashvars="&displayheight=#{self.height}&file=#{self.url}&width=#{self.width}&height=#{self.height}" />)
|
||||
end
|
||||
|
||||
def resolution
|
||||
self.width ? "#{self.width}x#{self.height}" : nil
|
||||
end
|
||||
|
||||
def video_bitrate_in_bits
|
||||
self.video_bitrate * 1024
|
||||
end
|
||||
|
||||
def audio_bitrate_in_bits
|
||||
self.audio_bitrate * 1024
|
||||
end
|
||||
|
||||
def copy_metadata
|
||||
[:width, :height, :container, :fps, :video_bitrate, :audio_bitrate].each do |x|
|
||||
self.send("#{x}=", self.quality.send(x))
|
||||
end
|
||||
end
|
||||
|
||||
def show_response
|
||||
{
|
||||
:id => self.token,
|
||||
:width => self.width,
|
||||
:height => self.height,
|
||||
:resolution => self.resolution,
|
||||
:duration => self.duration,
|
||||
:format => self.quality.format.code,
|
||||
:quality => self.quality.quality,
|
||||
:status => self.status,
|
||||
:filename => self.filename
|
||||
}
|
||||
end
|
||||
|
||||
def job_response
|
||||
hash = {}
|
||||
[:id, :token, :filename, :status, :width, :height, :resolution, :container, :fps, :video_bitrate, :video_bitrate_in_bits, :audio_bitrate, :audio_bitrate_in_bits].each do |x|
|
||||
hash[x] = self.send(x)
|
||||
end
|
||||
hash[:format] = self.quality.format.code
|
||||
hash[:quality] = self.quality.quality
|
||||
return hash
|
||||
end
|
||||
|
||||
def change_status(st)
|
||||
self.update_attribute(:status, st.to_s)
|
||||
end
|
||||
|
||||
def set_pending_status
|
||||
self.status = 'queued'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
class Format < ActiveRecord::Base
|
||||
has_many :qualities, :order => "position asc"
|
||||
end
|
|
@ -0,0 +1,19 @@
|
|||
class Invite < ActiveRecord::Base
|
||||
belongs_to :account
|
||||
|
||||
validates_presence_of :email
|
||||
validates_length_of :email, :within => 3..100
|
||||
validates_uniqueness_of :email, :case_sensitive => false
|
||||
validates_format_of(:email,
|
||||
:with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i,
|
||||
:message=>"is invalid")
|
||||
|
||||
def approve!
|
||||
self.update_attribute(:approved, Time.now)
|
||||
InviteMailer.new.dispatch_and_deliver(:approved, {
|
||||
:from => EMAIL_SENDER,
|
||||
:to => self.email,
|
||||
:subject => "Welcome to the Panda beta"
|
||||
})
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
class Job < ActiveRecord::Base
|
||||
belongs_to :video
|
||||
belongs_to :ec2
|
||||
|
||||
before_create :set_default_status
|
||||
|
||||
def set_default_status
|
||||
self.status = 'queued'
|
||||
end
|
||||
|
||||
def job_response
|
||||
{
|
||||
:id => self.id,
|
||||
:video => self.video.job_response
|
||||
}
|
||||
end
|
||||
|
||||
def assign_to_ec2(ec2_instance)
|
||||
self.ec2_id = ec2_instance.id
|
||||
self.status = 'processing'
|
||||
self.save
|
||||
|
||||
self.video.change_status(:processing)
|
||||
self.video.send_status
|
||||
end
|
||||
|
||||
def self.find_next_job
|
||||
self.find(:first, :conditions => "status = 'queued'", :order => "created_at asc")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
class Notification < ActiveRecord::Base
|
||||
belongs_to :encoding
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
class Quality < ActiveRecord::Base
|
||||
belongs_to :format
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
# class Upload < ActiveRecord::Base
|
||||
# attr_accessor :bitrate
|
||||
# attr_accessor :resolution
|
||||
# end
|
|
@ -0,0 +1,141 @@
|
|||
class Video < ActiveRecord::Base
|
||||
belongs_to :account
|
||||
|
||||
has_many :encodings, :dependent => :destroy
|
||||
has_many :jobs, :dependent => :destroy
|
||||
|
||||
before_create :set_default_status
|
||||
before_create :set_token
|
||||
before_destroy :delete_s3_files
|
||||
|
||||
def set_default_status
|
||||
self.status = 'empty'
|
||||
end
|
||||
|
||||
def empty?
|
||||
self.status == "empty"
|
||||
end
|
||||
|
||||
def set_token
|
||||
self.token = UUID.new
|
||||
end
|
||||
|
||||
def default_flv_url
|
||||
self.encodings.first.url
|
||||
end
|
||||
|
||||
def full_raw_path
|
||||
File.join(RAW_DIR,self.token)
|
||||
end
|
||||
|
||||
def duration_str
|
||||
s = (self.duration || 0) / 1000
|
||||
"#{sprintf("%02d", s/60)}:#{sprintf("%02d", s%60)}"
|
||||
end
|
||||
|
||||
def resolution
|
||||
self.width ? "#{self.width}x#{self.height}" : nil
|
||||
end
|
||||
|
||||
def upload_form_url
|
||||
%(http://#{PANDA_UPLOAD_DOMAIN}:#{PANDA_UPLOAD_PORT}/videos/#{self.token}/form)
|
||||
end
|
||||
|
||||
def show_response
|
||||
{:video => {
|
||||
:id => self.token,
|
||||
:resolution => self.resolution,
|
||||
:duration => self.duration,
|
||||
:status => self.status,
|
||||
:encodings => self.encodings.map {|e| e.show_response}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def create_response
|
||||
{:video => {
|
||||
:id => self.token
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def job_response
|
||||
{:token => self.token,
|
||||
:status => self.status,
|
||||
:encodings => self.encodings.map {|e| e.job_response}
|
||||
}
|
||||
end
|
||||
|
||||
def change_status(st)
|
||||
self.update_attribute(:status, st.to_s)
|
||||
end
|
||||
|
||||
def add_to_queue
|
||||
job = self.jobs.create
|
||||
self.change_status(:queued)
|
||||
self.send_status
|
||||
return job
|
||||
end
|
||||
|
||||
# TODO: Use notifications daemon
|
||||
def send_status
|
||||
url = self.account.state_update_url.gsub(/\$id/,self.token)
|
||||
# params = {"video" => self.show_response.to_yaml}
|
||||
|
||||
Rog.log :info, "Sending status update of video##{self.token} to client (#{self.account.login}): #{url}"
|
||||
|
||||
begin
|
||||
ressult = Net::HTTP.get_response(URI.parse(url))
|
||||
puts "--> #{result.code} #{result.message} (#{result.body.length})"
|
||||
puts "WOULD FETCH URL NOW: #{url}"
|
||||
rescue
|
||||
puts "Couldn't connect to #{url}"
|
||||
# TODO: Send back a nice error if we can't connect to the client
|
||||
end
|
||||
end
|
||||
|
||||
def save_metadata(metadata)
|
||||
[:width, :height, :duration, :container, :fps, :video_codec, :video_bitrate, :audio_codec, :audio_sample_rate].each do |x|
|
||||
self.send("#{x}=", metadata[x])
|
||||
end
|
||||
end
|
||||
|
||||
def add_encoding_for_quality(quality, force=nil)
|
||||
# Only create an encoding if it doesn't already exist for this format
|
||||
unless Encoding.find(:first, :conditions => {:video_id => self.id, :quality_id => quality.id})
|
||||
if force == :force or self.width >= quality.width
|
||||
status = "queued"
|
||||
Encoding.create(:token => UUID.new, :video_id => self.id, :quality_id => quality.id, :status => status)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def add_encodings
|
||||
# TODO: Only add formats which the user has added to their account
|
||||
Format.find(:all).each do |format|
|
||||
qualities = format.qualities
|
||||
# We always encode to the lowest quality (which will be the first, as they are ordered)
|
||||
self.add_encoding_for_quality(qualities.shift, :force)
|
||||
qualities.each do |quality|
|
||||
self.add_encoding_for_quality(quality)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def delete_s3_files
|
||||
S3RawVideoObject.delete(self.token)
|
||||
self.encodings.each do |e|
|
||||
S3VideoObject.delete(e.filename)
|
||||
end
|
||||
end
|
||||
|
||||
# def upload_and_encode(filename, format)
|
||||
# encoding = self.encodings.create(:format_id => format.id)
|
||||
|
||||
# Send to uploading queue
|
||||
# message = {:filename => filename, :token => self.token, :encoding => encoding.hash_for_queue}
|
||||
# puts "Adding to upload queue"
|
||||
# puts message.to_yaml
|
||||
# Queue.up.send_message message.to_yaml
|
||||
# end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
<%= catch_content :layout %>
|
|
@ -0,0 +1,21 @@
|
|||
<div class="dashboard">
|
||||
<% if @account.upload_redirect_url.blank? or @account.state_update_url.blank? %>
|
||||
<div class="notice">
|
||||
<p>You haven't set an upload redirect or state update url. <a href="/accounts">Edit your account details</a> and set these to allow Panda to communicate with your application.</p>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div id="new_video">
|
||||
<h2><a href="#" onclick="window.open('/videos/new?account_token=<%= @account.token %>', 'panda_upload', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=395,height=35');return false;">Upload a test video</a></h2>
|
||||
</div>
|
||||
|
||||
<h2>Queued videos</h2>
|
||||
|
||||
<%= partial "videos/list", :videos => @queued_videos %>
|
||||
|
||||
<h2>Recently completed videos</h2>
|
||||
|
||||
<%= partial "videos/list", :videos => @recently_completed_videos %>
|
||||
|
||||
<div class="more_videos"><a href="/videos">View all videos</a></div>
|
||||
</div>
|
|
@ -0,0 +1,18 @@
|
|||
<% form_for :account, :action => "/accounts", :method => :put do %>
|
||||
<h2>Account details</h2>
|
||||
|
||||
<p><%= text_control :name, :label => "Name" %></p>
|
||||
<p><%= text_control :email, :label => "Email" %></p>
|
||||
|
||||
<h2>Change password</h2>
|
||||
|
||||
<p><%= password_control :password, :label => "Password" %></p>
|
||||
<p><%= password_control :password_confirmation, :label => "Confirm password" %></p>
|
||||
|
||||
<h2>Settings</h2>
|
||||
|
||||
<p><%= text_control :upload_redirect_url, :label => "Upload redirect url", :size => 50 %></p>
|
||||
<p><%= text_control :state_update_url, :label => "State update url", :size => 50 %></p>
|
||||
|
||||
<%= submit_button "Save changes" %>
|
||||
<% end %>
|
|
@ -0,0 +1,14 @@
|
|||
<h2>Sign up to Panda</h2>
|
||||
|
||||
<div class="notice">You must sign up with the same email address your invite was sent to.</div>
|
||||
|
||||
<% form_for :account, :action => "/signup" do %>
|
||||
<p><%= text_control :name, :label => "Name" %></p>
|
||||
<p><%= text_control :login, :label => "Login" %></p>
|
||||
<p><%= text_control :email, :label => "Email" %></p>
|
||||
|
||||
<p><%= password_control :password, :label => "Password" %></p>
|
||||
<p><%= password_control :password_confirmation, :label => "Confirm password" %></p>
|
||||
|
||||
<%= submit_button "Sign up" %>
|
||||
<% end %>
|
|
@ -0,0 +1,29 @@
|
|||
<h2>Account details</h2>
|
||||
|
||||
<div class="account_details">
|
||||
<h3>Name</h3>
|
||||
<p class="note">Company, organisation or individual's name.</p>
|
||||
<p class="value"><%= @account.name %></p>
|
||||
|
||||
<h3>Email</h3>
|
||||
<p class="note">Email address to send notifications and error reports to.</p>
|
||||
<p class="value"><%= @account.email %></p>
|
||||
|
||||
<h3>Account key</h3>
|
||||
<p class="note">Your secret key used to authenticate API calls.</p>
|
||||
<p class="value"><%= @account.token %></p>
|
||||
</div>
|
||||
|
||||
<h2>Settings</h2>
|
||||
|
||||
<div class="account_details">
|
||||
<h3>Upload redirect url</h3>
|
||||
<p class="note">After uploading a video using the form served by Panda, the user will be redirect to this url. Typically you should display a confirmation that the video has been uploaded and will take several minutes to process.</p>
|
||||
<p class="value"><%= @account.upload_redirect_url %></p>
|
||||
|
||||
<h3>State update url</h3>
|
||||
<p class="note">Along each stage of the video upload process, Panda will report back to you the current state of a video. The variables $token and $state will be replaced with the video's token and it's new state: uploaded, encoding, error, encoding_error or done.</p>
|
||||
<p class="value"><%= @account.state_update_url %></p>
|
||||
</div>
|
||||
|
||||
<div class="edit_details"><a href="/accounts/edit">Edit details</a></div>
|
|
@ -0,0 +1,6 @@
|
|||
<%= notice %>
|
||||
<% form_for :account, :action => "/login", :method => :post do %>
|
||||
<p><%= text_control :login, :label => "Username" %></p>
|
||||
<p><%= password_control :password, :label => "Password" %></p>
|
||||
<%= submit_button "Login" %>
|
||||
<% end %>
|
|
@ -0,0 +1,3 @@
|
|||
<h2>Documentation</h2>
|
||||
|
||||
<p>Coming soon to a browser near you!</p>
|
|
@ -0,0 +1,207 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||
<title><%= @exception.name.humanize %></title>
|
||||
<style type="text/css" media="screen">
|
||||
body {
|
||||
font-family:arial;
|
||||
font-size:11px;
|
||||
}
|
||||
h1 {
|
||||
font-size:48px;
|
||||
letter-spacing:-4px;
|
||||
margin:0;
|
||||
line-height:36px;
|
||||
color:#333;
|
||||
}
|
||||
h1 sup {
|
||||
font-size: 0.5em;
|
||||
}
|
||||
h1 sup.error_500, h1 sup.error_400 {
|
||||
color:#990E05;
|
||||
}
|
||||
h1 sup.error_100, h1 sup.error_200 {
|
||||
color:#00BF10;
|
||||
}
|
||||
h1 sup.error_300 {
|
||||
/* pretty sure you cant 'see' status 300
|
||||
errors but if you could I think they
|
||||
would be blue */
|
||||
color:#1B2099;
|
||||
}
|
||||
h2 {
|
||||
font-size:36px;
|
||||
letter-spacing:-3px;
|
||||
margin:0;
|
||||
line-height:28px;
|
||||
color:#444;
|
||||
}
|
||||
a, a:visited {
|
||||
color:#00BF10;
|
||||
}
|
||||
.internalError {
|
||||
width:800px;
|
||||
margin:50px auto;
|
||||
}
|
||||
.header {
|
||||
border-bottom:10px solid #333;
|
||||
margin-bottom:1px;
|
||||
background-image: url("data:image/gif;base64,R0lGODlhAwADAIAAAP///8zMzCH5BAAAAAAALAAAAAADAAMAAAIEBHIJBQA7");
|
||||
padding:20px;
|
||||
}
|
||||
table.trace {
|
||||
width:100%;
|
||||
font-family:courier, monospace;
|
||||
letter-spacing:-1px;
|
||||
border-collapse: collapse;
|
||||
border-spacing:0;
|
||||
}
|
||||
table.trace tr td{
|
||||
padding:0;
|
||||
height:26px;
|
||||
font-size:13px;
|
||||
vertical-align:middle;
|
||||
}
|
||||
table.trace tr.file{
|
||||
border-top:2px solid #fff;
|
||||
background-color:#F3F3F3;
|
||||
}
|
||||
table.trace tr.source {
|
||||
background-color:#F8F8F8;
|
||||
display:none;
|
||||
}
|
||||
table.trace .open tr.source {
|
||||
display:table-row;
|
||||
}
|
||||
table.trace tr.file td.expand {
|
||||
width:23px;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAAXCAIAAABvSEP3AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAdVJREFUeNqMVL+TwUAYxaRIOlEhlZHGDAUzzOQ61+AqXMV1lJSU7q/QRqm8KFUcJTNn5qJkaPyoKKVz7y4mF8na5Kt29tt9+/Z97/u81+vVQ4r9frdarS6Xi7ETDIZisRjxMGPfmk4niNPpZE+xLAugbPaZ53nzvtfMBe/3+/3dbuehBrAKhZdUKkVAWa9Xsiybv0CPZDJZLr/qa5/BwgwRjYqOKIvFYjQa/aNommZh0Ww2K5UqzwfoQOPxaLPZ3FAmk0+7lplMpt1u53J5OpBOR0eZEE9wHJfP5zud93g88QhluwWbjW+5VOmKBgKBer3eaDTDYeGBQF8+x7rqIYoiPgixWJazpA6HA+MSxRArkUgMh0M409g8Ho8+9wYxxCqVSq1W26EDHGM2m4HOHQrEc38f/Yn7cLmlIRhBENzcx8cVRZnPZ/YUep2BWkjTIfA+PKVpZAXR5QxsjiqCKvGEqqp443w+0dvy17swqD0HB3S73V5PpkNg1qBqt8kwGCjmPkinM0QJbIoEa7U6UG6ToVgs4V9G2g0ESoP5Aoi7KYX5oCgf8IKbkvn9/mr1LRQKESamzgJy0g0tSZIuB3nuGqRU9Vv9C4sKkUhEkp4soxvxI8AAhWrrtXa3X8EAAAAASUVORK5CYII=);
|
||||
background-position:top left;
|
||||
background-repeat:no-repeat;
|
||||
}
|
||||
table.trace .open tr.file td.expand {
|
||||
width:19px;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAXZJREFUeNrslK1ywkAUhcMOBomEOiSdqLxEBJX0NaijOsjyHGGmCGyQQYaiiiw4gktkcOmZbpsuuzQ/M5XnqJ2d3S/n3nM3rTzPLUP7/Tt0+pLcGQwG3W53OLyHzPMtjYL7q9UqSRLrD4E1Gj1orCvKYuFHUWTVkOM44/HjDcp8/lL4r6NerzeZPMm1KFw0QkDn83m5fP2lHA4fNQvRtNvtjsfDd0WzmSfb2e/fdTqdOvdh/HLJZLOn0+d2HJ+KRGzbdl23EpFlmed5cp2maRzHQq1lvQ5KMi6EUZBGfup6E1pTfd+vrGW7jbQ2C9hTt9BpqNyIWaAwAy6xg2eBz5iRC/NomiZhGN5sqmnkauo0BUGgVQoBjQ80oCACgNQdZHfTYBkF2mxCtWWAqunWpahxIDUt3QYUxIFQpJHyIWpXjinabKbbwItMHT+NyjchrP8QKaSQQgoppJBCCimkkEIKKaSQQgoppJBCCimkkEIKKaSo+hRgAEFD17X08O2NAAAAAElFTkSuQmCC);
|
||||
background-position:top left;
|
||||
background-repeat:no-repeat;
|
||||
}
|
||||
table.trace tr.source td.collapse {
|
||||
width:19px;
|
||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAAB1CAIAAAAqdO2mAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAVxJREFUeNrs0zFygkAUBmBlUkgJHdABlQwVkVJKKUxBYWbkALTxMJwhltyDFkss03IF8pudIcwaDaDl/6pd2P327b7d+eHwMXs4lNkzggoVKlSoUKFChQoVKlSoUKFChQoVKlSoUKFChQqVEYqm6ft9+qiSJEkYho7jTlcw2fd9NOI4nq4gEdFwXXe1Cqco63VkWVbXRTqLhTpOwQRpF7quR1E0TgGhqvLKUFCyoQqG/rks3O6kZKW/eRFpevOCoGTXVTcMQ5EyxyDEkML1c5RzuZOICIyXqn7JBVez6282MWrx731HOv2qB8Hri2lamNk0DfpVVdV1Peodappmmua8bdvzuc7zfNprzrLMth1FnGh/X8MjCAIQv/cFz/+65PcDh7rbvYv2ZUfdj+PxsyzLgVl0hKwgTqeqKApx2LeOc7t98zyv/1FWOgvx9RPii23bmL9cetJ8Ed8CDAC6aFW8bCzFhwAAAABJRU5ErkJggg==);
|
||||
background-position:bottom left;
|
||||
background-repeat:no-repeat;
|
||||
background-color:#6F706F;
|
||||
}
|
||||
table.trace tr td.path {
|
||||
padding-left:10px;
|
||||
}
|
||||
table.trace tr td.code {
|
||||
padding-left:35px;
|
||||
white-space: pre;
|
||||
line-height:9px;
|
||||
padding-bottom:10px;
|
||||
}
|
||||
table.trace tr td.code em {
|
||||
font-weight:bold;
|
||||
color:#00BF10;
|
||||
}
|
||||
table.trace tr td.code .more {
|
||||
color:#666;
|
||||
}
|
||||
table.trace tr td.line {
|
||||
width:30px;
|
||||
text-align:right;
|
||||
padding-right:4px;
|
||||
}
|
||||
.footer {
|
||||
margin-top:5px;
|
||||
font-size:11px;
|
||||
color:#444;
|
||||
text-align:right;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="internalError">
|
||||
|
||||
<div class="header">
|
||||
<h1><%= @exception.name.humanize %> <sup class="error_<%= @exception.class::STATUS %>"><%= @exception.class::STATUS %></sup></h1>
|
||||
<% if show_details = ::Merb::Server.config[:exception_details] -%>
|
||||
<h2><%= @exception.message %></h2>
|
||||
<% else -%>
|
||||
<h2>Sorry about that...</h2>
|
||||
<% end -%>
|
||||
<h3>Parameters</h3>
|
||||
<ul>
|
||||
<% controller.params[:original_params].each do |param, value| %>
|
||||
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
|
||||
<% end %>
|
||||
<%= "<li>None</li>" if controller.params[:original_params].empty? %>
|
||||
</ul>
|
||||
|
||||
<h3>Session</h3>
|
||||
<ul>
|
||||
<% controller.params[:original_session].each do |param, value| %>
|
||||
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
|
||||
<% end %>
|
||||
<%= "<li>None</li>" if controller.params[:original_session].empty? %>
|
||||
</ul>
|
||||
|
||||
<h3>Cookies</h3>
|
||||
<ul>
|
||||
<% controller.params[:original_cookies].each do |param, value| %>
|
||||
<li><strong><%= param %>:</strong> <%= value.inspect %></li>
|
||||
<% end %>
|
||||
<%= "<li>None</li>" if controller.params[:original_cookies].empty? %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<% if show_details %>
|
||||
<table class="trace">
|
||||
<% @exception.backtrace.each_with_index do |line, index| %>
|
||||
<tbody class="close">
|
||||
<tr class="file">
|
||||
<td class="expand">
|
||||
</td>
|
||||
<td class="path">
|
||||
<%= (line.match(/^([^:]+)/)[1] rescue 'unknown').sub(/\/((opt|usr)\/local\/lib\/(ruby\/)?(gems\/)?(1.8\/)?(gems\/)?|.+\/app\/)/, '') %> in "<strong><%= line.match(/:in `(.+)'$/)[1] rescue '?' %></strong>"
|
||||
</td>
|
||||
<td class="line">
|
||||
<a href="txmt://open?url=file://<%=file = (line.match(/^([^:]+)/)[1] rescue 'unknown')%>&line=<%= lineno = line.match(/:([0-9]+):/)[1] rescue '?' %>"><%=lineno%></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="source">
|
||||
<td class="collapse">
|
||||
</td>
|
||||
<td class="code" colspan="2"><% (__caller_lines__(file, lineno, 5) rescue []).each do |llineno, lcode, lcurrent| %>
|
||||
<a href="txmt://open?url=file://<%=file%>&line=<%=llineno%>"><%= llineno %></a><%='<em>' if llineno==lineno.to_i %><%= lcode.size > 90 ? lcode[0..90]+'<span class="more">......</span>' : lcode %><%='</em>' if llineno==lineno.to_i %>
|
||||
<% end %>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<% end %>
|
||||
</table>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
// swop the open & closed classes
|
||||
els = document.getElementsByTagName('td');
|
||||
for(i=0; i<els.length; i++){
|
||||
if(els[i].className=='expand' || els[i].className=='collapse'){
|
||||
els[i].onclick = function(e){
|
||||
tbody = this.parentNode.parentNode;
|
||||
if(tbody.className=='open'){
|
||||
tbody.className='closed';
|
||||
}else{
|
||||
tbody.className='open';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
<div class="footer">
|
||||
lots of love, from <a href="#">merb</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,38 @@
|
|||
<div id="container">
|
||||
<div id="header-container">
|
||||
<img src="/images/merb.jpg">
|
||||
<!-- <h1>Mongrel + Erb</h1> -->
|
||||
<h2>pocket rocket web framework</h2>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="left-container">
|
||||
<h3>Exception:</h3>
|
||||
<p><%= params[:exception] %></p>
|
||||
</div>
|
||||
|
||||
<div id="main-container">
|
||||
<h3>Why am I seeing this page?</h3>
|
||||
<p>Merb couldn't find an appropriate content_type to return,
|
||||
based on what you said was available via provides() and
|
||||
what the client requested. For more information, visit
|
||||
http://merbivore.com/fixing_406_issues
|
||||
</p>
|
||||
|
||||
<h3>Where can I find help?</h3>
|
||||
<p>If you have any questions or if you can't figure something out, please take a
|
||||
look at our <a href="http://merb.devjavu.com/"> project development page</a> or,
|
||||
feel free to come chat at irc.freenode.net, channel #merb.</p>
|
||||
|
||||
<h3>How do I edit this page?</h3>
|
||||
<p>You can change what people see when this happens byy editing <tt>app/views/exceptions/not_found.html.erb</tt>.</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="footer-container">
|
||||
<hr />
|
||||
<div class="left"></div>
|
||||
<div class="right">© 2007 the merb dev team</div>
|
||||
<p> </p>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,40 @@
|
|||
<div id="container">
|
||||
<div id="header-container">
|
||||
<img src="/images/merb.jpg">
|
||||
<!-- <h1>Mongrel + Erb</h1> -->
|
||||
<h2>pocket rocket web framework</h2>
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div id="left-container">
|
||||
<h3>Exception:</h3>
|
||||
<p><%= params[:exception] %></p>
|
||||
</div>
|
||||
|
||||
<div id="main-container">
|
||||
<h3>Welcome to Merb!</h3>
|
||||
<p>Merb is a light-weight MVC framework written in Ruby. We hope you enjoy it.</p>
|
||||
|
||||
<h3>Where can I find help?</h3>
|
||||
<p>If you have any questions or if you can't figure something out, please take a
|
||||
look at our <a href="http://merb.devjavu.com/"> project development page</a> or,
|
||||
feel free to come chat at irc.freenode.net, channel #merb.</p>
|
||||
|
||||
<h3>How do I edit this page?</h3>
|
||||
<p>You're seeing this page because you need to edit the following files:
|
||||
<ul>
|
||||
<li>config/merb.yml <strong><em>(optional)</em></strong></li>
|
||||
<li>config/router.rb <strong><em>(recommended)</em></strong></li>
|
||||
<li>app/views/exceptions/not_found.html.erb <strong><em>(recommended)</em></strong></li>
|
||||
<li>app/views/layout/application.html.erb <strong><em>(change this layout)</em></strong></li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div id="footer-container">
|
||||
<hr />
|
||||
<div class="left"></div>
|
||||
<div class="right">© 2007 the merb dev team</div>
|
||||
<p> </p>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
<%# Form is in panda_site %>
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
|
||||
<head>
|
||||
<title>Panda</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" href="/stylesheets/admin.css" type="text/css" media="screen" charset="utf-8" />
|
||||
<script type="text/javascript" charset="utf-8" src="/javascripts/jquery.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="/javascripts/swfobject.js"></script>
|
||||
|
||||
<%= catch_content :head %>
|
||||
</head>
|
||||
<body>
|
||||
<div id="container">
|
||||
<div id="header" class="clearfix">
|
||||
<div id="account">
|
||||
<h2><%= @account.name %></h2>
|
||||
<p><a href="/logout">Logout</a></p>
|
||||
</div>
|
||||
|
||||
<h1><a href="/"><img src="/images/panda_logo.gif" alt="Panda" /></a></h1>
|
||||
|
||||
<ul id="nav" class="clearfix">
|
||||
<%= nav_item "dashboard", "/" %>
|
||||
<%= nav_item "videos" %>
|
||||
<%= nav_item "account", {:controller => "accounts", :actions => [:index,:edit]} %>
|
||||
<li><a href="http://pandastream.com/docs" target="_blank">Documentation</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="main">
|
||||
<%= catch_content :for_layout %>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,23 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
|
||||
<head>
|
||||
<title>Panda</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<link rel="stylesheet" href="/stylesheets/admin.css" type="text/css" media="screen" charset="utf-8" />
|
||||
<script type="text/javascript" charset="utf-8" src="/javascripts/jquery.js"></script>
|
||||
<script type="text/javascript" charset="utf-8" src="/javascripts/swfobject.js"></script>
|
||||
|
||||
<%= catch_content :head %>
|
||||
</head>
|
||||
<body class="auth">
|
||||
<div id="container">
|
||||
<div id="header" class="clearfix">
|
||||
<h1><a href="/"><img src="/images/panda_logo.gif" alt="Panda" /></a></h1>
|
||||
</div>
|
||||
|
||||
<div id="main">
|
||||
<%= catch_content :for_layout %>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,10 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-us" lang="en-us">
|
||||
<head>
|
||||
<title>Panda</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
</head>
|
||||
<body>
|
||||
<%= catch_content :for_layout %>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,15 @@
|
|||
<% throw_content(:head) do %>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
$(function() {
|
||||
jQuery.nginxUploadProgress({uuid: "<%= params[:id] %>"});
|
||||
});
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
<div id="pandaloader">
|
||||
<div id="uploading">
|
||||
<div id="progress" class="bar">
|
||||
<div id="progressbar"> </div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,8 @@
|
|||
<p>
|
||||
<label>Video format</label>
|
||||
<select name="panda_video[max_format]">
|
||||
<% Format.find_all.each do |f| %>
|
||||
<option value="<%= f.id %>"><%= f.full_name %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</p>
|
|
@ -0,0 +1,19 @@
|
|||
<table id="videos">
|
||||
<tr>
|
||||
<th>Filename</th>
|
||||
<th>Duration</th>
|
||||
<th>Status</th>
|
||||
<th>Created at</th>
|
||||
</tr>
|
||||
<% videos.each do |v| %>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/videos/<%= v.token %>" class="filename"><%= v.filename || "No filename" %></a>
|
||||
<span class="token"><%= v.token %></span>
|
||||
</td>
|
||||
<td><%= v.duration_str %></td>
|
||||
<td class="<%= v.status %>"><%= v.status %></td>
|
||||
<td><%= v.created_at.strftime("%a, %d %b %Y %H:%M:%S") %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
|
@ -0,0 +1,3 @@
|
|||
<h2>Videos</h2>
|
||||
|
||||
<%= partial :list, :videos => @videos %>
|
|
@ -0,0 +1,4 @@
|
|||
<form action="/videos" method="post">
|
||||
<input type="submit" value="Continue to upload form">
|
||||
<input type="hidden" name="panda_video[account_token]" id="panda_video_account_token" value="<%= params[:account_token] %>">
|
||||
</form>
|
|
@ -0,0 +1,29 @@
|
|||
<h2><%= @video.filename %></h2>
|
||||
|
||||
<dl>
|
||||
<dt>ID:</dt>
|
||||
<dd><%= @video.token %></dd>
|
||||
<dt>Status:</dt>
|
||||
<dd><%= @video.status %></dd>
|
||||
<dt>Duration:</dt>
|
||||
<dd><%= @video.duration_str %></dd>
|
||||
</dl>
|
||||
|
||||
<% @video.encodings.each do |enc| %>
|
||||
<h3><%= enc.quality.format.name %> (<%= enc.quality.quality %>)</h3>
|
||||
|
||||
<div class="video">
|
||||
<% if enc.status == "success" %>
|
||||
<%= enc.embed_html %>
|
||||
<% elsif enc.status == "error" %>
|
||||
<div class="response">There was an error encoding to this format. We have been notified and will look into the issue immediately.</div>
|
||||
<% else %>
|
||||
<div class="response">Once uploaded and encoded the video will be displayed here.</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="embed_code">
|
||||
<h4>Embed code</h4>
|
||||
<textarea cols="50" rows="6"><%= enc.embed_html %></textarea>
|
||||
</div>
|
||||
<% end %>
|
|
@ -0,0 +1,15 @@
|
|||
Merb.logger.info("Loaded DEVELOPMENT Environment...")
|
||||
Merb::Config.use { |c|
|
||||
c[:exception_details] = true
|
||||
c[:reload_classes] = true
|
||||
c[:reload_time] = 0.5
|
||||
c[:log_auto_flush ] = true
|
||||
}
|
||||
|
||||
PANDA_HOME = File.join(Merb.root, '..', 'panda_ec2', 'panda')
|
||||
PANDA_LOG_SERVER = "127.0.0.1"
|
||||
PANDA_DOMAIN = "127.0.0.1"
|
||||
PANDA_PORT = 4000
|
||||
PANDA_UPLOAD_DOMAIN = "127.0.0.1"
|
||||
PANDA_UPLOAD_PORT = 4001
|
||||
PANDA_VIDEOS_DOMAIN = "videos.pandastream.com"
|
|
@ -0,0 +1,15 @@
|
|||
Merb.logger.info("Loaded PRODUCTION Environment...")
|
||||
Merb::Config.use { |c|
|
||||
c[:exception_details] = false
|
||||
c[:reload_classes] = false
|
||||
c[:log_level] = :error
|
||||
c[:log_file] = Merb.log_path + "/production.log"
|
||||
}
|
||||
|
||||
PANDA_HOME = "/mnt/panda"
|
||||
PANDA_LOG_SERVER = "127.0.0.1"
|
||||
PANDA_DOMAIN = "hq.pandastream.com"
|
||||
PANDA_PORT = 80
|
||||
PANDA_UPLOAD_DOMAIN = "upload.pandastream.com"
|
||||
PANDA_UPLOAD_PORT = 80
|
||||
PANDA_VIDEOS_DOMAIN = "videos.pandastream.com"
|
|
@ -0,0 +1 @@
|
|||
puts "Loaded TEST Environment..."
|
|
@ -0,0 +1,55 @@
|
|||
# Make the app's "gems" directory a place where gems are loaded from
|
||||
Gem.clear_paths
|
||||
Gem.path.unshift(Merb.root / "gems")
|
||||
|
||||
# Make the app's "lib" directory a place where ruby files get "require"d from
|
||||
$LOAD_PATH.unshift(Merb.root / "lib")
|
||||
|
||||
|
||||
Merb::Config.use do |c|
|
||||
|
||||
### Sets up a custom session id key, if you want to piggyback sessions of other applications
|
||||
### with the cookie session store. If not specified, defaults to '_session_id'.
|
||||
# c[:session_id_key] = '_session_id'
|
||||
|
||||
c[:session_secret_key] = '4d5e9b90d9e92c236a2300d718059aef3a9b9cbe'
|
||||
c[:session_store] = 'cookie'
|
||||
end
|
||||
|
||||
use_orm :activerecord
|
||||
|
||||
dependencies 'merb_helpers', 'merb-mailer', 'uuid', 'to_simple_xml', 'rog'
|
||||
|
||||
# Not sure why dependencies won't load AWS::S3
|
||||
require 'aws/s3'
|
||||
|
||||
Merb::BootLoader.after_app_loads do
|
||||
# Panda specific
|
||||
|
||||
unless Merb.environment == "test"
|
||||
require File.join(Merb.root, '..', 'aws_connect')
|
||||
|
||||
AWS::S3::Base.establish_connection!(
|
||||
:access_key_id => ACCESS_KEY_ID,
|
||||
:secret_access_key => SECRET_ACCESS_KEY
|
||||
)
|
||||
|
||||
Rog.prefix = "HQ"
|
||||
Rog.host = PANDA_LOG_SERVER
|
||||
Rog.port = 3333
|
||||
Rog.log :info, "Panda HQ app awake"
|
||||
|
||||
Merb::Mailer.config = {
|
||||
:host=>'localhost',
|
||||
:domain => 'pandastream.com',
|
||||
:port=>'25'
|
||||
# :user=>'',
|
||||
# :pass=>'',
|
||||
# :auth=>:plain # :plain, :login, :cram_md5, the default is no auth
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
EMAIL_SENDER = "Panda <info@pandastream.com>"
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
# Hostname or IP address to bind to.
|
||||
:host: 0.0.0.0
|
||||
|
||||
# Port merb runs on or starting port for merb cluster.
|
||||
:port: "4001"
|
||||
|
||||
# Set if your app will be hosted in some dir other than the root
|
||||
#:path_prefix: "/my_app"
|
||||
|
||||
# In development mode your app's files are reloaded whenever Merb detects a
|
||||
# change. Templates are parsed each time and not cached. In production mode
|
||||
# templates are cached, as well as all your classes
|
||||
:environment: development
|
||||
|
||||
# Uncomment if you have more than one ORM or if you need to be specific about
|
||||
# which memory store to use. Built-in options are: memory, cookie, or mem_cache
|
||||
:session_store: memory
|
||||
|
||||
#:memory_session_ttl: 3600 # one hour
|
||||
|
||||
# A secret key is required when using the 'cookie' session store (default),
|
||||
# change this value to something unique to your application and keep it private
|
||||
:session_secret_key: PANDA_MERB91626923692367236784223
|
||||
|
||||
# Uncomment to use the merb upload progress. The 'path match' will be treated as
|
||||
# a regex for any URLs that should be considered for upload monitoring.
|
||||
#:upload_path_match: /files/\d
|
||||
#:upload_frequency: 3
|
||||
|
||||
# Uncomment to cache templates in dev mode. Templates are cached
|
||||
# automatically in production mode.
|
||||
#:cache_templates: true
|
||||
|
||||
# Uncomment and set this if you want to run a drb server for upload progress
|
||||
# or other drb services.
|
||||
#:drb_server_port: 32323
|
||||
|
||||
# If you want to protect some or all of your app with HTTP basic auth then
|
||||
# uncomment the following and fill in your credentials you want it to use.
|
||||
# You will then need to set a 'before' filter in a controller. For example:
|
||||
# before :basic_authentication
|
||||
#:basic_auth:
|
||||
# :username: ezra
|
||||
# :password: test
|
||||
# :domain: localhost
|
||||
|
||||
# Uncomment this if you want merb to daemonize when you start it. You can also
|
||||
# just use merb -d for the same effect. Don't uncomment this if you use the
|
||||
# cluster option.
|
||||
#:daemonize: true
|
||||
|
||||
# Uncomment this to set the number of members in your merb cluster. Don't set
|
||||
# this and :daemonize: at the same time.
|
||||
#:cluster: 3
|
||||
|
||||
# Uncomment this if you want to force merb to show full InternalServerError
|
||||
# details, even when in production mode
|
||||
:exception_details: true
|
||||
|
||||
# It is often useful to use a differant layout from 'application' for errors
|
||||
# set this to the layout template (or :none) that you want to use by default
|
||||
#:exception_layout: :none
|
||||
|
|
@ -0,0 +1 @@
|
|||
run Merb::Rack::Application.new
|
|
@ -0,0 +1,49 @@
|
|||
# Merb::Router is the request routing mapper for the merb framework.
|
||||
#
|
||||
# You can route a specific URL to a controller / action pair:
|
||||
#
|
||||
# r.match("/contact").
|
||||
# to(:controller => "info", :action => "contact")
|
||||
#
|
||||
# You can define placeholder parts of the url with the :symbol notation. These
|
||||
# placeholders will be available in the params hash of your controllers. For example:
|
||||
#
|
||||
# r.match("/books/:book_id/:action").
|
||||
# to(:controller => "books")
|
||||
#
|
||||
# Or, use placeholders in the "to" results for more complicated routing, e.g.:
|
||||
#
|
||||
# r.match("/admin/:module/:controller/:action/:id").
|
||||
# to(:controller => ":module/:controller")
|
||||
#
|
||||
# You can also use regular expressions, deferred routes, and many other options.
|
||||
# See merb/specs/merb/router.rb for a fairly complete usage sample.
|
||||
|
||||
Merb.logger.info("Compiling routes...")
|
||||
Merb::Router.prepare do |r|
|
||||
# RESTful routes
|
||||
r.resources :invites
|
||||
|
||||
# External API
|
||||
r.resources :videos, {:member => {:valid => :get, :uploaded => :post}}
|
||||
|
||||
r.resource :accounts
|
||||
r.match("/account").to(:controller => "accounts", :action => "index")
|
||||
|
||||
r.match("/signup").to(:controller => "accounts", :action => "new")
|
||||
r.match("/login").to(:controller => "auth", :action => "login")
|
||||
r.match("/logout").to(:controller => "auth", :action => "logout")
|
||||
|
||||
r.match("/docs").to(:controller => "docs", :action => "index")
|
||||
|
||||
# Internal API
|
||||
r.resources :uploads
|
||||
r.resources :jobs, {:member => {:done => :post}, :collection => {:next => :get}}
|
||||
|
||||
# This is the default route for /:controller/:action/:id
|
||||
# This is fine for most cases. If you're heavily using resource-based
|
||||
# routes, you may want to comment/remove this line to prevent
|
||||
# clients from calling your create or destroy actions with a GET
|
||||
# r.default_routes
|
||||
r.match("/").to(:controller => "accounts", :action => "dashboard")
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
# == Synopsis
|
||||
#
|
||||
# Simple remote debug class
|
||||
#
|
||||
# == Author
|
||||
# Stefan Saasen s@juretta.com
|
||||
#
|
||||
# == Copyright
|
||||
# Copyright (c) 2005 juretta.com Stefan Saasen
|
||||
# Licensed under the same terms as Ruby.
|
||||
# == Version
|
||||
# Version 0.1 ($Id: logger.rb 5 2006-01-01 12:51:04Z stefan $)
|
||||
|
||||
require 'socket'
|
||||
require 'singleton'
|
||||
require 'timeout'
|
||||
|
||||
class Rog
|
||||
include Singleton
|
||||
cattr_writer :port, :host, :prefix
|
||||
attr :session
|
||||
|
||||
def self.log(level, msg)
|
||||
begin
|
||||
Timeout::timeout(1) do
|
||||
@session = TCPSocket.new(@@host, @@port)
|
||||
@session.puts Time.new.strftime("%Y-%m-%d %H:%M:%S") + \
|
||||
" " + "[" + level.to_s.upcase + "] #{@@prefix}: " + msg + "\n"
|
||||
@session.close
|
||||
end
|
||||
rescue => e
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
class Hash
|
||||
def to_simple_xml
|
||||
hash_to_xml_string(self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hash_to_xml_string(h)
|
||||
s = ""
|
||||
h.each do |k,v|
|
||||
s += "<#{k}>"
|
||||
if v.class == Hash
|
||||
s += hash_to_xml_string(v)
|
||||
elsif v.class == Array
|
||||
v.each {|i| s += hash_to_xml_string(i) }
|
||||
else
|
||||
s += v.to_s
|
||||
end
|
||||
s += "</#{k}>"
|
||||
end
|
||||
return s
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0"?>
|
||||
<!DOCTYPE cross-domain-policy SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
|
||||
<cross-domain-policy>
|
||||
<allow-access-from domain="*" />
|
||||
</cross-domain-policy>
|
Двоичный файл не отображается.
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 39 KiB |
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 1.8 KiB |
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,54 @@
|
|||
interval = null;
|
||||
|
||||
function openProgressBar() {
|
||||
/* generate random progress-id */
|
||||
uuid = "";
|
||||
for (i = 0; i < 32; i++) {
|
||||
uuid += Math.floor(Math.random() * 16).toString(16);
|
||||
}
|
||||
/* patch the form-action tag to include the progress-id */
|
||||
document.getElementById("upload").action="/uploader/upload?X-Progress-ID=" + uuid;
|
||||
|
||||
/* call the progress-updater every 1000ms */
|
||||
interval = window.setInterval(
|
||||
function () {
|
||||
fetch(uuid);
|
||||
},
|
||||
1000
|
||||
);
|
||||
|
||||
// show the progress bar
|
||||
$('#uploader').hide();
|
||||
$('#uploading').show();
|
||||
}
|
||||
|
||||
function fetch(uuid) {
|
||||
req = new XMLHttpRequest();
|
||||
req.open("GET", "/progress", 1);
|
||||
req.setRequestHeader("X-Progress-ID", uuid);
|
||||
req.onreadystatechange = function () {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 200) {
|
||||
/* poor-man JSON parser */
|
||||
var upload = eval(req.responseText);
|
||||
|
||||
document.getElementById('tp').innerHTML = upload.state;
|
||||
|
||||
/* change the width if the inner progress-bar */
|
||||
if (upload.state == 'done' || upload.state == 'uploading') {
|
||||
bar = document.getElementById('progressbar');
|
||||
w = 300 * upload.received / upload.size;
|
||||
bar.style.width = w + 'px';
|
||||
$('#progressbar').show();
|
||||
}
|
||||
/* we are done, stop the interval */
|
||||
if (upload.state == 'done') {
|
||||
window.clearTimeout(interval);
|
||||
$('#uploading').hide();
|
||||
$('#weredone').show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
req.send(null);
|
||||
}
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
ARGV=["-F"]
|
||||
require 'merb/server'
|
||||
|
||||
Merb::Server.run
|
|
@ -0,0 +1,241 @@
|
|||
/* hax */
|
||||
.clearfix:after {
|
||||
content: ".";
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both;
|
||||
visibility: hidden;
|
||||
}
|
||||
.clearfix {
|
||||
zoom: 1;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Lucida Grande", Arial, Verdana, sans-serif;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
background-color: #fff;
|
||||
}
|
||||
* {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
color: #86b514;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
font-weight: normal;
|
||||
color: #444;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
clear: left;
|
||||
margin-top: 1em;
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 0.5em;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
dt,dd {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
dt {
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
width: 5.5em;
|
||||
padding-right: 0.5em;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* LAYOUT */
|
||||
|
||||
#container {
|
||||
margin: auto;
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
#main h2, #main h3 {
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
/* FORMS */
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
input {
|
||||
font-size: 1.1em;
|
||||
padding: 2px;
|
||||
}
|
||||
button {
|
||||
padding: 2px 3px;
|
||||
margin-top: 10px;
|
||||
font-size: 1.1em;
|
||||
color: #fff;
|
||||
background: #95c229;
|
||||
border: 1px solid #76a500;
|
||||
}
|
||||
|
||||
/* FLASH NOTICES */
|
||||
|
||||
.notice {
|
||||
border: 1px solid #dedede;
|
||||
background: #eee;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
/* HEADER */
|
||||
|
||||
#account {
|
||||
float: right;
|
||||
text-align: right;
|
||||
margin-top: 5px;
|
||||
}
|
||||
#account h2 {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#header {
|
||||
border-bottom: 1px solid #dedede;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
#nav {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#nav li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
#nav a {
|
||||
color: #999;
|
||||
display: block;
|
||||
padding: 4px 7px;
|
||||
}
|
||||
|
||||
#nav a:hover {
|
||||
color: #444;
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
/* DASHBOARD */
|
||||
|
||||
#new_video {
|
||||
border: 1px solid #dedede;
|
||||
background: #f9f9f9;
|
||||
float: right;
|
||||
padding: 5px;
|
||||
width: 11em;
|
||||
text-align: center;
|
||||
margin-top: 23px;
|
||||
}
|
||||
|
||||
#new_video h2 {
|
||||
margin: 0px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.more_videos {
|
||||
margin-top: 7px;
|
||||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* VIDEOS LIST */
|
||||
|
||||
#videos {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.dashboard #videos, .more_videos {
|
||||
width: 630px;
|
||||
}
|
||||
#videos th, #videos td {
|
||||
padding: 3px 4px;
|
||||
}
|
||||
#videos th {
|
||||
background: #eee;
|
||||
color: #888;
|
||||
font-size: 0.9em;
|
||||
text-align: left;
|
||||
}
|
||||
#videos tr {
|
||||
border-bottom: 1px solid #dedede;
|
||||
}
|
||||
#videos .filename {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
}
|
||||
#videos .token {
|
||||
font-size: 0.8em;
|
||||
color: #555;
|
||||
}
|
||||
#videos .error, #videos .encoding_error {
|
||||
font-weight: bold;
|
||||
color: #f90203;
|
||||
}
|
||||
#videos .done {
|
||||
font-weight: bold;
|
||||
color: #86b514;
|
||||
}
|
||||
|
||||
/* SHOW VIDEOS */
|
||||
|
||||
.video {
|
||||
float: left;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
background: #eee;
|
||||
}
|
||||
.video .response {
|
||||
width: 320px;
|
||||
height: 160px;
|
||||
padding: 5px;
|
||||
padding-top: 80px;
|
||||
text-align: center;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.embed_code {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
/* ACCOUNT */
|
||||
|
||||
.account_details h3 {
|
||||
font-size: 1em;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.account_details .note {
|
||||
font-size: 0.9em;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
.account_details .value {
|
||||
display: inline;
|
||||
background: #f9f9f9;
|
||||
border: 1px solid #ddd;
|
||||
padding: 2px;
|
||||
}
|
||||
.edit_details {
|
||||
font-weight: bold;
|
||||
border-top: 1px solid #dedede;
|
||||
margin-top: 15px;
|
||||
padding-top: 5px;
|
||||
}
|
|
@ -0,0 +1,119 @@
|
|||
body {
|
||||
font-family: Arial, Verdana, sans-serif;
|
||||
font-size: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
* {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
text-decoration: none;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
#container {
|
||||
width: 80%;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
}
|
||||
#header-container {
|
||||
width: 100%;
|
||||
padding-top: 15px;
|
||||
}
|
||||
#header-container h1, #header-container h2 {
|
||||
margin-left: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.spacer {
|
||||
width: 100%;
|
||||
height: 15px;
|
||||
}
|
||||
hr {
|
||||
border: 0px;
|
||||
color: #ccc;
|
||||
background-color: #cdcdcd;
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: #c55;
|
||||
background-color: #fff;
|
||||
font-family: Arial, Verdana, sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
h2 {
|
||||
font-size: 15px;
|
||||
color: #999;
|
||||
font-family: Arial, Verdana, sans-serif;
|
||||
font-weight: 300;
|
||||
background-color: #fff;
|
||||
}
|
||||
h3 {
|
||||
color: #4d9b12;
|
||||
font-size: 15px;
|
||||
text-align: left;
|
||||
font-weight: 300;
|
||||
padding: 5px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
#left-container {
|
||||
float: left;
|
||||
width: 250px;
|
||||
background-color: #FFFFFF;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#left-container h3 {
|
||||
color: #c55;
|
||||
}
|
||||
|
||||
#main-container {
|
||||
margin: 5px 5px 5px 260px;
|
||||
padding: 15px;
|
||||
border-left: 1px solid silver;
|
||||
min-height: 400px;
|
||||
}
|
||||
p {
|
||||
color: #000;
|
||||
background-color: #fff;
|
||||
line-height: 20px;
|
||||
padding: 5px;
|
||||
}
|
||||
a {
|
||||
color: #4d9b12;
|
||||
background-color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
color: #4d9b12;
|
||||
background-color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
#footer-container {
|
||||
clear: both;
|
||||
font-size: 12px;
|
||||
font-family: Verdana, Arial, sans-serif;
|
||||
}
|
||||
.right {
|
||||
float: right;
|
||||
font-size: 100%;
|
||||
margin-top: 5px;
|
||||
color: #999;
|
||||
background-color: #fff;
|
||||
}
|
||||
.left {
|
||||
float: left;
|
||||
font-size: 100%;
|
||||
margin-top: 5px;
|
||||
color: #999;
|
||||
background-color: #fff;
|
||||
}
|
||||
#main-container ul {
|
||||
margin-left: 3.0em;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
class AddModelAccounts < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :accounts do |t|
|
||||
t.column :name, :string
|
||||
t.column :login, :string
|
||||
t.column :email, :string
|
||||
t.column :crypted_password, :string, :limit => 40
|
||||
t.column :salt, :string, :limit => 40
|
||||
t.column :remember_token, :string
|
||||
t.column :remember_token_expires_at, :datetime
|
||||
t.column :token, :string
|
||||
t.column :upload_redirect_url, :string
|
||||
t.column :state_update_url, :string
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :accounts
|
||||
end
|
||||
end
|
|
@ -0,0 +1,48 @@
|
|||
class AddModelVideos < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :videos do |t|
|
||||
t.column :account_id, :integer
|
||||
t.column :token, :string
|
||||
t.column :filename, :string
|
||||
|
||||
t.column :resolution, :string
|
||||
t.column :duration, :integer
|
||||
t.column :container, :string
|
||||
t.column :fps, :string
|
||||
t.column :video_codec, :string
|
||||
t.column :video_bitrate, :integer
|
||||
t.column :audio_codec, :string
|
||||
t.column :audio_sample_rate, :integer
|
||||
|
||||
t.column :status, :string # NULL or 'uploaded'
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
|
||||
create_table :encodings do |t|
|
||||
t.column :video_id, :integer
|
||||
t.column :format_id, :integer
|
||||
|
||||
t.column :duration, :integer # For free accounts we might restrict the duration of encodings
|
||||
|
||||
# Copied from Format for safe keeping
|
||||
t.column :resolution, :string
|
||||
t.column :container, :string
|
||||
t.column :fps, :string
|
||||
t.column :video_codec, :string
|
||||
t.column :video_bitrate, :integer
|
||||
t.column :audio_codec, :string
|
||||
t.column :audio_sample_rate, :integer
|
||||
|
||||
t.column :status, :string # 'encoding', 'error' or 'done'
|
||||
t.column :encoding_time, :integer # Time it took to encode the video in seconds
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :videos
|
||||
drop_table :encodings
|
||||
end
|
||||
end
|
|
@ -0,0 +1,23 @@
|
|||
class AddModelFormats < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :formats do |t|
|
||||
t.column :name, :string
|
||||
t.column :quality, :string # low, med, hi, hd
|
||||
|
||||
t.column :resolution, :string
|
||||
t.column :container, :string # flv, mp4, mov
|
||||
t.column :fps, :string
|
||||
t.column :video_codec, :string
|
||||
t.column :video_bitrate, :integer
|
||||
t.column :audio_codec, :string
|
||||
t.column :audio_sample_rate, :integer
|
||||
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :formats
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
class FormatChanges < ActiveRecord::Migration
|
||||
def self.up
|
||||
remove_column :formats, :name
|
||||
remove_column :formats, :resolution
|
||||
add_column :formats, :width, :integer
|
||||
add_column :formats, :height, :integer
|
||||
add_column :formats, :position, :integer
|
||||
add_column :formats, :format_id, :integer
|
||||
rename_table :formats, :qualities
|
||||
|
||||
remove_column :videos, :resolution
|
||||
add_column :videos, :width, :integer
|
||||
add_column :videos, :height, :integer
|
||||
|
||||
remove_column :encodings, :resolution
|
||||
add_column :encodings, :width, :integer
|
||||
add_column :encodings, :height, :integer
|
||||
|
||||
create_table :formats do |t|
|
||||
t.column :name, :string
|
||||
t.column :format, :string
|
||||
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
|
||||
rename_column :encodings, :format_id, :quality_id
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
class AddEncodingToken < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :encodings, :token, :string
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
class RenameFormats < ActiveRecord::Migration
|
||||
def self.up
|
||||
rename_column :formats, :format, :code
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
class AddModelJobs < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :jobs do |t|
|
||||
t.column :video_id, :integer
|
||||
t.column :ec2_id, :integer
|
||||
t.column :status, :string
|
||||
t.column :result, :text
|
||||
t.column :force, :boolean # Force re-encoding of already encoded files
|
||||
t.column :encoding_time, :integer
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :jobs
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
class AddModelNotifications < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :notifications do |t|
|
||||
t.column :encoding_id, :integer
|
||||
t.column :tries, :integer
|
||||
t.column :response, :string
|
||||
t.column :state, :string # success, error (contacting client)
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :jobs
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class AddModelEc2s < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :ec2s do |t|
|
||||
t.column :amazon_id, :string
|
||||
t.column :address, :string
|
||||
t.column :instance_type, :string
|
||||
t.column :started_at, :datetime
|
||||
t.column :shutdown_at, :datetime
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :jobs
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
class ChangeQualities < ActiveRecord::Migration
|
||||
def self.up
|
||||
remove_column :qualities, :video_codec
|
||||
remove_column :qualities, :audio_codec
|
||||
rename_column :qualities, :audio_sample_rate, :audio_bitrate
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
class ChangeEncodings < ActiveRecord::Migration
|
||||
def self.up
|
||||
remove_column :encodings, :video_codec
|
||||
remove_column :encodings, :audio_codec
|
||||
rename_column :encodings, :audio_sample_rate, :audio_bitrate
|
||||
end
|
||||
|
||||
def self.down
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
class AddModelInvites < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :invites do |t|
|
||||
t.column :email, :string
|
||||
t.column :approved, :datetime
|
||||
t.column :account_id, :integer
|
||||
t.column :updated_at, :datetime
|
||||
t.column :created_at, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :invites
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env ruby
|
||||
APP_ROOT = File.join(File.dirname(__FILE__), '..')
|
||||
|
||||
begin
|
||||
require 'rubigen'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require 'rubigen'
|
||||
end
|
||||
|
||||
require File.join(File.dirname(__FILE__), "..", 'config', 'boot')
|
||||
require (APP_ROOT / "config" / "merb_init" )
|
||||
module Kernel
|
||||
undef dependency if defined?(Kernel.dependency)
|
||||
end
|
||||
|
||||
# Make the App's local gems available
|
||||
Gem.clear_paths
|
||||
Gem.path.unshift(APP_ROOT / "gems")
|
||||
|
||||
require 'rubigen/scripts/destroy'
|
||||
|
||||
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
||||
|
||||
# Default is to use rspec generators in gems. To change this to
|
||||
# Test::Unit use [:merb, :test_unit]
|
||||
RubiGen::Base.use_component_sources! Merb::GENERATOR_SCOPE
|
||||
RubiGen::Scripts::Destroy.new.run(ARGV)
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env ruby
|
||||
APP_ROOT = File.join(File.dirname(__FILE__), '..')
|
||||
|
||||
begin
|
||||
require 'rubigen'
|
||||
rescue LoadError
|
||||
require 'rubygems'
|
||||
require 'rubigen'
|
||||
end
|
||||
|
||||
require File.join(File.dirname(__FILE__), "..", 'config', 'boot')
|
||||
require (APP_ROOT / "config" / "merb_init" )
|
||||
module Kernel
|
||||
undef dependency if defined?(Kernel.dependency)
|
||||
end
|
||||
|
||||
# Make the App's local gems available
|
||||
Gem.clear_paths
|
||||
Gem.path.unshift(APP_ROOT / "gems")
|
||||
|
||||
require 'rubigen/scripts/generate'
|
||||
|
||||
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
||||
|
||||
# Default is to use rspec generators in gems. To change this to
|
||||
# Test::Unit use [:merb, :test_unit]
|
||||
RubiGen::Base.use_component_sources! Merb::GENERATOR_SCOPE
|
||||
RubiGen::Scripts::Generate.new.run(ARGV)
|
|
@ -0,0 +1,13 @@
|
|||
#!/usr/bin/env ruby
|
||||
require 'fileutils'
|
||||
|
||||
pids=[]
|
||||
|
||||
port_or_star = ARGV[0] || '*'
|
||||
|
||||
Dir[File.dirname(__FILE__)+"/../log/merb.#{port_or_star}.pid"].each do |f|
|
||||
pid = IO.read(f).chomp.to_i
|
||||
puts "killing PID: #{pid}"
|
||||
Process.kill(9, pid)
|
||||
FileUtils.rm f
|
||||
end
|
|
@ -0,0 +1,103 @@
|
|||
require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
|
||||
|
||||
# describe "Videos Controller", "index action" do
|
||||
# before(:each) do
|
||||
# @controller = Videos.build(fake_request)
|
||||
# @controller.dispatch('index')
|
||||
# end
|
||||
#
|
||||
# it "should return video details in yaml" do
|
||||
#
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# describe Videos, "show action" do
|
||||
# before(:each) do
|
||||
# # @controller = Videos.build(fake_request)
|
||||
# # @controller[:params][:id] = "123"
|
||||
# # @controller.dispatch('show')
|
||||
# end
|
||||
#
|
||||
# it "should return video details in yaml" do
|
||||
# # controller.stub!(:render)
|
||||
# # puts body.to_yaml
|
||||
# # puts status
|
||||
# # puts headers
|
||||
# # puts controller.instance_variables
|
||||
# video = Video.create
|
||||
# Video.should_receive(:find_by_token).with(video.token)
|
||||
# get("/videos/#{video.token}.yaml")
|
||||
# # puts controller.inspect
|
||||
# # puts response.inspect
|
||||
# # status.should == 404
|
||||
# controller.should be_success
|
||||
# # puts @controller.methods.sort
|
||||
# end
|
||||
# end
|
||||
|
||||
describe Videos, "valid action" do
|
||||
before(:each) do
|
||||
@video = mock(Video)
|
||||
@video.stub!(:token).and_return("123")
|
||||
end
|
||||
|
||||
it "should return 200 if video is empty" do
|
||||
@video.should_receive(:empty?).and_return(true)
|
||||
Video.should_receive(:find_by_token).with("123").and_return(@video)
|
||||
get("/videos/123/valid.yaml")
|
||||
status.should == 200
|
||||
end
|
||||
|
||||
it "should return 404 if video is not empty" do
|
||||
@video.should_receive(:empty?).and_return(false)
|
||||
Video.should_receive(:find_by_token).with("123").and_return(@video)
|
||||
get("/videos/123/valid.yaml")
|
||||
status.should == 404
|
||||
end
|
||||
end
|
||||
|
||||
describe Videos, "process action" do
|
||||
before(:each) do
|
||||
@video = mock(Video)
|
||||
@video.stub!(:token).and_return("123")
|
||||
@filename = "vid.avi"
|
||||
@raw_filename = "raw.avi"
|
||||
end
|
||||
|
||||
def setup_video
|
||||
@video.should_receive(:filename=).with("vid.avi")
|
||||
@video.should_receive(:empty?).and_return(true)
|
||||
@video.should_receive(:save_metadata).with({:metadata => :here})
|
||||
@video.should_receive(:save)
|
||||
|
||||
@video.should_receive(:add_encodings)
|
||||
@video.should_receive(:add_to_queue).and_return(OpenStruct.new(:id => 999))
|
||||
end
|
||||
|
||||
it "should return 200, add video to queue and set location header" do
|
||||
setup_video
|
||||
Video.should_receive(:find_by_token).with("123").and_return(@video)
|
||||
@video.stub!(:account).and_return(OpenStruct.new(:upload_redirect_url => "http://mysite.com/videos/done"))
|
||||
|
||||
post("/videos/123/uploaded.yaml", {:filename => "vid.avi", :metadata => {:metadata => :here}.to_yaml})
|
||||
status.should == 200
|
||||
headers['Location'].should == "http://mysite.com/videos/done"
|
||||
end
|
||||
|
||||
it "should return 200, add video to queue but not set location header if account.upload_redirect_url is blank" do
|
||||
setup_video
|
||||
Video.should_receive(:find_by_token).with("123").and_return(@video)
|
||||
@video.stub!(:account).and_return(OpenStruct.new(:upload_redirect_url => ""))
|
||||
|
||||
post("/videos/123/uploaded.yaml", {:filename => "vid.avi", :metadata => {:metadata => :here}.to_yaml})
|
||||
status.should == 200
|
||||
headers['Location'].should_not == "http://mysite.com/videos/done"
|
||||
end
|
||||
|
||||
it "should return 404 if video is not empty" do
|
||||
@video.should_receive(:empty?).and_return(false)
|
||||
Video.should_receive(:find_by_token).with("123").and_return(@video)
|
||||
post("/videos/123/uploaded.yaml")
|
||||
status.should == 404
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
require File.join( File.dirname(__FILE__), "..", "spec_helper" )
|
||||
|
||||
describe Account do
|
||||
|
||||
it "should have specs"
|
||||
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
require File.join( File.dirname(__FILE__), "..", "spec_helper" )
|
||||
|
||||
describe Format do
|
||||
|
||||
it "should have specs"
|
||||
|
||||
end
|
|
@ -0,0 +1,10 @@
|
|||
require File.join( File.dirname(__FILE__), "..", "spec_helper" )
|
||||
|
||||
describe Job do
|
||||
|
||||
it "should should be next in queue if latest job" do
|
||||
video = Video.create
|
||||
job = video.add_to_queue
|
||||
Job.find_next_job.should == job
|
||||
end
|
||||
end
|
|
@ -0,0 +1,49 @@
|
|||
require File.join( File.dirname(__FILE__), "..", "spec_helper" )
|
||||
|
||||
describe Video do
|
||||
before :each do
|
||||
@video = Video.new
|
||||
end
|
||||
|
||||
it "should have token after create" do
|
||||
@video.save
|
||||
@video.token.should_not be_nil
|
||||
end
|
||||
|
||||
it "should have default status of empty after create" do
|
||||
@video.save
|
||||
@video.status.should == "empty"
|
||||
end
|
||||
|
||||
it "should return 00:00 duration string for nil duration" do
|
||||
@video.duration_str.should == "00:00"
|
||||
end
|
||||
|
||||
it "should return correct duration string" do
|
||||
@video = Video.new
|
||||
@video.duration = 5586000
|
||||
@video.duration_str.should == "93:06"
|
||||
end
|
||||
|
||||
it "should be added to the job queue" do
|
||||
@video.save
|
||||
@video.should_receive(:send_status)
|
||||
job = @video.add_to_queue
|
||||
job.status.should == "queued"
|
||||
job.video_id.should == @video.id
|
||||
end
|
||||
|
||||
it "should add encoding" do
|
||||
f = Format.create(:name => "Flash video", :code => "flv")
|
||||
quality = Quality.create(:format_id => f.id, :quality => "sd", :container => "flv", :width => 320, :height => 240, :position => 0)
|
||||
|
||||
# Video only added if width is at least that of the quality
|
||||
@video.width = 320
|
||||
@video.save
|
||||
@video.add_encoding_for_quality(quality)
|
||||
|
||||
enc = @video.encodings.first
|
||||
enc.quality_id.should == quality.id
|
||||
enc.status.should == "queued"
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
$TESTING=true
|
||||
require File.join(File.dirname(__FILE__), "..", 'config', 'boot')
|
||||
Merb.environment="test"
|
||||
require File.join(Merb.root, 'config', 'merb_init')
|
||||
|
||||
require 'merb/test/helper'
|
||||
require 'merb/test/rspec'
|
||||
|
||||
Spec::Runner.configure do |config|
|
||||
config.include(Merb::Test::Helper)
|
||||
config.include(Merb::Test::RspecMatchers)
|
||||
end
|
||||
|
||||
|
||||
### METHODS BELOW THIS LINE SHOULD BE EXTRACTED TO MERB ITSELF
|
Загрузка…
Ссылка в новой задаче