Commit 7bd6c823e810cca77805d426b2a471c3e7dcc97c

Authored by Weblate
2 parents 355657b7 e1ab2910

Merge remote-tracking branch 'origin/master'

Showing 30 changed files with 2311 additions and 36 deletions   Show diff stats
app/helpers/tags_helper.rb
@@ -67,4 +67,17 @@ module TagsHelper @@ -67,4 +67,17 @@ module TagsHelper
67 end.join("\n").html_safe 67 end.join("\n").html_safe
68 end 68 end
69 69
  70 + def linked_article_tags(article)
  71 + if @profile
  72 + # We are rendering a page inside a profile, so link to the profile tag search.
  73 + url = { :controller => 'profile', :profile => @profile.identifier, :action => 'tags' }
  74 + tagname_option = :id
  75 + else
  76 + # We are rendering a page outside a profile, so link to the global tag search.
  77 + url = { :action => 'tag' }
  78 + tagname_option = :tag
  79 + end
  80 + article.tags.map { |t| link_to(t, url.merge(tagname_option=>t.name) ) }.join("\n")
  81 + end
  82 +
70 end 83 end
app/models/uploaded_file.rb
@@ -9,6 +9,12 @@ class UploadedFile < Article @@ -9,6 +9,12 @@ class UploadedFile < Article
9 9
10 attr_accessible :uploaded_data, :title 10 attr_accessible :uploaded_data, :title
11 11
  12 + include Noosfero::Plugin::HotSpot
  13 +
  14 + def environment
  15 + profile.environment
  16 + end
  17 +
12 def self.type_name 18 def self.type_name
13 _('File') 19 _('File')
14 end 20 end
features/step_definitions/noosfero_steps.rb
@@ -118,6 +118,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f @@ -118,6 +118,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f
118 language = item.delete("language") 118 language = item.delete("language")
119 category = item.delete("category") 119 category = item.delete("category")
120 filename = item.delete("filename") 120 filename = item.delete("filename")
  121 + mime = item.delete("mime") || 'binary/octet-stream'
121 translation_of_id = nil 122 translation_of_id = nil
122 if item["translation_of"] 123 if item["translation_of"]
123 if item["translation_of"] != "nil" 124 if item["translation_of"] != "nil"
@@ -131,7 +132,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f @@ -131,7 +132,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f
131 :language => language, 132 :language => language,
132 :translation_of_id => translation_of_id) 133 :translation_of_id => translation_of_id)
133 if !filename.blank? 134 if !filename.blank?
134 - item.merge!(:uploaded_data => fixture_file_upload("/files/#{filename}", 'binary/octet-stream')) 135 + item.merge!(:uploaded_data => fixture_file_upload("/files/#{filename}", mime))
135 end 136 end
136 result = klass.new(item) 137 result = klass.new(item)
137 if !parent.blank? 138 if !parent.blank?
@@ -366,6 +367,10 @@ Then /^The page should not contain "(.*)"$/ do |selector| @@ -366,6 +367,10 @@ Then /^The page should not contain "(.*)"$/ do |selector|
366 page.should have_no_css("#{selector}") 367 page.should have_no_css("#{selector}")
367 end 368 end
368 369
  370 +Then /^The page should contain only (\d+) "(.*)"$/ do |count, selector|
  371 + page.should have_css(selector, :count => count)
  372 +end
  373 +
369 Given /^the mailbox is empty$/ do 374 Given /^the mailbox is empty$/ do
370 ActionMailer::Base.deliveries = [] 375 ActionMailer::Base.deliveries = []
371 end 376 end
@@ -653,6 +658,15 @@ Given /^the environment is configured to (.*) after signup$/ do |option| @@ -653,6 +658,15 @@ Given /^the environment is configured to (.*) after signup$/ do |option|
653 environment.save 658 environment.save
654 end 659 end
655 660
  661 +When /^I click "(.*?)"$/ do |selector|
  662 + find(selector).click
  663 +end
  664 +
  665 +Then /^the element "(.*)" has class "(.*)"$/ do |el_selector, el_class|
  666 + class_list = find(el_selector)[:class].split(' ')
  667 + class_list.should include(el_class)
  668 +end
  669 +
656 When /^wait for the captcha signup time$/ do 670 When /^wait for the captcha signup time$/ do
657 environment = Environment.default 671 environment = Environment.default
658 sleep environment.min_signup_delay + 1 672 sleep environment.min_signup_delay + 1
@@ -671,3 +685,7 @@ Given /^the field (.*) is public for all users$/ do |field| @@ -671,3 +685,7 @@ Given /^the field (.*) is public for all users$/ do |field|
671 person.save! 685 person.save!
672 end 686 end
673 end 687 end
  688 +
  689 +When(/^I press "(.*?)" by selector$/) do |selector|
  690 + page.execute_script("jQuery('#{selector}').click();")
  691 +end
lib/file_presenter.rb
@@ -124,3 +124,8 @@ end @@ -124,3 +124,8 @@ end
124 Dir.glob(File.join('app', 'presenters', '*.rb')) do |file| 124 Dir.glob(File.join('app', 'presenters', '*.rb')) do |file|
125 load file 125 load file
126 end 126 end
  127 +
  128 +# Preload FilePresenters from plugins to allow `FilePresenter.for()` to work
  129 +Dir.glob(File.join('plugins', '*', 'lib', 'presenters', '*.rb')) do |file|
  130 + load file
  131 +end
lib/noosfero/plugin/settings.rb
@@ -10,7 +10,8 @@ class Noosfero::Plugin::Settings @@ -10,7 +10,8 @@ class Noosfero::Plugin::Settings
10 end 10 end
11 11
12 def settings 12 def settings
13 - @base.settings["#{@plugin.public_name}_plugin".to_sym] ||= {} 13 + settings_field = @base.class.settings_field
  14 + @base.send(settings_field)["#{@plugin.public_name}_plugin".to_sym] ||= {}
14 end 15 end
15 16
16 def method_missing(method, *args, &block) 17 def method_missing(method, *args, &block)
plugins/html5_video/features/video_player.feature 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +Feature: video player
  2 + As a noosfero visitor
  3 + I want to view a video page and play it
  4 +
  5 + Background:
  6 + Given plugin Html5Video is enabled on environment
  7 + And the following users
  8 + | login | name |
  9 + | joaosilva | Joao Silva |
  10 + And the following uploaded files
  11 + | owner | filename | mime |
  12 + | joaosilva | ../videos/old-movie.mpg | video/mpeg |
  13 + And there are no pending jobs
  14 +
  15 + @selenium
  16 + Scenario: controls must work
  17 + Given I am on /joaosilva/old-movie.mpg?view=true
  18 + Then The page should contain only 2 ".video-player .quality li.ui-button"
  19 + When I click ".video-player .video-box .zoom"
  20 + Then the element ".video-player" has class "zoom-in"
plugins/html5_video/lib/file_presenter/video.rb
@@ -1,10 +0,0 @@ @@ -1,10 +0,0 @@
1 -class FilePresenter::Video < FilePresenter  
2 - def self.accepts?(f)  
3 - return nil if !f.respond_to?(:content_type) || f.content_type.nil?  
4 - ( f.content_type[0..4] == 'video' ) ? 10 : nil  
5 - end  
6 -  
7 - def short_description  
8 - _('Video (%s)') % content_type.split('/')[1].upcase  
9 - end  
10 -end  
plugins/html5_video/lib/html5_video_plugin.rb
1 class Html5VideoPlugin < Noosfero::Plugin 1 class Html5VideoPlugin < Noosfero::Plugin
2 2
3 - FilePresenter::Video  
4 -  
5 def self.plugin_name 3 def self.plugin_name
6 "HTML5 Video" 4 "HTML5 Video"
7 end 5 end
8 6
  7 + def stylesheet?
  8 + true
  9 + end
  10 +
  11 + def js_files
  12 + ['video-channel.js']
  13 + end
  14 +
9 def self.plugin_description 15 def self.plugin_description
10 _("A plugin to enable the video suport, with auto conversion for the web.") 16 _("A plugin to enable the video suport, with auto conversion for the web.")
11 end 17 end
12 18
  19 + def content_types
  20 + [Html5VideoPlugin::VideoChannel]
  21 + end
  22 +
  23 + def view_page_layout(controller, page)
  24 + if FilePresenter.for(page).is_a? FilePresenter::Video and controller.params[:display] == 'iframe'
  25 + 'html5_video_plugin_iframe'
  26 + end
  27 + end
  28 +
  29 + def uploaded_file_after_create_callback(uploaded_file)
  30 + full_filename = uploaded_file.full_filename
  31 + file_presenter = FilePresenter.for(uploaded_file)
  32 + if file_presenter.is_a? FilePresenter::Video
  33 + job = Html5VideoPlugin::CreateVideoPreviewJob.new
  34 + job.file_type = uploaded_file.class.name
  35 + job.file_id = uploaded_file.id
  36 + job.full_filename = full_filename
  37 + Delayed::Job.enqueue job, priority: 10
  38 + [
  39 + [:OGV, :tiny, 11],
  40 + [:WEBM, :tiny, 12],
  41 + [:OGV, :nice, 13],
  42 + [:WEBM, :nice, 14],
  43 + ].each do |format, size, priority|
  44 + job = Html5VideoPlugin::CreateVideoForWebJob.new
  45 + job.file_type = uploaded_file.class.name
  46 + job.file_id = uploaded_file.id
  47 + job.full_filename = full_filename
  48 + job.format = format
  49 + job.size = size
  50 + Delayed::Job.enqueue job, priority: priority
  51 + end
  52 + end
  53 + end
13 end 54 end
plugins/html5_video/lib/html5_video_plugin/create_video_for_web_job.rb 0 → 100644
@@ -0,0 +1,161 @@ @@ -0,0 +1,161 @@
  1 +class Html5VideoPlugin::CreateVideoForWebJob
  2 +
  3 + # TODO: we must to add timeout to ffmpeg to allow this object to set this on error:
  4 + # @video.video_info[:conversion] = :timeout
  5 + # TODO: must remove web folder when delete the video
  6 +
  7 + attr_accessor :file_type, :file_id, :full_filename, :format, :size
  8 +
  9 + def perform
  10 + return unless file_type.constantize.exists?(file_id)
  11 + @ffmpeg = Html5VideoPlugin::Ffmpeg.new
  12 +
  13 + @video = FilePresenter.for file_type.constantize.find(file_id)
  14 + throw "Expected file #{file_id} to be a video" unless @video.is_a?(FilePresenter::Video)
  15 +
  16 + @video_file = full_filename
  17 +
  18 + @info = @ffmpeg.get_video_info @video_file
  19 +
  20 + if @info[:error][:code] == 0
  21 + @video.original_video = @info.except :output
  22 + register_conv_status 'started'
  23 + @video.save!
  24 + else
  25 + register_conv_status 'error reading'
  26 + register_conv_error @info[:error]
  27 + @video.save!
  28 + Rails.logger.error "FFmpeg ERROR while reading '#{@video_file}': #{@info[:error][:message]}"
  29 + return
  30 + end
  31 +
  32 + @orig_size = @info.video_stream[0][:size]
  33 + @brate = @info.video_stream[0][:bitrate] || @info[:global_bitrate] || 400
  34 +
  35 + convert_to_tiny if size == :tiny
  36 + convert_to_nice if size == :nice
  37 + @video.save!
  38 + 1
  39 + end
  40 +
  41 + def register_conv_status( status )
  42 + @video.web_versions[format] ||= {}
  43 + @video.web_versions[format][size] ||= {}
  44 + @video.web_versions[format][size][:status] = status
  45 + @video.save!
  46 + end
  47 +
  48 + def register_conv_conf( conf )
  49 + @video.web_versions[format] ||= {}
  50 + @video.web_versions[format][size] ||= {}
  51 + @video.web_versions[format][size].merge! conf
  52 + @video.save!
  53 + end
  54 +
  55 + def register_conv_error( error )
  56 + @video.web_versions[format] ||= {}
  57 + @video.web_versions[format][size] ||= {}
  58 + @video.web_versions[format][size][:error] = error
  59 + @video.save!
  60 + end
  61 +
  62 + def is_ogv
  63 + @info[:type] == 'ogv' &&
  64 + @info.video_stream[0][:codec] == 'theora' &&
  65 + @info.audio_stream[0][:codec] == 'vorbis'
  66 + end
  67 +
  68 + def is_mp4
  69 + @info[:type] == 'mp4' &&
  70 + @info.video_stream[0][:codec] == 'libx264' &&
  71 + @info.audio_stream[0][:codec] == 'libfaac'
  72 + end
  73 +
  74 + def is_webm
  75 + @info[:type] == 'webm' &&
  76 + @info.video_stream[0][:codec] == 'libvpx'
  77 + end
  78 +
  79 + def register_conversion response
  80 + conf = response[:conf].clone
  81 + if response[:error][:code] == 0
  82 + conf[:path] = conf[:out].sub /^.*(\/articles)/, '\1'
  83 + conf.delete :in
  84 + conf.delete :out
  85 + register_conv_conf conf
  86 + register_conv_status 'done'
  87 + @video.save!
  88 + else
  89 + register_conv_status 'error converting'
  90 + register_conv_conf conf
  91 + error = response[:error].clone
  92 + error[:output] = response[:output]
  93 + register_conv_error error
  94 + Rails.logger.error "FFmpeg ERROR while converting '#{conf[:in]}' to #{conf[:type]}: #{response[:error][:message]}"
  95 + end
  96 + end
  97 +
  98 + def is_big
  99 + @orig_size[:w] > 400 || @brate >= 400
  100 + end
  101 +
  102 + def is_toobig
  103 + @orig_size[:w] > 600
  104 + end
  105 +
  106 + # The smaller version for slow connections
  107 + def convert_to_tiny
  108 + audio_stream = @video.original_video[:streams].find{|s| s[:type] == 'audio'}
  109 + abrate = audio_stream.nil? ? 64 : audio_stream[:bitrate] || 64
  110 + abrate = 64 if abrate > 64
  111 + conf = { :size_name=>'tiny', :in=>@video_file,
  112 + :fps=>12, :vbrate=>250, :abrate=>abrate }
  113 + if is_big
  114 + # Low weight video dimension for each Aspect Ratio:
  115 + # * 320x240 for 4:3
  116 + # * 320x180 for 16:9
  117 + # This are Best and Good values with the same width based on this page:
  118 + # http://www.flashsupport.com/books/fvst/files/tools/video_sizes.html
  119 + h = ( 320.0 / (@orig_size[:w].to_f/@orig_size[:h].to_f) ).round
  120 + h -= 1 if h % 2 == 1
  121 + size = { :w=>320, :h=>h }
  122 + conf[:size] = size
  123 + end
  124 + if format == :OGV && ( is_big || ! is_ogv )
  125 + conf[:file_name] = 'tiny.ogv'
  126 + register_conversion @ffmpeg.make_ogv_for_web(conf)
  127 + end
  128 + if format == :WEBM && ( is_big || ! is_webm )
  129 + conf[:file_name] = 'tiny.webm'
  130 + register_conversion @ffmpeg.make_webm_for_web(conf)
  131 + end
  132 + end
  133 +
  134 + # The nicer common version
  135 + def convert_to_nice
  136 + if is_toobig
  137 + # Max video dimension for each Aspect Ratio:
  138 + # * 576x432 for 4:3
  139 + # * 576x324 for 16:9
  140 + # This are Best and Good values with the same width based on this page:
  141 + # http://www.flashsupport.com/books/fvst/files/tools/video_sizes.html
  142 + # Width 640 has Better result to 16:9, but that will also make bigger
  143 + # file weight.
  144 + h = ( 576.0 / (@orig_size[:w].to_f/@orig_size[:h].to_f) ).round
  145 + size = { :w=>576, :h=>h.to_i }
  146 + conf = { :in=>@video_file, :size=>size, :vbrate=>@brate }
  147 + else
  148 + conf = { :in=>@video_file, :vbrate=>@brate }
  149 + end
  150 + conf[:size_name] = 'nice'
  151 + if format == :OGV && ( is_toobig || ! is_ogv )
  152 + conf[:file_name] = 'nice.ogv'
  153 + register_conversion @ffmpeg.make_ogv_for_web(conf)
  154 + end
  155 + if format == :WEBM && ( is_toobig || ! is_webm )
  156 + conf[:file_name] = 'nice.webm'
  157 + register_conversion @ffmpeg.make_webm_for_web(conf)
  158 + end
  159 + end
  160 +
  161 +end
