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.
Files changed (129) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +5 -2
  5. data/History.md +6 -0
  6. data/README.md +40 -0
  7. data/Rakefile +4 -0
  8. data/app/assets/javascripts/sufia.js +3 -115
  9. data/app/assets/javascripts/sufia/batch_select_all.js +179 -0
  10. data/app/assets/javascripts/sufia/edit_metadata.js +86 -0
  11. data/app/assets/javascripts/sufia/multiForm.js +57 -0
  12. data/app/assets/javascripts/terms_of_service.js +7 -0
  13. data/app/assets/stylesheets/audio-js.css +3 -0
  14. data/app/assets/stylesheets/dashboard.css.scss +51 -0
  15. data/app/assets/stylesheets/generic_files.css +36 -0
  16. data/app/assets/stylesheets/sufia.css.scss +2 -0
  17. data/app/controllers/batch_controller.rb +11 -0
  18. data/app/controllers/batch_edits_controller.rb +1 -77
  19. data/app/controllers/generic_files_controller.rb +1 -0
  20. data/app/controllers/mailbox_controller.rb +1 -1
  21. data/app/controllers/single_use_link_controller.rb +11 -7
  22. data/app/helpers/generic_file_helper.rb +11 -3
  23. data/app/helpers/sufia_helper.rb +13 -10
  24. data/app/models/batch.rb +1 -1
  25. data/app/models/datastreams/fits_datastream.rb +2 -2
  26. data/app/models/datastreams/generic_file_rdf_datastream.rb +22 -18
  27. data/app/models/datastreams/properties_datastream.rb +2 -2
  28. data/app/views/_user_util_links.html.erb +2 -2
  29. data/app/views/batch/_metadata.html.erb +82 -0
  30. data/app/views/batch/_more_metadata.html.erb +6 -0
  31. data/app/views/batch/edit.html.erb +1 -8
  32. data/app/views/batch_edits/_check_all.html.erb +0 -157
  33. data/app/views/batch_edits/edit.html.erb +0 -29
  34. data/app/views/catalog/_index_partials/_list_files.html.erb +8 -10
  35. data/app/views/catalog/_recent_document.html.erb +9 -18
  36. data/app/views/catalog/_results_pagination.html.erb +1 -1
  37. data/app/views/contact_form/new.html.erb +1 -1
  38. data/app/views/dashboard/_index_partials/_default_group.html.erb +1 -1
  39. data/app/views/dashboard/_index_partials/_list_files.html.erb +12 -14
  40. data/app/views/dashboard/_index_partials/_thumbnail_display.html.erb +9 -19
  41. data/app/views/dashboard/_results_pagination.html.erb +1 -1
  42. data/app/views/dashboard/index.html.erb +6 -82
  43. data/app/views/error/single_use_error.html.erb +35 -0
  44. data/app/views/generic_files/_descriptions.html.erb +2 -2
  45. data/app/views/generic_files/_extra_fields_modal.html.erb +1 -1
  46. data/app/views/generic_files/_field_form.html.erb +2 -5
  47. data/app/views/generic_files/_media_display.html.erb +8 -6
  48. data/app/views/generic_files/_permission.html.erb +2 -2
  49. data/app/views/generic_files/_rights_modal.html.erb +1 -1
  50. data/app/views/generic_files/_show_actions.html.erb +1 -1
  51. data/app/views/generic_files/_show_details.html.erb +11 -6
  52. data/app/views/generic_files/edit.html.erb +0 -8
  53. data/app/views/generic_files/edit_fields/_type.html.erb +1 -1
  54. data/app/views/generic_files/show.html.erb +5 -8
  55. data/app/views/generic_files/show_fields/_based_near.html.erb +4 -1
  56. data/app/views/generic_files/show_fields/_contributor.html.erb +4 -1
  57. data/app/views/generic_files/show_fields/_creator.html.erb +4 -1
  58. data/app/views/generic_files/show_fields/_date_created.html.erb +4 -1
  59. data/app/views/generic_files/show_fields/_description.html.erb +4 -1
  60. data/app/views/generic_files/show_fields/_language.html.erb +1 -1
  61. data/app/views/generic_files/show_fields/_publisher.html.erb +4 -1
  62. data/app/views/generic_files/show_fields/_related_url.html.erb +3 -1
  63. data/app/views/generic_files/show_fields/_resource_type.html.erb +1 -1
  64. data/app/views/generic_files/show_fields/_subject.html.erb +4 -1
  65. data/app/views/generic_files/show_fields/_tag.html.erb +1 -1
  66. data/app/views/generic_files/show_fields/_title.html.erb +4 -1
  67. data/app/views/layouts/error.html.erb +0 -4
  68. data/app/views/layouts/hydra-head.html.erb +2 -6
  69. data/app/views/single_use_link/show.html.erb +1 -1
  70. data/app/views/users/index.html.erb +1 -1
  71. data/app/views/users/show.html.erb +1 -1
  72. data/config/locales/sufia.en.yml +1 -0
  73. data/config/routes.rb +11 -4
  74. data/lib/generators/sufia/sufia_generator.rb +2 -1
  75. data/lib/generators/sufia/templates/catalog_controller.rb +143 -117
  76. data/lib/generators/sufia/templates/config/resque_admin.rb +10 -0
  77. data/lib/generators/sufia/templates/config/sufia.rb +8 -0
  78. data/lib/sufia.rb +4 -14
  79. data/lib/sufia/batch_edits_controller_behavior.rb +89 -0
  80. data/lib/sufia/controller.rb +7 -5
  81. data/lib/sufia/downloads_controller_behavior.rb +14 -19
  82. data/lib/sufia/file_content/extract_metadata.rb +11 -4
  83. data/lib/sufia/files_controller_behavior.rb +63 -44
  84. data/lib/sufia/generic_file.rb +29 -11
  85. data/lib/sufia/generic_file/audit.rb +1 -1
  86. data/lib/sufia/generic_file/thumbnail.rb +51 -26
  87. data/lib/sufia/id_service.rb +28 -11
  88. data/lib/sufia/jobs/batch_update_job.rb +2 -2
  89. data/lib/sufia/jobs/characterize_job.rb +11 -3
  90. data/lib/sufia/jobs/ffmpeg_transcode_job.rb +61 -0
  91. data/lib/sufia/jobs/resolrize_job.rb +1 -1
  92. data/lib/sufia/jobs/transcode_audio_job.rb +40 -0
  93. data/lib/sufia/jobs/transcode_video_job.rb +9 -49
  94. data/lib/sufia/queue/resque.rb +2 -2
  95. data/lib/sufia/single_use_error.rb +4 -0
  96. data/lib/sufia/solr_document_behavior.rb +108 -1
  97. data/lib/sufia/version.rb +1 -1
  98. data/solr_conf/conf/schema.xml +332 -652
  99. data/solr_conf/conf/solrconfig.xml +60 -196
  100. data/spec/controllers/batch_controller_spec.rb +4 -5
  101. data/spec/controllers/catalog_controller_spec.rb +13 -13
  102. data/spec/controllers/dashboard_controller_spec.rb +2 -2
  103. data/spec/controllers/downloads_controller_spec.rb +74 -62
  104. data/spec/controllers/generic_files_controller_spec.rb +10 -8
  105. data/spec/controllers/single_use_link_controller_spec.rb +12 -4
  106. data/spec/fixtures/Example.ogg +0 -0
  107. data/spec/fixtures/piano_note.wav +0 -0
  108. data/spec/fixtures/sufia_generic_stub.descMeta.txt +1 -1
  109. data/spec/helpers/sufia_helper_spec.rb +12 -0
  110. data/spec/models/characterize_job_spec.rb +89 -0
  111. data/spec/models/checksum_audit_log_spec.rb +1 -0
  112. data/spec/models/event_jobs_spec.rb +9 -9
  113. data/spec/models/file_content_datastream_spec.rb +16 -10
  114. data/spec/models/fits_datastream_spec.rb +2 -8
  115. data/spec/models/generic_file_spec.rb +131 -60
  116. data/spec/models/solr_document_spec.rb +21 -0
  117. data/spec/models/transcode_audio_job_spec.rb +81 -0
  118. data/spec/models/transcode_video_job_spec.rb +2 -2
  119. data/spec/models/unzip_job_spec.rb +3 -3
  120. data/spec/spec_helper.rb +21 -0
  121. data/spec/support/Gemfile +7 -3
  122. data/sufia.gemspec +8 -11
  123. data/tasks/cucumber.rake +1 -2
  124. data/tasks/sufia-dev.rake +13 -2
  125. data/tasks/sufia.rake +1 -1
  126. metadata +77 -118
  127. data/app/views/batch_edits/_metadata.html.erb +0 -180
  128. data/lib/generators/sufia/templates/config/hydra_config.rb +0 -32
  129. data/lib/kaminari/helpers/tag.rb +0 -11
