sufia 0.1.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +6 -0
- data/Gemfile +5 -2
- data/History.md +6 -0
- data/README.md +40 -0
- data/Rakefile +4 -0
- data/app/assets/javascripts/sufia.js +3 -115
- data/app/assets/javascripts/sufia/batch_select_all.js +179 -0
- data/app/assets/javascripts/sufia/edit_metadata.js +86 -0
- data/app/assets/javascripts/sufia/multiForm.js +57 -0
- data/app/assets/javascripts/terms_of_service.js +7 -0
- data/app/assets/stylesheets/audio-js.css +3 -0
- data/app/assets/stylesheets/dashboard.css.scss +51 -0
- data/app/assets/stylesheets/generic_files.css +36 -0
- data/app/assets/stylesheets/sufia.css.scss +2 -0
- data/app/controllers/batch_controller.rb +11 -0
- data/app/controllers/batch_edits_controller.rb +1 -77
- data/app/controllers/generic_files_controller.rb +1 -0
- data/app/controllers/mailbox_controller.rb +1 -1
- data/app/controllers/single_use_link_controller.rb +11 -7
- data/app/helpers/generic_file_helper.rb +11 -3
- data/app/helpers/sufia_helper.rb +13 -10
- data/app/models/batch.rb +1 -1
- data/app/models/datastreams/fits_datastream.rb +2 -2
- data/app/models/datastreams/generic_file_rdf_datastream.rb +22 -18
- data/app/models/datastreams/properties_datastream.rb +2 -2
- data/app/views/_user_util_links.html.erb +2 -2
- data/app/views/batch/_metadata.html.erb +82 -0
- data/app/views/batch/_more_metadata.html.erb +6 -0
- data/app/views/batch/edit.html.erb +1 -8
- data/app/views/batch_edits/_check_all.html.erb +0 -157
- data/app/views/batch_edits/edit.html.erb +0 -29
- data/app/views/catalog/_index_partials/_list_files.html.erb +8 -10
- data/app/views/catalog/_recent_document.html.erb +9 -18
- data/app/views/catalog/_results_pagination.html.erb +1 -1
- data/app/views/contact_form/new.html.erb +1 -1
- data/app/views/dashboard/_index_partials/_default_group.html.erb +1 -1
- data/app/views/dashboard/_index_partials/_list_files.html.erb +12 -14
- data/app/views/dashboard/_index_partials/_thumbnail_display.html.erb +9 -19
- data/app/views/dashboard/_results_pagination.html.erb +1 -1
- data/app/views/dashboard/index.html.erb +6 -82
- data/app/views/error/single_use_error.html.erb +35 -0
- data/app/views/generic_files/_descriptions.html.erb +2 -2
- data/app/views/generic_files/_extra_fields_modal.html.erb +1 -1
- data/app/views/generic_files/_field_form.html.erb +2 -5
- data/app/views/generic_files/_media_display.html.erb +8 -6
- data/app/views/generic_files/_permission.html.erb +2 -2
- data/app/views/generic_files/_rights_modal.html.erb +1 -1
- data/app/views/generic_files/_show_actions.html.erb +1 -1
- data/app/views/generic_files/_show_details.html.erb +11 -6
- data/app/views/generic_files/edit.html.erb +0 -8
- data/app/views/generic_files/edit_fields/_type.html.erb +1 -1
- data/app/views/generic_files/show.html.erb +5 -8
- data/app/views/generic_files/show_fields/_based_near.html.erb +4 -1
- data/app/views/generic_files/show_fields/_contributor.html.erb +4 -1
- data/app/views/generic_files/show_fields/_creator.html.erb +4 -1
- data/app/views/generic_files/show_fields/_date_created.html.erb +4 -1
- data/app/views/generic_files/show_fields/_description.html.erb +4 -1
- data/app/views/generic_files/show_fields/_language.html.erb +1 -1
- data/app/views/generic_files/show_fields/_publisher.html.erb +4 -1
- data/app/views/generic_files/show_fields/_related_url.html.erb +3 -1
- data/app/views/generic_files/show_fields/_resource_type.html.erb +1 -1
- data/app/views/generic_files/show_fields/_subject.html.erb +4 -1
- data/app/views/generic_files/show_fields/_tag.html.erb +1 -1
- data/app/views/generic_files/show_fields/_title.html.erb +4 -1
- data/app/views/layouts/error.html.erb +0 -4
- data/app/views/layouts/hydra-head.html.erb +2 -6
- data/app/views/single_use_link/show.html.erb +1 -1
- data/app/views/users/index.html.erb +1 -1
- data/app/views/users/show.html.erb +1 -1
- data/config/locales/sufia.en.yml +1 -0
- data/config/routes.rb +11 -4
- data/lib/generators/sufia/sufia_generator.rb +2 -1
- data/lib/generators/sufia/templates/catalog_controller.rb +143 -117
- data/lib/generators/sufia/templates/config/resque_admin.rb +10 -0
- data/lib/generators/sufia/templates/config/sufia.rb +8 -0
- data/lib/sufia.rb +4 -14
- data/lib/sufia/batch_edits_controller_behavior.rb +89 -0
- data/lib/sufia/controller.rb +7 -5
- data/lib/sufia/downloads_controller_behavior.rb +14 -19
- data/lib/sufia/file_content/extract_metadata.rb +11 -4
- data/lib/sufia/files_controller_behavior.rb +63 -44
- data/lib/sufia/generic_file.rb +29 -11
- data/lib/sufia/generic_file/audit.rb +1 -1
- data/lib/sufia/generic_file/thumbnail.rb +51 -26
- data/lib/sufia/id_service.rb +28 -11
- data/lib/sufia/jobs/batch_update_job.rb +2 -2
- data/lib/sufia/jobs/characterize_job.rb +11 -3
- data/lib/sufia/jobs/ffmpeg_transcode_job.rb +61 -0
- data/lib/sufia/jobs/resolrize_job.rb +1 -1
- data/lib/sufia/jobs/transcode_audio_job.rb +40 -0
- data/lib/sufia/jobs/transcode_video_job.rb +9 -49
- data/lib/sufia/queue/resque.rb +2 -2
- data/lib/sufia/single_use_error.rb +4 -0
- data/lib/sufia/solr_document_behavior.rb +108 -1
- data/lib/sufia/version.rb +1 -1
- data/solr_conf/conf/schema.xml +332 -652
- data/solr_conf/conf/solrconfig.xml +60 -196
- data/spec/controllers/batch_controller_spec.rb +4 -5
- data/spec/controllers/catalog_controller_spec.rb +13 -13
- data/spec/controllers/dashboard_controller_spec.rb +2 -2
- data/spec/controllers/downloads_controller_spec.rb +74 -62
- data/spec/controllers/generic_files_controller_spec.rb +10 -8
- data/spec/controllers/single_use_link_controller_spec.rb +12 -4
- data/spec/fixtures/Example.ogg +0 -0
- data/spec/fixtures/piano_note.wav +0 -0
- data/spec/fixtures/sufia_generic_stub.descMeta.txt +1 -1
- data/spec/helpers/sufia_helper_spec.rb +12 -0
- data/spec/models/characterize_job_spec.rb +89 -0
- data/spec/models/checksum_audit_log_spec.rb +1 -0
- data/spec/models/event_jobs_spec.rb +9 -9
- data/spec/models/file_content_datastream_spec.rb +16 -10
- data/spec/models/fits_datastream_spec.rb +2 -8
- data/spec/models/generic_file_spec.rb +131 -60
- data/spec/models/solr_document_spec.rb +21 -0
- data/spec/models/transcode_audio_job_spec.rb +81 -0
- data/spec/models/transcode_video_job_spec.rb +2 -2
- data/spec/models/unzip_job_spec.rb +3 -3
- data/spec/spec_helper.rb +21 -0
- data/spec/support/Gemfile +7 -3
- data/sufia.gemspec +8 -11
- data/tasks/cucumber.rake +1 -2
- data/tasks/sufia-dev.rake +13 -2
- data/tasks/sufia.rake +1 -1
- metadata +77 -118
- data/app/views/batch_edits/_metadata.html.erb +0 -180
- data/lib/generators/sufia/templates/config/hydra_config.rb +0 -32
- data/lib/kaminari/helpers/tag.rb +0 -11
data/lib/sufia/generic_file.rb
CHANGED
|
@@ -25,7 +25,7 @@ module Sufia
|
|
|
25
25
|
has_metadata :name => "descMetadata", :type => GenericFileRdfDatastream
|
|
26
26
|
has_metadata :name => "properties", :type => PropertiesDatastream
|
|
27
27
|
has_file_datastream :name => "content", :type => FileContentDatastream
|
|
28
|
-
has_file_datastream :name => "thumbnail"
|
|
28
|
+
has_file_datastream :name => "thumbnail"
|
|
29
29
|
|
|
30
30
|
belongs_to :batch, :property => :is_part_of
|
|
31
31
|
|
|
@@ -36,6 +36,8 @@ module Sufia
|
|
|
36
36
|
:publisher, :date_created, :subject,
|
|
37
37
|
:resource_type, :identifier, :language]
|
|
38
38
|
around_save :characterize_if_changed, :retry_warming
|
|
39
|
+
before_save :remove_blank_assertions
|
|
40
|
+
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def delete
|
|
@@ -43,6 +45,12 @@ module Sufia
|
|
|
43
45
|
super
|
|
44
46
|
end
|
|
45
47
|
|
|
48
|
+
def remove_blank_assertions
|
|
49
|
+
terms_for_editing.each do |key|
|
|
50
|
+
self[key] = nil if self[key] == ['']
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
46
54
|
|
|
47
55
|
def record_version_committer(user)
|
|
48
56
|
version = content.latest_version
|
|
@@ -55,15 +63,21 @@ module Sufia
|
|
|
55
63
|
end
|
|
56
64
|
|
|
57
65
|
def pdf?
|
|
58
|
-
[
|
|
66
|
+
['application/pdf'].include? self.mime_type
|
|
59
67
|
end
|
|
60
68
|
|
|
61
69
|
def image?
|
|
62
|
-
[
|
|
70
|
+
['image/png','image/jpeg', 'image/jpg', 'image/jp2', 'image/bmp', 'image/gif'].include? self.mime_type
|
|
63
71
|
end
|
|
64
72
|
|
|
65
73
|
def video?
|
|
66
|
-
[
|
|
74
|
+
['video/mpeg', 'video/mp4', 'video/webm', 'video/x-msvideo', 'video/avi', 'video/quicktime', 'application/mxf'].include? self.mime_type
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def audio?
|
|
78
|
+
# audio/x-wave is the mime type that fits 0.6.0 returns for a wav file.
|
|
79
|
+
# audio/mpeg is the mime type that fits 0.6.0 returns for an mp3 file.
|
|
80
|
+
['audio/mp3', 'audio/mpeg', 'audio/x-wave', 'audio/x-wav', 'audio/ogg'].include? self.mime_type
|
|
67
81
|
end
|
|
68
82
|
|
|
69
83
|
def persistent_url
|
|
@@ -111,21 +125,25 @@ module Sufia
|
|
|
111
125
|
relateds = begin
|
|
112
126
|
self.batch.generic_files
|
|
113
127
|
rescue NoMethodError => e
|
|
114
|
-
#batch is nil
|
|
128
|
+
#batch is nil - When would this ever happen?
|
|
115
129
|
batch_id = self.object_relations["isPartOf"].first || self.object_relations[:is_part_of].first
|
|
116
130
|
return [] if batch_id.nil?
|
|
117
|
-
self.class.find(:
|
|
131
|
+
self.class.find(Solrizer.solr_name('is_part_of', :symbol) => batch_id)
|
|
118
132
|
end
|
|
119
133
|
relateds.reject { |gf| gf.pid == self.pid }
|
|
120
134
|
end
|
|
121
135
|
|
|
136
|
+
# Unstemmed, searchable, stored
|
|
137
|
+
def self.noid_indexer
|
|
138
|
+
@noid_indexer ||= Solrizer::Descriptor.new(:text, :indexed, :stored)
|
|
139
|
+
end
|
|
122
140
|
|
|
123
141
|
def to_solr(solr_doc={}, opts={})
|
|
124
142
|
super(solr_doc, opts)
|
|
125
|
-
solr_doc[
|
|
126
|
-
solr_doc[
|
|
127
|
-
solr_doc[
|
|
128
|
-
solr_doc[
|
|
143
|
+
solr_doc[Solrizer.solr_name('label')] = self.label
|
|
144
|
+
solr_doc[Solrizer.solr_name('noid', Sufia::GenericFile.noid_indexer)] = noid
|
|
145
|
+
solr_doc[Solrizer.solr_name('file_format')] = file_format
|
|
146
|
+
solr_doc[Solrizer.solr_name('file_format', :facetable)] = file_format
|
|
129
147
|
return solr_doc
|
|
130
148
|
end
|
|
131
149
|
|
|
@@ -161,7 +179,7 @@ module Sufia
|
|
|
161
179
|
|
|
162
180
|
def terms_for_editing
|
|
163
181
|
terms_for_display -
|
|
164
|
-
[:part_of, :date_modified, :date_uploaded, :format
|
|
182
|
+
[:part_of, :date_modified, :date_uploaded, :format] #, :resource_type]
|
|
165
183
|
end
|
|
166
184
|
|
|
167
185
|
def terms_for_display
|
|
@@ -4,29 +4,46 @@ module Sufia
|
|
|
4
4
|
# Create thumbnail requires that the characterization has already been run (so mime_type, width and height is available)
|
|
5
5
|
# and that the object is already has a pid set
|
|
6
6
|
def create_thumbnail
|
|
7
|
-
return
|
|
7
|
+
return unless self.content.has_content?
|
|
8
8
|
if pdf?
|
|
9
9
|
create_pdf_thumbnail
|
|
10
10
|
elsif image?
|
|
11
11
|
create_image_thumbnail
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
elsif video?
|
|
13
|
+
create_video_thumbnail
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
protected
|
|
18
|
+
def create_video_thumbnail
|
|
19
|
+
return unless Sufia::Engine.config.enable_ffmpeg
|
|
20
|
+
|
|
21
|
+
output_file = Dir::Tmpname.create(['sufia', ".png"], Sufia::Engine.config.temp_file_base){}
|
|
22
|
+
content.to_tempfile do |f|
|
|
23
|
+
# we could use something like this in order to find a frame in the middle.
|
|
24
|
+
#ffprobe -show_files video.avi 2> /dev/null | grep duration | cut -d= -f2 53.399999
|
|
25
|
+
command = "#{Sufia::Engine.config.ffmpeg_path} -i \"#{f.path}\" -loglevel quiet -vf \"scale=338:-1\" -r 1 -t 1 #{output_file}"
|
|
26
|
+
system(command)
|
|
27
|
+
raise "Unable to execute command \"#{command}\"" unless $?.success?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
self.thumbnail.content = File.open(output_file, 'rb').read
|
|
31
|
+
self.thumbnail.mimeType = 'image/png'
|
|
32
|
+
self.save
|
|
33
|
+
end
|
|
34
|
+
|
|
17
35
|
def create_pdf_thumbnail
|
|
18
36
|
retryCnt = 0
|
|
19
37
|
stat = false;
|
|
20
38
|
for retryCnt in 1..3
|
|
21
39
|
begin
|
|
22
|
-
pdf =
|
|
23
|
-
pdf.from_blob(content.content)
|
|
40
|
+
pdf = load_image_transformer
|
|
24
41
|
first = pdf.to_a[0]
|
|
25
42
|
first.format = "PNG"
|
|
26
43
|
thumb = first.scale(338, 493)
|
|
27
44
|
self.thumbnail.content = thumb.to_blob { self.format = "PNG" }
|
|
28
|
-
|
|
29
|
-
|
|
45
|
+
self.thumbnail.mimeType = 'image/png'
|
|
46
|
+
self.save
|
|
30
47
|
break
|
|
31
48
|
rescue => e
|
|
32
49
|
logger.warn "Rescued an error #{e.inspect} retry count = #{retryCnt}"
|
|
@@ -37,30 +54,38 @@ module Sufia
|
|
|
37
54
|
end
|
|
38
55
|
|
|
39
56
|
def create_image_thumbnail
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
#
|
|
57
|
+
self.thumbnail.content = scale_image.to_blob { self.format = "PNG" }
|
|
58
|
+
self.thumbnail.mimeType = 'image/png'
|
|
59
|
+
#logger.debug "Has the content before saving? #{self.content.changed?}"
|
|
60
|
+
self.save
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def scale_image
|
|
64
|
+
img = load_image_transformer
|
|
43
65
|
height = Float(self.height.first.to_i)
|
|
44
66
|
width = Float(self.width.first.to_i)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
if width > height && width > 150 && height > 105
|
|
68
|
+
# horizontal img
|
|
69
|
+
scale = 150 / width
|
|
70
|
+
img.scale(150, height * scale)
|
|
71
|
+
elsif height >= width && width > 150 && height > 200
|
|
72
|
+
# vertical or square
|
|
73
|
+
scale = 200 / height
|
|
74
|
+
img.scale(width*scale, 200)
|
|
53
75
|
else
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
else
|
|
57
|
-
thumb = img.scale(width, height)
|
|
58
|
-
end
|
|
76
|
+
# Too small to worry about resizing
|
|
77
|
+
img
|
|
59
78
|
end
|
|
60
|
-
self.thumbnail.content = thumb.to_blob
|
|
61
|
-
#logger.debug "Has the content before saving? #{self.content.changed?}"
|
|
62
|
-
self.save
|
|
63
79
|
end
|
|
80
|
+
|
|
81
|
+
# Override this method if you want a different transformer, or need to load the
|
|
82
|
+
# raw image from a different source (e.g. external datastream)
|
|
83
|
+
def load_image_transformer
|
|
84
|
+
xformer = Magick::ImageList.new
|
|
85
|
+
xformer.from_blob(content.content)
|
|
86
|
+
xformer
|
|
87
|
+
end
|
|
88
|
+
|
|
64
89
|
end
|
|
65
90
|
end
|
|
66
91
|
end
|
data/lib/sufia/id_service.rb
CHANGED
|
@@ -15,26 +15,43 @@
|
|
|
15
15
|
require 'noid'
|
|
16
16
|
|
|
17
17
|
module Sufia
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
module IdService
|
|
19
|
+
@minter = ::Noid::Minter.new(:template => '.reeddeeddk')
|
|
20
|
+
@pid = $$
|
|
21
|
+
@namespace = Sufia::Engine.config.id_namespace
|
|
22
|
+
@semaphore = Mutex.new
|
|
21
23
|
def self.valid?(identifier)
|
|
22
24
|
# remove the fedora namespace since it's not part of the noid
|
|
23
25
|
noid = identifier.split(":").last
|
|
24
|
-
return
|
|
26
|
+
return @minter.valid? noid
|
|
25
27
|
end
|
|
26
28
|
def self.mint
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
@semaphore.synchronize do
|
|
30
|
+
while true
|
|
31
|
+
pid = self.next_id
|
|
32
|
+
return pid unless ActiveFedora::Base.exists?(pid)
|
|
33
|
+
end
|
|
30
34
|
end
|
|
31
|
-
return pid
|
|
32
35
|
end
|
|
36
|
+
|
|
33
37
|
protected
|
|
38
|
+
|
|
34
39
|
def self.next_id
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
pid = ''
|
|
41
|
+
File.open("tmp/minter-state", File::RDWR|File::CREAT, 0644) {|f|
|
|
42
|
+
f.flock(File::LOCK_EX)
|
|
43
|
+
yaml = YAML::load(f.read)
|
|
44
|
+
yaml = {:template => '.reeddeeddk'} unless yaml
|
|
45
|
+
minter = ::Noid::Minter.new(yaml)
|
|
46
|
+
pid = "#{@namespace}:#{minter.mint}"
|
|
47
|
+
f.rewind
|
|
48
|
+
yaml = YAML::dump(minter.dump)
|
|
49
|
+
f.write yaml
|
|
50
|
+
f.flush
|
|
51
|
+
f.truncate(f.pos)
|
|
52
|
+
}
|
|
53
|
+
return pid
|
|
38
54
|
end
|
|
55
|
+
|
|
39
56
|
end
|
|
40
57
|
end
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
class BatchUpdateJob
|
|
16
|
-
include Hydra::
|
|
16
|
+
include Hydra::PermissionsQuery
|
|
17
17
|
include GenericFileHelper
|
|
18
18
|
include Rails.application.routes.url_helpers
|
|
19
19
|
|
|
@@ -55,7 +55,7 @@ class BatchUpdateJob
|
|
|
55
55
|
|
|
56
56
|
def update_file(gf, user)
|
|
57
57
|
unless user.can? :edit, gf
|
|
58
|
-
logger.error "User #{user.user_key}
|
|
58
|
+
logger.error "User #{user.user_key} DENIED access to #{gf.pid}!"
|
|
59
59
|
@denied << gf
|
|
60
60
|
return
|
|
61
61
|
end
|
|
@@ -18,18 +18,26 @@ class CharacterizeJob
|
|
|
18
18
|
:characterize
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
attr_accessor :generic_file_id
|
|
21
|
+
attr_accessor :generic_file_id, :generic_file
|
|
22
22
|
|
|
23
23
|
def initialize(generic_file_id)
|
|
24
24
|
self.generic_file_id = generic_file_id
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def run
|
|
28
|
-
generic_file = GenericFile.find(generic_file_id)
|
|
28
|
+
self.generic_file = GenericFile.find(generic_file_id)
|
|
29
29
|
generic_file.characterize
|
|
30
|
-
|
|
30
|
+
after_characterize
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def after_characterize
|
|
34
|
+
if generic_file.pdf? || generic_file.image? || generic_file.video?
|
|
35
|
+
generic_file.create_thumbnail
|
|
36
|
+
end
|
|
31
37
|
if generic_file.video?
|
|
32
38
|
Sufia.queue.push(TranscodeVideoJob.new(generic_file_id, 'content'))
|
|
39
|
+
elsif generic_file.audio?
|
|
40
|
+
Sufia.queue.push(TranscodeAudioJob.new(generic_file_id, 'content'))
|
|
33
41
|
end
|
|
34
42
|
end
|
|
35
43
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Created by: Justin Coyne
|
|
2
|
+
# 7 Feb 2013
|
|
3
|
+
# An abstract class for asyncronous jobs that transcode files using FFMpeg
|
|
4
|
+
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
|
|
7
|
+
class FfmpegTranscodeJob
|
|
8
|
+
extend Open3
|
|
9
|
+
|
|
10
|
+
attr_accessor :generic_file_id, :datastream_in, :datastream, :generic_file
|
|
11
|
+
|
|
12
|
+
def initialize(generic_file_id, datastream_in)
|
|
13
|
+
self.generic_file_id = generic_file_id
|
|
14
|
+
self.datastream_in = datastream_in
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def process
|
|
18
|
+
raise "You attempted to call process() on an abstract class. Implement process() on the concrete class"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def run
|
|
22
|
+
return unless Sufia::Engine.config.enable_ffmpeg
|
|
23
|
+
self.generic_file = GenericFile.find(generic_file_id)
|
|
24
|
+
self.datastream = generic_file.datastreams[datastream_in]
|
|
25
|
+
if datastream
|
|
26
|
+
process
|
|
27
|
+
generic_file.save!
|
|
28
|
+
else
|
|
29
|
+
logger.warn "No datastream for transcoding!!!!! pid: #{generic_file_id} dsid: #{datastream_in}"
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def encode_datastream(dest_dsid, mime_type, options)
|
|
34
|
+
file_suffix = dest_dsid
|
|
35
|
+
out_file = nil
|
|
36
|
+
output_file = Dir::Tmpname.create(['sufia', ".#{file_suffix}"], Sufia::Engine.config.temp_file_base){}
|
|
37
|
+
datastream.to_tempfile do |f|
|
|
38
|
+
self.class.encode(f.path, options, output_file)
|
|
39
|
+
end
|
|
40
|
+
out_file = File.open(output_file, "rb")
|
|
41
|
+
generic_file.add_file_datastream(out_file.read, :dsid=>dest_dsid, :mimeType=>mime_type)
|
|
42
|
+
File.unlink(output_file)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.encode(path, options, output_file)
|
|
46
|
+
command = "#{ffmpeg_path} -y -i \"#{path}\" #{options} #{output_file}"
|
|
47
|
+
stdin, stdout, stderr, wait_thr = popen3(command)
|
|
48
|
+
stdin.close
|
|
49
|
+
out = stdout.read
|
|
50
|
+
stdout.close
|
|
51
|
+
err = stderr.read
|
|
52
|
+
stderr.close
|
|
53
|
+
raise "Unable to execute command \"#{command}\"\n#{err}" unless wait_thr.value.success?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def self.ffmpeg_path
|
|
57
|
+
Sufia::Engine.config.ffmpeg_path
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Created by: Justin Coyne
|
|
2
|
+
# 7 Feb 2013
|
|
3
|
+
# An asyncronous job for transcoding audio files using FFMpeg
|
|
4
|
+
|
|
5
|
+
class TranscodeAudioJob < FfmpegTranscodeJob
|
|
6
|
+
def queue_name
|
|
7
|
+
:audio
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def process
|
|
11
|
+
encode_mp3()
|
|
12
|
+
encode_ogg()
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
def encode_ogg
|
|
17
|
+
opts = ""
|
|
18
|
+
if generic_file.mime_type == 'audio/ogg'
|
|
19
|
+
# Don't re-encode, just copy
|
|
20
|
+
generic_file.add_file_datastream(generic_file.content.read, :dsid=>'ogg', :mimeType=>'audio/ogg')
|
|
21
|
+
#generic_file.content.rewind
|
|
22
|
+
else
|
|
23
|
+
encode_datastream('ogg', 'audio/ogg', opts)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def encode_mp3
|
|
28
|
+
opts = ""
|
|
29
|
+
if generic_file.mime_type == 'audio/mpeg'
|
|
30
|
+
# Don't re-encode, just copy
|
|
31
|
+
generic_file.add_file_datastream(generic_file.content.read, :dsid=>'mp3', :mimeType=>'audio/mp3')
|
|
32
|
+
#generic_file.content.rewind
|
|
33
|
+
else
|
|
34
|
+
encode_datastream('mp3', 'audio/mp3', opts)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
@@ -2,35 +2,23 @@
|
|
|
2
2
|
# 13 Dec 2012
|
|
3
3
|
# An asyncronous job for transcoding video files using FFMpeg
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class TranscodeVideoJob
|
|
8
|
-
extend Open3
|
|
5
|
+
class TranscodeVideoJob < FfmpegTranscodeJob
|
|
9
6
|
def queue_name
|
|
10
7
|
:video
|
|
11
8
|
end
|
|
12
9
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
self.generic_file_id = generic_file_id
|
|
17
|
-
self.datastream_in = datastream_in
|
|
10
|
+
def process
|
|
11
|
+
encode_mp4()
|
|
12
|
+
encode_webm()
|
|
18
13
|
end
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
@generic_file = GenericFile.find(generic_file_id)
|
|
22
|
-
@datastream = @generic_file.datastreams[datastream_in]
|
|
23
|
-
if @datastream
|
|
24
|
-
encode_mp4()
|
|
25
|
-
encode_webm()
|
|
26
|
-
@generic_file.save!
|
|
27
|
-
else
|
|
28
|
-
logger.warn "No datastream for transcoding!!!!! pid: #{generic_file_id} dsid: #{datastream_in}"
|
|
29
|
-
end
|
|
30
|
-
end
|
|
15
|
+
private
|
|
31
16
|
|
|
32
17
|
def encode_webm
|
|
33
|
-
|
|
18
|
+
# -g 30 enforces keyframe generation every second (30fps)
|
|
19
|
+
# -b:v is the video bitrate
|
|
20
|
+
# -acodec is the audio codec
|
|
21
|
+
opts = "#{size_attributes} -g 30 -b:v 345k -acodec libvorbis #{audio_attributes}"
|
|
34
22
|
encode_datastream('webm', 'video/webm', opts)
|
|
35
23
|
end
|
|
36
24
|
|
|
@@ -47,33 +35,5 @@ class TranscodeVideoJob
|
|
|
47
35
|
def audio_attributes
|
|
48
36
|
"-ac 2 -ab 96k -ar 44100"
|
|
49
37
|
end
|
|
50
|
-
|
|
51
|
-
def encode_datastream(dest_dsid, mime_type, options)
|
|
52
|
-
file_suffix = dest_dsid
|
|
53
|
-
out_file = nil
|
|
54
|
-
output_file = Dir::Tmpname.create('sufia'){} + ".#{file_suffix}"
|
|
55
|
-
@datastream.to_tempfile do |f|
|
|
56
|
-
self.class.encode(f.path, options, output_file)
|
|
57
|
-
end
|
|
58
|
-
out_file = File.open(output_file, "rb")
|
|
59
|
-
@generic_file.add_file_datastream(out_file.read, :dsid=>dest_dsid, :mimeType=>mime_type)
|
|
60
|
-
File.unlink(output_file)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# TODO tmp file for output
|
|
64
|
-
def self.encode(path, options, output_file)
|
|
65
|
-
command = "#{ffmpeg_path} -y -i #{path} #{options} #{output_file}"
|
|
66
|
-
stdin, stdout, stderr, wait_thr = popen3(command)
|
|
67
|
-
stdin.close
|
|
68
|
-
out = stdout.read
|
|
69
|
-
stdout.close
|
|
70
|
-
err = stderr.read
|
|
71
|
-
stderr.close
|
|
72
|
-
raise "Unable to execute command \"#{command}\"\n#{err}" unless wait_thr.value.success?
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def self.ffmpeg_path
|
|
76
|
-
Sufia::Engine.config.ffmpeg_path
|
|
77
|
-
end
|
|
78
38
|
end
|
|
79
39
|
|