plugins/html5_video/lib/html5_video_plugin/create_video_preview_job.rb 0 → 100644
@@ -0,0 +1,27 @@ @@ -0,0 +1,27 @@
  1 +class Html5VideoPlugin::CreateVideoPreviewJob
  2 +
  3 + attr_accessor :file_type, :file_id, :full_filename
  4 +
  5 + def perform
  6 + return unless file_type.constantize.exists?(file_id)
  7 + ffmpeg = Html5VideoPlugin::Ffmpeg.new
  8 +
  9 + video = FilePresenter.for file_type.constantize.find(file_id)
  10 + throw "Expected file #{file_id} to be a video" unless video.is_a? FilePresenter::Video
  11 +
  12 + video_file = full_filename
  13 +
  14 + response = ffmpeg.video_thumbnail(video_file)
  15 +
  16 + if response.kind_of?(Hash) && response[:error] && response[:error][:code] != 0
  17 + video.previews = :fail
  18 + video.save!
  19 + Rails.logger.error "ERROR while generating '#{video_file}' image preview: #{response[:error][:message]}"
  20 + return
  21 + end
  22 +
  23 + video.previews = response
  24 + video.save!
  25 + end
  26 +
  27 +end
plugins/html5_video/lib/html5_video_plugin/ffmpeg.rb 0 → 100644
@@ -0,0 +1,354 @@ @@ -0,0 +1,354 @@
  1 +# Works for ffmpeg version 2.8.6-1~bpo8 shiped by Debian Jessie Backports
  2 +# https://packages.debian.org/jessie-backports/ffmpeg
  3 +# Add this line to your /etc/apt/sources.list:
  4 +# deb http://http.debian.net/debian jessie-backports main
  5 +# then: aptitude install ffmpeg
  6 +class Html5VideoPlugin::Ffmpeg
  7 +
  8 + def run(*parameters)
  9 + parameters = parameters.flatten
  10 + cmd = ['ffmpeg'] + parameters.map do |p|
  11 + p.kind_of?(Symbol) ? '-'+p.to_s : p.to_s
  12 + end
  13 + io = IO.popen({'LANG'=>'C.UTF-8'}, cmd, err: [:child, :out])
  14 + output = io.read
  15 + io.close
  16 + response = {
  17 + error: { :code => 0, :message => '' },
  18 + parameters: parameters,
  19 + output: output
  20 + }
  21 + if $?.exitstatus != 0 then
  22 + if $?.exitstatus == 127 then
  23 + throw 'There is no FFmpeg installed!'
  24 + end
  25 + response[:error][:code] = -1
  26 + response[:error][:message] = _('Unknow error')
  27 + if match = /\n\s*([^\n]*): No such file or directory\s*\n/i.match(output)
  28 + response[:error][:code] = 1
  29 + response[:error][:message] = _('No such file or directory "%s".') % match[1]
  30 + elsif output =~ /At least one output file must be specified/i
  31 + response[:error][:code] = 2
  32 + response[:error][:message] = _('No output defined.')
  33 + elsif match = /\n\s*Unknown encoder[\s']+([^\s']+)/i.match(output)
  34 + response[:error][:code] = 3
  35 + response[:error][:message] = _('Unknown encoder "%s".') % match[1]
  36 + elsif output =~ /\n\s*Error while opening encoder for output/i
  37 + response[:error][:code] = 4
  38 + response[:error][:message] = _('Error while opening encoder for output - maybe incorrect parameters such as bit rate, frame rate, width or height.')
  39 + elsif match = /\n\s*Could not open '([^\s']+)/i.match(output)
  40 + response[:error][:code] = 5
  41 + response[:error][:message] = _('Could not open "%s".') % match[1]
  42 + elsif match = /\n\s*Unsupported codec (.*) for (.*) stream (.*)/i.match(output)
  43 + response[:error][:code] = 6
  44 + response[:error][:message] = _('Unsupported codec %{codec} for %{act} stream %{id}.') %
  45 + { :codec=>match[1], :act=>match[2], :id=>match[3] }
  46 + elsif output =~ /Unable to find a suitable output format/i
  47 + response[:error][:code] = 7
  48 + response[:error][:message] = _('Unable to find a suitable output format for %{file}.') %
  49 + { :file=>parameters[-1] }
  50 + elsif output =~ /Invalid data found when processing input/i
  51 + response[:error][:code] = 8
  52 + response[:error][:message] = _('Invalid data found when processing input.')
  53 + end
  54 + end
  55 + return response
  56 + end
  57 +
  58 + def register_information
  59 + response = self.run(:formats)[:output]
  60 + @@version = /^\s*FFmpeg version ([0-9.]+)/i.match(response)[1]
  61 + @@formats = {}
  62 + response.split('--')[-1].strip.split("\n").each do |line|
  63 + if pieces = / (.)(.) ([^\s]+)\s+([^\s].*)/.match(line)
  64 + @@formats[pieces[3].to_sym] = {
  65 + demux: ( pieces[1] == 'D' ),
  66 + mux: ( pieces[2] == 'E' ),
  67 + description: pieces[4].strip
  68 + }
  69 + end
  70 + end
  71 + response = self.run(:codecs)[:output]
  72 + @@codecs = {}
  73 + response.split('--')[-1].strip.split("\n").each do |line|
  74 + if pieces = / (.)(.)(.)(.)(.)(.) ([^\s]+)\s+([^\s].*)/.match(line)
  75 + @@codecs[pieces[7].to_sym] = {
  76 + decode: ( pieces[1] == 'D' ),
  77 + encode: ( pieces[2] == 'E' ),
  78 + draw_horiz_band: ( pieces[4] == 'S' ),
  79 + direct_rendering: ( pieces[5] == 'D' ),
  80 + wf_trunc: ( pieces[6] == 'T' ),
  81 + type: (
  82 + if pieces[3] == 'V'; :video
  83 + elsif pieces[3] == 'A'; :audio
  84 + elsif pieces[3] == 'S'; :subtitle
  85 + else :unknown; end
  86 + ),
  87 + description: pieces[8].strip
  88 + }
  89 + end
  90 + end
  91 + {
  92 + version: @@version,
  93 + formats: @@formats,
  94 + codecs: @@codecs
  95 + }
  96 + end
  97 +
  98 + def timestr_to_secs str
  99 + return nil if ! /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/.match str
  100 + t = str.split(':').map(&:to_i)
  101 + t[0]*60*60 + t[1]*60 + t[2]
  102 + end
  103 +
  104 + def get_stream_info(stream_str)
  105 + stream_info = { :type => 'undefined' }
  106 + stream_info[:type] = 'video' if / Video:/.match(stream_str)
  107 + stream_info[:type] = 'audio' if / Audio:/.match(stream_str)
  108 + {
  109 + id: [ /^\s*Stream ([^:(]+)/ ],
  110 + codec: [ / (?:Audio|Video):\s*([^,\s]+)/x ],
  111 + bitrate: [ / ([0-9]+) kb\/s\b/ , :to_i ],
  112 + frequency: [ / ([0-9]+) Hz\b/ , :to_i ],
  113 + channels: [ / ([0-9]+) channels\b/ , :to_i ],
  114 + framerate: [ / ([0-9.]+) (fps|tbr)\b/ , :to_f ],
  115 + size: [ / ([0-9]+x[0-9]+)[, ]/ ],
  116 + }.each do |att, params|
  117 + re = params[0]
  118 + method = params[1] || :strip
  119 + if match = re.match(stream_str) then
  120 + stream_info[att] = match[1].send(method)
  121 + end
  122 + end
  123 + if stream_info[:size]
  124 + size_array = stream_info[:size].split('x').map{|s| s.to_i}
  125 + stream_info[:size] = { :w=>size_array[0], :h=>size_array[1] }
  126 + end
  127 + stream_info
  128 + end
  129 +
  130 + def webdir_for_original_video(video)
  131 + webdir = File.dirname(video) +'/web'
  132 + Dir.mkdir(webdir) if ! File.exist?(webdir)
  133 + webdir
  134 + end
  135 +
  136 + def valid_abrate_for_web(info)
  137 + if brate = info.audio_stream[0][:bitrate]
  138 + brate = 8 if brate < 8
  139 + (brate>128)? 128 : brate
  140 + else
  141 + 48
  142 + end
  143 + end
  144 +
  145 + def valid_vbrate_for_web(info)
  146 + if brate = info.video_stream[0][:bitrate] || info[:global_bitrate]
  147 + brate = 128 if brate < 128
  148 + (brate>1024)? 1024 : brate
  149 + else
  150 + 400
  151 + end
  152 + end
  153 +
  154 + def valid_size_for_web(info)
  155 + orig_size = info.video_stream[0][:size]
  156 + if info.video_stream[0][:size][:w] > 640
  157 + h = 640.0 / (orig_size[:w].to_f/orig_size[:h].to_f)
  158 + { :w=>640, :h=>h.to_i }
  159 + else
  160 + { :w=>orig_size[:w].to_i, :h=>orig_size[:h].to_i }
  161 + end
  162 + end
  163 +
  164 + def validate_conversion_conf_for_web(conf, file_type)
  165 + conf = conf.clone
  166 + if conf[:abrate].nil? || conf[:vbrate].nil? || conf[:size].nil?
  167 + info = get_video_info(conf[:in])
  168 + if info[:error][:code] == 0
  169 + conf[:abrate] ||= valid_abrate_for_web info
  170 + conf[:vbrate] ||= valid_vbrate_for_web info
  171 + conf[:size] ||= valid_size_for_web info
  172 + end
  173 + end
  174 + result_dir = webdir_for_original_video conf[:in]
  175 + size_str = "#{conf[:size][:w]}x#{conf[:size][:h]}"
  176 + unless conf[:file_name]
  177 + conf[:file_name] = "#{size_str}_#{conf[:vbrate]}.#{file_type}"
  178 + end
  179 + conf[:out] = result_dir+'/'+conf[:file_name]
  180 + conf
  181 + end
  182 +
  183 + public
  184 +
  185 + def get_video_info(file)
  186 + response = self.run :i, file
  187 + if response[:error][:code] == 2
  188 + # No output is not an error on this context
  189 + response[:error][:code] = 0
  190 + response[:error][:message] = _('Success.')
  191 + end
  192 + response[:metadata] = {}
  193 + {
  194 + author: /\n\s*author[\t ]*:([^\n]*)\n/i,
  195 + title: /\n\s*title[\t ]*:([^\n]*)\n/i,
  196 + comment: /\n\s*comment[\t ]*:([^\n]*)\n/i
  197 + }.each do |att, re|
  198 + if match = re.match(response[:output]) then
  199 + response[:metadata][att] = match[1].strip
  200 + end
  201 + end
  202 + {
  203 + type: /\nInput #0, ([a-z0-9]+), from/,
  204 + duration: /\n\s*Duration:[\t ]*([0-9:]+)[^\n]* bitrate:/,
  205 + global_bitrate: /\n\s*Duration:[^\n]* bitrate:[\t ]*([0-9]+)/
  206 + }.each do |att, re|
  207 + if match = re.match(response[:output]) then
  208 + response[att] = match[1].strip
  209 + end
  210 + end
  211 + response[:duration] = timestr_to_secs response[:duration]
  212 + response[:global_bitrate] = response[:global_bitrate].to_i
  213 + response[:streams] = []
  214 + response[:output].split("\n").grep(/^\s*Stream /).each do |stream|
  215 + response[:streams] << get_stream_info(stream)
  216 + end
  217 + def response.video_stream
  218 + self[:streams].select {|s| s[:type] == 'video' }
  219 + end
  220 + def response.audio_stream
  221 + self[:streams].select {|s| s[:type] == 'audio' }
  222 + end
  223 + return response
  224 + end
  225 +
  226 + def convert2ogv(conf)
  227 + conf[:type] = :OGV
  228 + conf[:vbrate] ||= 600
  229 + parameters = [ :i, conf[:in], :y, :'b:v', "#{conf[:vbrate]}k",
  230 + :f, 'ogg', :acodec, 'libvorbis', :vcodec, 'libtheora'
  231 + ]
  232 + parameters << :s << "#{conf[:size][:w]}x#{conf[:size][:h]}" if conf[:size]
  233 + parameters << :'b:a' << "#{conf[:abrate]}k" if conf[:abrate]
  234 + # Vorbis dá pau com -ar 8000Hz ???
  235 + parameters << :r << conf[:fps] if conf[:fps]
  236 + parameters << conf[:out]
  237 + response = self.run parameters
  238 + response[:conf] = conf
  239 + response
  240 + end
  241 +
  242 + def convert2mp4(conf)
  243 + conf[:type] = :MP4
  244 + conf[:vbrate] ||= 600
  245 + parameters = [ :i, conf[:in], :y, :'b:v', "#{conf[:vbrate]}k",
  246 + :preset, 'slow', :f, 'mp4', :acodec, 'aac', :vcodec, 'libx264',
  247 + :strict, '-2'
  248 + ]
  249 + parameters << :s << "#{conf[:size][:w]}x#{conf[:size][:h]}" if conf[:size]
  250 + parameters << :'b:a' << "#{conf[:abrate]}k" if conf[:abrate]
  251 + parameters << :r << conf[:fps] if conf[:fps]
  252 + parameters << conf[:out]
  253 + response = self.run parameters
  254 + response[:conf] = conf
  255 + response
  256 + end
  257 +
  258 + def convert2webm(conf)
  259 + conf[:type] = :WEBM
  260 + conf[:vbrate] ||= 600
  261 + parameters = [ :i, conf[:in], :y, :'b:v', "#{conf[:vbrate]}k",
  262 + :f, 'webm', :acodec, 'libvorbis', :vcodec, 'libvpx'
  263 + ]
  264 + parameters << :s << "#{conf[:size][:w]}x#{conf[:size][:h]}" if conf[:size]
  265 + parameters << :'b:a' << "#{conf[:abrate]}k" if conf[:abrate]
  266 + parameters << :r << conf[:fps] if conf[:fps]
  267 + parameters << conf[:out]
  268 + response = self.run parameters
  269 + response[:conf] = conf
  270 + response
  271 + end
  272 +
  273 + def make_ogv_for_web(conf)
  274 + conf = validate_conversion_conf_for_web conf, :ogv
  275 + convert2ogv(conf)
  276 + end
  277 +
  278 + def make_mp4_for_web(conf)
  279 + conf = validate_conversion_conf_for_web conf, :mp4
  280 + convert2mp4(conf)
  281 + end
  282 +
  283 + def make_webm_for_web(conf)
  284 + conf = validate_conversion_conf_for_web conf, :webm
  285 + convert2webm(conf)
  286 + end
  287 +
  288 + # video_thumbnail creates 2 preview images on the sub directory web
  289 + # from the video file parent dir. This preview images are six concatenated
  290 + # frames in one image each. The frames have fixed dimension. The bigger
  291 + # preview has frames with in 160x120, and smaller has frames whit in 107x80.
  292 + # Use this helper only on the original movie to have only one "web" sub-dir.
  293 + def video_thumbnail(video)
  294 + result_dir = webdir_for_original_video video
  295 + info = get_video_info(video)
  296 + if info[:duration] < 15
  297 + pos = 1
  298 + duration = info[:duration] - 2
  299 + frate = ( 7.0 / duration ).ceil
  300 + else
  301 + pos = ( info[:duration] / 2.5 ).ceil
  302 + duration = 7
  303 + frate = 1
  304 + end
  305 + response = self.run :i, video, :ss, pos, :t, duration, :r, frate,
  306 + :s, '320x240', result_dir+'/f%d.png'
  307 + img_names = [ '/preview_160x120.jpg', '/preview_107x80.jpg' ]
  308 + if response[:error][:code] == 0
  309 + imgs = (2..7).map { |num|
  310 + img = result_dir+"/f#{num}.png"
  311 + File.exists?(img) ? img : nil
  312 + }.compact
  313 + if imgs.size != 6
  314 + Rails.logger.error "Problem to create thumbs for video #{video} ???"
  315 + end
  316 + imgs = Magick::ImageList.new *imgs
  317 + imgs.montage{
  318 + self.geometry='160x120+0+0'
  319 + self.tile="1x#{imgs.size}"
  320 + self.frame = "0x0+0+0"
  321 + }.write result_dir+img_names[0]
  322 + imgs.montage{
  323 + self.geometry='107x80+0+0'
  324 + self.tile="1x#{imgs.size}"
  325 + self.frame = "0x0+0+0"
  326 + }.write result_dir+img_names[1]
  327 + end
  328 +
  329 + f_num = 1
  330 + while File.exists? result_dir+"/f#{f_num}.png" do
  331 + File.delete result_dir+"/f#{f_num}.png"
  332 + f_num += 1
  333 + end
  334 +
  335 + if response[:error][:code] == 0
  336 + return { big: '/web'+img_names[0], thumb: '/web'+img_names[1] }
  337 + else
  338 + return response
  339 + end
  340 + end
  341 +
  342 + def version
  343 + @@version ||= register_information[:version]
  344 + end
  345 +
  346 + def formats
  347 + @@formats ||= register_information[:formats]
  348 + end
  349 +
  350 + def codecs
  351 + @@codecs ||= register_information[:codecs]
  352 + end
  353 +
  354 +end
plugins/html5_video/lib/html5_video_plugin/video_channel.rb 0 → 100644
@@ -0,0 +1,30 @@ @@ -0,0 +1,30 @@
  1 +class Html5VideoPlugin::VideoChannel < Folder
  2 +
  3 + def self.short_description
  4 + _('Video Channel')
  5 + end
  6 +
  7 + def self.description
  8 + _('A video channel, where you can make your own web TV.')
  9 + end
  10 +
  11 + include ActionView::Helpers::TagHelper
  12 + def to_html(options={})
  13 + article = self
  14 + lambda do
  15 + render :file => 'content_viewer/video_channel', :locals => {:article => article}
  16 + end
  17 + end
  18 +
  19 + def video_channel?
  20 + true
  21 + end
  22 +
  23 + def self.icon_name(article = nil)
  24 + 'videochannel'
  25 + end
  26 +
  27 + def accept_article?
  28 + false
  29 + end
  30 +end
plugins/html5_video/lib/presenters/video.rb 0 → 100644
@@ -0,0 +1,145 @@ @@ -0,0 +1,145 @@
  1 +class FilePresenter::Video < FilePresenter
  2 +
  3 + def self.accepts?(f)
  4 + return nil if !f.respond_to?(:content_type) || f.content_type.nil?
  5 + ( f.content_type[0..4] == 'video' ) ? 10 : nil
  6 + end
  7 +
  8 + def short_description
  9 + _('Video (%s)') % content_type.split('/')[1].upcase
  10 + end
  11 +
  12 + def meta_data #video_info
  13 + Noosfero::Plugin::Settings.new(encapsulated_file, Html5VideoPlugin)
  14 + end
  15 +
  16 + def original_video
  17 + meta_data.original_video ||= {}
  18 + end
  19 +
  20 + def original_video=(hash)
  21 + meta_data.original_video = hash
  22 + end
  23 +
  24 + def web_versions
  25 + meta_data.web_versions ||= {}
  26 + end
  27 +
  28 + def web_versions=(hash)
  29 + meta_data.web_versions = hash
  30 + end
  31 +
  32 + # adds the orig version tho the web_versions if that is a valid to HTML5
  33 + def web_versions!
  34 + list = web_versions.clone
  35 + streams = original_video.empty? ? [] : original_video[:streams]
  36 + video_stream = streams.find{|s| s[:type] == 'video' }
  37 + audio_stream = streams.find{|s| s[:type] == 'audio' }
  38 + return list unless video_stream && audio_stream
  39 + type = original_video[:type].to_s.upcase.to_sym
  40 + type = :OGV if video_stream[:codec]=='theora' && original_video[:type]=='ogg'
  41 + if [:OGV, :MP4, :WEBM].include? type
  42 + vb = video_stream[:bitrate] || original_video[:global_bitrate] || 0
  43 + ab = audio_stream[:bitrate] || 0
  44 + info = {
  45 + :original => true,
  46 + :file_name => File.basename(public_filename),
  47 + :abrate => ab,
  48 + :vbrate => vb,
  49 + :size => video_stream[:size],
  50 + :size_name => 'orig',
  51 + :status => 'done',
  52 + :type => type,
  53 + :path => public_filename
  54 + }
  55 + list[type][:orig] = info
  56 + end
  57 + list
  58 + end
  59 +
  60 + def ready_web_versions
  61 + ready = {}
  62 + web_versions!.select do |type, type_block|
  63 + ready[type] = {}
  64 + type_block.select do |size, size_block|
  65 + ready[type][size] = size_block if size_block[:status] == 'done'
  66 + end
  67 + end
  68 + ready
  69 + end
  70 +
  71 + def has_ogv_version
  72 + not ready_web_versions[:OGV].blank?
  73 + end
  74 +
  75 + def has_mp4_version
  76 + not ready_web_versions[:MP4].blank?
  77 + end
  78 +
  79 + def has_webm_version
  80 + not ready_web_versions[:WEBM].blank?
  81 + end
  82 +
  83 + def has_web_version
  84 + ready = ready_web_versions
  85 + not (ready[:OGV].blank? and ready[:MP4].blank? and ready[:WEBM].blank?)
  86 + end
  87 +
  88 + def tiniest_web_version( type )
  89 + return nil if ready_web_versions[type].nil?
  90 + video = ready_web_versions[type].
  91 + select{|size,data| data[:status] == 'done' }.
  92 + sort_by{|v| v[1][:vbrate] }.first
  93 + video ? video[1] : nil
  94 + end
  95 +
  96 + #TODO: add this to the user interface:
  97 + def web_version_jobs
  98 + #FIXME: in a newer version, the Delayed::Job may be searcheable in a uglyless way.
  99 + Delayed::Job.where("handler LIKE '%CreateVideoForWebJob%file_id: #{self.id}%'").all
  100 + #Delayed::Job.all :conditions => ['handler LIKE ?',
  101 + # "%CreateVideoForWebJob%file_id: #{self.id}%"]
  102 + end
  103 +
  104 + def web_preview_jobs
  105 + #FIXME: in a newer version, the Delayed::Job may be searcheable in a uglyless way.
  106 + Delayed::Job.where("handler LIKE '%CreateVideoPreviewJob%file_id: #{self.id}%'").all
  107 + #Delayed::Job.all :conditions => ['handler LIKE ?',
  108 + # "%CreateVideoPreviewJob%file_id: #{self.id}%"]
  109 + end
  110 +
  111 + def has_previews?
  112 + not(previews.nil?) && previews.kind_of?(Hash) && !previews.empty?
  113 + end
  114 +
  115 + def previews
  116 + meta_data.image_previews
  117 + end
  118 +
  119 + def previews=(hash)
  120 + meta_data.image_previews = hash
  121 + end
  122 +
  123 + def image_preview(size=nil)
  124 + if has_previews? && previews[size]
  125 + File.dirname( public_filename ) + previews[size]
  126 + else
  127 + "/plugins/html5_video/images/video-preview-#{size}.png"
  128 + end
  129 + end
  130 +
  131 + def conversion_errors
  132 + errors = {}
  133 + web_versions!.select do |type, type_block|
  134 + type_block.select do |size, conv_info|
  135 + if conv_info[:status] == 'error converting'
  136 + errors[type] ||= {}
  137 + err_base = {:message=>_('Undefined'), :code=>-2, :output=>'undefined'}
  138 + errors[type][size] = err_base.merge( conv_info[:error] || {} )
  139 + end
  140 + end
  141 + end
  142 + errors
  143 + end
  144 +
  145 +end
plugins/html5_video/po/html5_video.pot
@@ -6,9 +6,10 @@ @@ -6,9 +6,10 @@
6 #, fuzzy 6 #, fuzzy
7 msgid "" 7 msgid ""
8 msgstr "" 8 msgstr ""
9 -"Project-Id-Version: 1.3~rc2-1-ga15645d\n"  
10 -"POT-Creation-Date: 2015-10-30 16:35-0300\n"  
11 -"PO-Revision-Date: 2015-08-06 17:21-0300\n" 9 +"Project-Id-Version: PACKAGE VERSION\n"
  10 +"Report-Msgid-Bugs-To: \n"
  11 +"POT-Creation-Date: 2016-08-19 19:54+0000\n"
  12 +"PO-Revision-Date: 2016-08-19 19:54+0000\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" 13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n" 14 "Language-Team: LANGUAGE <LL@li.org>\n"
14 "Language: \n" 15 "Language: \n"
@@ -17,10 +18,128 @@ msgstr &quot;&quot; @@ -17,10 +18,128 @@ msgstr &quot;&quot;
17 "Content-Transfer-Encoding: 8bit\n" 18 "Content-Transfer-Encoding: 8bit\n"
18 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" 19 "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
19 20
20 -#: plugins/html5_video/lib/file_presenter/video.rb:8 21 +#: ../lib/html5_video_plugin.rb:16
  22 +msgid "A plugin to enable the video suport, with auto conversion for the web."
  23 +msgstr ""
  24 +
  25 +#: ../lib/html5_video_plugin/ffmpeg.rb:26
  26 +msgid "Unknow error"
  27 +msgstr ""
  28 +
  29 +#: ../lib/html5_video_plugin/ffmpeg.rb:29
  30 +msgid "No such file or directory \"%s\"."
  31 +msgstr ""
  32 +
  33 +#: ../lib/html5_video_plugin/ffmpeg.rb:32
  34 +msgid "No output defined."
  35 +msgstr ""
  36 +
  37 +#: ../lib/html5_video_plugin/ffmpeg.rb:35
  38 +msgid "Unknown encoder \"%s\"."
  39 +msgstr ""
  40 +
  41 +#: ../lib/html5_video_plugin/ffmpeg.rb:38
  42 +msgid ""
  43 +"Error while opening encoder for output - maybe incorrect parameters such as bi"
  44 +"t rate, frame rate, width or height."
  45 +msgstr ""
  46 +
  47 +#: ../lib/html5_video_plugin/ffmpeg.rb:41
  48 +msgid "Could not open \"%s\"."
  49 +msgstr ""
  50 +
  51 +#: ../lib/html5_video_plugin/ffmpeg.rb:44
  52 +msgid "Unsupported codec %{codec} for %{act} stream %{id}."
  53 +msgstr ""
  54 +
  55 +#: ../lib/html5_video_plugin/ffmpeg.rb:48
  56 +msgid "Unable to find a suitable output format for %{file}."
  57 +msgstr ""
  58 +
  59 +#: ../lib/html5_video_plugin/ffmpeg.rb:52
  60 +msgid "Invalid data found when processing input."
  61 +msgstr ""
  62 +
  63 +#: ../lib/html5_video_plugin/ffmpeg.rb:190
  64 +msgid "Success."
  65 +msgstr ""
  66 +
  67 +#: ../lib/html5_video_plugin/video_channel.rb:4
  68 +msgid "Video Channel"
  69 +msgstr ""
  70 +
  71 +#: ../lib/html5_video_plugin/video_channel.rb:8
  72 +msgid "A video channel, where you can make your own web TV."
  73 +msgstr ""
  74 +
  75 +#: ../lib/presenters/video.rb:9
21 msgid "Video (%s)" 76 msgid "Video (%s)"
22 msgstr "" 77 msgstr ""
23 78
24 -#: plugins/html5_video/lib/html5_video_plugin.rb:10  
25 -msgid "A plugin to enable the video suport, with auto conversion for the web." 79 +#: ../lib/presenters/video.rb:137
  80 +msgid "Undefined"
  81 +msgstr ""
  82 +
  83 +#: ../views/content_viewer/_video_player.html.erb:21
  84 +msgid "Sorry, your browser doesn&rsquo;t support video."
  85 +msgstr ""
  86 +
  87 +#: ../views/content_viewer/_video_player.html.erb:22
  88 +msgid "Please try the new %s or %s."
  89 +msgstr ""
  90 +
  91 +#: ../views/content_viewer/_video_player.html.erb:27
  92 +msgid "Download"
  93 +msgstr ""
  94 +
  95 +#: ../views/content_viewer/video_channel.html.erb:7
  96 +msgid "This channel contains no videos yet"
  97 +msgstr ""
  98 +
  99 +#: ../views/content_viewer/video_channel.html.erb:14
  100 +#: ../views/file_presenter/_video.html.erb:15
  101 +msgid "Quality options"
  102 +msgstr ""
  103 +
  104 +#: ../views/content_viewer/video_channel.html.erb:18
  105 +msgid "Tags"
  106 +msgstr ""
  107 +
  108 +#: ../views/content_viewer/video_channel.html.erb:22
  109 +#: ../views/file_presenter/_video.html.erb:17
  110 +msgid "Description"
  111 +msgstr ""
  112 +
  113 +#: ../views/content_viewer/video_channel.html.erb:66
  114 +msgid "This channel has one video waiting to be converted"
  115 +msgid_plural "This channel has %d videos waiting to be converted"
  116 +msgstr[0] ""
  117 +msgstr[1] ""
  118 +
  119 +#: ../views/content_viewer/video_channel.html.erb:83
  120 +msgid "Non video files"
  121 +msgstr ""
  122 +
  123 +#: ../views/file_presenter/_video.html.erb:7
  124 +msgid "Queued to generate the web version. Come back soon."
  125 +msgstr ""
  126 +
  127 +#: ../views/file_presenter/_video.html.erb:11
  128 +msgid "This video is not queued to the video conversor. Contact the site admin."
  129 +msgstr ""
  130 +
  131 +#: ../views/file_presenter/_video.html.erb:34
  132 +msgid "Video conversion errors"
  133 +msgstr ""
  134 +
  135 +#: ../views/file_presenter/_video.html.erb:44
  136 +msgid "Error while converting %{orig_type} to %{new_type}, %{size} size."
  137 +msgstr ""
  138 +
  139 +#: ../views/file_presenter/_video.html.erb:47
  140 +msgid "Code %s"
  141 +msgstr ""
  142 +
  143 +#: ../views/file_presenter/_video.html.erb:49
  144 +msgid "display full output"
26 msgstr "" 145 msgstr ""
plugins/html5_video/po/pt/html5_video.po
@@ -12,11 +12,11 @@ @@ -12,11 +12,11 @@
12 msgid "" 12 msgid ""
13 msgstr "" 13 msgstr ""
14 "Project-Id-Version: 1.3~rc2-1-ga15645d\n" 14 "Project-Id-Version: 1.3~rc2-1-ga15645d\n"
15 -"POT-Creation-Date: 2015-10-30 16:35-0300\n" 15 +"POT-Creation-Date: 2016-08-19 19:54+0000\n"
16 "PO-Revision-Date: 2014-12-18 18:40-0200\n" 16 "PO-Revision-Date: 2014-12-18 18:40-0200\n"
17 "Last-Translator: Luciano Prestes Cavalcanti <lucianopcbr@gmail.com>\n" 17 "Last-Translator: Luciano Prestes Cavalcanti <lucianopcbr@gmail.com>\n"
18 -"Language-Team: Portuguese <https://hosted.weblate.org/projects/noosfero/"  
19 -"noosfero/pt/>\n" 18 +"Language-Team: Portuguese <https://hosted.weblate.org/projects/noosfero/noosfe"
  19 +"ro/pt/>\n"
20 "Language: pt\n" 20 "Language: pt\n"
21 "MIME-Version: 1.0\n" 21 "MIME-Version: 1.0\n"
22 "Content-Type: text/plain; charset=UTF-8\n" 22 "Content-Type: text/plain; charset=UTF-8\n"
@@ -24,11 +24,128 @@ msgstr &quot;&quot; @@ -24,11 +24,128 @@ msgstr &quot;&quot;
24 "Plural-Forms: nplurals=2; plural=n != 1;\n" 24 "Plural-Forms: nplurals=2; plural=n != 1;\n"
25 "X-Generator: Weblate 2.0\n" 25 "X-Generator: Weblate 2.0\n"
26 26
27 -#: plugins/html5_video/lib/file_presenter/video.rb:8 27 +#: ../lib/html5_video_plugin.rb:16
  28 +msgid "A plugin to enable the video suport, with auto conversion for the web."
  29 +msgstr "Um plugin para habilitar suporte de video, com conversão de audio para a web."
  30 +
  31 +#: ../lib/html5_video_plugin/ffmpeg.rb:26
  32 +msgid "Unknow error"
  33 +msgstr ""
  34 +
  35 +#: ../lib/html5_video_plugin/ffmpeg.rb:29
  36 +msgid "No such file or directory \"%s\"."
  37 +msgstr ""
  38 +
  39 +#: ../lib/html5_video_plugin/ffmpeg.rb:32
  40 +msgid "No output defined."
  41 +msgstr ""
  42 +
  43 +#: ../lib/html5_video_plugin/ffmpeg.rb:35
  44 +msgid "Unknown encoder \"%s\"."
  45 +msgstr ""
  46 +
  47 +#: ../lib/html5_video_plugin/ffmpeg.rb:38
  48 +msgid ""
  49 +"Error while opening encoder for output - maybe incorrect parameters such as bi"
  50 +"t rate, frame rate, width or height."
  51 +msgstr ""
  52 +
  53 +#: ../lib/html5_video_plugin/ffmpeg.rb:41
  54 +msgid "Could not open \"%s\"."
  55 +msgstr ""
  56 +
  57 +#: ../lib/html5_video_plugin/ffmpeg.rb:44
  58 +msgid "Unsupported codec %{codec} for %{act} stream %{id}."
  59 +msgstr ""
  60 +
  61 +#: ../lib/html5_video_plugin/ffmpeg.rb:48
  62 +msgid "Unable to find a suitable output format for %{file}."
  63 +msgstr ""
  64 +
  65 +#: ../lib/html5_video_plugin/ffmpeg.rb:52
  66 +msgid "Invalid data found when processing input."
  67 +msgstr ""
  68 +
  69 +#: ../lib/html5_video_plugin/ffmpeg.rb:190
  70 +msgid "Success."
  71 +msgstr ""
  72 +
  73 +#: ../lib/html5_video_plugin/video_channel.rb:4
  74 +msgid "Video Channel"
  75 +msgstr ""
  76 +
  77 +#: ../lib/html5_video_plugin/video_channel.rb:8
  78 +msgid "A video channel, where you can make your own web TV."
  79 +msgstr ""
  80 +
  81 +#: ../lib/presenters/video.rb:9
28 msgid "Video (%s)" 82 msgid "Video (%s)"
29 msgstr "Vídeo (%s)" 83 msgstr "Vídeo (%s)"
30 84
31 -#: plugins/html5_video/lib/html5_video_plugin.rb:10  
32 -msgid "A plugin to enable the video suport, with auto conversion for the web." 85 +#: ../lib/presenters/video.rb:137
  86 +msgid "Undefined"
  87 +msgstr ""
  88 +
  89 +#: ../views/content_viewer/_video_player.html.erb:21
  90 +msgid "Sorry, your browser doesn&rsquo;t support video."
  91 +msgstr ""
  92 +
  93 +#: ../views/content_viewer/_video_player.html.erb:22
  94 +msgid "Please try the new %s or %s."
  95 +msgstr ""
  96 +
  97 +#: ../views/content_viewer/_video_player.html.erb:27
  98 +msgid "Download"
  99 +msgstr ""
  100 +
  101 +#: ../views/content_viewer/video_channel.html.erb:7
  102 +msgid "This channel contains no videos yet"
  103 +msgstr ""
  104 +
  105 +#: ../views/content_viewer/video_channel.html.erb:14
  106 +#: ../views/file_presenter/_video.html.erb:15
  107 +msgid "Quality options"
  108 +msgstr ""
  109 +
  110 +#: ../views/content_viewer/video_channel.html.erb:18
  111 +msgid "Tags"
  112 +msgstr ""
  113 +
  114 +#: ../views/content_viewer/video_channel.html.erb:22
  115 +#: ../views/file_presenter/_video.html.erb:17
  116 +msgid "Description"
  117 +msgstr ""
  118 +
  119 +#: ../views/content_viewer/video_channel.html.erb:66
  120 +msgid "This channel has one video waiting to be converted"
  121 +msgid_plural "This channel has %d videos waiting to be converted"
  122 +msgstr[0] ""
  123 +msgstr[1] ""
  124 +
  125 +#: ../views/content_viewer/video_channel.html.erb:83
  126 +msgid "Non video files"
  127 +msgstr ""
  128 +
  129 +#: ../views/file_presenter/_video.html.erb:7
  130 +msgid "Queued to generate the web version. Come back soon."
  131 +msgstr ""
  132 +
  133 +#: ../views/file_presenter/_video.html.erb:11
  134 +msgid "This video is not queued to the video conversor. Contact the site admin."
  135 +msgstr ""
  136 +
  137 +#: ../views/file_presenter/_video.html.erb:34
  138 +msgid "Video conversion errors"
  139 +msgstr ""
  140 +
  141 +#: ../views/file_presenter/_video.html.erb:44
  142 +msgid "Error while converting %{orig_type} to %{new_type}, %{size} size."
  143 +msgstr ""
  144 +
  145 +#: ../views/file_presenter/_video.html.erb:47
  146 +msgid "Code %s"
  147 +msgstr ""
  148 +
  149 +#: ../views/file_presenter/_video.html.erb:49
  150 +msgid "display full output"
33 msgstr "" 151 msgstr ""
34 -"Um plugin para habilitar suporte de video, com conversão de audio para a web."  
plugins/html5_video/public/images/video-preview-big.png 0 → 100644

21.3 KB

plugins/html5_video/public/images/video-preview-thumb.png 0 → 100644

14.3 KB

plugins/html5_video/public/style.css 0 → 100644
@@ -0,0 +1,175 @@ @@ -0,0 +1,175 @@
  1 +.video-list {
  2 + margin: 0px;
  3 + padding: 0px;
  4 + text-align: left;
  5 +}
  6 +
  7 +.video-list-item {
  8 + margin: 2px 1px;
  9 + padding: 0px;
  10 + list-style: none;
  11 + display: inline-block;
  12 + width: 160px;
  13 + height: 120px;
  14 + overflow: hidden;
  15 +}
  16 +
  17 +.video-list-item a {
  18 + display: block;
  19 + height: 100%;
  20 + background-position: 50% -120px;
  21 + background-repeat: no-repeat;
  22 + position: relative;
  23 +}
  24 +
  25 +.video-list-item .frame-fade {
  26 + position: absolute;
  27 + top: 0px;
  28 + left: 0px;
  29 + width: 100%;
  30 + height: 100%;
  31 + display: block;
  32 +}
  33 +
  34 +@keyframes fadein {
  35 + from { opacity: 0.0 }
  36 + to { opacity: 1.0 }
  37 +}
  38 +@-moz-keyframes fadein {
  39 + from { opacity: 0.0 }
  40 + to { opacity: 1.0 }
  41 +}
  42 +@-webkit-keyframes fadein {
  43 + from { opacity: 0.0 }
  44 + to { opacity: 1.0 }
  45 +}
  46 +
  47 +.video-list-item a strong {
  48 + display: block;
  49 + width: 100%;
  50 + padding: 3px 5px;
  51 + position: absolute;
  52 + bottom: 0px;
  53 + background: rgba(0,0,0,0.5);
  54 + color: #FFF;
  55 +}
  56 +
  57 +.video-list-item ul, .video-list-item div {
  58 + display: none;
  59 +}
  60 +
  61 +.non-video-list {
  62 + border-top: 1px solid #DDD;
  63 + margin-top: 15px;
  64 +}
  65 +
  66 +.video-player {
  67 + padding-bottom: 10px;
  68 +}
  69 +
  70 +.video-player .vjs-no-video {
  71 + background: #888;
  72 + padding: 3px 5px;
  73 +}
  74 +
  75 +.video-box {
  76 + display: inline-block;
  77 + position: relative;
  78 +}
  79 +
  80 +.video-channel .video-box {
  81 + float: left;
  82 + width: 400px;
  83 +}
  84 +.video-channel .zoom-in .video-box {
  85 + float: none;
  86 + width: 100%;
  87 +}
  88 +
  89 +.video-box .video-ctrl {
  90 + position: absolute;
  91 + top: 5px;
  92 + right: 5px;
  93 + display: none;
  94 +}
  95 +.video-box:hover .video-ctrl {
  96 + display: block;
  97 +}
  98 +
  99 +.video-player video, .video-box {
  100 + min-width: 256px;
  101 + max-width: 100%;
  102 + background: #000;
  103 +}
  104 +
  105 +.video-channel .video-player.zoom-out video,
  106 +.video-player.zoom-in .video-box,
  107 +.video-player.zoom-in video {
  108 + width: 100%;
  109 +}
  110 +
  111 +.video-player-info {
  112 + text-align: left;
  113 + margin-top: 10px;
  114 +}
  115 +
  116 +.video-channel .video-player-info {
  117 + margin-left: 415px;
  118 +}
  119 +.video-channel .zoom-in .video-player-info {
  120 + margin: 0px;
  121 +}
  122 +
  123 +.video-player-info > div {
  124 + padding: 3px 0px;
  125 +}
  126 +
  127 +.video-player-info .data {
  128 + display: inline-block;
  129 +}
  130 +
  131 +.video-player-info .quality ul {
  132 + display: inline-block;
  133 + margin: 0px -4px;
  134 + padding: 0px;
  135 +}
  136 +.video-player-info .quality li {
  137 + margin: 4px;
  138 + padding: 0px;
  139 +}
  140 +
  141 +.unconverted-videos p span {
  142 + cursor: pointer;
  143 +}
  144 +
  145 +.conversion-error {
  146 + text-align: left;
  147 + border: 1px solid #A44;
  148 + background: #EAA;
  149 +}
  150 +
  151 +#content .conversion-error h2 {
  152 + margin: 10px;
  153 + color: #FFF;
  154 + text-shadow: 1px 1px 1px #944;
  155 + text-align: center;
  156 +}
  157 +
  158 +.conversion-error ul {
  159 + margin: 10px;
  160 + padding: 0px;
  161 +}
  162 +
  163 +.conversion-error li {
  164 + list-style: none;
  165 + margin: 0px;
  166 + padding: 0px;
  167 +}
  168 +
  169 +#article .conversion-error pre {
  170 + border: none;
  171 + background: rgba(222,222,222,0.3);
  172 +}
  173 +
  174 +.icon-videochannel { background-image: url(/designs/icons/tango/Tango/16x16/mimetypes/video-x-generic.png) }
  175 +.icon-newvideochannel { background-image: url(/designs/icons/tango/Tango/16x16/mimetypes/video-x-generic.png) }