@@ -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", :type => FileContentDatastream
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
- ["application/pdf"].include? self.mime_type
66
+ ['application/pdf'].include? self.mime_type
59
67
  end
60
68
 
61
69
  def image?
62
- ["image/png","image/jpeg", 'image/jpg', 'image/bmp', "image/gif"].include? self.mime_type
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
- ["video/mpeg", "video/mp4", "video/x-msvideo", "video/avi", "video/quicktime"].include? self.mime_type
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(:is_part_of_s => batch_id)
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["label_t"] = self.label
126
- solr_doc["noid_s"] = noid
127
- solr_doc["file_format_t"] = file_format
128
- solr_doc["file_format_facet"] = solr_doc["file_format_t"]
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, :resource_type]
182
+ [:part_of, :date_modified, :date_uploaded, :format] #, :resource_type]
165
183
  end
166
184
 
167
185
  def terms_for_display
@@ -89,7 +89,7 @@ module Sufia
89
89
  end
90
90
 
91
91
  def audit_everything(force = false)
92
- ::GenericFile.find(:all, :rows => ::GenericFile.count).each do |gf|
92
+ ::GenericFile.find_each do |gf|
93
93
  gf.per_version do |ver|
94
94
  ::GenericFile.audit(ver, force)
95
95
  end
