sufia 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|