plugins/html5_video/public/video-channel.js 0 → 100644
@@ -0,0 +1,215 @@ @@ -0,0 +1,215 @@
  1 +/*
  2 +** Noosfero's Video Channel specific client script
  3 +** Released under the same Noosfero's license
  4 +*/
  5 +
  6 +(function (exports, $) {
  7 +"use strict";
  8 +
  9 +var vEl = document.createElement('video');
  10 +var canPlay = {
  11 + webm: !!vEl.canPlayType('video/webm').replace(/no/,''),
  12 + ogg: !!vEl.canPlayType('video/ogg').replace(/no/,''),
  13 + mp4: !!vEl.canPlayType('video/mp4').replace(/no/,'')
  14 +};
  15 +
  16 +exports.VideoChannel = function VideoChannel(baseEl) {
  17 + this.baseEl = baseEl;
  18 + if ($('.video-player', this.baseEl)[0]){
  19 + this.player = new NoosferoVideoPlayer(this.baseEl, this);
  20 + this.init();
  21 + }
  22 +};
  23 +
  24 +VideoChannel.prototype.init = function() {
  25 + var me = this;
  26 + $('.video-list-item', this.baseEl).each(
  27 + function(num, item) {
  28 + me.initItem(item);
  29 + }
  30 + );
  31 + if ( $('.video-list li', this.baseEl)[0] ) {
  32 + this.updatePlayer( $('li', this.baseEl).first() );
  33 + } else {
  34 + log.info('there is no playable video yet.');
  35 + $('.video-player', this.baseEl).hide();
  36 + }
  37 +};
  38 +
  39 +VideoChannel.prototype.initItem = function(item) {
  40 + var me = this;
  41 + $(item).click(function(){ me.updatePlayer(item, true); });
  42 + var link = $('a', item)[0];
  43 + link.onclick = function(){ return false };
  44 + link.nextFrame = VideoChannel.nextFrame;
  45 + if ( !link.frameFade )
  46 + link.frameFade = $('<div class="frame-fade"></div>').prependTo(link)[0];
  47 + link.frameFade.style.backgroundImage = link.style.backgroundImage;
  48 + link.addEventListener("animationend", function(){ link.nextFrame() }, false);
  49 + link.addEventListener("webkitAnimationEnd", function(){ link.nextFrame() }, false);
  50 + link.nextFrame();
  51 +};
  52 +
  53 +VideoChannel.nextFrame = function(fade) {
  54 + if ( !fade ) {
  55 + this.frameFade.style.opacity = 0.0;
  56 + this.frameFade.style.animationName = "";
  57 + this.frameFade.style.MozAnimationName = "";
  58 + this.frameFade.style.webkitAnimationName = "";
  59 + if ( !this.bgYPos ) this.bgYPos = 0;
  60 + this.style.backgroundPosition = "50% "+ ( this.bgYPos++ * -120 ) +"px";
  61 + if ( this.bgYPos > 5 ) this.bgYPos = 0;
  62 + this.frameFade.style.backgroundPosition = "50% "+ ( this.bgYPos * -120 ) +"px";
  63 + var link = this;
  64 + setTimeout( function(){ link.nextFrame(true) }, 10 );
  65 + } else {
  66 + this.frameFade.style.animationDuration = "1s";
  67 + this.frameFade.style.animationName = "fadein";
  68 + this.frameFade.style.MozAnimationDuration = "1s";
  69 + this.frameFade.style.MozAnimationName = "fadein";
  70 + this.frameFade.style.webkitAnimationDuration = "1s";
  71 + this.frameFade.style.webkitAnimationName = "'fadein'";
  72 + }
  73 +};
  74 +
  75 +VideoChannel.prototype.updatePlayer = function(item, autoplay) {
  76 + var json = $('a', item)[0].getAttribute("data-webversions");
  77 + this.player.videoList = JSON.parse(json);
  78 + this.player.selectWebVersion();
  79 + this.player.update( this.getItemData(item), autoplay );
  80 +};
  81 +
  82 +VideoChannel.prototype.getItemData = function(item) {
  83 + var link = $('a', item)[0];
  84 + var spans = $('span', item);
  85 + var data = {};
  86 + data.pageURL = link.href;
  87 + data.videoURL = link.getAttribute('data-download');
  88 + data.posterURL = link.getAttribute('data-poster');
  89 + data.title = $(link).text();
  90 + data.abstract = $('.abstract', item)[0].innerHTML;
  91 + data.tags = $('.vli-data-tags > div', item)[0].innerHTML;
  92 + return data;
  93 +};
  94 +
  95 +///////////// Video Player /////////////////////////////////////////////////////
  96 +
  97 +exports.NoosferoVideoPlayer = function NoosferoVideoPlayer(place, channel) {
  98 + this.channel = channel;
  99 + this.divBase = $('.video-player', place)[0];
  100 + if(!this.divBase) return;
  101 + this.info = {
  102 + title : $('h2', this.divBase)[0],
  103 + quality : $('.quality ul', this.divBase)[0],
  104 + tags : $('.tags div', this.divBase)[0],
  105 + abstract : $('.abstract div', this.divBase)[0],
  106 + videoCtrl : $('.video-ctrl', this.divBase)[0],
  107 + downloadBt : $('.download-bt', this.divBase)
  108 + .button({ icons: { primary: "ui-icon-circle-arrow-s" } })[0]
  109 + };
  110 + this.videoBox = $('.video-box', this.divBase)[0];
  111 + var me = this;
  112 + this.zoomBt = $('<button class="zoom">Zoom</button>')
  113 + .button({ icons: { primary: "ui-icon-zoomin" }, text: false })
  114 + .click(function(){ me.toggleZoom() })
  115 + .appendTo(this.info.videoCtrl);
  116 + this.zoomBt[0].title = "Zoom in";
  117 + this.videoEl = $('video', this.divBase)[0];
  118 +};
  119 +
  120 +NoosferoVideoPlayer.prototype.update = function(data, autoplay) {
  121 + this.info.title.innerHTML = data.title;
  122 + this.videoEl.src = data.videoURL;
  123 + this.videoEl.autoplay = autoplay;
  124 + this.poster = data.posterURL;
  125 + this.videoEl.load();
  126 + var tags = data.tags || '<span class="empty">None</span>'
  127 + $(this.info.tags).empty().append(tags);
  128 + var desc = data.abstract || '<span class="empty">None</span>'
  129 + $(this.info.abstract).empty().append(desc);
  130 + this.info.downloadBt.href = data.videoURL;
  131 +};
  132 +
  133 +NoosferoVideoPlayer.prototype.updateQualityOpts = function(type) {
  134 + var me = this;
  135 + $(this.info.quality).empty();
  136 + for ( var size in this.videoList[type] ) {
  137 + var videoData = this.videoList[type][size];
  138 + log.info( 'Quality option:', videoData );
  139 + if ( videoData.status == "done" ) {
  140 + var txt = videoData.size_name;
  141 + if ( !this.channel ) {
  142 + txt = videoData.size.w +'x'+ videoData.size.h +
  143 + ' <small>'+ videoData.vbrate +' KB/s</small>';
  144 + }
  145 + var bt = $(
  146 + '<li data-path="'+videoData.path+'">'+ txt +'</li>')
  147 + .button({ icons: { primary:"ui-icon-video" } })
  148 + .click(function(){ me.load(this.video, true) })
  149 + .appendTo(this.info.quality)[0];
  150 + bt.video = videoData;
  151 + videoData.qualityBt = bt;
  152 + }
  153 + }
  154 +};
  155 +
  156 +NoosferoVideoPlayer.prototype.load = function (video, userSelection) {
  157 + if ( this.currentVideo ) $(this.currentVideo.qualityBt).button().button("enable");
  158 + $(video.qualityBt).button("disable");
  159 + this.currentVideo = video;
  160 + this.videoEl.src = video.path;
  161 + this.videoEl.preload = "metadata";
  162 + if ( userSelection )
  163 + $.cookie( "video_quality", video.size_name, {path:'/'} );
  164 + if ( $.cookie("video_zoom") == "true" ) this.zoomIn();
  165 + else this.zoomOut();
  166 +};
  167 +
  168 +NoosferoVideoPlayer.prototype.toggleZoom = function () {
  169 + if ( $(this.divBase).hasClass("zoom-in") ) this.zoomOut();
  170 + else this.zoomIn();
  171 +};
  172 +
  173 +NoosferoVideoPlayer.prototype.zoomIn = function () {
  174 + $.cookie( "video_zoom", "true", {path:'/'} );
  175 + $(this.divBase).removeClass("zoom-out").addClass("zoom-in");
  176 +};
  177 +
  178 +NoosferoVideoPlayer.prototype.zoomOut = function () {
  179 + $.cookie( "video_zoom", "false", {path:'/'} );
  180 + $(this.divBase).removeClass("zoom-in").addClass("zoom-out");
  181 +};
  182 +
  183 +NoosferoVideoPlayer.prototype.selectWebVersion = function () {
  184 + var video = null;
  185 + var me = this;
  186 + var q1 = $.cookie("video_quality") || "tiny";
  187 + var q2 = ( q1 == "tiny" ) ? "nice" : "tiny";
  188 + var type = canPlay.webm ? "WEBM" : canPlay.ogg ? "OGV" : "MP4";
  189 + if ( (video = this.getVideoFromList(type, q1))
  190 + || (video = this.getVideoFromList(type, q2))
  191 + ) {
  192 + this.updateQualityOpts(video.type);
  193 + setTimeout( function(){ me.load(video) }, 10 );
  194 + }
  195 +};
  196 +
  197 +NoosferoVideoPlayer.prototype.getVideoFromList = function (type, quality) {
  198 + log.info( 'Trying to getVideoFromList', type, quality );
  199 + if (!this.videoList && !this.videoList) {
  200 + log.info( 'The video list is empty' );
  201 + return null;
  202 + }
  203 + if ( quality.toLowerCase() != "nice" ) quality = "tiny";
  204 + var selected = this.videoList[type][quality];
  205 + log.info( 'getVideoFromList find:', selected );
  206 + if ( selected && selected.status == "done" ) {
  207 + log.info( 'getVideoFromList success' );
  208 + return selected;
  209 + } else {
  210 + log.info( 'getVideoFromList fail' );
  211 + return null;
  212 + }
  213 +};
  214 +
  215 +}(window, jQuery));
