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 67 end.join("\n").html_safe
68 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 83 end
... ...
app/models/uploaded_file.rb
... ... @@ -9,6 +9,12 @@ class UploadedFile < Article
9 9  
10 10 attr_accessible :uploaded_data, :title
11 11  
  12 + include Noosfero::Plugin::HotSpot
  13 +
  14 + def environment
  15 + profile.environment
  16 + end
  17 +
12 18 def self.type_name
13 19 _('File')
14 20 end
... ...
features/step_definitions/noosfero_steps.rb
... ... @@ -118,6 +118,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f
118 118 language = item.delete("language")
119 119 category = item.delete("category")
120 120 filename = item.delete("filename")
  121 + mime = item.delete("mime") || 'binary/octet-stream'
121 122 translation_of_id = nil
122 123 if item["translation_of"]
123 124 if item["translation_of"] != "nil"
... ... @@ -131,7 +132,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f
131 132 :language => language,
132 133 :translation_of_id => translation_of_id)
133 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 136 end
136 137 result = klass.new(item)
137 138 if !parent.blank?
... ... @@ -366,6 +367,10 @@ Then /^The page should not contain "(.*)"$/ do |selector|
366 367 page.should have_no_css("#{selector}")
367 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 374 Given /^the mailbox is empty$/ do
370 375 ActionMailer::Base.deliveries = []
371 376 end
... ... @@ -653,6 +658,15 @@ Given /^the environment is configured to (.*) after signup$/ do |option|
653 658 environment.save
654 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 670 When /^wait for the captcha signup time$/ do
657 671 environment = Environment.default
658 672 sleep environment.min_signup_delay + 1
... ... @@ -671,3 +685,7 @@ Given /^the field (.*) is public for all users$/ do |field|
671 685 person.save!
672 686 end
673 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 124 Dir.glob(File.join('app', 'presenters', '*.rb')) do |file|
125 125 load file
126 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 10 end
11 11  
12 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 15 end
15 16  
16 17 def method_missing(method, *args, &block)
... ...
plugins/html5_video/features/video_player.feature 0 → 100644
... ... @@ -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   -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 1 class Html5VideoPlugin < Noosfero::Plugin
2 2  
3   - FilePresenter::Video
4   -
5 3 def self.plugin_name
6 4 "HTML5 Video"
7 5 end
8 6  
  7 + def stylesheet?
  8 + true
  9 + end
  10 +
  11 + def js_files
  12 + ['video-channel.js']
  13 + end
  14 +
9 15 def self.plugin_description
10 16 _("A plugin to enable the video suport, with auto conversion for the web.")
11 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 54 end
... ...
plugins/html5_video/lib/html5_video_plugin/create_video_for_web_job.rb 0 → 100644
... ... @@ -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 @@
  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 @@
  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 @@
  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 @@
  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 6 #, fuzzy
7 7 msgid ""
8 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 13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 14 "Language-Team: LANGUAGE <LL@li.org>\n"
14 15 "Language: \n"
... ... @@ -17,10 +18,128 @@ msgstr &quot;&quot;
17 18 "Content-Transfer-Encoding: 8bit\n"
18 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 76 msgid "Video (%s)"
22 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 145 msgstr ""
... ...
plugins/html5_video/po/pt/html5_video.po
... ... @@ -12,11 +12,11 @@
12 12 msgid ""
13 13 msgstr ""
14 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 16 "PO-Revision-Date: 2014-12-18 18:40-0200\n"
17 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 20 "Language: pt\n"
21 21 "MIME-Version: 1.0\n"
22 22 "Content-Type: text/plain; charset=UTF-8\n"
... ... @@ -24,11 +24,128 @@ msgstr &quot;&quot;
24 24 "Plural-Forms: nplurals=2; plural=n != 1;\n"
25 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 82 msgid "Video (%s)"
29 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 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 @@
  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 @@
  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 @@
  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 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 10 class ContentViewerControllerTest < ActionController::TestCase
5 11  
6 12 all_fixtures
... ... @@ -10,11 +16,13 @@ class ContentViewerControllerTest &lt; ActionController::TestCase
10 16  
11 17 @profile = create_user('testinguser').person
12 18 @environment = @profile.environment
  19 + @environment.enable_plugin(Html5VideoPlugin)
13 20 end
14 21 attr_reader :profile, :environment
15 22  
16 23 should 'add html5 video tag to the page of file type video' do
17 24 file = UploadedFile.create!(:uploaded_data => fixture_file_upload('/files/test.txt', 'video/ogg'), :profile => profile)
  25 + process_delayed_job_queue
18 26 get :view_page, file.url.merge(:view=>:true)
19 27 assert_select '#article video'
20 28 end
... ...
plugins/html5_video/test/unit/create_video_for_web_job_test.rb 0 → 100755
... ... @@ -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 @@
  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 @@
  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 @@
  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 @@
  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 @@
  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 137 assert reference.blank?, "The following elements are not in the collection: #{reference.inspect}"
138 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 152 # For models that render views (blocks, articles, ...)
141 153 def self.action_view
142 154 @action_view ||= begin
... ...
test/unit/plugin_hot_spot_test.rb
... ... @@ -17,9 +17,13 @@ class PluginHotSpotTest &lt; ActiveSupport::TestCase
17 17  
18 18 Noosfero::Plugin::HotSpot::CALLBACK_HOTSPOTS.each do |callback|
19 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 27 Environment.default.enable_plugin(CoolPlugin)
24 28 CoolPlugin.any_instance.expects("comment_#{callback}_callback".to_sym)
25 29  
... ...