Commit 7bd6c823e810cca77805d426b2a471c3e7dcc97c
Exists in
staging
and in
3 other branches
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
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) | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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 "" |
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’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 "" |
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’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." | ... | ... |
21.3 KB
14.3 KB
... | ... | @@ -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) } | ... | ... |
... | ... | @@ -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)); | ... | ... |
... | ... | @@ -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 < 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 | ... | ... |
... | ... | @@ -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 | ... | ... |
... | ... | @@ -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’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) +' — '+ 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 < 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 | ... | ... |