plugins/html5_video/test/download_fixture.rb 0 → 100644
@@ -0,0 +1,34 @@ @@ -0,0 +1,34 @@
  1 +require File.dirname(__FILE__) + '/../../../test/test_helper'
  2 +
  3 +fixture_path = File.dirname(__FILE__) + '/../../../test/fixtures/videos'
  4 +Dir.mkdir(fixture_path) unless File.exist?(fixture_path)
  5 +
  6 +base_url = 'http://noosfero.org/pub/Development/HTML5VideoPlugin'
  7 +
  8 +videos = ['old-movie.mpg', 'atropelamento.ogv', 'firebus.3gp']
  9 +
  10 +def shutdown(fixture_path, videos)
  11 + videos.map do |v|
  12 + File.unlink(fixture_path+'/'+v) if File.exists?(fixture_path+'/'+v)
  13 + end
  14 + exit 1
  15 +end
  16 +
  17 +signals = %w{EXIT HUP INT QUIT TERM}
  18 +signals.map{|s| Signal.trap(s) { shutdown fixture_path, videos } }
  19 +
  20 +unless videos.select{|v| !File.exists? fixture_path+'/'+v }.empty?
  21 + # Open3.capture2e is the right way, but needs ruby 1.9
  22 + puts "\nDownloading video fixture..."
  23 + puts videos.map{|v| base_url+'/'+v}.join(' ')
  24 + output = `cd '#{fixture_path}';
  25 + LANG=C wget -c #{videos.map{|v| base_url+'/'+v}.join(' ')} || echo '\nERROR'`
  26 +
  27 + if output[-7..-1] == "\nERROR\n" then
  28 + puts "wget fail. Try again."
  29 + exit 0
  30 + end
  31 +end
  32 +
  33 +signals.map{|s| Signal.trap(s) { } }
  34 +