@@ -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 if self.content.content.nil?
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
- # elsif video?
13
- # create_video_thumbnail
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 = Magick::ImageList.new
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
- #logger.debug "Has the content changed before saving? #{self.content.changed?}"
29
- stat = self.save
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
- img = Magick::ImageList.new
41
- img.from_blob(content.content)
42
- # horizontal img
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
- scale = height / width
46
- if width > height
47
- if width > 150 and height > 105
48
- thumb = img.scale(150, height/scale)
49
- else
50
- thumb = img.scale(width, height)
51
- end
52
- # vertical img
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
- if width > 150 and height > 200
55
- thumb = img.scale(150*scale, 200)
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
@@ -15,26 +15,43 @@
15
15
  require 'noid'
16
16
 
17
17
  module Sufia
18
- class IdService
19
- @@minter = ::Noid::Minter.new(:template => '.reeddeeddk')
20
- @@namespace = Sufia::Engine.config.id_namespace
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 @@minter.valid? noid
26
+ return @minter.valid? noid
25
27
  end
26
28
  def self.mint
27
- while true
28
- pid = self.next_id
29
- break unless ActiveFedora::Base.exists?(pid)
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
- # seed with process id so that if two processes are running they do not come up with the same id.
36
- @@minter.seed($$)
37
- return "#{@@namespace}:#{@@minter.mint}"
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::AccessControlsEnforcement
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} DEEEENIED access to #{gf.pid}!"
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
- generic_file.create_thumbnail
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
+
@@ -18,6 +18,6 @@ class ResolrizeJob
18
18
  end
19
19
 
20
20
  def run
21
- Solrizer::Fedora::Solrizer.new.solrize_objects(:suppress_errors => false)
21
+ ActiveFedora::Base.reindex_everything
22
22
  end
23
23
  end
@@ -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
- require 'tmpdir'
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
- attr_accessor :generic_file_id, :datastream_in
14
-
15
- def initialize(generic_file_id, datastream_in)
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
- def run
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
- opts = "#{size_attributes} -b:v 345k -acodec libvorbis #{audio_attributes}"
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