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,4 +67,17 @@ module TagsHelper | ||
67 | end.join("\n").html_safe | 67 | end.join("\n").html_safe |
68 | end | 68 | end |
69 | 69 | ||
70 | + def linked_article_tags(article) | ||
71 | + if @profile | ||
72 | + # We are rendering a page inside a profile, so link to the profile tag search. | ||
73 | + url = { :controller => 'profile', :profile => @profile.identifier, :action => 'tags' } | ||
74 | + tagname_option = :id | ||
75 | + else | ||
76 | + # We are rendering a page outside a profile, so link to the global tag search. | ||
77 | + url = { :action => 'tag' } | ||
78 | + tagname_option = :tag | ||
79 | + end | ||
80 | + article.tags.map { |t| link_to(t, url.merge(tagname_option=>t.name) ) }.join("\n") | ||
81 | + end | ||
82 | + | ||
70 | end | 83 | end |
app/models/uploaded_file.rb
@@ -9,6 +9,12 @@ class UploadedFile < Article | @@ -9,6 +9,12 @@ class UploadedFile < Article | ||
9 | 9 | ||
10 | attr_accessible :uploaded_data, :title | 10 | attr_accessible :uploaded_data, :title |
11 | 11 | ||
12 | + include Noosfero::Plugin::HotSpot | ||
13 | + | ||
14 | + def environment | ||
15 | + profile.environment | ||
16 | + end | ||
17 | + | ||
12 | def self.type_name | 18 | def self.type_name |
13 | _('File') | 19 | _('File') |
14 | end | 20 | end |
features/step_definitions/noosfero_steps.rb
@@ -118,6 +118,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f | @@ -118,6 +118,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f | ||
118 | language = item.delete("language") | 118 | language = item.delete("language") |
119 | category = item.delete("category") | 119 | category = item.delete("category") |
120 | filename = item.delete("filename") | 120 | filename = item.delete("filename") |
121 | + mime = item.delete("mime") || 'binary/octet-stream' | ||
121 | translation_of_id = nil | 122 | translation_of_id = nil |
122 | if item["translation_of"] | 123 | if item["translation_of"] |
123 | if item["translation_of"] != "nil" | 124 | if item["translation_of"] != "nil" |
@@ -131,7 +132,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f | @@ -131,7 +132,7 @@ Given /^the following (articles|events|blogs|folders|forums|galleries|uploaded f | ||
131 | :language => language, | 132 | :language => language, |
132 | :translation_of_id => translation_of_id) | 133 | :translation_of_id => translation_of_id) |
133 | if !filename.blank? | 134 | if !filename.blank? |
134 | - item.merge!(:uploaded_data => fixture_file_upload("/files/#{filename}", 'binary/octet-stream')) | 135 | + item.merge!(:uploaded_data => fixture_file_upload("/files/#{filename}", mime)) |
135 | end | 136 | end |
136 | result = klass.new(item) | 137 | result = klass.new(item) |
137 | if !parent.blank? | 138 | if !parent.blank? |
@@ -366,6 +367,10 @@ Then /^The page should not contain "(.*)"$/ do |selector| | @@ -366,6 +367,10 @@ Then /^The page should not contain "(.*)"$/ do |selector| | ||
366 | page.should have_no_css("#{selector}") | 367 | page.should have_no_css("#{selector}") |
367 | end | 368 | end |
368 | 369 | ||
370 | +Then /^The page should contain only (\d+) "(.*)"$/ do |count, selector| | ||
371 | + page.should have_css(selector, :count => count) | ||
372 | +end | ||
373 | + | ||
369 | Given /^the mailbox is empty$/ do | 374 | Given /^the mailbox is empty$/ do |
370 | ActionMailer::Base.deliveries = [] | 375 | ActionMailer::Base.deliveries = [] |
371 | end | 376 | end |
@@ -653,6 +658,15 @@ Given /^the environment is configured to (.*) after signup$/ do |option| | @@ -653,6 +658,15 @@ Given /^the environment is configured to (.*) after signup$/ do |option| | ||
653 | environment.save | 658 | environment.save |
654 | end | 659 | end |
655 | 660 | ||
661 | +When /^I click "(.*?)"$/ do |selector| | ||
662 | + find(selector).click | ||
663 | +end | ||
664 | + | ||
665 | +Then /^the element "(.*)" has class "(.*)"$/ do |el_selector, el_class| | ||
666 | + class_list = find(el_selector)[:class].split(' ') | ||
667 | + class_list.should include(el_class) | ||
668 | +end | ||
669 | + | ||
656 | When /^wait for the captcha signup time$/ do | 670 | When /^wait for the captcha signup time$/ do |
657 | environment = Environment.default | 671 | environment = Environment.default |
658 | sleep environment.min_signup_delay + 1 | 672 | sleep environment.min_signup_delay + 1 |
@@ -671,3 +685,7 @@ Given /^the field (.*) is public for all users$/ do |field| | @@ -671,3 +685,7 @@ Given /^the field (.*) is public for all users$/ do |field| | ||
671 | person.save! | 685 | person.save! |
672 | end | 686 | end |
673 | end | 687 | end |
688 | + | ||
689 | +When(/^I press "(.*?)" by selector$/) do |selector| | ||
690 | + page.execute_script("jQuery('#{selector}').click();") | ||
691 | +end |
lib/file_presenter.rb
@@ -124,3 +124,8 @@ end | @@ -124,3 +124,8 @@ end | ||
124 | Dir.glob(File.join('app', 'presenters', '*.rb')) do |file| | 124 | Dir.glob(File.join('app', 'presenters', '*.rb')) do |file| |
125 | load file | 125 | load file |
126 | end | 126 | end |
127 | + | ||
128 | +# Preload FilePresenters from plugins to allow `FilePresenter.for()` to work | ||
129 | +Dir.glob(File.join('plugins', '*', 'lib', 'presenters', '*.rb')) do |file| | ||
130 | + load file | ||
131 | +end |
lib/noosfero/plugin/settings.rb
@@ -10,7 +10,8 @@ class Noosfero::Plugin::Settings | @@ -10,7 +10,8 @@ class Noosfero::Plugin::Settings | ||
10 | end | 10 | end |
11 | 11 | ||
12 | def settings | 12 | def settings |
13 | - @base.settings["#{@plugin.public_name}_plugin".to_sym] ||= {} | 13 | + settings_field = @base.class.settings_field |
14 | + @base.send(settings_field)["#{@plugin.public_name}_plugin".to_sym] ||= {} | ||
14 | end | 15 | end |
15 | 16 | ||
16 | def method_missing(method, *args, &block) | 17 | def method_missing(method, *args, &block) |
@@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
1 | +Feature: video player | ||
2 | + As a noosfero visitor | ||
3 | + I want to view a video page and play it | ||
4 | + | ||
5 | + Background: | ||
6 | + Given plugin Html5Video is enabled on environment | ||
7 | + And the following users | ||
8 | + | login | name | | ||
9 | + | joaosilva | Joao Silva | | ||
10 | + And the following uploaded files | ||
11 | + | owner | filename | mime | | ||
12 | + | joaosilva | ../videos/old-movie.mpg | video/mpeg | | ||
13 | + And there are no pending jobs | ||
14 | + | ||
15 | + @selenium | ||
16 | + Scenario: controls must work | ||
17 | + Given I am on /joaosilva/old-movie.mpg?view=true | ||
18 | + Then The page should contain only 2 ".video-player .quality li.ui-button" | ||
19 | + When I click ".video-player .video-box .zoom" | ||
20 | + Then the element ".video-player" has class "zoom-in" |
plugins/html5_video/lib/file_presenter/video.rb
@@ -1,10 +0,0 @@ | @@ -1,10 +0,0 @@ | ||
1 | -class FilePresenter::Video < FilePresenter | ||
2 | - def self.accepts?(f) | ||
3 | - return nil if !f.respond_to?(:content_type) || f.content_type.nil? | ||
4 | - ( f.content_type[0..4] == 'video' ) ? 10 : nil | ||
5 | - end | ||
6 | - | ||
7 | - def short_description | ||
8 | - _('Video (%s)') % content_type.split('/')[1].upcase | ||
9 | - end | ||
10 | -end |
plugins/html5_video/lib/html5_video_plugin.rb
1 | class Html5VideoPlugin < Noosfero::Plugin | 1 | class Html5VideoPlugin < Noosfero::Plugin |
2 | 2 | ||
3 | - FilePresenter::Video | ||
4 | - | ||
5 | def self.plugin_name | 3 | def self.plugin_name |
6 | "HTML5 Video" | 4 | "HTML5 Video" |
7 | end | 5 | end |
8 | 6 | ||
7 | + def stylesheet? | ||
8 | + true | ||
9 | + end | ||
10 | + | ||
11 | + def js_files | ||
12 | + ['video-channel.js'] | ||
13 | + end | ||
14 | + | ||
9 | def self.plugin_description | 15 | def self.plugin_description |
10 | _("A plugin to enable the video suport, with auto conversion for the web.") | 16 | _("A plugin to enable the video suport, with auto conversion for the web.") |
11 | end | 17 | end |
12 | 18 | ||
19 | + def content_types | ||
20 | + [Html5VideoPlugin::VideoChannel] | ||
21 | + end | ||
22 | + | ||
23 | + def view_page_layout(controller, page) | ||
24 | + if FilePresenter.for(page).is_a? FilePresenter::Video and controller.params[:display] == 'iframe' | ||
25 | + 'html5_video_plugin_iframe' | ||
26 | + end | ||
27 | + end | ||
28 | + | ||
29 | + def uploaded_file_after_create_callback(uploaded_file) | ||
30 | + full_filename = uploaded_file.full_filename | ||
31 | + file_presenter = FilePresenter.for(uploaded_file) | ||
32 | + if file_presenter.is_a? FilePresenter::Video | ||
33 | + job = Html5VideoPlugin::CreateVideoPreviewJob.new | ||
34 | + job.file_type = uploaded_file.class.name | ||
35 | + job.file_id = uploaded_file.id | ||
36 | + job.full_filename = full_filename | ||
37 | + Delayed::Job.enqueue job, priority: 10 | ||
38 | + [ | ||
39 | + [:OGV, :tiny, 11], | ||
40 | + [:WEBM, :tiny, 12], | ||
41 | + [:OGV, :nice, 13], | ||
42 | + [:WEBM, :nice, 14], | ||
43 | + ].each do |format, size, priority| | ||
44 | + job = Html5VideoPlugin::CreateVideoForWebJob.new | ||
45 | + job.file_type = uploaded_file.class.name | ||
46 | + job.file_id = uploaded_file.id | ||
47 | + job.full_filename = full_filename | ||
48 | + job.format = format | ||
49 | + job.size = size | ||
50 | + Delayed::Job.enqueue job, priority: priority | ||
51 | + end | ||
52 | + end | ||
53 | + end | ||
13 | end | 54 | end |
plugins/html5_video/lib/html5_video_plugin/create_video_for_web_job.rb
0 → 100644
@@ -0,0 +1,161 @@ | @@ -0,0 +1,161 @@ | ||
1 | +class Html5VideoPlugin::CreateVideoForWebJob | ||
2 | + | ||
3 | + # TODO: we must to add timeout to ffmpeg to allow this object to set this on error: | ||
4 | + # @video.video_info[:conversion] = :timeout | ||
5 | + # TODO: must remove web folder when delete the video | ||
6 | + | ||
7 | + attr_accessor :file_type, :file_id, :full_filename, :format, :size | ||
8 | + | ||
9 | + def perform | ||
10 | + return unless file_type.constantize.exists?(file_id) | ||
11 | + @ffmpeg = Html5VideoPlugin::Ffmpeg.new | ||
12 | + | ||
13 | + @video = FilePresenter.for file_type.constantize.find(file_id) | ||
14 | + throw "Expected file #{file_id} to be a video" unless @video.is_a?(FilePresenter::Video) | ||
15 | + | ||
16 | + @video_file = full_filename | ||
17 | + | ||
18 | + @info = @ffmpeg.get_video_info @video_file | ||
19 | + | ||
20 | + if @info[:error][:code] == 0 | ||
21 | + @video.original_video = @info.except :output | ||
22 | + register_conv_status 'started' | ||
23 | + @video.save! | ||
24 | + else | ||
25 | + register_conv_status 'error reading' | ||
26 | + register_conv_error @info[:error] | ||
27 | + @video.save! | ||
28 | + Rails.logger.error "FFmpeg ERROR while reading '#{@video_file}': #{@info[:error][:message]}" | ||
29 | + return | ||
30 | + end | ||
31 | + | ||
32 | + @orig_size = @info.video_stream[0][:size] | ||
33 | + @brate = @info.video_stream[0][:bitrate] || @info[:global_bitrate] || 400 | ||
34 | + | ||
35 | + convert_to_tiny if size == :tiny | ||
36 | + convert_to_nice if size == :nice | ||
37 | + @video.save! | ||
38 | + 1 | ||
39 | + end | ||
40 | + | ||
41 | + def register_conv_status( status ) | ||
42 | + @video.web_versions[format] ||= {} | ||
43 | + @video.web_versions[format][size] ||= {} | ||
44 | + @video.web_versions[format][size][:status] = status | ||
45 | + @video.save! | ||
46 | + end | ||
47 | + | ||
48 | + def register_conv_conf( conf ) | ||
49 | + @video.web_versions[format] ||= {} | ||
50 | + @video.web_versions[format][size] ||= {} | ||
51 | + @video.web_versions[format][size].merge! conf | ||
52 | + @video.save! | ||
53 | + end | ||
54 | + | ||
55 | + def register_conv_error( error ) | ||
56 | + @video.web_versions[format] ||= {} | ||
57 | + @video.web_versions[format][size] ||= {} | ||
58 | + @video.web_versions[format][size][:error] = error | ||
59 | + @video.save! | ||
60 | + end | ||
61 | + | ||
62 | + def is_ogv | ||
63 | + @info[:type] == 'ogv' && | ||
64 | + @info.video_stream[0][:codec] == 'theora' && | ||
65 | + @info.audio_stream[0][:codec] == 'vorbis' | ||
66 | + end | ||
67 | + | ||
68 | + def is_mp4 | ||
69 | + @info[:type] == 'mp4' && | ||
70 | + @info.video_stream[0][:codec] == 'libx264' && | ||
71 | + @info.audio_stream[0][:codec] == 'libfaac' | ||
72 | + end | ||
73 | + | ||
74 | + def is_webm | ||
75 | + @info[:type] == 'webm' && | ||
76 | + @info.video_stream[0][:codec] == 'libvpx' | ||
77 | + end | ||
78 | + | ||
79 | + def register_conversion response | ||
80 | + conf = response[:conf].clone | ||
81 | + if response[:error][:code] == 0 | ||
82 | + conf[:path] = conf[:out].sub /^.*(\/articles)/, '\1' | ||
83 | + conf.delete :in | ||
84 | + conf.delete :out | ||
85 | + register_conv_conf conf | ||
86 | + register_conv_status 'done' | ||
87 | + @video.save! | ||
88 | + else | ||
89 | + register_conv_status 'error converting' | ||
90 | + register_conv_conf conf | ||
91 | + error = response[:error].clone | ||
92 | + error[:output] = response[:output] | ||
93 | + register_conv_error error | ||
94 | + Rails.logger.error "FFmpeg ERROR while converting '#{conf[:in]}' to #{conf[:type]}: #{response[:error][:message]}" | ||
95 | + end | ||
96 | + end | ||
97 | + | ||
98 | + def is_big | ||
99 | + @orig_size[:w] > 400 || @brate >= 400 | ||
100 | + end | ||
101 | + | ||
102 | + def is_toobig | ||
103 | + @orig_size[:w] > 600 | ||
104 | + end | ||
105 | + | ||
106 | + # The smaller version for slow connections | ||
107 | + def convert_to_tiny | ||
108 | + audio_stream = @video.original_video[:streams].find{|s| s[:type] == 'audio'} | ||
109 | + abrate = audio_stream.nil? ? 64 : audio_stream[:bitrate] || 64 | ||
110 | + abrate = 64 if abrate > 64 | ||
111 | + conf = { :size_name=>'tiny', :in=>@video_file, | ||
112 | + :fps=>12, :vbrate=>250, :abrate=>abrate } | ||
113 | + if is_big | ||
114 | + # Low weight video dimension for each Aspect Ratio: | ||
115 | + # * 320x240 for 4:3 | ||
116 | + # * 320x180 for 16:9 | ||
117 | + # This are Best and Good values with the same width based on this page: | ||
118 | + # http://www.flashsupport.com/books/fvst/files/tools/video_sizes.html | ||
119 | + h = ( 320.0 / (@orig_size[:w].to_f/@orig_size[:h].to_f) ).round | ||
120 | + h -= 1 if h % 2 == 1 | ||
121 | + size = { :w=>320, :h=>h } | ||
122 | + conf[:size] = size | ||
123 | + end | ||
124 | + if format == :OGV && ( is_big || ! is_ogv ) | ||
125 | + conf[:file_name] = 'tiny.ogv' | ||
126 | + register_conversion @ffmpeg.make_ogv_for_web(conf) | ||
127 | + end | ||
128 | + if format == :WEBM && ( is_big || ! is_webm ) | ||
129 | + conf[:file_name] = 'tiny.webm' | ||
130 | + register_conversion @ffmpeg.make_webm_for_web(conf) | ||
131 | + end | ||
132 | + end | ||
133 | + | ||
134 | + # The nicer common version | ||
135 | + def convert_to_nice | ||
136 | + if is_toobig | ||
137 | + # Max video dimension for each Aspect Ratio: | ||
138 | + # * 576x432 for 4:3 | ||
139 | + # * 576x324 for 16:9 | ||
140 | + # This are Best and Good values with the same width based on this page: | ||
141 | + # http://www.flashsupport.com/books/fvst/files/tools/video_sizes.html | ||
142 | + # Width 640 has Better result to 16:9, but that will also make bigger | ||
143 | + # file weight. | ||
144 | + h = ( 576.0 / (@orig_size[:w].to_f/@orig_size[:h].to_f) ).round | ||
145 | + size = { :w=>576, :h=>h.to_i } | ||
146 | + conf = { :in=>@video_file, :size=>size, :vbrate=>@brate } | ||
147 | + else | ||
148 | + conf = { :in=>@video_file, :vbrate=>@brate } | ||
149 | + end | ||
150 | + conf[:size_name] = 'nice' | ||
151 | + if format == :OGV && ( is_toobig || ! is_ogv ) | ||
152 | + conf[:file_name] = 'nice.ogv' | ||
153 | + register_conversion @ffmpeg.make_ogv_for_web(conf) | ||
154 | + end | ||
155 | + if format == :WEBM && ( is_toobig || ! is_webm ) | ||
156 | + conf[:file_name] = 'nice.webm' | ||
157 | + register_conversion @ffmpeg.make_webm_for_web(conf) | ||
158 | + end | ||
159 | + end | ||
160 | + | ||
161 | +end |
plugins/html5_video/lib/html5_video_plugin/create_video_preview_job.rb
0 → 100644
@@ -0,0 +1,27 @@ | @@ -0,0 +1,27 @@ | ||
1 | +class Html5VideoPlugin::CreateVideoPreviewJob | ||
2 | + | ||
3 | + attr_accessor :file_type, :file_id, :full_filename | ||
4 | + | ||
5 | + def perform | ||
6 | + return unless file_type.constantize.exists?(file_id) | ||
7 | + ffmpeg = Html5VideoPlugin::Ffmpeg.new | ||
8 | + | ||
9 | + video = FilePresenter.for file_type.constantize.find(file_id) | ||
10 | + throw "Expected file #{file_id} to be a video" unless video.is_a? FilePresenter::Video | ||
11 | + | ||
12 | + video_file = full_filename | ||
13 | + | ||
14 | + response = ffmpeg.video_thumbnail(video_file) | ||
15 | + | ||
16 | + if response.kind_of?(Hash) && response[:error] && response[:error][:code] != 0 | ||
17 | + video.previews = :fail | ||
18 | + video.save! | ||
19 | + Rails.logger.error "ERROR while generating '#{video_file}' image preview: #{response[:error][:message]}" | ||
20 | + return | ||
21 | + end | ||
22 | + | ||
23 | + video.previews = response | ||
24 | + video.save! | ||
25 | + end | ||
26 | + | ||
27 | +end |
@@ -0,0 +1,354 @@ | @@ -0,0 +1,354 @@ | ||
1 | +# Works for ffmpeg version 2.8.6-1~bpo8 shiped by Debian Jessie Backports | ||
2 | +# https://packages.debian.org/jessie-backports/ffmpeg | ||
3 | +# Add this line to your /etc/apt/sources.list: | ||
4 | +# deb http://http.debian.net/debian jessie-backports main | ||
5 | +# then: aptitude install ffmpeg | ||
6 | +class Html5VideoPlugin::Ffmpeg | ||
7 | + | ||
8 | + def run(*parameters) | ||
9 | + parameters = parameters.flatten | ||
10 | + cmd = ['ffmpeg'] + parameters.map do |p| | ||
11 | + p.kind_of?(Symbol) ? '-'+p.to_s : p.to_s | ||
12 | + end | ||
13 | + io = IO.popen({'LANG'=>'C.UTF-8'}, cmd, err: [:child, :out]) | ||
14 | + output = io.read | ||
15 | + io.close | ||
16 | + response = { | ||
17 | + error: { :code => 0, :message => '' }, | ||
18 | + parameters: parameters, | ||
19 | + output: output | ||
20 | + } | ||
21 | + if $?.exitstatus != 0 then | ||
22 | + if $?.exitstatus == 127 then | ||
23 | + throw 'There is no FFmpeg installed!' | ||
24 | + end | ||
25 | + response[:error][:code] = -1 | ||
26 | + response[:error][:message] = _('Unknow error') | ||
27 | + if match = /\n\s*([^\n]*): No such file or directory\s*\n/i.match(output) | ||
28 | + response[:error][:code] = 1 | ||
29 | + response[:error][:message] = _('No such file or directory "%s".') % match[1] | ||
30 | + elsif output =~ /At least one output file must be specified/i | ||
31 | + response[:error][:code] = 2 | ||
32 | + response[:error][:message] = _('No output defined.') | ||
33 | + elsif match = /\n\s*Unknown encoder[\s']+([^\s']+)/i.match(output) | ||
34 | + response[:error][:code] = 3 | ||
35 | + response[:error][:message] = _('Unknown encoder "%s".') % match[1] | ||
36 | + elsif output =~ /\n\s*Error while opening encoder for output/i | ||
37 | + response[:error][:code] = 4 | ||
38 | + response[:error][:message] = _('Error while opening encoder for output - maybe incorrect parameters such as bit rate, frame rate, width or height.') | ||
39 | + elsif match = /\n\s*Could not open '([^\s']+)/i.match(output) | ||
40 | + response[:error][:code] = 5 | ||
41 | + response[:error][:message] = _('Could not open "%s".') % match[1] | ||
42 | + elsif match = /\n\s*Unsupported codec (.*) for (.*) stream (.*)/i.match(output) | ||
43 | + response[:error][:code] = 6 | ||
44 | + response[:error][:message] = _('Unsupported codec %{codec} for %{act} stream %{id}.') % | ||
45 | + { :codec=>match[1], :act=>match[2], :id=>match[3] } | ||
46 | + elsif output =~ /Unable to find a suitable output format/i | ||
47 | + response[:error][:code] = 7 | ||
48 | + response[:error][:message] = _('Unable to find a suitable output format for %{file}.') % | ||
49 | + { :file=>parameters[-1] } | ||
50 | + elsif output =~ /Invalid data found when processing input/i | ||
51 | + response[:error][:code] = 8 | ||
52 | + response[:error][:message] = _('Invalid data found when processing input.') | ||
53 | + end | ||
54 | + end | ||
55 | + return response | ||
56 | + end | ||
57 | + | ||
58 | + def register_information | ||
59 | + response = self.run(:formats)[:output] | ||
60 | + @@version = /^\s*FFmpeg version ([0-9.]+)/i.match(response)[1] | ||
61 | + @@formats = {} | ||
62 | + response.split('--')[-1].strip.split("\n").each do |line| | ||
63 | + if pieces = / (.)(.) ([^\s]+)\s+([^\s].*)/.match(line) | ||
64 | + @@formats[pieces[3].to_sym] = { | ||
65 | + demux: ( pieces[1] == 'D' ), | ||
66 | + mux: ( pieces[2] == 'E' ), | ||
67 | + description: pieces[4].strip | ||
68 | + } | ||
69 | + end | ||
70 | + end | ||
71 | + response = self.run(:codecs)[:output] | ||
72 | + @@codecs = {} | ||
73 | + response.split('--')[-1].strip.split("\n").each do |line| | ||
74 | + if pieces = / (.)(.)(.)(.)(.)(.) ([^\s]+)\s+([^\s].*)/.match(line) | ||
75 | + @@codecs[pieces[7].to_sym] = { | ||
76 | + decode: ( pieces[1] == 'D' ), | ||
77 | + encode: ( pieces[2] == 'E' ), | ||
78 | + draw_horiz_band: ( pieces[4] == 'S' ), | ||
79 | + direct_rendering: ( pieces[5] == 'D' ), | ||
80 | + wf_trunc: ( pieces[6] == 'T' ), | ||
81 | + type: ( | ||
82 | + if pieces[3] == 'V'; :video | ||
83 | + elsif pieces[3] == 'A'; :audio | ||
84 | + elsif pieces[3] == 'S'; :subtitle | ||
85 | + else :unknown; end | ||
86 | + ), | ||
87 | + description: pieces[8].strip | ||
88 | + } | ||
89 | + end | ||
90 | + end | ||
91 | + { | ||
92 | + version: @@version, | ||
93 | + formats: @@formats, | ||
94 | + codecs: @@codecs | ||
95 | + } | ||
96 | + end | ||
97 | + | ||
98 | + def timestr_to_secs str | ||
99 | + return nil if ! /^[0-9]{2}:[0-9]{2}:[0-9]{2}$/.match str | ||
100 | + t = str.split(':').map(&:to_i) | ||
101 | + t[0]*60*60 + t[1]*60 + t[2] | ||
102 | + end | ||
103 | + | ||
104 | + def get_stream_info(stream_str) | ||
105 | + stream_info = { :type => 'undefined' } | ||
106 | + stream_info[:type] = 'video' if / Video:/.match(stream_str) | ||
107 | + stream_info[:type] = 'audio' if / Audio:/.match(stream_str) | ||
108 | + { | ||
109 | + id: [ /^\s*Stream ([^:(]+)/ ], | ||
110 | + codec: [ / (?:Audio|Video):\s*([^,\s]+)/x ], | ||
111 | + bitrate: [ / ([0-9]+) kb\/s\b/ , :to_i ], | ||
112 | + frequency: [ / ([0-9]+) Hz\b/ , :to_i ], | ||
113 | + channels: [ / ([0-9]+) channels\b/ , :to_i ], | ||
114 | + framerate: [ / ([0-9.]+) (fps|tbr)\b/ , :to_f ], | ||
115 | + size: [ / ([0-9]+x[0-9]+)[, ]/ ], | ||
116 | + }.each do |att, params| | ||
117 | + re = params[0] | ||
118 | + method = params[1] || :strip | ||
119 | + if match = re.match(stream_str) then | ||
120 | + stream_info[att] = match[1].send(method) | ||
121 | + end | ||
122 | + end | ||
123 | + if stream_info[:size] | ||
124 | + size_array = stream_info[:size].split('x').map{|s| s.to_i} | ||
125 | + stream_info[:size] = { :w=>size_array[0], :h=>size_array[1] } | ||
126 | + end | ||
127 | + stream_info | ||
128 | + end | ||
129 | + | ||
130 | + def webdir_for_original_video(video) | ||
131 | + webdir = File.dirname(video) +'/web' | ||
132 | + Dir.mkdir(webdir) if ! File.exist?(webdir) | ||
133 | + webdir | ||
134 | + end | ||
135 | + | ||
136 | + def valid_abrate_for_web(info) | ||
137 | + if brate = info.audio_stream[0][:bitrate] | ||
138 | + brate = 8 if brate < 8 | ||
139 | + (brate>128)? 128 : brate | ||
140 | + else | ||
141 | + 48 | ||
142 | + end | ||
143 | + end | ||
144 | + | ||
145 | + def valid_vbrate_for_web(info) | ||
146 | + if brate = info.video_stream[0][:bitrate] || info[:global_bitrate] | ||
147 | + brate = 128 if brate < 128 | ||
148 | + (brate>1024)? 1024 : brate | ||
149 | + else | ||
150 | + 400 | ||
151 | + end | ||
152 | + end | ||
153 | + | ||
154 | + def valid_size_for_web(info) | ||
155 | + orig_size = info.video_stream[0][:size] | ||
156 | + if info.video_stream[0][:size][:w] > 640 | ||
157 | + h = 640.0 / (orig_size[:w].to_f/orig_size[:h].to_f) | ||
158 | + { :w=>640, :h=>h.to_i } | ||
159 | + else | ||
160 | + { :w=>orig_size[:w].to_i, :h=>orig_size[:h].to_i } | ||
161 | + end | ||
162 | + end | ||
163 | + | ||
164 | + def validate_conversion_conf_for_web(conf, file_type) | ||
165 | + conf = conf.clone | ||
166 | + if conf[:abrate].nil? || conf[:vbrate].nil? || conf[:size].nil? | ||
167 | + info = get_video_info(conf[:in]) | ||
168 | + if info[:error][:code] == 0 | ||
169 | + conf[:abrate] ||= valid_abrate_for_web info | ||
170 | + conf[:vbrate] ||= valid_vbrate_for_web info | ||
171 | + conf[:size] ||= valid_size_for_web info | ||
172 | + end | ||
173 | + end | ||
174 | + result_dir = webdir_for_original_video conf[:in] | ||
175 | + size_str = "#{conf[:size][:w]}x#{conf[:size][:h]}" | ||
176 | + unless conf[:file_name] | ||
177 | + conf[:file_name] = "#{size_str}_#{conf[:vbrate]}.#{file_type}" | ||
178 | + end | ||
179 | + conf[:out] = result_dir+'/'+conf[:file_name] | ||
180 | + conf | ||
181 | + end | ||
182 | + | ||
183 | + public | ||
184 | + | ||
185 | + def get_video_info(file) | ||
186 | + response = self.run :i, file | ||
187 | + if response[:error][:code] == 2 | ||
188 | + # No output is not an error on this context | ||
189 | + response[:error][:code] = 0 | ||
190 | + response[:error][:message] = _('Success.') | ||
191 | + end | ||
192 | + response[:metadata] = {} | ||
193 | + { | ||
194 | + author: /\n\s*author[\t ]*:([^\n]*)\n/i, | ||
195 | + title: /\n\s*title[\t ]*:([^\n]*)\n/i, | ||
196 | + comment: /\n\s*comment[\t ]*:([^\n]*)\n/i | ||
197 | + }.each do |att, re| | ||
198 | + if match = re.match(response[:output]) then | ||
199 | + response[:metadata][att] = match[1].strip | ||
200 | + end | ||
201 | + end | ||
202 | + { | ||
203 | + type: /\nInput #0, ([a-z0-9]+), from/, | ||
204 | + duration: /\n\s*Duration:[\t ]*([0-9:]+)[^\n]* bitrate:/, | ||
205 | + global_bitrate: /\n\s*Duration:[^\n]* bitrate:[\t ]*([0-9]+)/ | ||
206 | + }.each do |att, re| | ||
207 | + if match = re.match(response[:output]) then | ||
208 | + response[att] = match[1].strip | ||
209 | + end | ||
210 | + end | ||
211 | + response[:duration] = timestr_to_secs response[:duration] | ||
212 | + response[:global_bitrate] = response[:global_bitrate].to_i | ||
213 | + response[:streams] = [] | ||
214 | + response[:output].split("\n").grep(/^\s*Stream /).each do |stream| | ||
215 | + response[:streams] << get_stream_info(stream) | ||
216 | + end | ||
217 | + def response.video_stream | ||
218 | + self[:streams].select {|s| s[:type] == 'video' } | ||
219 | + end | ||
220 | + def response.audio_stream | ||
221 | + self[:streams].select {|s| s[:type] == 'audio' } | ||
222 | + end | ||
223 | + return response | ||
224 | + end | ||
225 | + | ||
226 | + def convert2ogv(conf) | ||
227 | + conf[:type] = :OGV | ||
228 | + conf[:vbrate] ||= 600 | ||
229 | + parameters = [ :i, conf[:in], :y, :'b:v', "#{conf[:vbrate]}k", | ||
230 | + :f, 'ogg', :acodec, 'libvorbis', :vcodec, 'libtheora' | ||
231 | + ] | ||
232 | + parameters << :s << "#{conf[:size][:w]}x#{conf[:size][:h]}" if conf[:size] | ||
233 | + parameters << :'b:a' << "#{conf[:abrate]}k" if conf[:abrate] | ||
234 | + # Vorbis dá pau com -ar 8000Hz ??? | ||
235 | + parameters << :r << conf[:fps] if conf[:fps] | ||
236 | + parameters << conf[:out] | ||
237 | + response = self.run parameters | ||
238 | + response[:conf] = conf | ||
239 | + response | ||
240 | + end | ||
241 | + | ||
242 | + def convert2mp4(conf) | ||
243 | + conf[:type] = :MP4 | ||
244 | + conf[:vbrate] ||= 600 | ||
245 | + parameters = [ :i, conf[:in], :y, :'b:v', "#{conf[:vbrate]}k", | ||
246 | + :preset, 'slow', :f, 'mp4', :acodec, 'aac', :vcodec, 'libx264', | ||
247 | + :strict, '-2' | ||
248 | + ] | ||
249 | + parameters << :s << "#{conf[:size][:w]}x#{conf[:size][:h]}" if conf[:size] | ||
250 | + parameters << :'b:a' << "#{conf[:abrate]}k" if conf[:abrate] | ||
251 | + parameters << :r << conf[:fps] if conf[:fps] | ||
252 | + parameters << conf[:out] | ||
253 | + response = self.run parameters | ||
254 | + response[:conf] = conf | ||
255 | + response | ||
256 | + end | ||
257 | + | ||
258 | + def convert2webm(conf) | ||
259 | + conf[:type] = :WEBM | ||
260 | + conf[:vbrate] ||= 600 | ||
261 | + parameters = [ :i, conf[:in], :y, :'b:v', "#{conf[:vbrate]}k", | ||
262 | + :f, 'webm', :acodec, 'libvorbis', :vcodec, 'libvpx' | ||
263 | + ] | ||
264 | + parameters << :s << "#{conf[:size][:w]}x#{conf[:size][:h]}" if conf[:size] | ||
265 | + parameters << :'b:a' << "#{conf[:abrate]}k" if conf[:abrate] | ||
266 | + parameters << :r << conf[:fps] if conf[:fps] | ||
267 | + parameters << conf[:out] | ||
268 | + response = self.run parameters | ||
269 | + response[:conf] = conf | ||
270 | + response | ||
271 | + end | ||
272 | + | ||
273 | + def make_ogv_for_web(conf) | ||
274 | + conf = validate_conversion_conf_for_web conf, :ogv | ||
275 | + convert2ogv(conf) | ||
276 | + end | ||
277 | + | ||
278 | + def make_mp4_for_web(conf) | ||
279 | + conf = validate_conversion_conf_for_web conf, :mp4 | ||
280 | + convert2mp4(conf) | ||
281 | + end | ||
282 | + | ||
283 | + def make_webm_for_web(conf) | ||
284 | + conf = validate_conversion_conf_for_web conf, :webm | ||
285 | + convert2webm(conf) | ||
286 | + end | ||
287 | + | ||
288 | + # video_thumbnail creates 2 preview images on the sub directory web | ||
289 | + # from the video file parent dir. This preview images are six concatenated | ||
290 | + # frames in one image each. The frames have fixed dimension. The bigger | ||
291 | + # preview has frames with in 160x120, and smaller has frames whit in 107x80. | ||
292 | + # Use this helper only on the original movie to have only one "web" sub-dir. | ||
293 | + def video_thumbnail(video) | ||
294 | + result_dir = webdir_for_original_video video | ||
295 | + info = get_video_info(video) | ||
296 | + if info[:duration] < 15 | ||
297 | + pos = 1 | ||
298 | + duration = info[:duration] - 2 | ||
299 | + frate = ( 7.0 / duration ).ceil | ||
300 | + else | ||
301 | + pos = ( info[:duration] / 2.5 ).ceil | ||
302 | + duration = 7 | ||
303 | + frate = 1 | ||
304 | + end | ||
305 | + response = self.run :i, video, :ss, pos, :t, duration, :r, frate, | ||
306 | + :s, '320x240', result_dir+'/f%d.png' | ||
307 | + img_names = [ '/preview_160x120.jpg', '/preview_107x80.jpg' ] | ||
308 | + if response[:error][:code] == 0 | ||
309 | + imgs = (2..7).map { |num| | ||
310 | + img = result_dir+"/f#{num}.png" | ||
311 | + File.exists?(img) ? img : nil | ||
312 | + }.compact | ||
313 | + if imgs.size != 6 | ||
314 | + Rails.logger.error "Problem to create thumbs for video #{video} ???" | ||
315 | + end | ||
316 | + imgs = Magick::ImageList.new *imgs | ||
317 | + imgs.montage{ | ||
318 | + self.geometry='160x120+0+0' | ||
319 | + self.tile="1x#{imgs.size}" | ||
320 | + self.frame = "0x0+0+0" | ||
321 | + }.write result_dir+img_names[0] | ||
322 | + imgs.montage{ | ||
323 | + self.geometry='107x80+0+0' | ||
324 | + self.tile="1x#{imgs.size}" | ||
325 | + self.frame = "0x0+0+0" | ||
326 | + }.write result_dir+img_names[1] | ||
327 | + end | ||
328 | + | ||
329 | + f_num = 1 | ||
330 | + while File.exists? result_dir+"/f#{f_num}.png" do | ||
331 | + File.delete result_dir+"/f#{f_num}.png" | ||
332 | + f_num += 1 | ||
333 | + end | ||
334 | + | ||
335 | + if response[:error][:code] == 0 | ||
336 | + return { big: '/web'+img_names[0], thumb: '/web'+img_names[1] } | ||
337 | + else | ||
338 | + return response | ||
339 | + end | ||
340 | + end | ||
341 | + | ||
342 | + def version | ||
343 | + @@version ||= register_information[:version] | ||
344 | + end | ||
345 | + | ||
346 | + def formats | ||
347 | + @@formats ||= register_information[:formats] | ||
348 | + end | ||
349 | + | ||
350 | + def codecs | ||
351 | + @@codecs ||= register_information[:codecs] | ||
352 | + end | ||
353 | + | ||
354 | +end |
plugins/html5_video/lib/html5_video_plugin/video_channel.rb
0 → 100644
@@ -0,0 +1,30 @@ | @@ -0,0 +1,30 @@ | ||
1 | +class Html5VideoPlugin::VideoChannel < Folder | ||
2 | + | ||
3 | + def self.short_description | ||
4 | + _('Video Channel') | ||
5 | + end | ||
6 | + | ||
7 | + def self.description | ||
8 | + _('A video channel, where you can make your own web TV.') | ||
9 | + end | ||
10 | + | ||
11 | + include ActionView::Helpers::TagHelper | ||
12 | + def to_html(options={}) | ||
13 | + article = self | ||
14 | + lambda do | ||
15 | + render :file => 'content_viewer/video_channel', :locals => {:article => article} | ||
16 | + end | ||
17 | + end | ||
18 | + | ||
19 | + def video_channel? | ||
20 | + true | ||
21 | + end | ||
22 | + | ||
23 | + def self.icon_name(article = nil) | ||
24 | + 'videochannel' | ||
25 | + end | ||
26 | + | ||
27 | + def accept_article? | ||
28 | + false | ||
29 | + end | ||
30 | +end |
@@ -0,0 +1,145 @@ | @@ -0,0 +1,145 @@ | ||
1 | +class FilePresenter::Video < FilePresenter | ||
2 | + | ||
3 | + def self.accepts?(f) | ||
4 | + return nil if !f.respond_to?(:content_type) || f.content_type.nil? | ||
5 | + ( f.content_type[0..4] == 'video' ) ? 10 : nil | ||
6 | + end | ||
7 | + | ||
8 | + def short_description | ||
9 | + _('Video (%s)') % content_type.split('/')[1].upcase | ||
10 | + end | ||
11 | + | ||
12 | + def meta_data #video_info | ||
13 | + Noosfero::Plugin::Settings.new(encapsulated_file, Html5VideoPlugin) | ||
14 | + end | ||
15 | + | ||
16 | + def original_video | ||
17 | + meta_data.original_video ||= {} | ||
18 | + end | ||
19 | + | ||
20 | + def original_video=(hash) | ||
21 | + meta_data.original_video = hash | ||
22 | + end | ||
23 | + | ||
24 | + def web_versions | ||
25 | + meta_data.web_versions ||= {} | ||
26 | + end | ||
27 | + | ||
28 | + def web_versions=(hash) | ||
29 | + meta_data.web_versions = hash | ||
30 | + end | ||
31 | + | ||
32 | + # adds the orig version tho the web_versions if that is a valid to HTML5 | ||
33 | + def web_versions! | ||
34 | + list = web_versions.clone | ||
35 | + streams = original_video.empty? ? [] : original_video[:streams] | ||
36 | + video_stream = streams.find{|s| s[:type] == 'video' } | ||
37 | + audio_stream = streams.find{|s| s[:type] == 'audio' } | ||
38 | + return list unless video_stream && audio_stream | ||
39 | + type = original_video[:type].to_s.upcase.to_sym | ||
40 | + type = :OGV if video_stream[:codec]=='theora' && original_video[:type]=='ogg' | ||
41 | + if [:OGV, :MP4, :WEBM].include? type | ||
42 | + vb = video_stream[:bitrate] || original_video[:global_bitrate] || 0 | ||
43 | + ab = audio_stream[:bitrate] || 0 | ||
44 | + info = { | ||
45 | + :original => true, | ||
46 | + :file_name => File.basename(public_filename), | ||
47 | + :abrate => ab, | ||
48 | + :vbrate => vb, | ||
49 | + :size => video_stream[:size], | ||
50 | + :size_name => 'orig', | ||
51 | + :status => 'done', | ||
52 | + :type => type, | ||
53 | + :path => public_filename | ||
54 | + } | ||
55 | + list[type][:orig] = info | ||
56 | + end | ||
57 | + list | ||
58 | + end | ||
59 | + | ||
60 | + def ready_web_versions | ||
61 | + ready = {} | ||
62 | + web_versions!.select do |type, type_block| | ||
63 | + ready[type] = {} | ||
64 | + type_block.select do |size, size_block| | ||
65 | + ready[type][size] = size_block if size_block[:status] == 'done' | ||
66 | + end | ||
67 | + end | ||
68 | + ready | ||
69 | + end | ||
70 | + | ||
71 | + def has_ogv_version | ||
72 | + not ready_web_versions[:OGV].blank? | ||
73 | + end | ||
74 | + | ||
75 | + def has_mp4_version | ||
76 | + not ready_web_versions[:MP4].blank? | ||
77 | + end | ||
78 | + | ||
79 | + def has_webm_version | ||
80 | + not ready_web_versions[:WEBM].blank? | ||
81 | + end | ||
82 | + | ||
83 | + def has_web_version | ||
84 | + ready = ready_web_versions | ||
85 | + not (ready[:OGV].blank? and ready[:MP4].blank? and ready[:WEBM].blank?) | ||
86 | + end | ||
87 | + | ||
88 | + def tiniest_web_version( type ) | ||
89 | + return nil if ready_web_versions[type].nil? | ||
90 | + video = ready_web_versions[type]. | ||
91 | + select{|size,data| data[:status] == 'done' }. | ||
92 | + sort_by{|v| v[1][:vbrate] }.first | ||
93 | + video ? video[1] : nil | ||
94 | + end | ||
95 | + | ||
96 | + #TODO: add this to the user interface: | ||
97 | + def web_version_jobs | ||
98 | + #FIXME: in a newer version, the Delayed::Job may be searcheable in a uglyless way. | ||
99 | + Delayed::Job.where("handler LIKE '%CreateVideoForWebJob%file_id: #{self.id}%'").all | ||
100 | + #Delayed::Job.all :conditions => ['handler LIKE ?', | ||
101 | + # "%CreateVideoForWebJob%file_id: #{self.id}%"] | ||
102 | + end | ||
103 | + | ||
104 | + def web_preview_jobs | ||
105 | + #FIXME: in a newer version, the Delayed::Job may be searcheable in a uglyless way. | ||
106 | + Delayed::Job.where("handler LIKE '%CreateVideoPreviewJob%file_id: #{self.id}%'").all | ||
107 | + #Delayed::Job.all :conditions => ['handler LIKE ?', | ||
108 | + # "%CreateVideoPreviewJob%file_id: #{self.id}%"] | ||
109 | + end | ||
110 | + | ||
111 | + def has_previews? | ||
112 | + not(previews.nil?) && previews.kind_of?(Hash) && !previews.empty? | ||
113 | + end | ||
114 | + | ||
115 | + def previews | ||
116 | + meta_data.image_previews | ||
117 | + end | ||
118 | + | ||
119 | + def previews=(hash) | ||
120 | + meta_data.image_previews = hash | ||
121 | + end | ||
122 | + | ||
123 | + def image_preview(size=nil) | ||
124 | + if has_previews? && previews[size] | ||
125 | + File.dirname( public_filename ) + previews[size] | ||
126 | + else | ||
127 | + "/plugins/html5_video/images/video-preview-#{size}.png" | ||
128 | + end | ||
129 | + end | ||
130 | + | ||
131 | + def conversion_errors | ||
132 | + errors = {} | ||
133 | + web_versions!.select do |type, type_block| | ||
134 | + type_block.select do |size, conv_info| | ||
135 | + if conv_info[:status] == 'error converting' | ||
136 | + errors[type] ||= {} | ||
137 | + err_base = {:message=>_('Undefined'), :code=>-2, :output=>'undefined'} | ||
138 | + errors[type][size] = err_base.merge( conv_info[:error] || {} ) | ||
139 | + end | ||
140 | + end | ||
141 | + end | ||
142 | + errors | ||
143 | + end | ||
144 | + | ||
145 | +end |
plugins/html5_video/po/html5_video.pot
@@ -6,9 +6,10 @@ | @@ -6,9 +6,10 @@ | ||
6 | #, fuzzy | 6 | #, fuzzy |
7 | msgid "" | 7 | msgid "" |
8 | msgstr "" | 8 | msgstr "" |
9 | -"Project-Id-Version: 1.3~rc2-1-ga15645d\n" | ||
10 | -"POT-Creation-Date: 2015-10-30 16:35-0300\n" | ||
11 | -"PO-Revision-Date: 2015-08-06 17:21-0300\n" | 9 | +"Project-Id-Version: PACKAGE VERSION\n" |
10 | +"Report-Msgid-Bugs-To: \n" | ||
11 | +"POT-Creation-Date: 2016-08-19 19:54+0000\n" | ||
12 | +"PO-Revision-Date: 2016-08-19 19:54+0000\n" | ||
12 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | 13 | "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" |
13 | "Language-Team: LANGUAGE <LL@li.org>\n" | 14 | "Language-Team: LANGUAGE <LL@li.org>\n" |
14 | "Language: \n" | 15 | "Language: \n" |
@@ -17,10 +18,128 @@ msgstr "" | @@ -17,10 +18,128 @@ msgstr "" | ||
17 | "Content-Transfer-Encoding: 8bit\n" | 18 | "Content-Transfer-Encoding: 8bit\n" |
18 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" | 19 | "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" |
19 | 20 | ||
20 | -#: plugins/html5_video/lib/file_presenter/video.rb:8 | 21 | +#: ../lib/html5_video_plugin.rb:16 |
22 | +msgid "A plugin to enable the video suport, with auto conversion for the web." | ||
23 | +msgstr "" | ||
24 | + | ||
25 | +#: ../lib/html5_video_plugin/ffmpeg.rb:26 | ||
26 | +msgid "Unknow error" | ||
27 | +msgstr "" | ||
28 | + | ||
29 | +#: ../lib/html5_video_plugin/ffmpeg.rb:29 | ||
30 | +msgid "No such file or directory \"%s\"." | ||
31 | +msgstr "" | ||
32 | + | ||
33 | +#: ../lib/html5_video_plugin/ffmpeg.rb:32 | ||
34 | +msgid "No output defined." | ||
35 | +msgstr "" | ||
36 | + | ||
37 | +#: ../lib/html5_video_plugin/ffmpeg.rb:35 | ||
38 | +msgid "Unknown encoder \"%s\"." | ||
39 | +msgstr "" | ||
40 | + | ||
41 | +#: ../lib/html5_video_plugin/ffmpeg.rb:38 | ||
42 | +msgid "" | ||
43 | +"Error while opening encoder for output - maybe incorrect parameters such as bi" | ||
44 | +"t rate, frame rate, width or height." | ||
45 | +msgstr "" | ||
46 | + | ||
47 | +#: ../lib/html5_video_plugin/ffmpeg.rb:41 | ||
48 | +msgid "Could not open \"%s\"." | ||
49 | +msgstr "" | ||
50 | + | ||
51 | +#: ../lib/html5_video_plugin/ffmpeg.rb:44 | ||
52 | +msgid "Unsupported codec %{codec} for %{act} stream %{id}." | ||
53 | +msgstr "" | ||
54 | + | ||
55 | +#: ../lib/html5_video_plugin/ffmpeg.rb:48 | ||
56 | +msgid "Unable to find a suitable output format for %{file}." | ||
57 | +msgstr "" | ||
58 | + | ||
59 | +#: ../lib/html5_video_plugin/ffmpeg.rb:52 | ||
60 | +msgid "Invalid data found when processing input." | ||
61 | +msgstr "" | ||
62 | + | ||
63 | +#: ../lib/html5_video_plugin/ffmpeg.rb:190 | ||
64 | +msgid "Success." | ||
65 | +msgstr "" | ||
66 | + | ||
67 | +#: ../lib/html5_video_plugin/video_channel.rb:4 | ||
68 | +msgid "Video Channel" | ||
69 | +msgstr "" | ||
70 | + | ||
71 | +#: ../lib/html5_video_plugin/video_channel.rb:8 | ||
72 | +msgid "A video channel, where you can make your own web TV." | ||
73 | +msgstr "" | ||
74 | + | ||
75 | +#: ../lib/presenters/video.rb:9 | ||
21 | msgid "Video (%s)" | 76 | msgid "Video (%s)" |
22 | msgstr "" | 77 | msgstr "" |
23 | 78 | ||
24 | -#: plugins/html5_video/lib/html5_video_plugin.rb:10 | ||
25 | -msgid "A plugin to enable the video suport, with auto conversion for the web." | 79 | +#: ../lib/presenters/video.rb:137 |
80 | +msgid "Undefined" | ||
81 | +msgstr "" | ||
82 | + | ||
83 | +#: ../views/content_viewer/_video_player.html.erb:21 | ||
84 | +msgid "Sorry, your browser doesn’t support video." | ||
85 | +msgstr "" | ||
86 | + | ||
87 | +#: ../views/content_viewer/_video_player.html.erb:22 | ||
88 | +msgid "Please try the new %s or %s." | ||
89 | +msgstr "" | ||
90 | + | ||
91 | +#: ../views/content_viewer/_video_player.html.erb:27 | ||
92 | +msgid "Download" | ||
93 | +msgstr "" | ||
94 | + | ||
95 | +#: ../views/content_viewer/video_channel.html.erb:7 | ||
96 | +msgid "This channel contains no videos yet" | ||
97 | +msgstr "" | ||
98 | + | ||
99 | +#: ../views/content_viewer/video_channel.html.erb:14 | ||
100 | +#: ../views/file_presenter/_video.html.erb:15 | ||
101 | +msgid "Quality options" | ||
102 | +msgstr "" | ||
103 | + | ||
104 | +#: ../views/content_viewer/video_channel.html.erb:18 | ||
105 | +msgid "Tags" | ||
106 | +msgstr "" | ||
107 | + | ||
108 | +#: ../views/content_viewer/video_channel.html.erb:22 | ||
109 | +#: ../views/file_presenter/_video.html.erb:17 | ||
110 | +msgid "Description" | ||
111 | +msgstr "" | ||
112 | + | ||
113 | +#: ../views/content_viewer/video_channel.html.erb:66 | ||
114 | +msgid "This channel has one video waiting to be converted" | ||
115 | +msgid_plural "This channel has %d videos waiting to be converted" | ||
116 | +msgstr[0] "" | ||
117 | +msgstr[1] "" | ||
118 | + | ||
119 | +#: ../views/content_viewer/video_channel.html.erb:83 | ||
120 | +msgid "Non video files" | ||
121 | +msgstr "" | ||
122 | + | ||
123 | +#: ../views/file_presenter/_video.html.erb:7 | ||
124 | +msgid "Queued to generate the web version. Come back soon." | ||
125 | +msgstr "" | ||
126 | + | ||
127 | +#: ../views/file_presenter/_video.html.erb:11 | ||
128 | +msgid "This video is not queued to the video conversor. Contact the site admin." | ||
129 | +msgstr "" | ||
130 | + | ||
131 | +#: ../views/file_presenter/_video.html.erb:34 | ||
132 | +msgid "Video conversion errors" | ||
133 | +msgstr "" | ||
134 | + | ||
135 | +#: ../views/file_presenter/_video.html.erb:44 | ||
136 | +msgid "Error while converting %{orig_type} to %{new_type}, %{size} size." | ||
137 | +msgstr "" | ||
138 | + | ||
139 | +#: ../views/file_presenter/_video.html.erb:47 | ||
140 | +msgid "Code %s" | ||
141 | +msgstr "" | ||
142 | + | ||
143 | +#: ../views/file_presenter/_video.html.erb:49 | ||
144 | +msgid "display full output" | ||
26 | msgstr "" | 145 | msgstr "" |
plugins/html5_video/po/pt/html5_video.po
@@ -12,11 +12,11 @@ | @@ -12,11 +12,11 @@ | ||
12 | msgid "" | 12 | msgid "" |
13 | msgstr "" | 13 | msgstr "" |
14 | "Project-Id-Version: 1.3~rc2-1-ga15645d\n" | 14 | "Project-Id-Version: 1.3~rc2-1-ga15645d\n" |
15 | -"POT-Creation-Date: 2015-10-30 16:35-0300\n" | 15 | +"POT-Creation-Date: 2016-08-19 19:54+0000\n" |
16 | "PO-Revision-Date: 2014-12-18 18:40-0200\n" | 16 | "PO-Revision-Date: 2014-12-18 18:40-0200\n" |
17 | "Last-Translator: Luciano Prestes Cavalcanti <lucianopcbr@gmail.com>\n" | 17 | "Last-Translator: Luciano Prestes Cavalcanti <lucianopcbr@gmail.com>\n" |
18 | -"Language-Team: Portuguese <https://hosted.weblate.org/projects/noosfero/" | ||
19 | -"noosfero/pt/>\n" | 18 | +"Language-Team: Portuguese <https://hosted.weblate.org/projects/noosfero/noosfe" |
19 | +"ro/pt/>\n" | ||
20 | "Language: pt\n" | 20 | "Language: pt\n" |
21 | "MIME-Version: 1.0\n" | 21 | "MIME-Version: 1.0\n" |
22 | "Content-Type: text/plain; charset=UTF-8\n" | 22 | "Content-Type: text/plain; charset=UTF-8\n" |
@@ -24,11 +24,128 @@ msgstr "" | @@ -24,11 +24,128 @@ msgstr "" | ||
24 | "Plural-Forms: nplurals=2; plural=n != 1;\n" | 24 | "Plural-Forms: nplurals=2; plural=n != 1;\n" |
25 | "X-Generator: Weblate 2.0\n" | 25 | "X-Generator: Weblate 2.0\n" |
26 | 26 | ||
27 | -#: plugins/html5_video/lib/file_presenter/video.rb:8 | 27 | +#: ../lib/html5_video_plugin.rb:16 |
28 | +msgid "A plugin to enable the video suport, with auto conversion for the web." | ||
29 | +msgstr "Um plugin para habilitar suporte de video, com conversão de audio para a web." | ||
30 | + | ||
31 | +#: ../lib/html5_video_plugin/ffmpeg.rb:26 | ||
32 | +msgid "Unknow error" | ||
33 | +msgstr "" | ||
34 | + | ||
35 | +#: ../lib/html5_video_plugin/ffmpeg.rb:29 | ||
36 | +msgid "No such file or directory \"%s\"." | ||
37 | +msgstr "" | ||
38 | + | ||
39 | +#: ../lib/html5_video_plugin/ffmpeg.rb:32 | ||
40 | +msgid "No output defined." | ||
41 | +msgstr "" | ||
42 | + | ||
43 | +#: ../lib/html5_video_plugin/ffmpeg.rb:35 | ||
44 | +msgid "Unknown encoder \"%s\"." | ||
45 | +msgstr "" | ||
46 | + | ||
47 | +#: ../lib/html5_video_plugin/ffmpeg.rb:38 | ||
48 | +msgid "" | ||
49 | +"Error while opening encoder for output - maybe incorrect parameters such as bi" | ||
50 | +"t rate, frame rate, width or height." | ||
51 | +msgstr "" | ||
52 | + | ||
53 | +#: ../lib/html5_video_plugin/ffmpeg.rb:41 | ||
54 | +msgid "Could not open \"%s\"." | ||
55 | +msgstr "" | ||
56 | + | ||
57 | +#: ../lib/html5_video_plugin/ffmpeg.rb:44 | ||
58 | +msgid "Unsupported codec %{codec} for %{act} stream %{id}." | ||
59 | +msgstr "" | ||
60 | + | ||
61 | +#: ../lib/html5_video_plugin/ffmpeg.rb:48 | ||
62 | +msgid "Unable to find a suitable output format for %{file}." | ||
63 | +msgstr "" | ||
64 | + | ||
65 | +#: ../lib/html5_video_plugin/ffmpeg.rb:52 | ||
66 | +msgid "Invalid data found when processing input." | ||
67 | +msgstr "" | ||
68 | + | ||
69 | +#: ../lib/html5_video_plugin/ffmpeg.rb:190 | ||
70 | +msgid "Success." | ||
71 | +msgstr "" | ||
72 | + | ||
73 | +#: ../lib/html5_video_plugin/video_channel.rb:4 | ||
74 | +msgid "Video Channel" | ||
75 | +msgstr "" | ||
76 | + | ||
77 | +#: ../lib/html5_video_plugin/video_channel.rb:8 | ||
78 | +msgid "A video channel, where you can make your own web TV." | ||
79 | +msgstr "" | ||
80 | + | ||
81 | +#: ../lib/presenters/video.rb:9 | ||
28 | msgid "Video (%s)" | 82 | msgid "Video (%s)" |
29 | msgstr "Vídeo (%s)" | 83 | msgstr "Vídeo (%s)" |
30 | 84 | ||
31 | -#: plugins/html5_video/lib/html5_video_plugin.rb:10 | ||
32 | -msgid "A plugin to enable the video suport, with auto conversion for the web." | 85 | +#: ../lib/presenters/video.rb:137 |
86 | +msgid "Undefined" | ||
87 | +msgstr "" | ||
88 | + | ||
89 | +#: ../views/content_viewer/_video_player.html.erb:21 | ||
90 | +msgid "Sorry, your browser doesn’t support video." | ||
91 | +msgstr "" | ||
92 | + | ||
93 | +#: ../views/content_viewer/_video_player.html.erb:22 | ||
94 | +msgid "Please try the new %s or %s." | ||
95 | +msgstr "" | ||
96 | + | ||
97 | +#: ../views/content_viewer/_video_player.html.erb:27 | ||
98 | +msgid "Download" | ||
99 | +msgstr "" | ||
100 | + | ||
101 | +#: ../views/content_viewer/video_channel.html.erb:7 | ||
102 | +msgid "This channel contains no videos yet" | ||
103 | +msgstr "" | ||
104 | + | ||
105 | +#: ../views/content_viewer/video_channel.html.erb:14 | ||
106 | +#: ../views/file_presenter/_video.html.erb:15 | ||
107 | +msgid "Quality options" | ||
108 | +msgstr "" | ||
109 | + | ||
110 | +#: ../views/content_viewer/video_channel.html.erb:18 | ||
111 | +msgid "Tags" | ||
112 | +msgstr "" | ||
113 | + | ||
114 | +#: ../views/content_viewer/video_channel.html.erb:22 | ||
115 | +#: ../views/file_presenter/_video.html.erb:17 | ||
116 | +msgid "Description" | ||
117 | +msgstr "" | ||
118 | + | ||
119 | +#: ../views/content_viewer/video_channel.html.erb:66 | ||
120 | +msgid "This channel has one video waiting to be converted" | ||
121 | +msgid_plural "This channel has %d videos waiting to be converted" | ||
122 | +msgstr[0] "" | ||
123 | +msgstr[1] "" | ||
124 | + | ||
125 | +#: ../views/content_viewer/video_channel.html.erb:83 | ||
126 | +msgid "Non video files" | ||
127 | +msgstr "" | ||
128 | + | ||
129 | +#: ../views/file_presenter/_video.html.erb:7 | ||
130 | +msgid "Queued to generate the web version. Come back soon." | ||
131 | +msgstr "" | ||
132 | + | ||
133 | +#: ../views/file_presenter/_video.html.erb:11 | ||
134 | +msgid "This video is not queued to the video conversor. Contact the site admin." | ||
135 | +msgstr "" | ||
136 | + | ||
137 | +#: ../views/file_presenter/_video.html.erb:34 | ||
138 | +msgid "Video conversion errors" | ||
139 | +msgstr "" | ||
140 | + | ||
141 | +#: ../views/file_presenter/_video.html.erb:44 | ||
142 | +msgid "Error while converting %{orig_type} to %{new_type}, %{size} size." | ||
143 | +msgstr "" | ||
144 | + | ||
145 | +#: ../views/file_presenter/_video.html.erb:47 | ||
146 | +msgid "Code %s" | ||
147 | +msgstr "" | ||
148 | + | ||
149 | +#: ../views/file_presenter/_video.html.erb:49 | ||
150 | +msgid "display full output" | ||
33 | msgstr "" | 151 | msgstr "" |
34 | -"Um plugin para habilitar suporte de video, com conversão de audio para a web." |
21.3 KB
14.3 KB
@@ -0,0 +1,175 @@ | @@ -0,0 +1,175 @@ | ||
1 | +.video-list { | ||
2 | + margin: 0px; | ||
3 | + padding: 0px; | ||
4 | + text-align: left; | ||
5 | +} | ||
6 | + | ||
7 | +.video-list-item { | ||
8 | + margin: 2px 1px; | ||
9 | + padding: 0px; | ||
10 | + list-style: none; | ||
11 | + display: inline-block; | ||
12 | + width: 160px; | ||
13 | + height: 120px; | ||
14 | + overflow: hidden; | ||
15 | +} | ||
16 | + | ||
17 | +.video-list-item a { | ||
18 | + display: block; | ||
19 | + height: 100%; | ||
20 | + background-position: 50% -120px; | ||
21 | + background-repeat: no-repeat; | ||
22 | + position: relative; | ||
23 | +} | ||
24 | + | ||
25 | +.video-list-item .frame-fade { | ||
26 | + position: absolute; | ||
27 | + top: 0px; | ||
28 | + left: 0px; | ||
29 | + width: 100%; | ||
30 | + height: 100%; | ||
31 | + display: block; | ||
32 | +} | ||
33 | + | ||
34 | +@keyframes fadein { | ||
35 | + from { opacity: 0.0 } | ||
36 | + to { opacity: 1.0 } | ||
37 | +} | ||
38 | +@-moz-keyframes fadein { | ||
39 | + from { opacity: 0.0 } | ||
40 | + to { opacity: 1.0 } | ||
41 | +} | ||
42 | +@-webkit-keyframes fadein { | ||
43 | + from { opacity: 0.0 } | ||
44 | + to { opacity: 1.0 } | ||
45 | +} | ||
46 | + | ||
47 | +.video-list-item a strong { | ||
48 | + display: block; | ||
49 | + width: 100%; | ||
50 | + padding: 3px 5px; | ||
51 | + position: absolute; | ||
52 | + bottom: 0px; | ||
53 | + background: rgba(0,0,0,0.5); | ||
54 | + color: #FFF; | ||
55 | +} | ||
56 | + | ||
57 | +.video-list-item ul, .video-list-item div { | ||
58 | + display: none; | ||
59 | +} | ||
60 | + | ||
61 | +.non-video-list { | ||
62 | + border-top: 1px solid #DDD; | ||
63 | + margin-top: 15px; | ||
64 | +} | ||
65 | + | ||
66 | +.video-player { | ||
67 | + padding-bottom: 10px; | ||
68 | +} | ||
69 | + | ||
70 | +.video-player .vjs-no-video { | ||
71 | + background: #888; | ||
72 | + padding: 3px 5px; | ||
73 | +} | ||
74 | + | ||
75 | +.video-box { | ||
76 | + display: inline-block; | ||
77 | + position: relative; | ||
78 | +} | ||
79 | + | ||
80 | +.video-channel .video-box { | ||
81 | + float: left; | ||
82 | + width: 400px; | ||
83 | +} | ||
84 | +.video-channel .zoom-in .video-box { | ||
85 | + float: none; | ||
86 | + width: 100%; | ||
87 | +} | ||
88 | + | ||
89 | +.video-box .video-ctrl { | ||
90 | + position: absolute; | ||
91 | + top: 5px; | ||
92 | + right: 5px; | ||
93 | + display: none; | ||
94 | +} | ||
95 | +.video-box:hover .video-ctrl { | ||
96 | + display: block; | ||
97 | +} | ||
98 | + | ||
99 | +.video-player video, .video-box { | ||
100 | + min-width: 256px; | ||
101 | + max-width: 100%; | ||
102 | + background: #000; | ||
103 | +} | ||
104 | + | ||
105 | +.video-channel .video-player.zoom-out video, | ||
106 | +.video-player.zoom-in .video-box, | ||
107 | +.video-player.zoom-in video { | ||
108 | + width: 100%; | ||
109 | +} | ||
110 | + | ||
111 | +.video-player-info { | ||
112 | + text-align: left; | ||
113 | + margin-top: 10px; | ||
114 | +} | ||
115 | + | ||
116 | +.video-channel .video-player-info { | ||
117 | + margin-left: 415px; | ||
118 | +} | ||
119 | +.video-channel .zoom-in .video-player-info { | ||
120 | + margin: 0px; | ||
121 | +} | ||
122 | + | ||
123 | +.video-player-info > div { | ||
124 | + padding: 3px 0px; | ||
125 | +} | ||
126 | + | ||
127 | +.video-player-info .data { | ||
128 | + display: inline-block; | ||
129 | +} | ||
130 | + | ||
131 | +.video-player-info .quality ul { | ||
132 | + display: inline-block; | ||
133 | + margin: 0px -4px; | ||
134 | + padding: 0px; | ||
135 | +} | ||
136 | +.video-player-info .quality li { | ||
137 | + margin: 4px; | ||
138 | + padding: 0px; | ||
139 | +} | ||
140 | + | ||
141 | +.unconverted-videos p span { | ||
142 | + cursor: pointer; | ||
143 | +} | ||
144 | + | ||
145 | +.conversion-error { | ||
146 | + text-align: left; | ||
147 | + border: 1px solid #A44; | ||
148 | + background: #EAA; | ||
149 | +} | ||
150 | + | ||
151 | +#content .conversion-error h2 { | ||
152 | + margin: 10px; | ||
153 | + color: #FFF; | ||
154 | + text-shadow: 1px 1px 1px #944; | ||
155 | + text-align: center; | ||
156 | +} | ||
157 | + | ||
158 | +.conversion-error ul { | ||
159 | + margin: 10px; | ||
160 | + padding: 0px; | ||
161 | +} | ||
162 | + | ||
163 | +.conversion-error li { | ||
164 | + list-style: none; | ||
165 | + margin: 0px; | ||
166 | + padding: 0px; | ||
167 | +} | ||
168 | + | ||
169 | +#article .conversion-error pre { | ||
170 | + border: none; | ||
171 | + background: rgba(222,222,222,0.3); | ||
172 | +} | ||
173 | + | ||
174 | +.icon-videochannel { background-image: url(/designs/icons/tango/Tango/16x16/mimetypes/video-x-generic.png) } | ||
175 | +.icon-newvideochannel { background-image: url(/designs/icons/tango/Tango/16x16/mimetypes/video-x-generic.png) } |
@@ -0,0 +1,215 @@ | @@ -0,0 +1,215 @@ | ||
1 | +/* | ||
2 | +** Noosfero's Video Channel specific client script | ||
3 | +** Released under the same Noosfero's license | ||
4 | +*/ | ||
5 | + | ||
6 | +(function (exports, $) { | ||
7 | +"use strict"; | ||
8 | + | ||
9 | +var vEl = document.createElement('video'); | ||
10 | +var canPlay = { | ||
11 | + webm: !!vEl.canPlayType('video/webm').replace(/no/,''), | ||
12 | + ogg: !!vEl.canPlayType('video/ogg').replace(/no/,''), | ||
13 | + mp4: !!vEl.canPlayType('video/mp4').replace(/no/,'') | ||
14 | +}; | ||
15 | + | ||
16 | +exports.VideoChannel = function VideoChannel(baseEl) { | ||
17 | + this.baseEl = baseEl; | ||
18 | + if ($('.video-player', this.baseEl)[0]){ | ||
19 | + this.player = new NoosferoVideoPlayer(this.baseEl, this); | ||
20 | + this.init(); | ||
21 | + } | ||
22 | +}; | ||
23 | + | ||
24 | +VideoChannel.prototype.init = function() { | ||
25 | + var me = this; | ||
26 | + $('.video-list-item', this.baseEl).each( | ||
27 | + function(num, item) { | ||
28 | + me.initItem(item); | ||
29 | + } | ||
30 | + ); | ||
31 | + if ( $('.video-list li', this.baseEl)[0] ) { | ||
32 | + this.updatePlayer( $('li', this.baseEl).first() ); | ||
33 | + } else { | ||
34 | + log.info('there is no playable video yet.'); | ||
35 | + $('.video-player', this.baseEl).hide(); | ||
36 | + } | ||
37 | +}; | ||
38 | + | ||
39 | +VideoChannel.prototype.initItem = function(item) { | ||
40 | + var me = this; | ||
41 | + $(item).click(function(){ me.updatePlayer(item, true); }); | ||
42 | + var link = $('a', item)[0]; | ||
43 | + link.onclick = function(){ return false }; | ||
44 | + link.nextFrame = VideoChannel.nextFrame; | ||
45 | + if ( !link.frameFade ) | ||
46 | + link.frameFade = $('<div class="frame-fade"></div>').prependTo(link)[0]; | ||
47 | + link.frameFade.style.backgroundImage = link.style.backgroundImage; | ||
48 | + link.addEventListener("animationend", function(){ link.nextFrame() }, false); | ||
49 | + link.addEventListener("webkitAnimationEnd", function(){ link.nextFrame() }, false); | ||
50 | + link.nextFrame(); | ||
51 | +}; | ||
52 | + | ||
53 | +VideoChannel.nextFrame = function(fade) { | ||
54 | + if ( !fade ) { | ||
55 | + this.frameFade.style.opacity = 0.0; | ||
56 | + this.frameFade.style.animationName = ""; | ||
57 | + this.frameFade.style.MozAnimationName = ""; | ||
58 | + this.frameFade.style.webkitAnimationName = ""; | ||
59 | + if ( !this.bgYPos ) this.bgYPos = 0; | ||
60 | + this.style.backgroundPosition = "50% "+ ( this.bgYPos++ * -120 ) +"px"; | ||
61 | + if ( this.bgYPos > 5 ) this.bgYPos = 0; | ||
62 | + this.frameFade.style.backgroundPosition = "50% "+ ( this.bgYPos * -120 ) +"px"; | ||
63 | + var link = this; | ||
64 | + setTimeout( function(){ link.nextFrame(true) }, 10 ); | ||
65 | + } else { | ||
66 | + this.frameFade.style.animationDuration = "1s"; | ||
67 | + this.frameFade.style.animationName = "fadein"; | ||
68 | + this.frameFade.style.MozAnimationDuration = "1s"; | ||
69 | + this.frameFade.style.MozAnimationName = "fadein"; | ||
70 | + this.frameFade.style.webkitAnimationDuration = "1s"; | ||
71 | + this.frameFade.style.webkitAnimationName = "'fadein'"; | ||
72 | + } | ||
73 | +}; | ||
74 | + | ||
75 | +VideoChannel.prototype.updatePlayer = function(item, autoplay) { | ||
76 | + var json = $('a', item)[0].getAttribute("data-webversions"); | ||
77 | + this.player.videoList = JSON.parse(json); | ||
78 | + this.player.selectWebVersion(); | ||
79 | + this.player.update( this.getItemData(item), autoplay ); | ||
80 | +}; | ||
81 | + | ||
82 | +VideoChannel.prototype.getItemData = function(item) { | ||
83 | + var link = $('a', item)[0]; | ||
84 | + var spans = $('span', item); | ||
85 | + var data = {}; | ||
86 | + data.pageURL = link.href; | ||
87 | + data.videoURL = link.getAttribute('data-download'); | ||
88 | + data.posterURL = link.getAttribute('data-poster'); | ||
89 | + data.title = $(link).text(); | ||
90 | + data.abstract = $('.abstract', item)[0].innerHTML; | ||
91 | + data.tags = $('.vli-data-tags > div', item)[0].innerHTML; | ||
92 | + return data; | ||
93 | +}; | ||
94 | + | ||
95 | +///////////// Video Player ///////////////////////////////////////////////////// | ||
96 | + | ||
97 | +exports.NoosferoVideoPlayer = function NoosferoVideoPlayer(place, channel) { | ||
98 | + this.channel = channel; | ||
99 | + this.divBase = $('.video-player', place)[0]; | ||
100 | + if(!this.divBase) return; | ||
101 | + this.info = { | ||
102 | + title : $('h2', this.divBase)[0], | ||
103 | + quality : $('.quality ul', this.divBase)[0], | ||
104 | + tags : $('.tags div', this.divBase)[0], | ||
105 | + abstract : $('.abstract div', this.divBase)[0], | ||
106 | + videoCtrl : $('.video-ctrl', this.divBase)[0], | ||
107 | + downloadBt : $('.download-bt', this.divBase) | ||
108 | + .button({ icons: { primary: "ui-icon-circle-arrow-s" } })[0] | ||
109 | + }; | ||
110 | + this.videoBox = $('.video-box', this.divBase)[0]; | ||
111 | + var me = this; | ||
112 | + this.zoomBt = $('<button class="zoom">Zoom</button>') | ||
113 | + .button({ icons: { primary: "ui-icon-zoomin" }, text: false }) | ||
114 | + .click(function(){ me.toggleZoom() }) | ||
115 | + .appendTo(this.info.videoCtrl); | ||
116 | + this.zoomBt[0].title = "Zoom in"; | ||
117 | + this.videoEl = $('video', this.divBase)[0]; | ||
118 | +}; | ||
119 | + | ||
120 | +NoosferoVideoPlayer.prototype.update = function(data, autoplay) { | ||
121 | + this.info.title.innerHTML = data.title; | ||
122 | + this.videoEl.src = data.videoURL; | ||
123 | + this.videoEl.autoplay = autoplay; | ||
124 | + this.poster = data.posterURL; | ||
125 | + this.videoEl.load(); | ||
126 | + var tags = data.tags || '<span class="empty">None</span>' | ||
127 | + $(this.info.tags).empty().append(tags); | ||
128 | + var desc = data.abstract || '<span class="empty">None</span>' | ||
129 | + $(this.info.abstract).empty().append(desc); | ||
130 | + this.info.downloadBt.href = data.videoURL; | ||
131 | +}; | ||
132 | + | ||
133 | +NoosferoVideoPlayer.prototype.updateQualityOpts = function(type) { | ||
134 | + var me = this; | ||
135 | + $(this.info.quality).empty(); | ||
136 | + for ( var size in this.videoList[type] ) { | ||
137 | + var videoData = this.videoList[type][size]; | ||
138 | + log.info( 'Quality option:', videoData ); | ||
139 | + if ( videoData.status == "done" ) { | ||
140 | + var txt = videoData.size_name; | ||
141 | + if ( !this.channel ) { | ||
142 | + txt = videoData.size.w +'x'+ videoData.size.h + | ||
143 | + ' <small>'+ videoData.vbrate +' KB/s</small>'; | ||
144 | + } | ||
145 | + var bt = $( | ||
146 | + '<li data-path="'+videoData.path+'">'+ txt +'</li>') | ||
147 | + .button({ icons: { primary:"ui-icon-video" } }) | ||
148 | + .click(function(){ me.load(this.video, true) }) | ||
149 | + .appendTo(this.info.quality)[0]; | ||
150 | + bt.video = videoData; | ||
151 | + videoData.qualityBt = bt; | ||
152 | + } | ||
153 | + } | ||
154 | +}; | ||
155 | + | ||
156 | +NoosferoVideoPlayer.prototype.load = function (video, userSelection) { | ||
157 | + if ( this.currentVideo ) $(this.currentVideo.qualityBt).button().button("enable"); | ||
158 | + $(video.qualityBt).button("disable"); | ||
159 | + this.currentVideo = video; | ||
160 | + this.videoEl.src = video.path; | ||
161 | + this.videoEl.preload = "metadata"; | ||
162 | + if ( userSelection ) | ||
163 | + $.cookie( "video_quality", video.size_name, {path:'/'} ); | ||
164 | + if ( $.cookie("video_zoom") == "true" ) this.zoomIn(); | ||
165 | + else this.zoomOut(); | ||
166 | +}; | ||
167 | + | ||
168 | +NoosferoVideoPlayer.prototype.toggleZoom = function () { | ||
169 | + if ( $(this.divBase).hasClass("zoom-in") ) this.zoomOut(); | ||
170 | + else this.zoomIn(); | ||
171 | +}; | ||
172 | + | ||
173 | +NoosferoVideoPlayer.prototype.zoomIn = function () { | ||
174 | + $.cookie( "video_zoom", "true", {path:'/'} ); | ||
175 | + $(this.divBase).removeClass("zoom-out").addClass("zoom-in"); | ||
176 | +}; | ||
177 | + | ||
178 | +NoosferoVideoPlayer.prototype.zoomOut = function () { | ||
179 | + $.cookie( "video_zoom", "false", {path:'/'} ); | ||
180 | + $(this.divBase).removeClass("zoom-in").addClass("zoom-out"); | ||
181 | +}; | ||
182 | + | ||
183 | +NoosferoVideoPlayer.prototype.selectWebVersion = function () { | ||
184 | + var video = null; | ||
185 | + var me = this; | ||
186 | + var q1 = $.cookie("video_quality") || "tiny"; | ||
187 | + var q2 = ( q1 == "tiny" ) ? "nice" : "tiny"; | ||
188 | + var type = canPlay.webm ? "WEBM" : canPlay.ogg ? "OGV" : "MP4"; | ||
189 | + if ( (video = this.getVideoFromList(type, q1)) | ||
190 | + || (video = this.getVideoFromList(type, q2)) | ||
191 | + ) { | ||
192 | + this.updateQualityOpts(video.type); | ||
193 | + setTimeout( function(){ me.load(video) }, 10 ); | ||
194 | + } | ||
195 | +}; | ||
196 | + | ||
197 | +NoosferoVideoPlayer.prototype.getVideoFromList = function (type, quality) { | ||
198 | + log.info( 'Trying to getVideoFromList', type, quality ); | ||
199 | + if (!this.videoList && !this.videoList) { | ||
200 | + log.info( 'The video list is empty' ); | ||
201 | + return null; | ||
202 | + } | ||
203 | + if ( quality.toLowerCase() != "nice" ) quality = "tiny"; | ||
204 | + var selected = this.videoList[type][quality]; | ||
205 | + log.info( 'getVideoFromList find:', selected ); | ||
206 | + if ( selected && selected.status == "done" ) { | ||
207 | + log.info( 'getVideoFromList success' ); | ||
208 | + return selected; | ||
209 | + } else { | ||
210 | + log.info( 'getVideoFromList fail' ); | ||
211 | + return null; | ||
212 | + } | ||
213 | +}; | ||
214 | + | ||
215 | +}(window, jQuery)); |
@@ -0,0 +1,34 @@ | @@ -0,0 +1,34 @@ | ||
1 | +require File.dirname(__FILE__) + '/../../../test/test_helper' | ||
2 | + | ||
3 | +fixture_path = File.dirname(__FILE__) + '/../../../test/fixtures/videos' | ||
4 | +Dir.mkdir(fixture_path) unless File.exist?(fixture_path) | ||
5 | + | ||
6 | +base_url = 'http://noosfero.org/pub/Development/HTML5VideoPlugin' | ||
7 | + | ||
8 | +videos = ['old-movie.mpg', 'atropelamento.ogv', 'firebus.3gp'] | ||
9 | + | ||
10 | +def shutdown(fixture_path, videos) | ||
11 | + videos.map do |v| | ||
12 | + File.unlink(fixture_path+'/'+v) if File.exists?(fixture_path+'/'+v) | ||
13 | + end | ||
14 | + exit 1 | ||
15 | +end | ||
16 | + | ||
17 | +signals = %w{EXIT HUP INT QUIT TERM} | ||
18 | +signals.map{|s| Signal.trap(s) { shutdown fixture_path, videos } } | ||
19 | + | ||
20 | +unless videos.select{|v| !File.exists? fixture_path+'/'+v }.empty? | ||
21 | + # Open3.capture2e is the right way, but needs ruby 1.9 | ||
22 | + puts "\nDownloading video fixture..." | ||
23 | + puts videos.map{|v| base_url+'/'+v}.join(' ') | ||
24 | + output = `cd '#{fixture_path}'; | ||
25 | + LANG=C wget -c #{videos.map{|v| base_url+'/'+v}.join(' ')} || echo '\nERROR'` | ||
26 | + | ||
27 | + if output[-7..-1] == "\nERROR\n" then | ||
28 | + puts "wget fail. Try again." | ||
29 | + exit 0 | ||
30 | + end | ||
31 | +end | ||
32 | + | ||
33 | +signals.map{|s| Signal.trap(s) { } } | ||
34 | + |
plugins/html5_video/test/functional/content_viewer_controler_test.rb
1 | -require 'test_helper' | 1 | +require File.dirname(__FILE__) + '/../../../../test/test_helper' |
2 | require 'content_viewer_controller' | 2 | require 'content_viewer_controller' |
3 | 3 | ||
4 | +class ContentViewerController | ||
5 | + # Re-raise errors caught by the controller. | ||
6 | + def rescue_action(e) raise e end | ||
7 | + append_view_path File.join(File.dirname(__FILE__) + '/../../views') | ||
8 | +end | ||
9 | + | ||
4 | class ContentViewerControllerTest < ActionController::TestCase | 10 | class ContentViewerControllerTest < ActionController::TestCase |
5 | 11 | ||
6 | all_fixtures | 12 | all_fixtures |
@@ -10,11 +16,13 @@ class ContentViewerControllerTest < ActionController::TestCase | @@ -10,11 +16,13 @@ class ContentViewerControllerTest < ActionController::TestCase | ||
10 | 16 | ||
11 | @profile = create_user('testinguser').person | 17 | @profile = create_user('testinguser').person |
12 | @environment = @profile.environment | 18 | @environment = @profile.environment |
19 | + @environment.enable_plugin(Html5VideoPlugin) | ||
13 | end | 20 | end |
14 | attr_reader :profile, :environment | 21 | attr_reader :profile, :environment |
15 | 22 | ||
16 | should 'add html5 video tag to the page of file type video' do | 23 | should 'add html5 video tag to the page of file type video' do |
17 | file = UploadedFile.create!(:uploaded_data => fixture_file_upload('/files/test.txt', 'video/ogg'), :profile => profile) | 24 | file = UploadedFile.create!(:uploaded_data => fixture_file_upload('/files/test.txt', 'video/ogg'), :profile => profile) |
25 | + process_delayed_job_queue | ||
18 | get :view_page, file.url.merge(:view=>:true) | 26 | get :view_page, file.url.merge(:view=>:true) |
19 | assert_select '#article video' | 27 | assert_select '#article video' |
20 | end | 28 | end |
plugins/html5_video/test/unit/create_video_for_web_job_test.rb
0 → 100755
@@ -0,0 +1,100 @@ | @@ -0,0 +1,100 @@ | ||
1 | +require File.dirname(__FILE__) + '/../../../../test/test_helper' | ||
2 | +require File.dirname(__FILE__) + '/../download_fixture' | ||
3 | +$LOAD_PATH << File.dirname(__FILE__) + '/../../lib/' | ||
4 | +require 'html5_video_plugin.rb' | ||
5 | + | ||
6 | +class CreateVideoForWebJobTest < ActiveSupport::TestCase | ||
7 | + | ||
8 | + ffmpeg = Html5VideoPlugin::Ffmpeg.new | ||
9 | + | ||
10 | + def setup | ||
11 | + Environment.default.enable_plugin Html5VideoPlugin | ||
12 | + # Create a temporary directory to write testing files | ||
13 | + @temp = %x{ mktemp -d }[0..-2] | ||
14 | + end | ||
15 | + | ||
16 | + def teardown | ||
17 | + # Remove the temporary directory | ||
18 | + %x{ rm -r '#{@temp}' } | ||
19 | + end | ||
20 | + | ||
21 | + def run_CreateVideoForWebJobs_for_video(video) | ||
22 | + jobs = video.web_version_jobs | ||
23 | + # Run all CreateVideoForWebJob's for this created video: | ||
24 | + print '['; STDOUT.flush | ||
25 | + jobs.each do |job| | ||
26 | + YAML.load(job.handler).perform | ||
27 | + STDOUT.write '+'; STDOUT.flush # a progress to the user see something. | ||
28 | + end | ||
29 | + print ']'; STDOUT.flush | ||
30 | + video.reload | ||
31 | + end | ||
32 | + | ||
33 | + should 'create web compatible version to a uploaded MPEG video' do | ||
34 | + video = FilePresenter.for UploadedFile.create!( | ||
35 | + :uploaded_data => fixture_file_upload('/videos/old-movie.mpg', 'video/mpeg'), | ||
36 | + :profile => fast_create(Person) ) | ||
37 | + assert_equal({}, video.web_versions, 'video.web_versions starts as empty list') | ||
38 | + | ||
39 | + run_CreateVideoForWebJobs_for_video(video) | ||
40 | + | ||
41 | + video.web_versions.each do |format, format_block| | ||
42 | + format_block.each { |size, video_conv| assert !video_conv[:original] } | ||
43 | + end | ||
44 | + assert_equal 'tiny.ogv', video.web_versions[:OGV][:tiny][:file_name] | ||
45 | + assert_equal({:w=>320,:h=>212}, video.web_versions[:OGV][:tiny][:size]) | ||
46 | + assert_equal 250, video.web_versions[:OGV][:tiny][:vbrate] | ||
47 | + assert_equal 64, video.web_versions[:OGV][:tiny][:abrate] | ||
48 | + assert_equal 'nice.ogv', video.web_versions[:OGV][:nice][:file_name] | ||
49 | + assert_equal({:w=>576,:h=>384}, video.web_versions[:OGV][:nice][:size]) | ||
50 | + assert_equal 104857, video.web_versions[:OGV][:nice][:vbrate] | ||
51 | + assert_equal 128, video.web_versions[:OGV][:nice][:abrate] | ||
52 | + assert_equal 'tiny.webm', video.web_versions[:WEBM][:tiny][:file_name] | ||
53 | + assert_equal({:w=>320,:h=>212}, video.web_versions[:WEBM][:tiny][:size]) | ||
54 | + assert_equal 250, video.web_versions[:WEBM][:tiny][:vbrate] | ||
55 | + assert_equal 64, video.web_versions[:WEBM][:tiny][:abrate] | ||
56 | + assert_equal 'nice.webm', video.web_versions[:WEBM][:nice][:file_name] | ||
57 | + assert_equal({:w=>576,:h=>384}, video.web_versions[:WEBM][:nice][:size]) | ||
58 | + assert_equal 104857, video.web_versions[:WEBM][:nice][:vbrate] | ||
59 | + assert_equal 128, video.web_versions[:WEBM][:nice][:abrate] | ||
60 | + | ||
61 | + webdir = ffmpeg.webdir_for_original_video video.public_filename | ||
62 | + assert File.exists?( webdir+'/tiny.ogv' ) | ||
63 | + assert File.exists?( webdir+'/nice.ogv' ) | ||
64 | + assert File.exists?( webdir+'/tiny.webm' ) | ||
65 | + assert File.exists?( webdir+'/nice.webm' ) | ||
66 | + end | ||
67 | + | ||
68 | + should 'create web compatible version to a uploaded OGG video' do | ||
69 | + resp = ffmpeg.run [ :i, "#{fixture_path}/videos/firebus.3gp", | ||
70 | + :t, 4, :f, 'ogg', | ||
71 | + :vcodec, 'libtheora', :vb, '800k', | ||
72 | + :acodec, 'libvorbis', :ar, 44100, :ab, '192k', | ||
73 | + "#{@temp}/firebus.ogv" ] | ||
74 | + assert_equal 0, resp[:error][:code], 'creating a valid OGV' | ||
75 | + | ||
76 | + video = FilePresenter.for UploadedFile.create!( | ||
77 | + uploaded_data: Rack::Test::UploadedFile.new("#{@temp}/firebus.ogv", 'video/ogv'), | ||
78 | + profile: fast_create(Person) ) | ||
79 | + assert_equal({}, video.web_versions, 'video.web_versions starts as empty list') | ||
80 | + | ||
81 | + run_CreateVideoForWebJobs_for_video(video) | ||
82 | + | ||
83 | + video.web_versions.each do |format, format_block| | ||
84 | + format_block.each { |size, video_conv| assert !video_conv[:original] } | ||
85 | + end | ||
86 | + web_versions = video.web_versions! | ||
87 | + assert_equal 'tiny.ogv', web_versions[:OGV][:tiny][:file_name] | ||
88 | + assert_equal({:w=>320,:h=>240}, web_versions[:OGV][:tiny][:size]) | ||
89 | + assert_equal 250, web_versions[:OGV][:tiny][:vbrate] | ||
90 | + assert_equal 64, web_versions[:OGV][:tiny][:abrate] | ||
91 | + assert_equal 'tiny.webm', web_versions[:WEBM][:tiny][:file_name] | ||
92 | + assert_equal({:w=>320,:h=>240}, web_versions[:WEBM][:tiny][:size]) | ||
93 | + assert_equal 250, web_versions[:WEBM][:tiny][:vbrate] | ||
94 | + assert_equal 64, web_versions[:WEBM][:tiny][:abrate] | ||
95 | + assert_equal 'firebus.ogv', web_versions[:OGV][:orig][:file_name] | ||
96 | + assert_equal({:w=>128,:h=>96}, web_versions[:OGV][:orig][:size]) | ||
97 | + assert_equal 192, web_versions[:OGV][:orig][:abrate] | ||
98 | + end | ||
99 | + | ||
100 | +end |
plugins/html5_video/test/unit/create_video_preview_job_test.rb
0 → 100755
@@ -0,0 +1,48 @@ | @@ -0,0 +1,48 @@ | ||
1 | +require File.dirname(__FILE__) + '/../../../../test/test_helper' | ||
2 | +require File.dirname(__FILE__) + '/../download_fixture' | ||
3 | +$LOAD_PATH << File.dirname(__FILE__) + '/../../lib/' | ||
4 | +require 'html5_video_plugin.rb' | ||
5 | + | ||
6 | +class CreateVideoPreviewJobTest < ActiveSupport::TestCase | ||
7 | + | ||
8 | + def setup | ||
9 | + Environment.default.enable_plugin Html5VideoPlugin | ||
10 | + end | ||
11 | + | ||
12 | + def run_CreateVideoPreviewJob_for_video(video) | ||
13 | + jobs = video.web_preview_jobs | ||
14 | + # Run all CreateVideoPreviewJob's for this created video: | ||
15 | + print '['; STDOUT.flush | ||
16 | + jobs.each do |job| | ||
17 | + YAML.load(job.handler).perform | ||
18 | + STDOUT.write '+'; STDOUT.flush # a progress to the user see something. | ||
19 | + end | ||
20 | + print ']'; STDOUT.flush | ||
21 | + video.reload | ||
22 | + end | ||
23 | + | ||
24 | + should 'create preview images to uploaded videos' do | ||
25 | + | ||
26 | + video = FilePresenter.for UploadedFile.create!( | ||
27 | + :uploaded_data => fixture_file_upload('/videos/firebus.3gp', 'video/3gpp'), | ||
28 | + :profile => fast_create(Person) ) | ||
29 | + assert not(video.has_previews?) | ||
30 | + | ||
31 | + run_CreateVideoPreviewJob_for_video video | ||
32 | + video.reload | ||
33 | + | ||
34 | + assert video.has_previews?, 'must have built preview images' | ||
35 | + assert_equal({:big => '/web/preview_160x120.jpg', | ||
36 | + :thumb => '/web/preview_107x80.jpg'}, | ||
37 | + video.previews) | ||
38 | + | ||
39 | + video_path = File.dirname(video.full_filename) | ||
40 | + | ||
41 | + assert File.exist?(video_path + video.previews[:big]) | ||
42 | + assert File.exist?(video_path + video.previews[:thumb]) | ||
43 | + assert_match /^\/[^ ]*\/[0-9]+\/+web\/preview_160x120.jpg JPEG 160x720 /, `identify #{video_path + video.previews[:big]}` | ||
44 | + assert_match /^\/[^ ]*\/[0-9]+\/+web\/preview_107x80.jpg JPEG 107x480 /, `identify #{video_path + video.previews[:thumb]}` | ||
45 | + | ||
46 | + end | ||
47 | + | ||
48 | +end |
@@ -0,0 +1,267 @@ | @@ -0,0 +1,267 @@ | ||
1 | +#require File.dirname(__FILE__) + '/../../../../test/test_helper' | ||
2 | +require 'test_helper' | ||
3 | +#require File.dirname(__FILE__) + '/../download_fixture' | ||
4 | +require_relative '../download_fixture' | ||
5 | +$LOAD_PATH << File.dirname(__FILE__) + '/../../lib/' | ||
6 | +require 'html5_video_plugin.rb' | ||
7 | +require 'html5_video_plugin/ffmpeg.rb' | ||
8 | + | ||
9 | +class FfmpegTest < ActiveSupport::TestCase | ||
10 | + | ||
11 | + ffmpeg = Html5VideoPlugin::Ffmpeg.new | ||
12 | + | ||
13 | + def create_video(file, mime) | ||
14 | + file = UploadedFile.create!( | ||
15 | + :uploaded_data => fixture_file_upload('/videos/'+file, mime), | ||
16 | + :profile => fast_create(Person)) | ||
17 | + end | ||
18 | + | ||
19 | + def video_path(file='') | ||
20 | + "#{fixture_path}/videos/#{file}" | ||
21 | + end | ||
22 | + | ||
23 | + # Some tests wil create a "web" dir inside fixture videos dir, so we must remove it. | ||
24 | + def rm_web_videos_dir | ||
25 | + webdir = video_path 'web' | ||
26 | + return unless Dir.exist? webdir | ||
27 | + Dir.foreach(webdir) do|file| | ||
28 | + File.unlink webdir +'/'+ file unless file.match /^\.+$/ | ||
29 | + end | ||
30 | + Dir.delete webdir | ||
31 | + end | ||
32 | + | ||
33 | + def setup | ||
34 | + Environment.default.enable_plugin Html5VideoPlugin | ||
35 | + @temp = [] | ||
36 | + rm_web_videos_dir | ||
37 | + end | ||
38 | + | ||
39 | + def teardown | ||
40 | + @temp.each do |file| | ||
41 | + if File.exist? file | ||
42 | + File.unlink file | ||
43 | + end | ||
44 | + end | ||
45 | + rm_web_videos_dir | ||
46 | + end | ||
47 | + | ||
48 | + # Create a temp filename, not a file. | ||
49 | + # If a file with this name is created, it will be removed by the teardown. | ||
50 | + def mkTempName(ext='') | ||
51 | + ( @temp << "/tmp/#{SecureRandom.hex}.#{ext}" ).last | ||
52 | + end | ||
53 | + | ||
54 | + should 'has the right version of ffmpeg' do | ||
55 | + response = ffmpeg.run :version | ||
56 | + assert_match /^ffmpeg version 3\.0/, response[:output] | ||
57 | + end | ||
58 | + | ||
59 | + should 'complain about missing input' do | ||
60 | + response = ffmpeg.run :i, 'ups-i-dont-exixt.ogv' | ||
61 | + assert_equal 1, response[:error][:code] | ||
62 | + end | ||
63 | + | ||
64 | + should 'complain about missing output' do | ||
65 | + response = ffmpeg.run :i, video_path('old-movie.mpg') | ||
66 | + assert_equal 2, response[:error][:code] | ||
67 | + end | ||
68 | + | ||
69 | + should 'complain about unknown encoder' do | ||
70 | + tmpogv = mkTempName :ogv | ||
71 | + response = ffmpeg.run :i, video_path('old-movie.mpg'), :vcodec, 'noCodec', tmpogv | ||
72 | + assert_equal 3, response[:error][:code] | ||
73 | + end | ||
74 | + | ||
75 | + should 'complain about wrong encoder' do | ||
76 | + tmpvid = mkTempName :mpg | ||
77 | + response = ffmpeg.run :i, video_path('firebus.3gp'), :'b:v', 3, tmpvid | ||
78 | + assert_equal 4, response[:error][:code] | ||
79 | + end | ||
80 | + | ||
81 | +# #TODO: cant reproduce this error | ||
82 | +# should 'complain about not being able to open encoder' do | ||
83 | +# tmpvid = mkTempName :mpg | ||
84 | +# response = run_ffmpeg [:i, video_path('old-movie.mpg'), tmpvid] | ||
85 | +# assert_equal 5, response[:error][:code] | ||
86 | +# end | ||
87 | + | ||
88 | +# #TODO: cant reproduce this error | ||
89 | +# should 'complain about unsuported codec' do | ||
90 | +# tmpvid = mkTempName :webm | ||
91 | +# response = run_ffmpeg [:i, video_path('firebus.3gp'), :vcodec, 'libtheora', tmpvid] | ||
92 | +# assert_equal 6, response[:error][:code] | ||
93 | +# end | ||
94 | + | ||
95 | + should 'complain about unknown output format' do | ||
96 | + tmpvid = mkTempName :nop | ||
97 | + response = ffmpeg.run :i, video_path('old-movie.mpg'), tmpvid | ||
98 | + assert_equal 7, response[:error][:code] | ||
99 | + end | ||
100 | + | ||
101 | + should 'complain about invalid input data' do | ||
102 | + tmpvid = mkTempName :mpg | ||
103 | + fakevid = Tempfile.new ['fake', '.mpg'] | ||
104 | + response = ffmpeg.run :i, fakevid.path, tmpvid | ||
105 | + fakevid.close | ||
106 | + fakevid.unlink | ||
107 | + assert_equal 8, response[:error][:code] | ||
108 | + end | ||
109 | + | ||
110 | + should 'read ffmpeg information and features' do | ||
111 | + response = ffmpeg.register_information | ||
112 | + assert_match /^[0-9]\.[0-9]\.[0-9]$/, response[:version] | ||
113 | + formatWebM = /^\{demux:false,description:WebM,mux:true\}$/ | ||
114 | + assert_match formatWebM, h2s(response[:formats][:webm]) | ||
115 | + codecVorbis = /^\{decode:true,description:Vorbis[^,]+,direct_rendering:false,draw_horiz_band:false,encode:true,type:audio,wf_trunc:false\}$/ | ||
116 | + assert_match codecVorbis, h2s(response[:codecs][:vorbis]) | ||
117 | + end | ||
118 | + | ||
119 | + should 'convert time string to seconds int' do | ||
120 | + assert_equal 30, ffmpeg.timestr_to_secs('00:00:30') | ||
121 | + assert_equal 630, ffmpeg.timestr_to_secs('00:10:30') | ||
122 | + assert_equal 7830, ffmpeg.timestr_to_secs('02:10:30') | ||
123 | + assert_equal nil, ffmpeg.timestr_to_secs('invalid time string') | ||
124 | + end | ||
125 | + | ||
126 | + should 'parse video stream info' do | ||
127 | + response = ffmpeg.get_stream_info 'Stream #0:0[0x1e0]: Video: mpeg1video, yuv420p(tv), 720x480 [SAR 200:219 DAR 100:73], 104857 kb/s, 23.98 fps, 23.98 tbr, 90k tbn, 23.98 tbc' | ||
128 | + | ||
129 | + assert_equal 'video', response[:type] | ||
130 | + assert_equal 'mpeg1video', response[:codec] | ||
131 | + assert_equal 104857, response[:bitrate] | ||
132 | + assert_equal 23.98, response[:framerate] | ||
133 | + assert_equal 'video', response[:type] | ||
134 | + assert_equal 720, response[:size][:w] | ||
135 | + assert_equal 480, response[:size][:h] | ||
136 | + end | ||
137 | + | ||
138 | + should 'parse audio stream info' do | ||
139 | + response = ffmpeg.get_stream_info 'Stream #0:1[0x1c0]: Audio: mp2, 48000 Hz, 2 channels, stereo, s16p, 128 kb/s' | ||
140 | + assert_equal 'audio', response[:type] | ||
141 | + assert_equal 'mp2', response[:codec] | ||
142 | + assert_equal 48000, response[:frequency] | ||
143 | + assert_equal 128, response[:bitrate] | ||
144 | + assert_equal 2, response[:channels] | ||
145 | + end | ||
146 | + | ||
147 | + should 'fetch webdir' do | ||
148 | + video = mkTempName :mpg | ||
149 | + assert_equal '/tmp/web', ffmpeg.webdir_for_original_video(video) | ||
150 | + end | ||
151 | + | ||
152 | + should 'validate conversion conf for web' do | ||
153 | + conf = { in: video_path('old-movie.mpg') } | ||
154 | + validConf = ffmpeg.validate_conversion_conf_for_web conf, :webm | ||
155 | + assert_match /^\{abrate:128,file_name:640x426_1024.webm,in:[^:]+\/old-movie.mpg,out:[^:]+\/web\/640x426_1024.webm,size:\{h:426,w:640\},vbrate:1024\}$/, h2s(validConf) | ||
156 | + end | ||
157 | + | ||
158 | + should 'validate conversion conf for web with given output filename' do | ||
159 | + conf = { in: video_path('old-movie.mpg'), file_name: 'test.webm' } | ||
160 | + validConf = ffmpeg.validate_conversion_conf_for_web conf, :webm | ||
161 | + assert_match /^\/.+\/web\/test.webm$/, validConf[:out] | ||
162 | + end | ||
163 | + | ||
164 | + should 'get video info' do | ||
165 | + resp = ffmpeg.get_video_info video_path('old-movie.mpg') | ||
166 | + assert_equal [:error, :parameters, :output, :metadata, :type, :duration, :global_bitrate, :streams], resp.keys | ||
167 | + assert_equal '{code:0,message:Success.}', h2s(resp[:error]) | ||
168 | + assert_equal 'mpeg', resp[:type] | ||
169 | + assert_equal 5, resp[:duration] | ||
170 | + assert_equal 2428, resp[:global_bitrate] | ||
171 | + assert_equal '{}', h2s(resp[:metadata]) | ||
172 | + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg\]$/, h2s(resp[:parameters]) | ||
173 | + assert_match /^\{bitrate:104857,codec:mpeg1video,framerate:23.98,id:#0,size:\{h:480,w:720\},type:video\}$/, h2s(resp[:streams][0]) | ||
174 | + assert_match /^\{bitrate:128,codec:mp2,frequency:48000,id:#0,type:audio\}$/, h2s(resp[:streams][1]) | ||
175 | + end | ||
176 | + | ||
177 | + should 'get video info with metadata' do | ||
178 | + resp = ffmpeg.get_video_info video_path('atropelamento.ogv') | ||
179 | + assert_equal '{comment:Stop-motion movie,title:Atropelamento}', h2s(resp[:metadata]) | ||
180 | + end | ||
181 | + | ||
182 | + should 'convert to OGV' do | ||
183 | + out_video = mkTempName :ogv | ||
184 | + resp = ffmpeg.convert2ogv in: video_path('old-movie.mpg'), out: out_video | ||
185 | + assert_equal [:error, :parameters, :output, :conf], resp.keys | ||
186 | + assert_equal '{code:0,message:}', h2s(resp[:error]) | ||
187 | + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,600k,f,ogg,acodec,libvorbis,vcodec,libtheora,\/tmp\/[^,\/]*.ogv\]$/, h2s(resp[:parameters]) | ||
188 | + assert_match /^\{in:\/[^,]*\/videos\/old-movie.mpg,out:\/tmp\/[^,\/]*.ogv,type:OGV,vbrate:600\}$/, h2s(resp[:conf]) | ||
189 | + assert File.exist? out_video | ||
190 | + end | ||
191 | + | ||
192 | + should 'convert to MP4' do | ||
193 | + out_video = mkTempName :mp4 | ||
194 | + resp = ffmpeg.convert2mp4 in: video_path('old-movie.mpg'), out: out_video | ||
195 | + assert_equal [:error, :parameters, :output, :conf], resp.keys | ||
196 | + assert_equal '{code:0,message:}', h2s(resp[:error]) | ||
197 | + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,600k,preset,slow,f,mp4,acodec,aac,vcodec,libx264,strict,-2,\/tmp\/[^,\/]*.mp4\]$/, h2s(resp[:parameters]) | ||
198 | + assert_match /^\{in:\/[^,]*\/videos\/old-movie.mpg,out:\/tmp\/[^,\/]*.mp4,type:MP4,vbrate:600\}$/, h2s(resp[:conf]) | ||
199 | + assert File.exist? out_video | ||
200 | + end | ||
201 | + | ||
202 | + should 'convert to WebM' do | ||
203 | + out_video = mkTempName :webm | ||
204 | + resp = ffmpeg.convert2webm in: video_path('old-movie.mpg'), out: out_video | ||
205 | + assert_equal [:error, :parameters, :output, :conf], resp.keys | ||
206 | + assert_equal '{code:0,message:}', h2s(resp[:error]) | ||
207 | + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,600k,f,webm,acodec,libvorbis,vcodec,libvpx,\/tmp\/[^,\/]*.webm\]$/, h2s(resp[:parameters]) | ||
208 | + assert_match /^\{in:\/[^,]*\/videos\/old-movie.mpg,out:\/tmp\/[^,\/]*.webm,type:WEBM,vbrate:600\}$/, h2s(resp[:conf]) | ||
209 | + assert File.exist? out_video | ||
210 | + end | ||
211 | + | ||
212 | + should 'convert to OGV for the web' do | ||
213 | + resp = ffmpeg.make_ogv_for_web in: video_path('old-movie.mpg') | ||
214 | + assert_equal [:error, :parameters, :output, :conf], resp.keys | ||
215 | + assert_equal '{code:0,message:}', h2s(resp[:error]) | ||
216 | + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,1024k,f,ogg,acodec,libvorbis,vcodec,libtheora,s,640x426,b:a,128k,\/[^,]*\/videos\/web\/640x426_1024.ogv\]$/, h2s(resp[:parameters]) | ||
217 | + assert_match /^\/[^,]*\/videos\/web\/640x426_1024.ogv$/, resp[:conf][:out] | ||
218 | + assert File.exist? resp[:conf][:out] | ||
219 | + end | ||
220 | + | ||
221 | + should 'convert to MP4 for the web' do | ||
222 | + resp = ffmpeg.make_mp4_for_web in: video_path('old-movie.mpg') | ||
223 | + assert_equal [:error, :parameters, :output, :conf], resp.keys | ||
224 | + assert_equal '{code:0,message:}', h2s(resp[:error]) | ||
225 | + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,1024k,preset,slow,f,mp4,acodec,aac,vcodec,libx264,strict,-2,s,640x426,b:a,128k,\/[^,]*\/videos\/web\/640x426_1024.mp4\]$/, h2s(resp[:parameters]) | ||
226 | + assert_match /^\/[^,]*\/videos\/web\/640x426_1024.mp4$/, resp[:conf][:out] | ||
227 | + assert File.exist? resp[:conf][:out] | ||
228 | + end | ||
229 | + | ||
230 | + should 'convert to WebM for the web' do | ||
231 | + resp = ffmpeg.make_webm_for_web in: video_path('old-movie.mpg') | ||
232 | + assert_equal [:error, :parameters, :output, :conf], resp.keys | ||
233 | + assert_equal '{code:0,message:}', h2s(resp[:error]) | ||
234 | + assert_match /^\[i,\/[^,]*\/videos\/old-movie.mpg,y,b:v,1024k,f,webm,acodec,libvorbis,vcodec,libvpx,s,640x426,b:a,128k,\/[^,]*\/videos\/web\/640x426_1024.webm\]$/, h2s(resp[:parameters]) | ||
235 | + assert_match /^\/[^,]*\/videos\/web\/640x426_1024.webm$/, resp[:conf][:out] | ||
236 | + assert File.exist? resp[:conf][:out] | ||
237 | + end | ||
238 | + | ||
239 | + should 'create video thumbnail' do | ||
240 | + resp = ffmpeg.video_thumbnail video_path('old-movie.mpg') | ||
241 | + assert_match /^\/web\/preview_160x120.jpg$/, resp[:big] | ||
242 | + assert_match /^\/web\/preview_107x80.jpg$/, resp[:thumb] | ||
243 | + assert File.exist?(video_path resp[:big]) | ||
244 | + assert File.exist?(video_path resp[:thumb]) | ||
245 | + assert_match /^\/[^ ]*\/videos\/+web\/preview_160x120.jpg JPEG 160x720 /, `identify #{video_path resp[:big]}` | ||
246 | + assert_match /^\/[^ ]*\/videos\/+web\/preview_107x80.jpg JPEG 107x480 /, `identify #{video_path resp[:thumb]}` | ||
247 | + end | ||
248 | + | ||
249 | + should 'recognize ffmpeg version' do | ||
250 | + assert_match /^[0-9]\.[0-9]\.[0-9]$/, ffmpeg.version | ||
251 | + end | ||
252 | + | ||
253 | + should 'list supported formats' do | ||
254 | + formatMpeg = /^\{demux:true,description:MPEG-1 Systems[^}]+,mux:true\}$/ | ||
255 | + formatWebM = /^\{demux:false,description:WebM,mux:true\}$/ | ||
256 | + assert_match formatMpeg, h2s(ffmpeg.formats[:mpeg]) | ||
257 | + assert_match formatWebM, h2s(ffmpeg.formats[:webm]) | ||
258 | + end | ||
259 | + | ||
260 | + should 'list supported codecs' do | ||
261 | + codecOpus = /^\{decode:true,description:Opus[^,]+,direct_rendering:false,draw_horiz_band:false,encode:true,type:audio,wf_trunc:false\}$/ | ||
262 | + codecVorb = /^\{decode:true,description:Vorbis[^,]+,direct_rendering:false,draw_horiz_band:false,encode:true,type:audio,wf_trunc:false\}$/ | ||
263 | + assert_match codecOpus, h2s(ffmpeg.codecs[:opus]) | ||
264 | + assert_match codecVorb, h2s(ffmpeg.codecs[:vorbis]) | ||
265 | + end | ||
266 | + | ||
267 | +end |
@@ -0,0 +1,193 @@ | @@ -0,0 +1,193 @@ | ||
1 | +require File.dirname(__FILE__) + '/../../../../test/test_helper' | ||
2 | +require File.dirname(__FILE__) + '/../download_fixture' | ||
3 | +$LOAD_PATH << File.dirname(__FILE__) + '/../../lib/' | ||
4 | +require 'html5_video_plugin.rb' | ||
5 | + | ||
6 | +class VideoPresenterTest < ActiveSupport::TestCase | ||
7 | + | ||
8 | + #include Html5VideoPlugin::Ffmpeg | ||
9 | + | ||
10 | + def create_video(file, mime) | ||
11 | + file = UploadedFile.create!( | ||
12 | + :uploaded_data => fixture_file_upload('/videos/'+file, mime), | ||
13 | + :profile => fast_create(Person)) | ||
14 | + end | ||
15 | + | ||
16 | + def process_video(file) | ||
17 | + process_delayed_job_queue | ||
18 | + file.reload | ||
19 | + FilePresenter::Video.new file | ||
20 | + end | ||
21 | + | ||
22 | + def create_and_proc_video(file, mime) | ||
23 | + process_video(create_video(file, mime)) | ||
24 | + end | ||
25 | + | ||
26 | + def setup | ||
27 | + Environment.default.enable_plugin Html5VideoPlugin | ||
28 | + end | ||
29 | + | ||
30 | + should 'accept to encapsulate a video file' do | ||
31 | + file = create_video 'old-movie.mpg', 'video/mpeg' | ||
32 | + assert_equal 10, FilePresenter::Video.accepts?(file) | ||
33 | + end | ||
34 | + | ||
35 | + should 'retrieve meta-data' do | ||
36 | + video = create_and_proc_video('old-movie.mpg', 'video/mpeg') | ||
37 | + assert_equal 'Video (MPEG)', video.short_description, 'describe the file type' | ||
38 | + assert_equivalent video.meta_data.settings.keys, | ||
39 | + [:image_previews, :original_video, :web_versions] | ||
40 | + assert_equivalent video.original_video.keys, | ||
41 | + [:metadata, :type, :streams, :global_bitrate, :error, :duration, :parameters] | ||
42 | + assert_equal h2s([:OGV, :WEBM]), h2s(video.web_versions.keys) | ||
43 | + end | ||
44 | + | ||
45 | + should 'retrieve all web versions' do | ||
46 | + video1 = create_and_proc_video('old-movie.mpg', 'video/mpeg') | ||
47 | + video2 = create_and_proc_video('atropelamento.ogv', 'video/ogg') | ||
48 | + # make video2 as fake valid web video: | ||
49 | + audio = video2.original_video[:streams].find{|s| s[:type] == 'audio' } | ||
50 | + audio = audio[:codec] = 'vorbis' | ||
51 | + assert_equal video1.web_versions, video1.web_versions!, 'all web versions (1)' | ||
52 | + assert_equal h2s(video2.web_versions[:OGV].merge(:orig => { | ||
53 | + :type => :OGV, | ||
54 | + :status => "done", | ||
55 | + :vbrate => 334, | ||
56 | + :size_name => "orig", | ||
57 | + :file_name => "atropelamento.ogv", | ||
58 | + :size => {:h=>130, :w=>208}, | ||
59 | + :original => true, | ||
60 | + :path => video2.public_filename, | ||
61 | + :abrate => 0 })), | ||
62 | + h2s(video2.web_versions![:OGV]), 'all web versions (2)' | ||
63 | + # test get the tiniest web version: | ||
64 | + data = video1.tiniest_web_version(:OGV) | ||
65 | + assert_equal h2s(data), h2s( | ||
66 | + :type=>:OGV, :size_name=>"tiny", :status=>"done", | ||
67 | + :fps=>12, :abrate=>64, :vbrate=>250, :size=>{:h=>212, :w=>320}, | ||
68 | + :path=>File.join(Rails.root,"/test/tmp/0000/#{'%04d'%video1.id}/web/tiny.ogv"), | ||
69 | + :file_name=>"tiny.ogv" ) | ||
70 | + end | ||
71 | + | ||
72 | + should 'know if it has ready_web_versions' do | ||
73 | + file = create_video 'old-movie.mpg', 'video/mpeg' | ||
74 | + video = FilePresenter::Video.new file | ||
75 | + assert_equal h2s(video.ready_web_versions), h2s({}) | ||
76 | + video = process_video file | ||
77 | + web_versions = video.ready_web_versions | ||
78 | + assert_equal 'nice.ogv', web_versions[:OGV][:nice][:file_name] | ||
79 | + assert_equal 'tiny.ogv', web_versions[:OGV][:tiny][:file_name] | ||
80 | + assert_equal 'nice.webm', web_versions[:WEBM][:nice][:file_name] | ||
81 | + assert_equal 'tiny.webm', web_versions[:WEBM][:tiny][:file_name] | ||
82 | + end | ||
83 | + | ||
84 | + should 'know its tiniest_web_version' do | ||
85 | + video = create_and_proc_video 'atropelamento.ogv', 'video/ogg' | ||
86 | + tiniestOGV = video.tiniest_web_version :OGV | ||
87 | + tiniestWEBM = video.tiniest_web_version :WEBM | ||
88 | + assert_equal h2s({w:208,h:130}), h2s(tiniestOGV[:size]) | ||
89 | + assert_equal :OGV, tiniestOGV[:type] | ||
90 | + assert_equal h2s({w:208,h:130}), h2s(tiniestWEBM[:size]) | ||
91 | + assert_equal :WEBM, tiniestWEBM[:type] | ||
92 | + assert_equal nil, video.tiniest_web_version(:MP4) | ||
93 | + end | ||
94 | + | ||
95 | + should 'know if it has_ogv_version' do | ||
96 | + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg' | ||
97 | + assert video.has_ogv_version | ||
98 | + end | ||
99 | + | ||
100 | + should 'know if it has_mp4_version' do | ||
101 | + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg' | ||
102 | + assert not(video.has_mp4_version), 'must NOT to list MP4' | ||
103 | + video.web_versions[:MP4] = {SIZE: {file_name:'sized.mp4', status:'done'} } | ||
104 | + assert video.has_mp4_version, 'must to list MP4' | ||
105 | + end | ||
106 | + | ||
107 | + should 'know if it has_webm_version' do | ||
108 | + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg' | ||
109 | + assert video.has_webm_version | ||
110 | + end | ||
111 | + | ||
112 | + should 'list its web_version_jobs' do | ||
113 | + videoA = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg' | ||
114 | + videoB = FilePresenter::Video.new create_video 'atropelamento.ogv', 'video/ogg' | ||
115 | + jobA = videoA.web_version_jobs.map &:payload_object | ||
116 | + jobB = videoB.web_version_jobs.map &:payload_object | ||
117 | + # TODO: jobA.length must be 4. | ||
118 | + # `Html5VideoPlugin::uploaded_file_after_create_callback` is been called two times | ||
119 | + #assert_equal 4, jobA.length | ||
120 | + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobA[0].class | ||
121 | + assert_equal :OGV, jobA[0].format | ||
122 | + assert_equal :tiny, jobA[0].size | ||
123 | + assert_match /.*\/old-movie.mpg$/, jobA[0].full_filename | ||
124 | + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobA[1].class | ||
125 | + assert_equal :WEBM, jobA[1].format | ||
126 | + assert_equal :tiny, jobA[1].size | ||
127 | + assert_match /.*\/old-movie.mpg$/, jobA[1].full_filename | ||
128 | + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobA[2].class | ||
129 | + assert_equal :OGV, jobA[2].format | ||
130 | + assert_equal :nice, jobA[2].size | ||
131 | + assert_match /.*\/old-movie.mpg$/, jobA[1].full_filename | ||
132 | + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobA[3].class | ||
133 | + assert_equal :WEBM, jobA[3].format | ||
134 | + assert_equal :nice, jobA[3].size | ||
135 | + assert_match /.*\/old-movie.mpg$/, jobA[1].full_filename | ||
136 | + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobB[0].class | ||
137 | + assert_equal :OGV, jobB[0].format | ||
138 | + assert_equal :tiny, jobB[0].size | ||
139 | + assert_match /.*\/atropelamento.ogv$/, jobB[0].full_filename | ||
140 | + assert_equal Html5VideoPlugin::CreateVideoForWebJob, jobB[1].class | ||
141 | + assert_equal :WEBM, jobB[1].format | ||
142 | + assert_equal :tiny, jobB[1].size | ||
143 | + assert_match /.*\/atropelamento.ogv$/, jobB[1].full_filename | ||
144 | + end | ||
145 | + | ||
146 | + should 'list its web_preview_jobs' do | ||
147 | + videoA = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg' | ||
148 | + videoB = FilePresenter::Video.new create_video 'atropelamento.ogv', 'video/ogg' | ||
149 | + jobA = videoA.web_preview_jobs.map &:payload_object | ||
150 | + jobB = videoB.web_preview_jobs.map &:payload_object | ||
151 | + # TODO: jobA.length must be 1. | ||
152 | + # `Html5VideoPlugin::uploaded_file_after_create_callback` is been called two times | ||
153 | + #assert_equal 1, jobA.length | ||
154 | + assert_equal Html5VideoPlugin::CreateVideoPreviewJob, jobA[0].class | ||
155 | + assert_match /.*\/old-movie.mpg$/, jobA[0].full_filename | ||
156 | + assert_equal Html5VideoPlugin::CreateVideoPreviewJob, jobB[0].class | ||
157 | + assert_match /.*\/atropelamento.ogv$/, jobB[0].full_filename | ||
158 | + end | ||
159 | + | ||
160 | + should 'know if it has_previews' do | ||
161 | + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg' | ||
162 | + assert video.has_previews? | ||
163 | + end | ||
164 | + | ||
165 | + should 'list its image previews' do | ||
166 | + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg' | ||
167 | + assert_equal h2s(big:'/web/preview_160x120.jpg', thumb:'/web/preview_107x80.jpg'), h2s(video.previews) | ||
168 | + end | ||
169 | + | ||
170 | + should 'set its image previews' do | ||
171 | + video = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg' | ||
172 | + assert_equal nil, video.previews | ||
173 | + video.previews = {big:'big.jpg', thumb:'thumb.jpg'} | ||
174 | + assert_equal h2s(big:'big.jpg', thumb:'thumb.jpg'), h2s(video.previews) | ||
175 | + end | ||
176 | + | ||
177 | + should 'get image_preview for a processed video' do | ||
178 | + video = create_and_proc_video 'old-movie.mpg', 'video/mpeg' | ||
179 | + assert_match /\/[0-9]+\/web\/preview_160x120\.jpg/, video.image_preview(:big) | ||
180 | + end | ||
181 | + | ||
182 | + should 'get default image_preview for non processed video' do | ||
183 | + video = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg' | ||
184 | + assert_match /\/html5_video\/images\/video-preview-big\.png/, video.image_preview(:big) | ||
185 | + end | ||
186 | + | ||
187 | + should 'list its conversion_errors' do | ||
188 | + video = FilePresenter::Video.new create_video 'old-movie.mpg', 'video/mpeg' | ||
189 | + video.web_versions[:MP4] = {nice: {status:'error converting', error:{code:-99,message:'some error',output:'abcde'}} } | ||
190 | + assert_equal h2s(MP4:{nice:{code:-99,message:'some error',output:'abcde'}}), h2s(video.conversion_errors) | ||
191 | + end | ||
192 | + | ||
193 | +end |
plugins/html5_video/views/content_viewer/_video_player.html.erb
0 → 100644
@@ -0,0 +1,29 @@ | @@ -0,0 +1,29 @@ | ||
1 | +<div class="video-box"> | ||
2 | + <% | ||
3 | + if video | ||
4 | + video_ogv = video.tiniest_web_version :OGV | ||
5 | + video_mp4 = video.tiniest_web_version :MP4 | ||
6 | + video_webm = video.tiniest_web_version :WEBM | ||
7 | + video_ogv_path = video_ogv ? video_ogv[:path] : '' | ||
8 | + video_mp4_path = video_mp4 ? video_mp4[:path] : '' | ||
9 | + video_webm_path = video_webm ? video_webm[:path] : '' | ||
10 | + embed_video_path = video_webm ? video_webm_path : \ | ||
11 | + video_ogv ? video_ogv_path : video_mp4_path | ||
12 | + end | ||
13 | + %> | ||
14 | + <video class="article-video-player" | ||
15 | + style="width:100%; height:auto;" | ||
16 | + preload="metadata" | ||
17 | + controls="controls"> | ||
18 | + <% if video_webm %><source src="<%= video_webm_path %>" type="video/webm" /><% end %> | ||
19 | + <% if video_ogv %><source src="<%= video_ogv_path %>" type="video/ogg" /><% end %> | ||
20 | + <% if video_mp4 %><source src="<%= video_mp4_path %>" type="video/mp4" /><% end %> | ||
21 | + <strong><%= _('Sorry, your browser doesn’t support video.') %></strong> | ||
22 | + <br/> <%= _('Please try the new %s or %s.') % [ | ||
23 | + '<a href="http://getfirefox.com">Firefox</a>', | ||
24 | + '<a href="http://www.chromium.org/Home">Chromium</a>' ] %> | ||
25 | + </video> | ||
26 | + <div class="video-ctrl"> | ||
27 | + <a href="<%= video ? video.public_filename : '' %>" class="download-bt"><%=_('Download')%></a> | ||
28 | + </div> | ||
29 | +</div><!-- video-box --> |
plugins/html5_video/views/content_viewer/video_channel.html.erb
0 → 100644
@@ -0,0 +1,93 @@ | @@ -0,0 +1,93 @@ | ||
1 | +<div> | ||
2 | + <%= @page.body %> | ||
3 | +</div> | ||
4 | + | ||
5 | +<div class="video-channel"> | ||
6 | + <% if ! @page.children.all.find {|f| f.content_type =~ /^video\//} %> | ||
7 | + <p><em><%= _('This channel contains no videos yet') %></em></p> | ||
8 | + <% else %> | ||
9 | + <div class="video-player"> | ||
10 | + <h2></h2> | ||
11 | + <%= render :partial => 'video_player', :locals => {:video => nil} %> | ||
12 | + <div class="video-player-info"> | ||
13 | + <div class="quality"> | ||
14 | + <strong><%=_('Quality options')%>:</strong> | ||
15 | + <ul></ul> | ||
16 | + </div> | ||
17 | + <div class="tags"> | ||
18 | + <strong><%=_('Tags')%>:</strong> | ||
19 | + <div class="data"></div> | ||
20 | + </div> | ||
21 | + <div class="abstract"> | ||
22 | + <strong><%=_('Description')%>:</strong> | ||
23 | + <div class="data"></div> | ||
24 | + </div> | ||
25 | + </div> | ||
26 | + <br style="clear: both"/> | ||
27 | + </div> | ||
28 | + <ul class="video-list"> | ||
29 | + <% unconverted_videos = [] | ||
30 | + @page.children. | ||
31 | + map{|f| FilePresenter.for f }. | ||
32 | + select{|f| f.class == FilePresenter::Video}. | ||
33 | + sort_by{|f| - f.created_at.to_i}.each do |f| | ||
34 | + unless f.has_web_version | ||
35 | + unconverted_videos << f | ||
36 | + else %> | ||
37 | + <li class="video-list-item" title="<%=html_escape f.title%>"> | ||
38 | + <%= link_to( | ||
39 | + content_tag('strong', | ||
40 | + f.title. | ||
41 | + gsub(/([a-z0-9])_+([a-z0-9])/i, '\1 \2'). | ||
42 | + gsub(/\.[a-z0-9]{2,4}$/i, '') | ||
43 | + ), | ||
44 | + f.view_url, | ||
45 | + { | ||
46 | + 'data-poster' => f.image_preview(:big), | ||
47 | + 'data-download' => f.public_filename, | ||
48 | + 'data-webversions' => CGI::escapeHTML(f.web_versions!.to_json), | ||
49 | + :style => "background-image:url(#{f.image_preview(:big)})" | ||
50 | + }) %> | ||
51 | + <ul> | ||
52 | + <li class="vli-data-tags"> | ||
53 | + <strong>tags:</strong> | ||
54 | + <div><%= linked_article_tags f %></div> | ||
55 | + </li> | ||
56 | + </ul> | ||
57 | + <div class="abstract"><%= f.abstract %></div> | ||
58 | + </li> | ||
59 | + <% end end %> | ||
60 | + </ul> | ||
61 | + <% end %> | ||
62 | + | ||
63 | + <% if unconverted_videos && !unconverted_videos.empty? %> | ||
64 | + <div class="unconverted-videos"> | ||
65 | + <p onclick="jQuery('ul',this.parentNode).slideToggle()"> | ||
66 | + <span><%= n_( | ||
67 | + 'This channel has one video waiting to be converted', | ||
68 | + 'This channel has %d videos waiting to be converted', | ||
69 | + unconverted_videos.length | ||
70 | + ) % unconverted_videos.length | ||
71 | + %></span> | ||
72 | + </p> | ||
73 | + <ul style="display:none"> | ||
74 | + <% unconverted_videos.each do |f| %> | ||
75 | + <li><%= link_to f.title, f.view_url %></li> | ||
76 | + <% end %> | ||
77 | + </ul> | ||
78 | + </div> | ||
79 | + <% end %> | ||
80 | + | ||
81 | + <% if @page.children.all.find {|f| f.content_type !~ /^video\//} %> | ||
82 | + <div class="non-video-list"> | ||
83 | + <h3><%=_('Non video files')%></h3> | ||
84 | + <%= list_articles( @page.children.all.select {|f| f.content_type !~ /^video\// } ) %> | ||
85 | + </div> | ||
86 | + <% end %> | ||
87 | + | ||
88 | +</div> | ||
89 | + | ||
90 | +<script type="text/javascript"> | ||
91 | + new VideoChannel( jQuery(".video-channel").last() ); | ||
92 | +</script> | ||
93 | + |
plugins/html5_video/views/file_presenter/_video.html.erb
1 | -<video class="video-js vjs-default-skin" controls poster="video.jpg" preload="auto" data-setup="{}"> | ||
2 | - <source type="video/ogg" src="<%= video.public_filename %>"/> | ||
3 | -</video> | 1 | +<% pub_path = __FILE__.sub /.*(\/plugins\/[^\/]+\/).*/, '\1' %> |
2 | +<div class="video-player"> | ||
3 | + <% if video.has_web_version %> | ||
4 | + <%= render :partial => 'video_player', :locals => {:video => video} %> | ||
5 | + <% elsif not video.web_version_jobs.empty? %> | ||
6 | + <div class="message"> | ||
7 | + <%=_('Queued to generate the web version. Come back soon.')%> | ||
8 | + </div> | ||
9 | + <% else %> | ||
10 | + <div class="message"> | ||
11 | + <%=_('This video is not queued to the video conversor. Contact the site admin.')%> | ||
12 | + </div> | ||
13 | + <% end %> | ||
14 | + <div class="video-player-info"> | ||
15 | + <div class="quality"><strong><%=_('Quality options')%>:</strong> <ul></ul></div> | ||
16 | + <div class="abstract"> | ||
17 | + <strong><%=_('Description')%>:</strong> | ||
18 | + <div class="data"><%= video.abstract %></div> | ||
19 | + </div> | ||
20 | + </div><!-- class="video-player-info" --> | ||
21 | + <br style="clear: both"/> | ||
22 | +</div><!-- class="video-player" --> | ||
4 | 23 | ||
5 | -<div class="uploaded-file-description <%= 'empty' if video.abstract.blank? %>"> | ||
6 | - <%= video.abstract %> | ||
7 | -</div> | 24 | +<% if video.has_web_version %> |
25 | + <script type="text/javascript"> | ||
26 | + var player = new NoosferoVideoPlayer( jQuery(".article-body").last() ); | ||
27 | + player.videoList = <%= video.web_versions!.to_json %>; | ||
28 | + player.selectWebVersion(); | ||
29 | + </script> | ||
30 | +<% end %> | ||
8 | 31 | ||
32 | +<% if video.allow_edit?(user) && !video.conversion_errors.blank? %> | ||
33 | +<div class="conversion-error"> | ||
34 | +<h2><%=_('Video conversion errors')%></h2> | ||
35 | +<ul> | ||
36 | + <% video.conversion_errors.each do |type, type_block| %> | ||
37 | + <% type_block.each do |size, size_block| %> | ||
38 | + <% | ||
39 | + message, code, output = size_block[:message], size_block[:code], size_block[:output] | ||
40 | + # hide version header to non admins | ||
41 | + message.sub!(/^([^\n]*\n){2}/,'') unless user.is_admin? | ||
42 | + %> | ||
43 | + <li> | ||
44 | + <h3><%= _('Error while converting %{orig_type} to %{new_type}, %{size} size.') % { | ||
45 | + :orig_type=>video.content_type.split('/')[1], :new_type=>type, :size=>size | ||
46 | + } %></h3> | ||
47 | + <%= _('Code %s') % content_tag('strong',code) +' — '+ message.to_s %> | ||
48 | + <pre class="output" style="display:none"><%= output %></pre> | ||
49 | + <%= link_to _('display full output'), '#', | ||
50 | + :class => 'show-output', | ||
51 | + :onclick => 'jQuery(".output",this.parentNode).show(); jQuery(this).hide(); return false' | ||
52 | + %> | ||
53 | + </li> | ||
54 | + <% end %> | ||
55 | + <% end %> | ||
56 | +</ul> | ||
57 | +</div> | ||
58 | +<% end %> |
test/test_helper.rb
@@ -137,6 +137,18 @@ class ActiveSupport::TestCase | @@ -137,6 +137,18 @@ class ActiveSupport::TestCase | ||
137 | assert reference.blank?, "The following elements are not in the collection: #{reference.inspect}" | 137 | assert reference.blank?, "The following elements are not in the collection: #{reference.inspect}" |
138 | end | 138 | end |
139 | 139 | ||
140 | + def h2s(value) # make a string from ordered hash to simplify tests | ||
141 | + case value | ||
142 | + when Hash, HashWithIndifferentAccess | ||
143 | + '{'+ value.stringify_keys.to_a.sort{|a,b|a[0]<=>b[0]}.map{ |k,v| k+':'+h2s(v) }.join(',') +'}' | ||
144 | + when Array | ||
145 | + '['+ value.map{|i|h2s(i)}.join(',') +']' | ||
146 | + when NilClass | ||
147 | + '<nil>' | ||
148 | + else value.to_s | ||
149 | + end | ||
150 | + end | ||
151 | + | ||
140 | # For models that render views (blocks, articles, ...) | 152 | # For models that render views (blocks, articles, ...) |
141 | def self.action_view | 153 | def self.action_view |
142 | @action_view ||= begin | 154 | @action_view ||= begin |
test/unit/plugin_hot_spot_test.rb
@@ -17,9 +17,13 @@ class PluginHotSpotTest < ActiveSupport::TestCase | @@ -17,9 +17,13 @@ class PluginHotSpotTest < ActiveSupport::TestCase | ||
17 | 17 | ||
18 | Noosfero::Plugin::HotSpot::CALLBACK_HOTSPOTS.each do |callback| | 18 | Noosfero::Plugin::HotSpot::CALLBACK_HOTSPOTS.each do |callback| |
19 | should "call #{callback} hotspot" do | 19 | should "call #{callback} hotspot" do |
20 | - class CoolPlugin < Noosfero::Plugin; end | 20 | + class CoolPlugin < Noosfero::Plugin |
21 | + include Noosfero::Plugin::HotSpot | ||
22 | + end | ||
21 | 23 | ||
22 | - Noosfero::Plugin.stubs(:all).returns([CoolPlugin.name]) | 24 | + CoolPlugin.any_instance.stubs("comment_#{callback}_callback".to_sym).returns(";)") |
25 | + | ||
26 | + Noosfero::Plugin.stubs(:all).returns(['PluginHotSpotTest::CoolPlugin']) | ||
23 | Environment.default.enable_plugin(CoolPlugin) | 27 | Environment.default.enable_plugin(CoolPlugin) |
24 | CoolPlugin.any_instance.expects("comment_#{callback}_callback".to_sym) | 28 | CoolPlugin.any_instance.expects("comment_#{callback}_callback".to_sym) |
25 | 29 |