plugins/html5_video/test/functional/content_viewer_controler_test.rb
1 -require 'test_helper' 1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
2 require 'content_viewer_controller' 2 require 'content_viewer_controller'
3 3
  4 +class ContentViewerController
  5 + # Re-raise errors caught by the controller.
  6 + def rescue_action(e) raise e end
  7 + append_view_path File.join(File.dirname(__FILE__) + '/../../views')
  8 +end
  9 +
4 class ContentViewerControllerTest < ActionController::TestCase 10 class ContentViewerControllerTest < ActionController::TestCase
5 11
6 all_fixtures 12 all_fixtures
@@ -10,11 +16,13 @@ class ContentViewerControllerTest &lt; ActionController::TestCase @@ -10,11 +16,13 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
10 16
11 @profile = create_user('testinguser').person 17 @profile = create_user('testinguser').person
12 @environment = @profile.environment 18 @environment = @profile.environment
  19 + @environment.enable_plugin(Html5VideoPlugin)
13 end 20 end
14 attr_reader :profile, :environment 21 attr_reader :profile, :environment
15 22
16 should 'add html5 video tag to the page of file type video' do 23 should 'add html5 video tag to the page of file type video' do
17 file = UploadedFile.create!(:uploaded_data => fixture_file_upload('/files/test.txt', 'video/ogg'), :profile => profile) 24 file = UploadedFile.create!(:uploaded_data => fixture_file_upload('/files/test.txt', 'video/ogg'), :profile => profile)
  25 + process_delayed_job_queue
18 get :view_page, file.url.merge(:view=>:true) 26 get :view_page, file.url.merge(:view=>:true)
19 assert_select '#article video' 27 assert_select '#article video'
20 end 28 end
plugins/html5_video/test/unit/create_video_for_web_job_test.rb 0 → 100755
@@ -0,0 +1,100 @@ @@ -0,0 +1,100 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../download_fixture'
  3 +$LOAD_PATH << File.dirname(__FILE__) + '/../../lib/'
  4 +require 'html5_video_plugin.rb'
  5 +
  6 +class CreateVideoForWebJobTest < ActiveSupport::TestCase
  7 +
  8 + ffmpeg = Html5VideoPlugin::Ffmpeg.new
  9 +
  10 + def setup
  11 + Environment.default.enable_plugin Html5VideoPlugin
  12 + # Create a temporary directory to write testing files
  13 + @temp = %x{ mktemp -d }[0..-2]
  14 + end
  15 +
  16 + def teardown
  17 + # Remove the temporary directory
  18 + %x{ rm -r '#{@temp}' }
  19 + end
  20 +
  21 + def run_CreateVideoForWebJobs_for_video(video)
  22 + jobs = video.web_version_jobs
  23 + # Run all CreateVideoForWebJob's for this created video:
  24 + print '['; STDOUT.flush
  25 + jobs.each do |job|
  26 + YAML.load(job.handler).perform
  27 + STDOUT.write '+'; STDOUT.flush # a progress to the user see something.
  28 + end
  29 + print ']'; STDOUT.flush
  30 + video.reload
  31 + end
  32 +
  33 + should 'create web compatible version to a uploaded MPEG video' do
  34 + video = FilePresenter.for UploadedFile.create!(
  35 + :uploaded_data => fixture_file_upload('/videos/old-movie.mpg', 'video/mpeg'),
  36 + :profile => fast_create(Person) )
  37 + assert_equal({}, video.web_versions, 'video.web_versions starts as empty list')
  38 +
  39 + run_CreateVideoForWebJobs_for_video(video)
  40 +
  41 + video.web_versions.each do |format, format_block|
  42 + format_block.each { |size, video_conv| assert !video_conv[:original] }
  43 + end
  44 + assert_equal 'tiny.ogv', video.web_versions[:OGV][:tiny][:file_name]
  45 + assert_equal({:w=>320,:h=>212}, video.web_versions[:OGV][:tiny][:size])
  46 + assert_equal 250, video.web_versions[:OGV][:tiny][:vbrate]
  47 + assert_equal 64, video.web_versions[:OGV][:tiny][:abrate]
  48 + assert_equal 'nice.ogv', video.web_versions[:OGV][:nice][:file_name]
  49 + assert_equal({:w=>576,:h=>384}, video.web_versions[:OGV][:nice][:size])
  50 + assert_equal 104857, video.web_versions[:OGV][:nice][:vbrate]
  51 + assert_equal 128, video.web_versions[:OGV][:nice][:abrate]
  52 + assert_equal 'tiny.webm', video.web_versions[:WEBM][:tiny][:file_name]
  53 + assert_equal({:w=>320,:h=>212}, video.web_versions[:WEBM][:tiny][:size])
  54 + assert_equal 250, video.web_versions[:WEBM][:tiny][:vbrate]
  55 + assert_equal 64, video.web_versions[:WEBM][:tiny][:abrate]
  56 + assert_equal 'nice.webm', video.web_versions[:WEBM][:nice][:file_name]
  57 + assert_equal({:w=>576,:h=>384}, video.web_versions[:WEBM][:nice][:size])
  58 + assert_equal 104857, video.web_versions[:WEBM][:nice][:vbrate]
  59 + assert_equal 128, video.web_versions[:WEBM][:nice][:abrate]
  60 +
  61 + webdir = ffmpeg.webdir_for_original_video video.public_filename
  62 + assert File.exists?( webdir+'/tiny.ogv' )
  63 + assert File.exists?( webdir+'/nice.ogv' )
  64 + assert File.exists?( webdir+'/tiny.webm' )
  65 + assert File.exists?( webdir+'/nice.webm' )
  66 + end
  67 +
  68 + should 'create web compatible version to a uploaded OGG video' do
  69 + resp = ffmpeg.run [ :i, "#{fixture_path}/videos/firebus.3gp",
  70 + :t, 4, :f, 'ogg',
  71 + :vcodec, 'libtheora', :vb, '800k',
  72 + :acodec, 'libvorbis', :ar, 44100, :ab, '192k',
  73 + "#{@temp}/firebus.ogv" ]
  74 + assert_equal 0, resp[:error][:code], 'creating a valid OGV'
  75 +
  76 + video = FilePresenter.for UploadedFile.create!(
  77 + uploaded_data: Rack::Test::UploadedFile.new("#{@temp}/firebus.ogv", 'video/ogv'),
  78 + profile: fast_create(Person) )
  79 + assert_equal({}, video.web_versions, 'video.web_versions starts as empty list')
  80 +
  81 + run_CreateVideoForWebJobs_for_video(video)
  82 +
  83 + video.web_versions.each do |format, format_block|
  84 + format_block.each { |size, video_conv| assert !video_conv[:original] }
  85 + end
  86 + web_versions = video.web_versions!
  87 + assert_equal 'tiny.ogv', web_versions[:OGV][:tiny][:file_name]
  88 + assert_equal({:w=>320,:h=>240}, web_versions[:OGV][:tiny][:size])
  89 + assert_equal 250, web_versions[:OGV][:tiny][:vbrate]
  90 + assert_equal 64, web_versions[:OGV][:tiny][:abrate]
  91 + assert_equal 'tiny.webm', web_versions[:WEBM][:tiny][:file_name]
  92 + assert_equal({:w=>320,:h=>240}, web_versions[:WEBM][:tiny][:size])
  93 + assert_equal 250, web_versions[:WEBM][:tiny][:vbrate]
  94 + assert_equal 64, web_versions[:WEBM][:tiny][:abrate]
  95 + assert_equal 'firebus.ogv', web_versions[:OGV][:orig][:file_name]
  96 + assert_equal({:w=>128,:h=>96}, web_versions[:OGV][:orig][:size])
  97 + assert_equal 192, web_versions[:OGV][:orig][:abrate]
  98 + end
  99 +
  100 +end
plugins/html5_video/test/unit/create_video_preview_job_test.rb 0 → 100755
@@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../download_fixture'
  3 +$LOAD_PATH << File.dirname(__FILE__) + '/../../lib/'
  4 +require 'html5_video_plugin.rb'
  5 +
  6 +class CreateVideoPreviewJobTest < ActiveSupport::TestCase
  7 +
  8 + def setup
  9 + Environment.default.enable_plugin Html5VideoPlugin
  10 + end
  11 +
  12 + def run_CreateVideoPreviewJob_for_video(video)
  13 + jobs = video.web_preview_jobs
  14 + # Run all CreateVideoPreviewJob's for this created video:
  15 + print '['; STDOUT.flush
  16 + jobs.each do |job|
  17 + YAML.load(job.handler).perform
  18 + STDOUT.write '+'; STDOUT.flush # a progress to the user see something.
  19 + end
  20 + print ']'; STDOUT.flush
  21 + video.reload
  22 + end
  23 +
  24 + should 'create preview images to uploaded videos' do
  25 +
  26 + video = FilePresenter.for UploadedFile.create!(
  27 + :uploaded_data => fixture_file_upload('/videos/firebus.3gp', 'video/3gpp'),
  28 + :profile => fast_create(Person) )
  29 + assert not(video.has_previews?)
  30 +
  31 + run_CreateVideoPreviewJob_for_video video
  32 + video.reload
  33 +
  34 + assert video.has_previews?, 'must have built preview images'
  35 + assert_equal({:big => '/web/preview_160x120.jpg',
  36 + :thumb => '/web/preview_107x80.jpg'},
  37 + video.previews)
  38 +
  39 + video_path = File.dirname(video.full_filename)
  40 +
  41 + assert File.exist?(video_path + video.previews[:big])
  42 + assert File.exist?(video_path + video.previews[:thumb])
  43 + assert_match /^\/[^ ]*\/[0-9]+\/+web\/preview_160x120.jpg JPEG 160x720 /, `identify #{video_path + video.previews[:big]}`
  44 + assert_match /^\/[^ ]*\/[0-9]+\/+web\/preview_107x80.jpg JPEG 107x480 /, `identify #{video_path + video.previews[:thumb]}`
  45 +
  46 + end
  47 +
  48 +end
plugins/html5_video/test/unit/ffmpeg_test.rb 0 → 100644
@@ -0,0 +1,267 @@ @@ -0,0 +1,267 @@
  1 +#require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require 'test_helper'
  3 +#require File.dirname(__FILE__) + '/../download_fixture'
  4 +require_relative '../download_fixture'
  5 +$LOAD_PATH << File.dirname(__FILE__) + '/../../lib/'
  6 +require 'html5_video_plugin.rb'
  7 +require 'html5_video_plugin/ffmpeg.rb'
  8 +
  9 +class FfmpegTest < ActiveSupport::TestCase
  10 +
  11 + ffmpeg = Html5VideoPlugin::Ffmpeg.new
  12 +
  13 + def create_video(file, mime)
  14 + file = UploadedFile.create!(
  15 + :uploaded_data => fixture_file_upload('/videos/'+file, mime),
  16 + :profile => fast_create(Person))
  17 + end
  18 +
  19 + def video_path(file='')
  20 + "#{fixture_path}/videos/#{file}"
  21 + end
  22 +
  23 + # Some tests wil create a "web" dir inside fixture videos dir, so we must remove it.
  24 + def rm_web_videos_dir
  25 + webdir = video_path 'web'
  26 + return unless Dir.exist? webdir
  27 + Dir.foreach(webdir) do|file|
  28 + File.unlink webdir +'/'+ file unless file.match /^\.+$/
  29 + end
  30 + Dir.delete webdir
  31 + end
  32 +
  33 + def setup
  34 + Environment.default.enable_plugin Html5VideoPlugin
  35 + @temp = []
  36 + rm_web_videos_dir
  37 + end
  38 +
  39 + def teardown
  40 + @temp.each do |file|
  41 + if File.exist? file
  42 + File.unlink file
  43 + end
  44 + end
  45 + rm_web_videos_dir
  46 + end
  47 +
  48 + # Create a temp filename, not a file.
  49 + # If a file with this name is created, it will be removed by the teardown.
  50 + def mkTempName(ext='')
  51 + ( @temp << "/tmp/#{SecureRandom.hex}.#{ext}" ).last
  52 + end
  53 +
  54 + should 'has the right version of ffmpeg' do
  55 + response = ffmpeg.run :version
  56 + assert_match /^ffmpeg version 3\.0/, response[:output]
  57 + end
  58 +
  59 + should 'complain about missing input' do
  60 + response = ffmpeg.run :i, 'ups-i-dont-exixt.ogv'
  61 + assert_equal 1, response[:error][:code]
  62 + end
  63 +
  64 + should 'complain about missing output' do
  65 + response = ffmpeg.run :i, video_path('old-movie.mpg')
  66 + assert_equal 2, response[:error][:code]
  67 + end
  68 +
  69 + should 'complain about unknown encoder' do
  70 + tmpogv = mkTempName :ogv
  71 + response = ffmpeg.run :i, video_path('old-movie.mpg'), :vcodec, 'noCodec', tmpogv
  72 + assert_equal 3, response[:error][:code]
  73 + end
  74 +
  75 + should 'complain about wrong encoder' do
  76 + tmpvid = mkTempName :mpg
  77 + response = ffmpeg.run :i, video_path('firebus.3gp'), :'b:v', 3, tmpvid
  78 + assert_equal 4, response[:error][:code]
  79 + end
  80 +
  81 +# #TODO: cant reproduce this error
  82 +# should 'complain about not being able to open encoder' do
  83 +# tmpvid = mkTempName :mpg
  84 +# response = run_ffmpeg [:i, video_path('old-movie.mpg'), tmpvid]
  85 +# assert_equal 5, response[:error][:code]
  86 +# end
  87 +
  88 +# #TODO: cant reproduce this error
  89 +# should 'complain about unsuported codec' do
  90 +# tmpvid = mkTempName :webm
  91 +# response = run_ffmpeg [:i, video_path('firebus.3gp'), :vcodec, 'libtheora', tmpvid]
  92 +# assert_equal 6, response[:error][:code]
  93 +# end
  94 +
  95 + should 'complain about unknown output format' do
  96 + tmpvid = mkTempName :nop
  97 + response = ffmpeg.run :i, video_path('old-movie.mpg'), tmpvid
  98 + assert_equal 7, response[:error][:code]
  99 + end
  100 +
  101 + should 'complain about invalid input data' do
  102 + tmpvid = mkTempName :mpg
  103 + fakevid = Tempfile.new ['fake', '.mpg']
  104 + response = ffmpeg.run :i, fakevid.path, tmpvid
  105 + fakevid.close
  106 + fakevid.unlink
  107 + assert_equal 8, response[:error][:code]
  108 + end
  109 +
  110 + should 'read ffmpeg information and features' do
  111 + response = ffmpeg.register_information
  112 + assert_match /^[0-9]\.[0-9]\.[0-9]$/, response[:version]
  113 + formatWebM = /^\{demux:false,description:WebM,mux:true\}$/
  114 + assert_match formatWebM, h2s(response[:formats][:webm])
  115 + codecVorbis = /^\{decode:true,description:Vorbis[^,]+,direct_rendering:false,draw_horiz_band:false,encode:true,type:audio,wf_trunc:false\}$/
  116 + assert_match codecVorbis, h2s(response[:codecs][:vorbis])
  117 + end
  118 +
  119 + should 'convert time string to seconds int' do
  120 + assert_equal 30, ffmpeg.timestr_to_secs('00:00:30')
  121 + assert_equal 630, ffmpeg.timestr_to_secs('00:10:30')
  122 + assert_equal 7830, ffmpeg.timestr_to_secs('02:10:30')
  123 + assert_equal nil, ffmpeg.timestr_to_secs('invalid time string')
  124 + end
  125 +
  126 + should 'parse video stream info' do
  127 + response = ffmpeg.get_stream_info 'Stream #0:0[0x1e0]: Video: mpeg1video, yuv420p(tv), 720x480 [SAR 200:219 DAR 100:73], 104857 kb/s, 23.98 fps, 23.98 tbr, 90k tbn, 23.98 tbc'
  128 +
  129 + assert_equal 'video', response[:type]
  130 + assert_equal 'mpeg1video', response[:codec]
  131 + assert_equal 104857, response[:bitrate]
  132 + assert_equal 23.98, response[:framerate]
  133 + assert_equal 'video', response[:type]
  134 + assert_equal 720, response[:size][:w]
  135 + assert_equal 480, response[:size][:h]
  136 + end
  137 +
  138 + should 'parse audio stream info' do
  139 + response = ffmpeg.get_stream_info 'Stream #0:1[0x1c0]: Audio: mp2, 48000 Hz, 2 channels, stereo, s16p, 128 kb/s'
  140 + assert_equal 'audio', response[:type]
  141 + assert_equal 'mp2', response[:codec]
  142 + assert_equal 48000, response[:frequency]
  143 + assert_equal 128, response[:bitrate]
  144 + assert_equal 2, response[:channels]
  145 + end
  146 +
  147 + should 'fetch webdir' do
  148 + video = mkTempName :mpg
  149 + assert_equal '/tmp/web', ffmpeg.webdir_for_original_video(video)
  150 + end
  151 +
  152 + should 'validate conversion conf for web' do
  153 + conf = { in: video_path('old-movie.mpg') }
  154 + validConf = ffmpeg.validate_conversion_conf_for_web conf, :webm
  155 + assert_match /^\{abrate:128,file_name:640x426_1024.webm,in:[^:]+\/old-movie.mpg,out:[^:]+\/web\/640x426_1024.webm,size:\{h:426,w:640\},vbrate:1024\}$/, h2s(validConf)
  156 + end
  157 +
  158 + should 'validate conversion conf for web with given output filename' do
  159 + conf = { in: video_path('old-movie.mpg'), file_name: 'test.webm' }
  160 + validConf = ffmpeg.validate_conversion_conf_for_web conf, :webm
  161 + assert_match /^\/.+\/web\/test.webm$/, validConf[:out]
  162 + end
  163 +
  164 + should 'get video info' do
  165 + resp = ffmpeg.get_video_info video_path('old-movie.mpg')
  166 + assert_equal [:error, :parameters, :output, :metadata, :type, :duration, :global_bitrate, :streams], resp.keys
  167 + assert_equal '{code:0,message:Success.}', h2s(resp[:error])
  168 + assert_equal 'mpeg', resp[:type]
  169 + assert_equal 5, resp[:duration]
  170 + assert_equal 2428, resp[:global_bitrate]
  171 + assert_equal '{}', h2s(resp[:metadata])
  172 + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg\]$/, h2s(resp[:parameters])
  173 + assert_match /^\{bitrate:104857,codec:mpeg1video,framerate:23.98,id:#0,size:\{h:480,w:720\},type:video\}$/, h2s(resp[:streams][0])
  174 + assert_match /^\{bitrate:128,codec:mp2,frequency:48000,id:#0,type:audio\}$/, h2s(resp[:streams][1])
  175 + end
  176 +
  177 + should 'get video info with metadata' do
  178 + resp = ffmpeg.get_video_info video_path('atropelamento.ogv')
  179 + assert_equal '{comment:Stop-motion movie,title:Atropelamento}', h2s(resp[:metadata])
  180 + end
  181 +
  182 + should 'convert to OGV' do
  183 + out_video = mkTempName :ogv
  184 + resp = ffmpeg.convert2ogv in: video_path('old-movie.mpg'), out: out_video
  185 + assert_equal [:error, :parameters, :output, :conf], resp.keys
  186 + assert_equal '{code:0,message:}', h2s(resp[:error])
  187 + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,600k,f,ogg,acodec,libvorbis,vcodec,libtheora,\/tmp\/[^,\/]*.ogv\]$/, h2s(resp[:parameters])
  188 + assert_match /^\{in:\/[^,]*\/videos\/old-movie.mpg,out:\/tmp\/[^,\/]*.ogv,type:OGV,vbrate:600\}$/, h2s(resp[:conf])
  189 + assert File.exist? out_video
  190 + end
  191 +
  192 + should 'convert to MP4' do
  193 + out_video = mkTempName :mp4
  194 + resp = ffmpeg.convert2mp4 in: video_path('old-movie.mpg'), out: out_video
  195 + assert_equal [:error, :parameters, :output, :conf], resp.keys
  196 + assert_equal '{code:0,message:}', h2s(resp[:error])
  197 + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,600k,preset,slow,f,mp4,acodec,aac,vcodec,libx264,strict,-2,\/tmp\/[^,\/]*.mp4\]$/, h2s(resp[:parameters])
  198 + assert_match /^\{in:\/[^,]*\/videos\/old-movie.mpg,out:\/tmp\/[^,\/]*.mp4,type:MP4,vbrate:600\}$/, h2s(resp[:conf])
  199 + assert File.exist? out_video
  200 + end
  201 +
  202 + should 'convert to WebM' do
  203 + out_video = mkTempName :webm
  204 + resp = ffmpeg.convert2webm in: video_path('old-movie.mpg'), out: out_video
  205 + assert_equal [:error, :parameters, :output, :conf], resp.keys
  206 + assert_equal '{code:0,message:}', h2s(resp[:error])
  207 + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,600k,f,webm,acodec,libvorbis,vcodec,libvpx,\/tmp\/[^,\/]*.webm\]$/, h2s(resp[:parameters])
  208 + assert_match /^\{in:\/[^,]*\/videos\/old-movie.mpg,out:\/tmp\/[^,\/]*.webm,type:WEBM,vbrate:600\}$/, h2s(resp[:conf])
  209 + assert File.exist? out_video
  210 + end
  211 +
  212 + should 'convert to OGV for the web' do
  213 + resp = ffmpeg.make_ogv_for_web in: video_path('old-movie.mpg')
  214 + assert_equal [:error, :parameters, :output, :conf], resp.keys
  215 + assert_equal '{code:0,message:}', h2s(resp[:error])
  216 + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,1024k,f,ogg,acodec,libvorbis,vcodec,libtheora,s,640x426,b:a,128k,\/[^,]*\/videos\/web\/640x426_1024.ogv\]$/, h2s(resp[:parameters])
  217 + assert_match /^\/[^,]*\/videos\/web\/640x426_1024.ogv$/, resp[:conf][:out]
  218 + assert File.exist? resp[:conf][:out]
  219 + end
  220 +
  221 + should 'convert to MP4 for the web' do
  222 + resp = ffmpeg.make_mp4_for_web in: video_path('old-movie.mpg')
  223 + assert_equal [:error, :parameters, :output, :conf], resp.keys
  224 + assert_equal '{code:0,message:}', h2s(resp[:error])
  225 + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,1024k,preset,slow,f,mp4,acodec,aac,vcodec,libx264,strict,-2,s,640x426,b:a,128k,\/[^,]*\/videos\/web\/640x426_1024.mp4\]$/, h2s(resp[:parameters])
  226 + assert_match /^\/[^,]*\/videos\/web\/640x426_1024.mp4$/, resp[:conf][:out]
  227 + assert File.exist? resp[:conf][:out]
  228 + end
  229 +
  230 + should 'convert to WebM for the web' do
  231 + resp = ffmpeg.make_webm_for_web in: video_path('old-movie.mpg')
  232 + assert_equal [:error, :parameters, :output, :conf], resp.keys
  233 + assert_equal '{code:0,message:}', h2s(resp[:error])
  234 + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,1024k,f,webm,acodec,libvorbis,vcodec,libvpx,s,640x426,b:a,128k,\/[^,]*\/videos\/web\/640x426_1024.webm\]$/, h2s(resp[:parameters])
  235 + assert_match /^\/[^,]*\/videos\/web\/640x426_1024.webm$/, resp[:conf][:out]
  236 + assert File.exist? resp[:conf][:out]
  237 + end
  238 +
  239 + should 'create video thumbnail' do
  240 + resp = ffmpeg.video_thumbnail video_path('old-movie.mpg')
  241 + assert_match /^\/web\/preview_160x120.jpg$/, resp[:big]
  242 + assert_match /^\/web\/preview_107x80.jpg$/, resp[:thumb]
  243 + assert File.exist?(video_path resp[:big])
  244 + assert File.exist?(video_path resp[:thumb])
  245 + assert_match /^\/[^ ]*\/videos\/+web\/preview_160x120.jpg JPEG 160x720 /, `identify #{video_path resp[:big]}`
  246 + assert_match /^\/[^ ]*\/videos\/+web\/preview_107x80.jpg JPEG 107x480 /, `identify #{video_path resp[:thumb]}`
  247 + end
  248 +
  249 + should 'recognize ffmpeg version' do
  250 + assert_match /^[0-9]\.[0-9]\.[0-9]$/, ffmpeg.version
  251 + end
  252 +
  253 + should 'list supported formats' do
  254 + formatMpeg = /^\{demux:true,description:MPEG-1 Systems[^}]+,mux:true\}$/
  255 + formatWebM = /^\{demux:false,description:WebM,mux:true\}$/
  256 + assert_match formatMpeg, h2s(ffmpeg.formats[:mpeg])
  257 + assert_match formatWebM, h2s(ffmpeg.formats[:webm])
  258 + end
  259 +
  260 + should 'list supported codecs' do
  261 + codecOpus = /^\{decode:true,description:Opus[^,]+,direct_rendering:false,draw_horiz_band:false,encode:true,type:audio,wf_trunc:false\}$/
  262 + codecVorb = /^\{decode:true,description:Vorbis[^,]+,direct_rendering:false,draw_horiz_band:false,encode:true,type:audio,wf_trunc:false\}$/
  263 + assert_match codecOpus, h2s(ffmpeg.codecs[:opus])
  264 + assert_match codecVorb, h2s(ffmpeg.codecs[:vorbis])
  265 + end
  266 +
  267 +end
plugins/html5_video/test/unit/video_presenter_test.rb 0 → 100644
@@ -0,0 +1,193 @@ @@ -0,0 +1,193 @@
  1 +require File.dirname(__FILE__) + '/../../../../test/test_helper'
  2 +require File.dirname(__FILE__) + '/../download_fixture'
  3 +$LOAD_PATH << File.dirname(__FILE__) + '/../../lib/'
  4 +require 'html5_video_plugin.rb'
  5 +
  6 +class VideoPresenterTest < ActiveSupport::TestCase
  7 +
  8 + #include Html5VideoPlugin::Ffmpeg
  9 +
  10 + def create_video(file, mime)
  11 + file = UploadedFile.create!(
  12 + :uploaded_data => fixture_file_upload('/videos/'+file, mime),
  13 + :profile => fast_create(Person))
  14 + end
  15 +
  16 + def process_video(file)
  17 + process_delayed_job_queue
  18 + file.reload
  19 + FilePresenter::Video.new file
  20 + end
  21 +
  22 + def create_and_proc_video(file, mime)
  23 + process_video(create_video(file, mime))
  24 + end
  25 +
  26 + def setup
  27 + Environment.default.enable_plugin Html5VideoPlugin
  28 + end
  29 +
  30 + should 'accept to encapsulate a video file' do
  31 + file = create_video 'old-movie.mpg', 'video/mpeg'
  32 + assert_equal 10, FilePresenter::Video.accepts?(file)
  33 + end
  34 +
  35 + should 'retrieve meta-data' do
  36 + video = create_and_proc_video('old-movie.mpg', 'video/mpeg')
  37 + assert_equal 'Video (MPEG)', video.short_description, 'describe the file type'
  38 + assert_equivalent video.meta_data.settings.keys,
  39 + [:image_previews, :original_video, :web_versions]
  40 + assert_equivalent video.original_video.keys,
  41 + [:metadata, :type, :streams, :global_bitrate, :error, :duration, :parameters]
  42 + assert_equal h2s([:OGV, :WEBM]), h2s(video.web_versions.keys)
  43 + end
  44 +
  45 + should 'retrieve all web versions' do
  46 + video1 = create_and_proc_video('old-movie.mpg', 'video/mpeg')
  47 + video2 = create_and_proc_video('atropelamento.ogv', 'video/ogg')
  48 + # make video2 as fake valid web video:
  49 + audio = video2.original_video[:streams].find{|s| s[:type] == 'audio' }
  50 + audio = audio[:codec] = 'vorbis'
  51 + assert_equal video1.web_versions, video1.web_versions!, 'all web versions (1)'
  52 + assert_equal h2s(video2.web_versions[:OGV].merge(:orig => {
  53 + :type => :OGV,
  54 + :status => "done",
  55 + :vbrate => 334,
  56 + :size_name => "orig",
  57 + :file_name => "atropelamento.ogv",
  58 + :size => {:h=>130, :w=>208},
  59 + :original => true,
  60 + :path => video2.public_filename,
  61 + :abrate => 0 })),
  62 + h2s(video2.web_versions![:OGV]), 'all web versions (2)'
  63 + # test get the tiniest web version:
  64 + data = video1.tiniest_web_version(:OGV)
  65 + assert_equal h2s(data), h2s(
  66 + :type=>:OGV, :size_name=>"tiny", :status=>"done",
  67 + :fps=>12, :abrate=>64, :vbrate=>250, :size=>{:h=>212, :w=>320},
  68 + :path=>File.join(Rails.root,"/test/tmp/0000/#{'%04d'%video1.id}/web/tiny.ogv"),
  69 + :file_name=>"tiny.ogv" )
  70 + end
  71 +
  72 + should 'know if it has ready_web_versions' do
  73 + file = create_video 'old-movie.mpg', 'video/mpeg'
  74 + video = FilePresenter::Video.new file
  75 + assert_equal h2s(video.ready_web_versions), h2s({})
  76 + video = process_video file
  77 + web_versions = video.ready_web_versions
  78 + assert_equal 'nice.ogv', web_versions[:OGV][:nice][:file_name]
  79 + assert_equal 'tiny.ogv', web_versions[:OGV][:tiny][:file_name]
  80 + assert_equal 'nice.webm', web_versions[:WEBM][:nice][:file_name]
  81 + assert_equal 'tiny.webm', web_versions[:WEBM][:tiny][:file_name]
  82 + end
  83 +
  84 + should 'know its tiniest_web_version' do
  85 + video = create_and_proc_video 'atropelamento.ogv', 'video/ogg'
  86 + tiniestOGV = video.tiniest_web_version :OGV
  87 + tiniestWEBM = video.tiniest_web_version :WEBM
  88 + assert_equal h2s({w:208,h:130}), h2s(tiniestOGV[:size])
  89 + assert_equal :OGV, tiniestOGV[:type]
  90 + assert_equal h2s({w:208,h:130}), h2s(tiniestWEBM[:size])
  91 + assert_equal :WEBM, tiniestWEBM[:type]
  92 + assert_equal nil, video.tiniest_web_version(:MP4)
  93 + end
  94 +
  95 + should 'know if it has_ogv_version' do
  96 + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg'
  97 + assert video.has_ogv_version
  98 + end
  99 +
  100 + should 'know if it has_mp4_version' do
  101 + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg'
  102 + assert not(video.has_mp4_version), 'must NOT to list MP4'
  103 + video.web_versions[:MP4] = {SIZE: {file_name:'sized.mp4', status:'done'} }
  104 + assert video.has_mp4_version, 'must to list MP4'
  105 + end
  106 +
  107 + should 'know if it has_webm_version' do
  108 + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg'
  109 + assert video.has_webm_version
  110 + end
  111 +
  112 + should 'list its web_version_jobs' do
  113 + videoA = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg'
  114 + videoB = FilePresenter::Video.new create_video 'atropelamento.ogv', 'video/ogg'
  115 + jobA = videoA.web_version_jobs.map &:payload_object
  116 + jobB = videoB.web_version_jobs.map &:payload_object
  117 + # TODO: jobA.length must be 4.
  118 + # `Html5VideoPlugin::uploaded_file_after_create_callback` is been called two times
  119 + #assert_equal 4, jobA.length
  120 + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobA[0].class
  121 + assert_equal :OGV, jobA[0].format
  122 + assert_equal :tiny, jobA[0].size
  123 + assert_match /.*\/old-movie.mpg$/, jobA[0].full_filename
  124 + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobA[1].class
  125 + assert_equal :WEBM, jobA[1].format
  126 + assert_equal :tiny, jobA[1].size
  127 + assert_match /.*\/old-movie.mpg$/, jobA[1].full_filename
  128 + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobA[2].class
  129 + assert_equal :OGV, jobA[2].format
  130 + assert_equal :nice, jobA[2].size
  131 + assert_match /.*\/old-movie.mpg$/, jobA[1].full_filename
  132 + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobA[3].class
  133 + assert_equal :WEBM, jobA[3].format
  134 + assert_equal :nice, jobA[3].size
  135 + assert_match /.*\/old-movie.mpg$/, jobA[1].full_filename
  136 + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobB[0].class
  137 + assert_equal :OGV, jobB[0].format
  138 + assert_equal :tiny, jobB[0].size
  139 + assert_match /.*\/atropelamento.ogv$/, jobB[0].full_filename
  140 + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobB[1].class
  141 + assert_equal :WEBM, jobB[1].format
  142 + assert_equal :tiny, jobB[1].size
  143 + assert_match /.*\/atropelamento.ogv$/, jobB[1].full_filename
  144 + end
  145 +
  146 + should 'list its web_preview_jobs' do
  147 + videoA = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg'
  148 + videoB = FilePresenter::Video.new create_video 'atropelamento.ogv', 'video/ogg'
  149 + jobA = videoA.web_preview_jobs.map &:payload_object
  150 + jobB = videoB.web_preview_jobs.map &:payload_object
  151 + # TODO: jobA.length must be 1.
  152 + # `Html5VideoPlugin::uploaded_file_after_create_callback` is been called two times
  153 + #assert_equal 1, jobA.length
  154 + assert_equal Html5VideoPlugin::CreateVideoPreviewJob, jobA[0].class
  155 + assert_match /.*\/old-movie.mpg$/, jobA[0].full_filename
  156 + assert_equal Html5VideoPlugin::CreateVideoPreviewJob, jobB[0].class
  157 + assert_match /.*\/atropelamento.ogv$/, jobB[0].full_filename
  158 + end
  159 +
  160 + should 'know if it has_previews' do
  161 + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg'
  162 + assert video.has_previews?
  163 + end
  164 +
  165 + should 'list its image previews' do
  166 + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg'
  167 + assert_equal h2s(big:'/web/preview_160x120.jpg', thumb:'/web/preview_107x80.jpg'), h2s(video.previews)
  168 + end
  169 +
  170 + should 'set its image previews' do
  171 + video = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg'
  172 + assert_equal nil, video.previews
  173 + video.previews = {big:'big.jpg', thumb:'thumb.jpg'}
  174 + assert_equal h2s(big:'big.jpg', thumb:'thumb.jpg'), h2s(video.previews)
  175 + end
  176 +
  177 + should 'get image_preview for a processed video' do
  178 + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg'
  179 + assert_match /\/[0-9]+\/web\/preview_160x120\.jpg/, video.image_preview(:big)
  180 + end
  181 +
  182 + should 'get default image_preview for non processed video' do
  183 + video = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg'
  184 + assert_match /\/html5_video\/images\/video-preview-big\.png/, video.image_preview(:big)
  185 + end
  186 +
  187 + should 'list its conversion_errors' do
  188 + video = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg'
  189 + video.web_versions[:MP4] = {nice: {status:'error converting', error:{code:-99,message:'some error',output:'abcde'}} }
  190 + assert_equal h2s(MP4:{nice:{code:-99,message:'some error',output:'abcde'}}), h2s(video.conversion_errors)
  191 + end
  192 +
  193 +end
plugins/html5_video/views/content_viewer/_video_player.html.erb 0 → 100644
@@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
  1 +<div class="video-box">
  2 + <%
  3 + if video
  4 + video_ogv = video.tiniest_web_version :OGV
  5 + video_mp4 = video.tiniest_web_version :MP4
  6 + video_webm = video.tiniest_web_version :WEBM
  7 + video_ogv_path = video_ogv ? video_ogv[:path] : ''
  8 + video_mp4_path = video_mp4 ? video_mp4[:path] : ''
  9 + video_webm_path = video_webm ? video_webm[:path] : ''
  10 + embed_video_path = video_webm ? video_webm_path : \
  11 + video_ogv ? video_ogv_path : video_mp4_path
  12 + end
  13 + %>
  14 + <video class="article-video-player"
  15 + style="width:100%; height:auto;"
  16 + preload="metadata"
  17 + controls="controls">
  18 + <% if video_webm %><source src="<%= video_webm_path %>" type="video/webm" /><% end %>
  19 + <% if video_ogv %><source src="<%= video_ogv_path %>" type="video/ogg" /><% end %>
  20 + <% if video_mp4 %><source src="<%= video_mp4_path %>" type="video/mp4" /><% end %>
  21 + <strong><%= _('Sorry, your browser doesn&rsquo;t support video.') %></strong>
  22 + <br/> <%= _('Please try the new %s or %s.') % [
  23 + '<a href="http://getfirefox.com">Firefox</a>',
  24 + '<a href="http://www.chromium.org/Home">Chromium</a>' ] %>
  25 + </video>
  26 + <div class="video-ctrl">
  27 + <a href="<%= video ? video.public_filename : '' %>" class="download-bt"><%=_('Download')%></a>
  28 + </div>
  29 +</div><!-- video-box -->
plugins/html5_video/views/content_viewer/video_channel.html.erb 0 → 100644
@@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
  1 +<div>
  2 + <%= @page.body %>
  3 +</div>
  4 +
  5 +<div class="video-channel">
  6 + <% if ! @page.children.all.find {|f| f.content_type =~ /^video\//} %>
  7 + <p><em><%= _('This channel contains no videos yet') %></em></p>
  8 + <% else %>
  9 + <div class="video-player">
  10 + <h2></h2>
  11 + <%= render :partial => 'video_player', :locals => {:video => nil} %>
  12 + <div class="video-player-info">
  13 + <div class="quality">
  14 + <strong><%=_('Quality options')%>:</strong>
  15 + <ul></ul>
  16 + </div>
  17 + <div class="tags">
  18 + <strong><%=_('Tags')%>:</strong>
  19 + <div class="data"></div>
  20 + </div>
  21 + <div class="abstract">
  22 + <strong><%=_('Description')%>:</strong>
  23 + <div class="data"></div>
  24 + </div>
  25 + </div>
  26 + <br style="clear: both"/>
  27 + </div>
  28 + <ul class="video-list">
  29 + <% unconverted_videos = []
  30 + @page.children.
  31 + map{|f| FilePresenter.for f }.
  32 + select{|f| f.class == FilePresenter::Video}.
  33 + sort_by{|f| - f.created_at.to_i}.each do |f|
  34 + unless f.has_web_version
  35 + unconverted_videos << f
  36 + else %>
  37 + <li class="video-list-item" title="<%=html_escape f.title%>">
  38 + <%= link_to(
  39 + content_tag('strong',
  40 + f.title.
  41 + gsub(/([a-z0-9])_+([a-z0-9])/i, '\1 \2').
  42 + gsub(/\.[a-z0-9]{2,4}$/i, '')
  43 + ),
  44 + f.view_url,
  45 + {
  46 + 'data-poster' => f.image_preview(:big),
  47 + 'data-download' => f.public_filename,
  48 + 'data-webversions' => CGI::escapeHTML(f.web_versions!.to_json),
  49 + :style => "background-image:url(#{f.image_preview(:big)})"
  50 + }) %>
  51 + <ul>
  52 + <li class="vli-data-tags">
  53 + <strong>tags:</strong>
  54 + <div><%= linked_article_tags f %></div>
  55 + </li>
  56 + </ul>
  57 + <div class="abstract"><%= f.abstract %></div>
  58 + </li>
  59 + <% end end %>
  60 + </ul>
  61 + <% end %>
  62 +
  63 + <% if unconverted_videos && !unconverted_videos.empty? %>
  64 + <div class="unconverted-videos">
  65 + <p onclick="jQuery('ul',this.parentNode).slideToggle()">
  66 + <span><%= n_(
  67 + 'This channel has one video waiting to be converted',
  68 + 'This channel has %d videos waiting to be converted',
  69 + unconverted_videos.length
  70 + ) % unconverted_videos.length
  71 + %></span>
  72 + </p>
  73 + <ul style="display:none">
  74 + <% unconverted_videos.each do |f| %>
  75 + <li><%= link_to f.title, f.view_url %></li>
  76 + <% end %>
  77 + </ul>
  78 + </div>
  79 + <% end %>
  80 +
  81 + <% if @page.children.all.find {|f| f.content_type !~ /^video\//} %>
  82 + <div class="non-video-list">
  83 + <h3><%=_('Non video files')%></h3>
  84 + <%= list_articles( @page.children.all.select {|f| f.content_type !~ /^video\// } ) %>
  85 + </div>
  86 + <% end %>
  87 +
  88 +</div>
  89 +
  90 +<script type="text/javascript">
  91 + new VideoChannel( jQuery(".video-channel").last() );
  92 +</script>
  93 +
plugins/html5_video/views/file_presenter/_video.html.erb
1 -<video class="video-js vjs-default-skin" controls poster="video.jpg" preload="auto" data-setup="{}">  
2 - <source type="video/ogg" src="<%= video.public_filename %>"/>  
3 -</video> 1 +<% pub_path = __FILE__.sub /.*(\/plugins\/[^\/]+\/).*/, '\1' %>
  2 +<div class="video-player">
  3 + <% if video.has_web_version %>
  4 + <%= render :partial => 'video_player', :locals => {:video => video} %>
  5 + <% elsif not video.web_version_jobs.empty? %>
  6 + <div class="message">
  7 + <%=_('Queued to generate the web version. Come back soon.')%>
  8 + </div>
  9 + <% else %>
  10 + <div class="message">
  11 + <%=_('This video is not queued to the video conversor. Contact the site admin.')%>
  12 + </div>
  13 + <% end %>
  14 + <div class="video-player-info">
  15 + <div class="quality"><strong><%=_('Quality options')%>:</strong> <ul></ul></div>
  16 + <div class="abstract">
  17 + <strong><%=_('Description')%>:</strong>
  18 + <div class="data"><%= video.abstract %></div>
  19 + </div>
  20 + </div><!-- class="video-player-info" -->
  21 + <br style="clear: both"/>
  22 +</div><!-- class="video-player" -->
4 23
5 -<div class="uploaded-file-description <%= 'empty' if video.abstract.blank? %>">  
6 - <%= video.abstract %>  
7 -</div> 24 +<% if video.has_web_version %>
  25 + <script type="text/javascript">
  26 + var player = new NoosferoVideoPlayer( jQuery(".article-body").last() );
  27 + player.videoList = <%= video.web_versions!.to_json %>;
  28 + player.selectWebVersion();
  29 + </script>
  30 +<% end %>
8 31
  32 +<% if video.allow_edit?(user) && !video.conversion_errors.blank? %>
  33 +<div class="conversion-error">
  34 +<h2><%=_('Video conversion errors')%></h2>
  35 +<ul>
  36 + <% video.conversion_errors.each do |type, type_block| %>
  37 + <% type_block.each do |size, size_block| %>
  38 + <%
  39 + message, code, output = size_block[:message], size_block[:code], size_block[:output]
  40 + # hide version header to non admins
  41 + message.sub!(/^([^\n]*\n){2}/,'') unless user.is_admin?
  42 + %>
  43 + <li>
  44 + <h3><%= _('Error while converting %{orig_type} to %{new_type}, %{size} size.') % {
  45 + :orig_type=>video.content_type.split('/')[1], :new_type=>type, :size=>size
  46 + } %></h3>
  47 + <%= _('Code %s') % content_tag('strong',code) +' &mdash; '+ message.to_s %>
  48 + <pre class="output" style="display:none"><%= output %></pre>
  49 + <%= link_to _('display full output'), '#',
  50 + :class => 'show-output',
  51 + :onclick => 'jQuery(".output",this.parentNode).show(); jQuery(this).hide(); return false'
  52 + %>
  53 + </li>
  54 + <% end %>
  55 + <% end %>
  56 +</ul>
  57 +</div>
  58 +<% end %>
test/test_helper.rb
@@ -137,6 +137,18 @@ class ActiveSupport::TestCase @@ -137,6 +137,18 @@ class ActiveSupport::TestCase
137 assert reference.blank?, "The following elements are not in the collection: #{reference.inspect}" 137 assert reference.blank?, "The following elements are not in the collection: #{reference.inspect}"
138 end 138 end
139 139
  140 + def h2s(value) # make a string from ordered hash to simplify tests
  141 + case value
  142 + when Hash, HashWithIndifferentAccess
  143 + '{'+ value.stringify_keys.to_a.sort{|a,b|a[0]<=>b[0]}.map{ |k,v| k+':'+h2s(v) }.join(',') +'}'
  144 + when Array
  145 + '['+ value.map{|i|h2s(i)}.join(',') +']'
  146 + when NilClass
  147 + '<nil>'
  148 + else value.to_s
  149 + end
  150 + end
  151 +
140 # For models that render views (blocks, articles, ...) 152 # For models that render views (blocks, articles, ...)
141 def self.action_view 153 def self.action_view
142 @action_view ||= begin 154 @action_view ||= begin
test/unit/plugin_hot_spot_test.rb
@@ -17,9 +17,13 @@ class PluginHotSpotTest &lt; ActiveSupport::TestCase @@ -17,9 +17,13 @@ class PluginHotSpotTest &lt; ActiveSupport::TestCase
17 17
18 Noosfero::Plugin::HotSpot::CALLBACK_HOTSPOTS.each do |callback| 18 Noosfero::Plugin::HotSpot::CALLBACK_HOTSPOTS.each do |callback|
19 should "call #{callback} hotspot" do 19 should "call #{callback} hotspot" do
20 - class CoolPlugin < Noosfero::Plugin; end 20 + class CoolPlugin < Noosfero::Plugin
  21 + include Noosfero::Plugin::HotSpot
  22 + end
21 23
22 - Noosfero::Plugin.stubs(:all).returns([CoolPlugin.name]) 24 + CoolPlugin.any_instance.stubs("comment_#{callback}_callback".to_sym).returns(";)")
  25 +
  26 + Noosfero::Plugin.stubs(:all).returns(['PluginHotSpotTest::CoolPlugin'])
23 Environment.default.enable_plugin(CoolPlugin) 27 Environment.default.enable_plugin(CoolPlugin)
24 CoolPlugin.any_instance.expects("comment_#{callback}_callback".to_sym) 28 CoolPlugin.any_instance.expects("comment_#{callback}_callback".to_sym)
25 29