vidispine 1.4.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 (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/Gemfile +11 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +296 -0
  6. data/Rakefile +2 -0
  7. data/bin/vidispine +7 -0
  8. data/bin/vidispine-notification-handler +339 -0
  9. data/bin/vidispine-utilities-http-server +13 -0
  10. data/dist/vidispine-1.4.0.gem +0 -0
  11. data/lib/vidispine.rb +5 -0
  12. data/lib/vidispine/api/client.rb +981 -0
  13. data/lib/vidispine/api/client/http_client.rb +291 -0
  14. data/lib/vidispine/api/client/requests.rb +27 -0
  15. data/lib/vidispine/api/client/requests/base_request.rb +234 -0
  16. data/lib/vidispine/api/client/requests/collection_access_add.rb +30 -0
  17. data/lib/vidispine/api/client/requests/collection_access_delete.rb +26 -0
  18. data/lib/vidispine/api/client/requests/collection_metadata_set.rb +24 -0
  19. data/lib/vidispine/api/client/requests/import_placeholder.rb +36 -0
  20. data/lib/vidispine/api/client/requests/import_placeholder_item.rb +38 -0
  21. data/lib/vidispine/api/client/requests/import_sidecar_file.rb +28 -0
  22. data/lib/vidispine/api/client/requests/import_using_uri.rb +44 -0
  23. data/lib/vidispine/api/client/requests/item_access_add.rb +30 -0
  24. data/lib/vidispine/api/client/requests/item_access_delete.rb +26 -0
  25. data/lib/vidispine/api/client/requests/item_delete.rb +17 -0
  26. data/lib/vidispine/api/client/requests/item_export.rb +44 -0
  27. data/lib/vidispine/api/client/requests/item_metadata_get.rb +37 -0
  28. data/lib/vidispine/api/client/requests/item_metadata_set.rb +29 -0
  29. data/lib/vidispine/api/client/requests/item_search.rb +0 -0
  30. data/lib/vidispine/api/client/requests/item_transcode.rb +23 -0
  31. data/lib/vidispine/api/client/requests/items_search.rb +40 -0
  32. data/lib/vidispine/api/client/requests/search.rb +59 -0
  33. data/lib/vidispine/api/client/requests/storage_file_create.rb +23 -0
  34. data/lib/vidispine/api/client/requests/storage_file_get.rb +40 -0
  35. data/lib/vidispine/api/client/requests/storage_files_get.rb +42 -0
  36. data/lib/vidispine/api/utilities.rb +1608 -0
  37. data/lib/vidispine/api/utilities/cli.rb +168 -0
  38. data/lib/vidispine/api/utilities/http_server.rb +230 -0
  39. data/lib/vidispine/api/utilities/http_server/cli.rb +77 -0
  40. data/lib/vidispine/cli.rb +174 -0
  41. data/lib/vidispine/version.rb +3 -0
  42. data/resources/postman/Vidispine API (From WADL v4.11).postman_collection.json +47437 -0
  43. data/vidispine.gemspec +26 -0
  44. metadata +117 -0
@@ -0,0 +1,40 @@
1
+ module Vidispine::API::Client::Requests
2
+
3
+ # @see http://apidoc.vidispine.com/latest/ref/item/item.html#search-items
4
+ class ItemsSearch < BaseRequest
5
+
6
+ HTTP_METHOD = :put
7
+ HTTP_PATH = '/item'
8
+ DEFAULT_PARAMETER_SEND_IN_VALUE = :matrix
9
+
10
+ PARAMETERS = [
11
+ { :name => :result, :send_in => :query },
12
+ { :name => :content, :send_in => :query },
13
+
14
+
15
+ :library,
16
+ :first,
17
+ :number,
18
+ :libraryId,
19
+ :autoRefresh,
20
+ :updateMode,
21
+ :updateFrequency,
22
+
23
+ { :name => :ItemSearchDocument, :send_in => :body }
24
+ ]
25
+
26
+ def body
27
+ @body ||= arguments[:ItemSearchDocument]
28
+ end
29
+
30
+ def body_as_xml
31
+ <<-XML
32
+ <ItemSearchDocument xmlns="http://xml.vidispine.com/schema/vidispine">
33
+ </ItemSearchDocument>
34
+ XML
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+
@@ -0,0 +1,59 @@
1
+ module Vidispine::API::Client::Requests
2
+
3
+ class Search < BaseRequest
4
+
5
+ HTTP_METHOD = :put
6
+ HTTP_PATH = 'search'
7
+
8
+ PARAMETERS = [
9
+ :content,
10
+ :interval,
11
+ :field,
12
+ :group,
13
+ :language,
14
+ :samplerate,
15
+ :track,
16
+ :terse,
17
+ :include,
18
+ :type,
19
+ :tag,
20
+ :scheme,
21
+ :closedFiles,
22
+ 'noauth-url',
23
+ :defaultValue,
24
+ :methodType,
25
+ :version,
26
+ :revision,
27
+
28
+ { :name => :first, :send_in => :matrix },
29
+
30
+ { :name => :ItemSearchDocument, :default_value => { }, :send_in => :body },
31
+ ]
32
+
33
+ # {
34
+ # "field": [
35
+ # {
36
+ # "name": "portal_mf48881",
37
+ # "value": [
38
+ # {
39
+ # "value": "something"
40
+ # }
41
+ # ]
42
+ # }
43
+ # ]
44
+ # }
45
+ def body
46
+ @body ||= arguments[:ItemSearchDocument]
47
+ end
48
+
49
+ def body_as_xml
50
+ <<-XML
51
+ <ItemSearchDocument xmlns="http://xml.vidispine.com/schema/vidispine">
52
+ </ItemDocument>
53
+ XML
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
@@ -0,0 +1,23 @@
1
+ module Vidispine::API::Client::Requests
2
+
3
+ # @see http://apidoc.vidispine.com/4.2/ref/storage/file.html#list-files-in-storage
4
+ class StorageFileCreate < BaseRequest
5
+
6
+ HTTP_METHOD = :post
7
+ HTTP_PATH = '/storage/#{path_arguments[:storage_id]}/file'
8
+
9
+ PARAMETERS = [
10
+ # Path Parameters
11
+ { :name => :storage_id, :required => true, :send_in => :path },
12
+
13
+ # Query Parameters
14
+ :createOnly,
15
+ :state,
16
+
17
+ # Body Parameters
18
+ { :name => :path, :send_in => :body }
19
+ ]
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,40 @@
1
+ module Vidispine::API::Client::Requests
2
+
3
+ # Exposes two functions
4
+ # 1. Get status of file in storage
5
+ # @see http://apidoc.vidispine.com/4.2.3/ref/storage/file.html#get-status-of-file-in-storage
6
+ #
7
+ # 2. Get direct download access to file in storage
8
+ # @see http://apidoc.vidispine.com/4.2.3/ref/storage/file.html#get-direct-download-access-to-file-in-storage
9
+ class StorageFileGet < BaseRequest
10
+
11
+ HTTP_PATH = '/storage/#{path_arguments[:storage_id]}/file/#{path_arguments[:file_id]}'
12
+
13
+ PARAMETERS = [
14
+ # Path Parameters
15
+ { :name => :storage_id, :required => true, :send_in => :path },
16
+ { :name => :file_id, :required => true, :send_in => :path },
17
+
18
+ # Matrix Parameters
19
+ { :name => :includeItem, :send_in => :matrix },
20
+ { :name => :path, :send_in => :matrix },
21
+ { :name => :uri, :send_in => :matrix },
22
+
23
+
24
+ # Query Parameters
25
+ :methodType
26
+ ]
27
+
28
+ def after_process_parameters
29
+ # URI Needs to be escaped twice, so we do it once here and then again when the query is built
30
+ # @see http://apidoc.vidispine.com/4.2.6/storage/uri.html#api-calls
31
+ _uri = arguments[:uri]
32
+ arguments[:uri] = CGI.escape(_uri).gsub('+', '%20') if _uri
33
+
34
+ _path =arguments[:path]
35
+ arguments[:path] = CGI.escape(_path).gsub('+', '%20') if _path
36
+ end
37
+
38
+ end
39
+
40
+ end
@@ -0,0 +1,42 @@
1
+ module Vidispine::API::Client::Requests
2
+
3
+ class StorageFilesGet < BaseRequest
4
+ # @see http://apidoc.vidispine.com/4.2/ref/storage/file.html#list-files-in-storage
5
+
6
+ HTTP_PATH = '/storage/#{path_arguments[:storage_id]}/file'
7
+
8
+ PARAMETERS = [
9
+ # Path Parameters
10
+ { :name => :storage_id, :required => true, :send_in => :path },
11
+
12
+ # Matrix Parameters
13
+ { :name => :start, :send_in => :matrix },
14
+ { :name => :number, :send_in => :matrix },
15
+ { :name => :filter, :send_in => :matrix },
16
+ { :name => :includeItem, :send_in => :matrix },
17
+ { :name => :excludeQueued, :send_in => :matrix },
18
+ { :name => :ignorecase, :send_in => :matrix },
19
+ { :name => :sort, :send_in => :matrix },
20
+ { :name => :storage, :send_in => :matrix },
21
+
22
+ # Query Parameters
23
+ :path,
24
+ :id,
25
+ :recursive,
26
+ :wildcard,
27
+ :type,
28
+ :hash,
29
+ :algorithm,
30
+ :count,
31
+ ]
32
+
33
+ def after_process_parameters
34
+ # Path needs to be escaped twice, so we do it once here and then again when the query is built
35
+ # @see http://apidoc.vidispine.com/4.2.6/storage/uri.html#api-calls
36
+ # _path = arguments[:path]
37
+ # arguments[:path] = CGI.escape(_path).gsub('+', '%20') if _path
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,1608 @@
1
+ require 'vidispine/api/client'
2
+ require 'rexml/document'
3
+
4
+ module Vidispine
5
+
6
+ module API
7
+
8
+ class Utilities < Client
9
+
10
+ attr_accessor :default_metadata_map, :default_storage_map
11
+
12
+ def initialize(args = { })
13
+ @default_storage_map = args[:storage_map] || { }
14
+ @default_metadata_map = args[:metadata_map] || { }
15
+
16
+ super
17
+ end
18
+
19
+ # Converts hash keys to symbols
20
+ #
21
+ # @param [Hash] value hash
22
+ # @param [Boolean] recursive Will recurse into any values that are hashes or arrays
23
+ def symbolize_keys (value, recursive = true)
24
+ case value
25
+ when Hash
26
+ new_val = {}
27
+ value.each { |k,v|
28
+ k = (k.to_sym rescue k)
29
+ v = symbolize_keys(v, true) if recursive and (v.is_a? Hash or v.is_a? Array)
30
+ new_val[k] = v
31
+ }
32
+ return new_val
33
+ when Array
34
+ return value.map { |v| symbolize_keys(v, true) }
35
+ else
36
+ return value
37
+ end
38
+ end # symbolize_keys
39
+
40
+ # Tries to find a collection using either the collection id, name, or a file path with a collection name position.
41
+ # For use in other methods that need to perform the same type of lookup
42
+ # @param [Hash] :collection
43
+ # @param [String] :collection_id
44
+ # @param [String] :collection_name
45
+ def determine_collection(args, options = { })
46
+ collection = args[:collection] || { }
47
+
48
+ # 3 Get Collection
49
+ collection_id = args[:collection_id] || collection['id']
50
+ unless collection_id
51
+ collection_name = args[:collection_name] || args[:name]
52
+ unless collection_name
53
+ file_path_collection_name_position = args[:file_path_collection_name_position]
54
+ raise ArgumentError, ':collection_id, :collection_name, or :file_path_collection_name_position argument is required.' unless file_path_collection_name_position
55
+
56
+ file_path = args[:file_path]
57
+ raise ArgumentError, ':file_path is a required argument when using :file_path_collection_name_position' unless file_path
58
+
59
+ file_path_split = (file_path.start_with?('/') ? file_path[1..-1] : file_path).split('/')
60
+ collection_name = file_path_split[file_path_collection_name_position]
61
+ raise ArgumentError, 'Unable to determine collection name from path.' unless collection_name
62
+ logger.debug { "Using '#{collection_name}' as collection_name. File Path Array: #{file_path_split.inspect}" }
63
+ end
64
+ # Determine Collection
65
+ #collection = collection_create_if_not_exists(:collection_name => collection_name)
66
+ collection = collection_get_by_name({ :collection_name => collection_name }, options)
67
+ else
68
+ collection = collection_get(:collection_id => collection_id)
69
+ raise ArgumentError, 'Collection not found.' unless collection and collection['id']
70
+ end
71
+ collection
72
+ end
73
+
74
+ # Adds an Item to a Collection but Gives Multiple ways of Determining the Collection
75
+ # @param [Hash] :item
76
+ # @param [Hash] :collection
77
+ # @param [String] :collection_id
78
+ # @param [String] :collection_name
79
+ # @param [String] :file_path Required if using :file_path_collection_name_position
80
+ # @param [Integer] :file_path_collection_name_position
81
+ def collection_item_add_extended(args = { }, options = { })
82
+ args = symbolize_keys(args, false)
83
+
84
+ _response = { }
85
+
86
+ item = args[:item] || { }
87
+ item_id = args[:item_id] || item['id']
88
+
89
+ # # 3 Get Collection
90
+ # collection_id = args[:collection_id] || collection['id']
91
+ # unless collection_id
92
+ # collection_name = args[:collection_name]
93
+ # unless collection_name
94
+ # file_path_collection_name_position = args[:file_path_collection_name_position]
95
+ # raise ArgumentError, ':collection_id, :collection_name, or :file_path_collection_name_position argument is required.' unless file_path_collection_name_position
96
+ #
97
+ # file_path_split = (file_path.start_with?('/') ? file_path[1..-1] : file_path).split('/')
98
+ # collection_name = file_path_split[file_path_collection_name_position]
99
+ # raise ArgumentError, 'Unable to determine collection name from path.' unless collection_name
100
+ # logger.debug { "Using '#{collection_name}' as collection_name. File Path Array: #{file_path_split.inspect}" }
101
+ # end
102
+ # # Determine Collection
103
+ # collection = collection_create_if_not_exists(:collection_name => collection_name)
104
+ # collection_id = collection['id']
105
+ # else
106
+ # collection ||= collection_get(:collection_id => collection_id)
107
+ # raise ArgumentError, 'Collection not found.' unless collection
108
+ # end
109
+ collection = determine_collection(args)
110
+ _response[:collection] = collection
111
+ collection_id = collection['id']
112
+
113
+ # 5. Add Item to the Collection
114
+ logger.debug { 'Adding Item to Collection.' }
115
+ collection_object_add_response = collection_object_add(:collection_id => collection_id, :object_id => item_id)
116
+ _response[:collection_object_add] = collection_object_add_response
117
+
118
+ _response
119
+ end
120
+
121
+ # # Transforms metadata from key value to field format
122
+ # # { k1 => v1, k2 => v2} becomes [ { :name => k1, :value => [ { :value => v1 } ] }, { :name => k2, :value => [ { :value => v2 } ] } ]
123
+ # def transform_metadata_to_fields(metadata_in, options = { })
124
+ # _metadata_map = default_metadata_map.merge(options[:metadata_map] || { })
125
+ # metadata_in.map { |k,v| { :name => (_metadata_map[k] || k), :value => [ { :value => v } ] } }
126
+ # end
127
+
128
+ # Transforms metadata from key value to MetadataDocument field and group sequences
129
+ # { k1 => v1, k2 => v2 } becomes
130
+ # {
131
+ # :field => [
132
+ # { :name => map[k1][:field], :value => [ { :value => v1 } ] },
133
+ # { :name => map[k2][:field], :value => [ { :value => v2 } ] }
134
+ # ],
135
+ # :group => [ ]
136
+ # }
137
+ #
138
+ # Metadata Map Example
139
+ # metadata_map = {
140
+ # 'Campaign Title' => { :group => 'Film', :field => 'portal_mf409876' },
141
+ # 'Client' => { :group => 'Editorial and Film', :field => 'portal_mf982459' },
142
+ # 'Product' => { :group => 'Film', :field => 'portal_mf264604' },
143
+ # 'Studio Tracking ID' => { :group => 'Film', :field => 'portal_mf846239' },
144
+ # #'File Path' => { :field => 'portal_mf48881', :group => 'Film' }
145
+ # #'File Path' => { :field => 'portal_mf48881' }
146
+ # 'File Path' => 'portal_mf48881'
147
+ # }
148
+ #
149
+ # @see http://apidoc.vidispine.com/4.2.3/ref/xml-schema.html#schema-element-MetadataDocument
150
+ #
151
+ # @param [Hash] metadata_in Key value pair where the key is an alias for a vidispine metadata field name
152
+ # @param [Hash] map A mapping of metadata field name aliases to proper metadata field name and optionally
153
+ # metadata group name { 'Some Field' => { :field => 'properFieldName', :group => 'groupName' } }
154
+ # @param [Hash] options
155
+ # @option options [Hash] :default_metadata_map (default_metadata_map)
156
+ # @option options [Hash] :metadata_map
157
+ def transform_metadata_to_fields(metadata_in, map = { }, options = { })
158
+ map = (options[:default_metadata_map] || default_metadata_map).merge(map.merge(options[:metadata_map] || { }))
159
+ groups = { }
160
+ metadata_in.each do |k,v|
161
+ _map = map[k]
162
+ next unless _map
163
+ [*_map].each do |_map_|
164
+ _map_ = { :field => _map_ } if _map_.is_a?(String)
165
+ (groups[_map_[:group]] ||= { })[_map_[:field]] = v
166
+ end
167
+ end
168
+
169
+ _field = groups.delete(nil) { { } }.map { |fname, values| { :name => fname, :value => [*values].map { |v| { :value => v } } } }
170
+ _group = groups.map { |name, fields| { :name => name, :field => fields.map { |fname, values| { :name => fname, :value => [*values].map { |v| { :value => v } } } } } }
171
+
172
+ # metadata_out = { }
173
+ # metadata_out[:field] = _field unless _field.empty?
174
+ # metadata_out[:group] = _group unless _group.empty?
175
+ # metadata_out
176
+ { :field => _field, :group => _group }
177
+ end
178
+
179
+ # Reads an item's metadata array and return a hash version of the metadata
180
+ #
181
+ # {
182
+ # "item": [
183
+ # {
184
+ # "metadata": {
185
+ # "revision": "VX-348,VX-766,VX-350,VX-352,VX-767,VX-353,VX-816,VX-815,VX-346",
186
+ # "group": [
187
+ # "Film"
188
+ # ],
189
+ # "timespan": [
190
+ # {
191
+ # "start": "-INF",
192
+ # "end": "+INF",
193
+ # "field": [
194
+ # {
195
+ # "name": "portal_mf778031",
196
+ # "uuid": "4150479f-b15e-475b-bc48-80ef85d3c2cf",
197
+ # "change": "VX-767",
198
+ # "user": "admin",
199
+ # "value": [
200
+ # {
201
+ # "uuid": "a7f91e7c-ffc6-4ba1-9658-3458bec886e9",
202
+ # "change": "VX-767",
203
+ # "user": "admin",
204
+ # "value": "556dd36a02a760d6bd000071",
205
+ # "timestamp": "2015-07-06T22:25:17.926+0000"
206
+ # }
207
+ # ],
208
+ # "timestamp": "2015-07-06T22:25:17.926+0000"
209
+ # },
210
+ # {
211
+ # "name": "portal_mf897662"
212
+ # },
213
+ # {
214
+ # "name": "portal_mf396149"
215
+ # }
216
+ # ],
217
+ # "group": [
218
+ #
219
+ # ]
220
+ # }
221
+ # ]
222
+ # },
223
+ # "id": "VX-84"
224
+ # }
225
+ # ]
226
+ # }
227
+ #
228
+ # @param [Hash] _response A vidispine item or hash with the item key
229
+ # @return [Hash]
230
+ def self.transform_metadata_get_response_to_hash(_response, options = { })
231
+ items = _response['item'] || _response
232
+ item = items.is_a?(Array) ? items.first : items
233
+ item_metadata = item['metadata'] || item
234
+ metadata = { }
235
+
236
+ # group = item_metadata['group'].first
237
+ timespans = item_metadata['timespan']
238
+ timespans.each do |t|
239
+ metadata.merge!(transform_metadata_group(t))
240
+ end
241
+ metadata
242
+ end
243
+
244
+ def transform_metadata_get_response_to_hash(_response, options = { })
245
+ self.class.transform_metadata_get_response_to_hash(_response, options)
246
+ end
247
+
248
+ # @param [Hash] group
249
+ # @param [Array] breadcrumbs
250
+ # @return [Hash]
251
+ def self.transform_metadata_group(group, breadcrumbs = [ ])
252
+ metadata = { }
253
+ name = group['name']
254
+
255
+ _breadcrumbs = breadcrumbs.dup << name
256
+ bc = _breadcrumbs.compact.join(':')
257
+ bc << ':' unless bc.empty?
258
+
259
+ groups = group['group']
260
+ if groups.length == 1 and (_group_name = groups.first).is_a?(String)
261
+ # group_name = _group_name.is_a?(String) ? _group_name : ''
262
+ else
263
+ # group_name = ''
264
+ groups.each do |g|
265
+ metadata.merge!(transform_metadata_group(g, _breadcrumbs))
266
+ end
267
+ end
268
+
269
+ fields = group['field']
270
+ fields.each do |f|
271
+ # field_name = "#{group_name}#{group_name.empty? ? '' : ':'}#{f['name']}"
272
+ field_name = "#{bc}#{f['name']}"
273
+ field_value_raw = f['value']
274
+ if field_value_raw.is_a?(Array)
275
+ field_value = field_value_raw.map { |v| v['value'] }
276
+ field_value = field_value.first if field_value.length == 1
277
+ else
278
+ field_value = field_value_raw
279
+ end
280
+ metadata[field_name] = field_value
281
+ end
282
+ metadata
283
+ end
284
+
285
+ # @param [Hash] criteria
286
+ # @return [Hash]
287
+ def build_item_search_document(criteria, options = { })
288
+ fields = criteria[:fields] || criteria
289
+ item_search_document = {
290
+ :field => fields.map { |fname, values|
291
+ if values.is_a?(Hash)
292
+ #_values = values.map { |k, values| { k => [ { :value => [*values].map { |v| { :value => v } } } ] } }.inject({}) { |hash, value| hash.merge(value) }
293
+ _values = Hash[ values.map { |k, values| [ k, [ { :value => [*values].map { |v| { :value => v } } } ] ] } ]
294
+ else
295
+ _values = { :value => [*values].map { |v| { :value => v } } }
296
+ end
297
+ { :name => fname }.merge(_values)
298
+ }
299
+ }
300
+ item_search_document
301
+ end
302
+
303
+ # @param [Hash] metadata_in
304
+ # @param [Hash] map A hash of alias field names to vidispine field or field and groups.
305
+ # Will merge into @default_metadata_map, options[:default_metadata_map], and options[:metadata_map]
306
+ # @param [Hash] options
307
+ # @option options [Hash] :default_metadata_map Allows for overriding @default_metadata_map
308
+ # @option options [Hash] :metadata_map An option for passing the map in options
309
+ # @return [Hash]
310
+ def self.build_metadata_document(metadata_in, map = { }, options = { })
311
+ # map = (options[:default_metadata_map]).merge((options[:metadata_map] || { }).merge(map))
312
+ groups = { }
313
+ metadata_in.each do |k,v|
314
+ _map = map[k]
315
+ next unless _map
316
+ _map = [ _map ] unless _map.is_a?(Array)
317
+ _map.each do |_map_|
318
+ _map_ = { :field => _map_ } if _map_.is_a?(String)
319
+ #puts "##{_map_[:group].inspect} #{_map_.class.name} #{_map_.inspect}"
320
+ (groups[_map_[:group]] ||= { })[_map_[:field]] = v
321
+ end
322
+ end
323
+
324
+ map_options = map[:__build_metadata_document_options] || map['__build_metadata_document_options'] || { }
325
+ options.merge!(map_options)
326
+ parent_group_name = options[:parent_group_name]
327
+
328
+ _field = groups.delete(nil) { { } }.map { |fname, values| { :name => fname, :value => [*values].map { |v| { :value => v } } } }
329
+ _group = groups.map { |name, fields| { :name => name, :field => fields.map { |fname, values| { :name => fname, :value => [*values].map { |v| { :value => v } } } } } }
330
+
331
+ # metadata_out = { }
332
+ # metadata_out[:field] = _field unless _field.empty?
333
+ # metadata_out[:group] = _group unless _group.empty?
334
+ # metadata_out
335
+
336
+ #{ :field => _field, :group => _group }
337
+ if (_field.empty? && _group.length == 1 && (!parent_group_name || parent_group_name.empty?))
338
+ _group = _group.first
339
+ group_name = _group[:name]
340
+ group_fields = _group[:field]
341
+ return { :group => [ group_name ], :timespan => [ { :start => '-INF', :end => '+INF', :field => group_fields } ] }
342
+ end
343
+
344
+ _data_out = { :timespan => [ { :start => '-INF', :end => '+INF', :field => _field, :group => _group } ] }
345
+ _data_out[:group] ||= [ parent_group_name ] if parent_group_name
346
+
347
+ _data_out
348
+ end
349
+
350
+ def build_metadata_document(metadata_in, map = { }, options = { })
351
+ map = (options[:default_metadata_map] || default_metadata_map).merge((options[:metadata_map] || { }).merge(map))
352
+ self.class.build_metadata_document(metadata_in, map, options)
353
+ end
354
+
355
+ # @return [Array]
356
+ def build_metadata_documents(metadata_in, map = { }, options = { })
357
+ map = (options[:default_metadata_map] || default_metadata_map).merge(map.merge(options[:metadata_map] || { }))
358
+ groups = { }
359
+ metadata_in.each do |k,v|
360
+ _map = map[k]
361
+ next unless _map
362
+ _map = [ _map ] unless _map.is_a?(Array)
363
+ _map.each do |_map_|
364
+ _map_ = { :field => _map_ } if _map_.is_a?(String)
365
+ #puts "##{_map_[:group].inspect} #{_map_.class.name} #{_map_.inspect}"
366
+ (groups[_map_[:group]] ||= { })[_map_[:field]] = v
367
+ end
368
+ end
369
+
370
+ _field = groups.delete(nil) { { } }.map { |fname, values| { :name => fname, :value => [*values].map { |v| { :value => v } } } }
371
+ _groups = groups.map { |name, fields| { :name => name, :field => fields.map { |fname, values| { :name => fname, :value => [*values].map { |v| { :value => v } } } } } }
372
+
373
+ docs = [ ]
374
+
375
+ if !_field.empty?
376
+ docs << { :timespan => [ { :start => '-INF', :end => '+INF', :field => _field, :group => [ ] } ] }
377
+ end
378
+
379
+ _groups.each do |_group|
380
+ group_name = _group[:name]
381
+ group_fields = _group[:field]
382
+ docs << { :group => [ group_name ], :timespan => [ { :start => '-INF', :end => '+INF', :field => group_fields } ] }
383
+ end
384
+
385
+ docs
386
+ end
387
+
388
+ # @param [String|Hash] field_group
389
+ # @return [Hash]
390
+ def cantemo_metadata_field_group_map(field_group, options = { })
391
+ field_group = metadata_field_group_get(:group_name => field_group, :include_values => true, :traverse => true) if field_group.is_a?(String)
392
+
393
+ field_map = { }
394
+ group_name = field_group['schema']['name']
395
+ fields = field_group['field']
396
+ fields.each do |field|
397
+ cp_field = cantemo_metadata_field_process(field)
398
+ cp_field[:group] = group_name
399
+ cp_field[:vs_def] = field
400
+
401
+ field_map[cp_field[:label]] = cp_field
402
+ end
403
+
404
+ field_map
405
+ end
406
+
407
+ # @param [Hash] field
408
+ # @param [Hash] options
409
+ # @return [Hash]
410
+ def cantemo_metadata_field_process(field, options = { })
411
+ field_name = field['name']
412
+ field_data = field['data']
413
+ field_data = JSON.parse(field['data']) if field_data.is_a?(String) && field_data.start_with?('{')
414
+
415
+ field_extra_data = field_data.find { |fd| fd['key'] == 'extradata' } || { }
416
+ field_extra_data_value = field_extra_data['value']
417
+ field_extra_data_value = JSON.parse(field_extra_data_value) if field_extra_data_value.is_a?(String) && field_extra_data_value.start_with?('{')
418
+ return nil unless field_extra_data_value.is_a?(Hash)
419
+
420
+ field_label = field_extra_data_value['name']
421
+
422
+ cp_field_type = field_extra_data_value['type']
423
+ cp_field = {
424
+ :name => field_name,
425
+ :type => cp_field_type,
426
+ :label => field_label,
427
+ :cp_def => field_extra_data_value
428
+ }
429
+
430
+ case cp_field_type
431
+ when 'checkbox', 'dropdown'
432
+ _values = field_extra_data_value['values']
433
+ choices = Hash[ _values.map { |v| [ v['key'], v['value'] ] } ]
434
+ cp_field[:choices] = choices
435
+ when 'lookup'
436
+ field_data__values = (field_data.find { |fd| fd['key'] == '__values' } || { })['value']
437
+ if field_data__values
438
+ __values_xml = field_data__values.gsub('\"', '"')
439
+ __values_doc = REXML::Document.new(StringIO.new(__values_xml))
440
+
441
+ choices = { }
442
+ __values_doc.elements.each('SimpleMetadataDocument/field') do |e|
443
+ choices[e.elements['key'].text] = e.elements['value'].text
444
+ end
445
+ cp_field[:choices] = choices
446
+ end
447
+ end
448
+
449
+ cp_field
450
+ end
451
+
452
+ # Searches for a collection by name and if a match is not found then a new collection is created
453
+ # This method will only return the first match if an existing collection is found.
454
+ #
455
+ # @param [Hash] args
456
+ # @option args [String] :collection_name
457
+ # @param [Hash] options
458
+ # @option options [Boolean] :case_sensitive (true)
459
+ #
460
+ # @return [Hash]
461
+ def collection_create_if_not_exists(args = { }, options = { })
462
+ return args.map { |v| collection_create_if_not_exists(v, options) } if args.is_a?(Array)
463
+ args = args.is_a?(Hash) ? args : { :collection_name => args }
464
+
465
+ collection_name = args[:collection_name] || args[:name]
466
+ raise ArgumentError, 'collection_name is required.' unless collection_name
467
+ case_sensitive = options.fetch(:case_sensitive, true)
468
+
469
+ collection = collection_get_by_name( :collection_name => collection_name, :case_sensitive => case_sensitive )
470
+ collection_already_existed = collection && !collection.empty? ? true : false
471
+ collection ||= collection_create(collection_name)
472
+ options[:extended_response] ?
473
+ { :collection => collection, :collection_already_existed => collection_already_existed } :
474
+ collection
475
+ end
476
+
477
+ # Searches for a collection by name
478
+ # @param [Hash] args
479
+ # @option args [String] :collection_name
480
+ # @param [Hash] options
481
+ # @option options [Boolean] :return_first_match (true)
482
+ # @option options [Boolean] :case_sensitive (true)
483
+ #
484
+ # @return [Hash|Array|nil]
485
+ def collection_get_by_name(args = { }, options = { })
486
+ return collection_create_if_not_exists(args, options) if options[:collection_create_if_not_exists]
487
+ args = args.is_a?(Hash) ? args : { :collection_name => args }
488
+
489
+ collection_name = args[:collection_name] || args[:name]
490
+ return_first_match = options.fetch(:return_first_match, true)
491
+
492
+ unless collection_name
493
+ raise ArgumentError, 'collection_name is required.'
494
+ end
495
+
496
+ # collections = ( (collections_get || { })['collection'] || [ ] )
497
+
498
+ first = 1
499
+ limit = 1000
500
+ collections = [ ]
501
+ loop do
502
+ r = collections_get(:first => first, :number => limit)
503
+ _collections = r['collection']
504
+ break if _collections.empty?
505
+ collections.concat _collections
506
+ first += _collections.length
507
+ end
508
+
509
+ comparison_method, comparison_value = options.fetch(:case_sensitive, true) ? [ :eql?, true ] : [ :casecmp, 0 ]
510
+ collections_search_method = return_first_match ? :find : :select
511
+ collections.send(collections_search_method) { |c| c['name'].send(comparison_method, collection_name) == comparison_value }
512
+ end
513
+
514
+ # Adds a file using the files path
515
+ #
516
+ # @param [Hash] args
517
+ # @option args [String] :file_path
518
+ # @option args [Hash|null] :storage_path_map
519
+ # @option args [String] :storage_method_type ('file')
520
+ # @option args [Hash] :metadata ({})
521
+ # @option args [Hash] :metadata_map ({})
522
+ # @option args [Hash] :file
523
+ # @option args [String] :file_id
524
+ # @option args [Boolean] :create_thumbnails (true)
525
+ # @option args [Integer|false] :create_posters (3)
526
+ #
527
+ # @param [Hash] options
528
+ # @option options [Boolean] :add_item_to_collection
529
+ # @option options [Boolean] :wait_for_transcode_job (false)
530
+ # @option options [Boolean] :skip_transcode_if_shape_with_tag_exists (true)
531
+ #
532
+ # @return [Hash]
533
+ def item_add_using_file_path(args = { }, options = { })
534
+ args = symbolize_keys(args, false)
535
+ _response = { }
536
+
537
+ # 1. Receive a File Path
538
+ file_path = args[:file_path]
539
+ raise ArgumentError, ':file_path is a required argument.' unless file_path
540
+
541
+ # 2. Determine Storage ID
542
+ storage_method = args[:storage_method] || 'file'
543
+ storage_path_map = args[:storage_path_map]
544
+ storage_path_map = storage_file_path_map_create(:storage_method => storage_method) unless storage_path_map and !storage_path_map.empty?
545
+
546
+ storage_id = args[:storage_id]
547
+ if storage_id
548
+ volume_path, storage = storage_path_map.find { |_, id| id == storage_id }
549
+ raise "Unable to find match in storage path map for '#{storage_id}'. Storage Map: #{storage_path_map.inspect}" unless volume_path
550
+ else
551
+ volume_path, storage = storage_path_map.find { |path, _| file_path.start_with?(path) }
552
+ raise "Unable to find match in storage path map for '#{file_path}'. Storage Map: #{storage_path_map.inspect}" unless volume_path
553
+ end
554
+
555
+
556
+ file_path_relative_to_storage_path = file_path.sub(volume_path, '')
557
+ logger.debug { "File Path Relative to Storage Path: #{file_path_relative_to_storage_path}" }
558
+
559
+ storage = storage_get(:id => storage) if storage.is_a?(String)
560
+ _response[:storage] = storage
561
+ storage_id = storage['id']
562
+ raise "Error Retrieving Storage Record. Storage: #{storage.inspect}" unless storage_id
563
+
564
+ # The URI Lookup part was commented out as it should be handled by the storage_file_path_map_create
565
+ # The method type of the URI to lookup
566
+ # storage_method_type = args[:storage_method_type] ||= 'file'
567
+ #
568
+ # storage_uri_method = "#{storage_method_type}:"
569
+ # storage_uri_raw = (storage['method'].find { |v| v['uri'].start_with?(storage_uri_method) } || { })['uri'] rescue nil
570
+ # raise "Error Getting URI from Storage Method. Storage: #{storage.inspect}" unless storage_uri_raw
571
+ # storage_uri = URI.parse(storage_uri_raw)
572
+ #
573
+ # vidispine_file_path = File.join(storage_uri.path, file_path_relative_to_storage_path)
574
+
575
+ vidispine_file_path = File.join(volume_path, file_path_relative_to_storage_path)
576
+ logger.debug { "Vidispine File Path: '#{vidispine_file_path}'" }
577
+ _response[:vidispine_file_path] = vidispine_file_path
578
+
579
+ _metadata = args[:metadata] || { }
580
+ _metadata_map = args[:metadata_map] || { }
581
+
582
+ # map metadata assuming 1 value per field
583
+ #_metadata_as_fields = transform_metadata_to_fields(_metadata, _metadata_map, options)
584
+ #metadata_document = build_metadata_document(_metadata, _metadata_map, options)
585
+ metadata_documents = build_metadata_documents(_metadata, _metadata_map, options)
586
+ metadata_document = metadata_documents.shift || { }
587
+
588
+ # Allow the file to be passed in
589
+ file = args[:file]
590
+ unless file
591
+ file_id = args[:file_id]
592
+ file = { 'id' => file_id }
593
+ end
594
+
595
+ if file and !file['item']
596
+ # If the passed file doesn't have an item then requery to verify that the item is absent
597
+ storage_file_get_response = storage_file_get(:storage_id => storage_id, :file_id => file_id, :include_item => true)
598
+
599
+ raise "Error Getting Storage File. '#{storage_file_get_response.inspect}'" unless storage_file_get_response and storage_file_get_response['id']
600
+ _response[:storage_file_get_response] = storage_file_get_response
601
+
602
+ file = storage_file_get_response
603
+ else
604
+ storage_file_get_or_create_response = storage_file_get_or_create(storage_id, file_path_relative_to_storage_path, :extended_response => true)
605
+ _response[:storage_file_get_or_create_response] = storage_file_get_or_create_response
606
+ file = storage_file_get_or_create_response[:file]
607
+ file_found = storage_file_get_or_create_response[:file_already_existed]
608
+ end
609
+
610
+ if file
611
+ _response[:item] = item = file['item']
612
+ file_found = true
613
+ end
614
+
615
+ _response[:file_already_existed] = file_found
616
+ _response[:item_already_existed] = !!item
617
+ return _response if item
618
+
619
+ file_id = file['id']
620
+
621
+ unless item
622
+ # 4.2 Create a Placeholder
623
+ logger.debug { 'Creating Placeholder.' }
624
+ #placeholder_args = args[:placeholder_args] ||= { :container => 1, :video => 1, :metadata_document => { :group => [ 'Film' ], :timespan => [ { :field => [ { :name => metadata_file_path_field_id, :value => [ { :value => vidispine_file_path } ] } ], :start => '-INF', :end => '+INF' } ] } }
625
+ #placeholder_args = args[:placeholder_args] ||= { :container => 1, :video => 1, :metadata_document => { :timespan => [ { :start => '-INF', :end => '+INF' }.merge(_metadata_as_fields) ] } }
626
+ #placeholder_args = args[:placeholder_args] ||= { :container => 1, :metadata_document => { :timespan => [ { :start => '-INF', :end => '+INF' }.merge(_metadata_as_fields) ] } }
627
+ placeholder_args = args[:placeholder_args] ||= { :container => 1, :metadata_document => metadata_document }
628
+ _response[:item] = item = import_placeholder(placeholder_args)
629
+ end
630
+ item_id = item['id']
631
+ shape = item['shape']
632
+
633
+ raise "Error Creating Placeholder: #{item.inspect}" unless item_id
634
+
635
+ # Add any additional metadata (Vidispine will only take one group at a time)
636
+ metadata_documents.each do |metadata_document|
637
+ item_metadata_set(:item_id => item_id, :metadata_document => metadata_document)
638
+ end
639
+
640
+ if options[:add_item_to_collection]
641
+ logger.debug { 'Determining Collection to Add the Item to.' }
642
+ collection = determine_collection(args, options)
643
+ _response[:collection] = collection
644
+ collection_id = collection['id']
645
+
646
+ logger.debug { 'Adding Item to Collection.' }
647
+ collection_object_add_response = collection_object_add(:collection_id => collection_id, :object_id => item_id)
648
+ _response[:collection_object_add] = collection_object_add_response
649
+ end
650
+
651
+ unless shape
652
+ # 6. Add the file as the original shape
653
+ logger.debug { 'Adding the file as the Original Shape.' }
654
+ item_shape_import_response = item_shape_import(:item_id => item_id, :file_id => file_id, :tag => 'original')
655
+ _response[:item_shape_import] = item_shape_import_response
656
+
657
+ job_id = item_shape_import_response['jobId']
658
+ unless job_id
659
+ invalid_input = item_shape_import_response['invalidInput']
660
+ if invalid_input
661
+ explanation = invalid_input['explanation']
662
+ job_id = $1 if explanation.match(/.*\[(.*)\]$/)
663
+ end
664
+ end
665
+ raise "Error Creating Item Shape Import Job. Response: #{item_shape_import_response.inspect}" unless job_id
666
+
667
+ job_monitor_response = wait_for_job_completion(:job_id => job_id) { |env|
668
+ logger.debug { "Waiting for Item Shape Import Job to Complete. Time Elapsed: #{Time.now - env[:time_started]} seconds" }
669
+ }
670
+ last_response = job_monitor_response[:last_response]
671
+ raise "Error Adding file As Original Shape. Response: #{last_response.inspect}" unless last_response['status'] == 'FINISHED'
672
+
673
+ # 7. Generate the Transcode of the item
674
+ transcode_tag = args.fetch(:transcode_tag, 'lowres')
675
+ if transcode_tag and !transcode_tag.empty? and transcode_tag.to_s.downcase != 'false'
676
+ wait_for_transcode_job = options[:wait_for_transcode_job]
677
+ skip_transcode_if_shape_with_tag_exists = options.fetch(:skip_transcode_if_shape_with_tag_exists, true)
678
+ [*transcode_tag].each do |_transcode_tag|
679
+ transcode_response = item_transcode_shape({
680
+ :item_id => item_id,
681
+ :transcode_tag => _transcode_tag
682
+ },
683
+ {
684
+ :wait_for_transcode_job => wait_for_transcode_job,
685
+ :skip_if_shape_with_tag_exists => skip_transcode_if_shape_with_tag_exists
686
+ })
687
+ (_response[:transcode] ||= { })[transcode_tag] = transcode_response
688
+
689
+ # each transcode_tag
690
+ end
691
+
692
+ # if transcode_tag
693
+ end
694
+
695
+ # 8. Generate the Thumbnails and Poster Frame
696
+ create_thumbnails = args.fetch(:create_thumbnails, true)
697
+ create_posters = args.fetch(:create_posters, 3)
698
+ if (create_thumbnails or create_posters)
699
+ logger.debug { 'Generating Thumbnails(s) and Poster Frame.' }
700
+ args_out = { :item_id => item_id }
701
+ args_out[:create_thumbnails] = create_thumbnails if create_thumbnails
702
+ args_out[:create_posters] = create_posters if create_posters
703
+ item_thumbnail_response = item_thumbnail(args_out)
704
+ _response[:item_thumbnail] = item_thumbnail_response
705
+ end
706
+ end
707
+
708
+ _response
709
+ end
710
+
711
+ # Add an item to the system using file path metadata field as the key
712
+ # 1. Search for pre existing asset
713
+ # 2. Create a placeholder with metadata (if asset doesn't exist)
714
+ # 3. Create an original shape.
715
+ # 4. Poll the Job status of the shape creation
716
+ # 5. Trigger the Transcode of the Proxy, thumbnails
717
+ # 6. Trigger the Transcode of the thumbnail.
718
+ # 7. Trigger the Transcode of the poster frame
719
+ # This was an early experiment
720
+ def item_add_using_file_path_metadata(args = { }, options = { })
721
+ args = symbolize_keys(args, false)
722
+ _response = { }
723
+
724
+ # 1. Receive a File Path
725
+ file_path = args[:file_path]
726
+ raise ArgumentError, ':file_path is a required argument.' unless file_path
727
+
728
+ metadata_file_path_field_id = args[:metadata_file_path_field_id]
729
+ raise ArgumentError, ':metadata_file_path_field_id is a required argument.' unless metadata_file_path_field_id
730
+
731
+ # 2. Determine Storage ID
732
+ storage_path_map = args[:storage_path_map]
733
+ raise ArgumentError, ':storage_path_map is a required argument.' unless storage_path_map
734
+
735
+ # Make sure the keys are strings
736
+ storage_path_map = Hash[storage_path_map.map { |k,v| [k.to_s, v] }] if storage_path_map.is_a?(Hash)
737
+
738
+ volume_path, storage = storage_path_map.find { |path, _| file_path.start_with?(path) }
739
+ raise "Unable to find match in storage path map for '#{file_path}'. Storage Map: #{storage_path_map.inspect}" unless volume_path
740
+
741
+ file_path_relative_to_storage_path = file_path.sub(volume_path, '')
742
+ logger.debug { "File Path Relative to Storage Path: #{file_path_relative_to_storage_path}" }
743
+
744
+ storage = storage_get(:id => storage) if storage.is_a?(String)
745
+ _response[:storage] = storage
746
+ raise "Error Retrieving Storage Record. Storage Id: #{storage.inspect}" unless storage
747
+
748
+ storage_id = storage['id']
749
+ storage_uri_raw = storage['method'].first['uri']
750
+ storage_uri = URI.parse(storage_uri_raw)
751
+
752
+ vidispine_file_path = File.join(storage_uri.path, file_path_relative_to_storage_path)
753
+ logger.debug { "Vidispine File Path: '#{vidispine_file_path}'" }
754
+ _response[:vidispine_file_path] = vidispine_file_path
755
+
756
+ _metadata = args[:metadata] || { }
757
+ _metadata[metadata_file_path_field_id] ||= vidispine_file_path
758
+
759
+ _metadata_map = args[:metadata_map] || { }
760
+
761
+ # map metadata assuming 1 value per field
762
+ _metadata_as_fields = transform_metadata_to_fields(_metadata, _metadata_map, options)
763
+
764
+ # 4.1 Search for Item using File Path
765
+ search_response = search(:content => 'metadata', :item_search_document => { :field => [ { :name => metadata_file_path_field_id, :value => [ { :value => vidispine_file_path } ] } ] } ) || { 'entry' => [ ] }
766
+ _response[:search] = search_response
767
+
768
+ item = (search_response['entry'].first || { })['item']
769
+ unless item
770
+ # If the item wasn't found then get the file id for the file
771
+ # 4.1 Search for the storage file record
772
+ storage_file_get_response = storage_files_get(:storage_id => storage_id, :path => file_path_relative_to_storage_path) || { 'file' => [ ] }
773
+ raise "Error Getting Storage File. '#{response.inspect}'" unless storage_file_get_response and storage_file_get_response['id']
774
+ file = storage_file_get_response['file'].first
775
+ _response[:storage_file_get_response] = storage_file_get_response
776
+
777
+ unless file
778
+ # 4.1.1 Create the storage file record if it does not exist
779
+ file = storage_file_create_response = storage_file_create(:storage_id => storage_id, :path => file_path_relative_to_storage_path, :state => 'CLOSED')
780
+ raise "Error Creating File on Storage. Response: #{response}" unless file
781
+ _response[:storage_file_create_response] = storage_file_create_response
782
+ end
783
+
784
+ # 4.2 Create a Placeholder
785
+ logger.debug { 'Creating Placeholder.' }
786
+ #placeholder_args = args[:placeholder_args] ||= { :container => 1, :video => 1, :metadata_document => { :group => [ 'Film' ], :timespan => [ { :field => [ { :name => metadata_file_path_field_id, :value => [ { :value => vidispine_file_path } ] } ], :start => '-INF', :end => '+INF' } ] } }
787
+ placeholder_args = args[:placeholder_args] ||= { :container => 1, :video => 1, :metadata_document => { :timespan => [ { :start => '-INF', :end => '+INF' }.merge(_metadata_as_fields) ] } }
788
+ item = placeholder = import_placeholder(placeholder_args)
789
+ _response[:placeholder] = placeholder
790
+ end
791
+ _response[:item] = item
792
+ item_id = item['id']
793
+
794
+ if options[:add_item_to_collection]
795
+ logger.debug { 'Determining Collection to Add the Item to.' }
796
+ collection = determine_collection(args, options)
797
+ _response[:collection] = collection
798
+ collection_id = collection['id']
799
+
800
+ logger.debug { 'Adding Item to Collection.' }
801
+ collection_object_add_response = collection_object_add(:collection_id => collection_id, :object_id => item_id)
802
+ _response[:collection_object_add] = collection_object_add_response
803
+ end
804
+
805
+ # Item was already in the system so exit here
806
+ return _response unless file
807
+
808
+ file_id = file['id']
809
+ raise "File Id Not Found. #{file.inspect}" unless file_id
810
+
811
+ # 6. Add the file as the original shape
812
+ logger.debug { 'Adding the file as the Original Shape.' }
813
+ item_shape_import_response = item_shape_import(:item_id => item_id, :file_id => file_id, :tag => 'original')
814
+ _response[:item_shape_import] = item_shape_import_response
815
+
816
+ job_id = item_shape_import_response['jobId']
817
+ job_monitor_response = wait_for_job_completion(:job_id => job_id) { |env|
818
+ logger.debug { "Waiting for Item Shape Import Job to Complete. Time Elapsed: #{Time.now - env[:time_started]} seconds" }
819
+ }
820
+ last_response = job_monitor_response[:last_response]
821
+ raise "Error Adding file As Original Shape. Response: #{last_response.inspect}" unless last_response['status'] == 'FINISHED'
822
+
823
+ # 7. Generate the Transcode of the item
824
+ transcode_tag = args[:transcode_tag] || 'lowres'
825
+ logger.debug { 'Generating Transcode of the Item.' }
826
+ item_transcode_response = item_transcode(:item_id => item_id, :tag => transcode_tag)
827
+ _response[:item_transcode] = item_transcode_response
828
+
829
+ # 8. Generate the Thumbnails and Poster Frame
830
+ create_thumbnails = args.fetch(:create_thumbnails, true)
831
+ create_posters = args[:create_posters] || 3
832
+ logger.debug { 'Generating Thumbnails(s) and Poster Frame.' }
833
+ item_thumbnail_response = item_thumbnail(:item_id => item_id, :createThumbnails => create_thumbnails, :createPosters => create_posters)
834
+ _response[:item_thumbnail] = item_thumbnail_response
835
+
836
+ _response
837
+ end
838
+
839
+ def item_export_extended(args = { }, options = { })
840
+ logger.debug { "#{__method__} Args: #{args.inspect} Opts: #{options.inspect}" }
841
+ _options = options.dup
842
+ wait_for_job_completion = _options.delete(:wait_for_job_completion) { }
843
+ item_export_response = item_export(args, _options)
844
+
845
+ if wait_for_job_completion
846
+ job_id = item_export_response['jobId']
847
+ job_monitor_callback = options[:job_monitor_callback_function]
848
+ job_monitor_response = wait_for_job_completion(:job_id => job_id) do |env|
849
+ logger.debug { "Waiting for '#{args.inspect}' Export Job to Complete. Time Elapsed: #{Time.now - env[:time_started]} seconds" }
850
+ job_monitor_callback.call(env) if job_monitor_callback
851
+ end
852
+
853
+ last_response = job_monitor_response[:last_response]
854
+ # if last_response['status'] == 'FINISHED'
855
+ # data = last_response['data']
856
+ # data = Hash[ data.map { |d| [ d['key'], d['value'] ] } ]
857
+ # end
858
+
859
+ # if wait_for_transcode_job
860
+ end
861
+
862
+ last_response || item_export_response
863
+ end
864
+
865
+
866
+ def item_shape_add_using_file_path(args = { }, options = { })
867
+ logger.debug { "#{__method__}:#{args.inspect}" }
868
+ _response = { }
869
+
870
+ storage_path_map = args[:storage_path_map]
871
+
872
+ item = args[:item] || { }
873
+ item_id = args[:item_id] || item['id']
874
+
875
+ tag = args[:tag]
876
+
877
+ file = args[:file] || { }
878
+ file_id = args[:file_id] || file['id']
879
+
880
+ unless file_id
881
+
882
+ storage = args[:storage] || { }
883
+ storage_id = args[:storage_id] || storage['id']
884
+
885
+ file_path = args[:file_path]
886
+ file_path_relative_to_storage_path = args[:relative_file_path]
887
+
888
+ unless file_path_relative_to_storage_path
889
+ if storage_id
890
+ # Process file path using storage information
891
+ else
892
+ process_file_path_response = process_file_path_using_storage_map(file_path, storage_path_map)
893
+ end
894
+
895
+ file_path_relative_to_storage_path = process_file_path_response[:relative_file_path]
896
+ storage_id ||= process_file_path_response[:storage_id]
897
+ end
898
+
899
+ file = storage_file_get_or_create(storage_id, file_path_relative_to_storage_path)
900
+
901
+ file_id = file['id']
902
+ raise "File Error: #{file} Response: #{_response}" unless file_id
903
+ end
904
+
905
+ item_shape_import_args = { :item_id => item_id, :file_id => file_id, :tag => tag }
906
+ item_shape_import(item_shape_import_args)
907
+ end
908
+ alias :item_add_shape_using_file_path :item_shape_add_using_file_path
909
+
910
+ #
911
+ # @param [Hash] args
912
+ # @param [Hash] options
913
+ def item_shapes_get_extended(args = { }, options = { })
914
+ item_id = args[:item_id]
915
+ tag = args[:tag]
916
+ return_as_hash = options.fetch(:return_as_hash, false)
917
+
918
+ if return_as_hash
919
+ key_by_field = options[:hash_key] || 'id'
920
+ end
921
+
922
+ shapes_response = item_shapes_get(:item_id => item_id, :tag => tag)
923
+
924
+ shape_ids = shapes_response['uri'] || [ ]
925
+ shapes = [ ]
926
+ shape_ids.each do |shape_id|
927
+ shape = item_shape_get(:item_id => item_id, :shape_id => shape_id)
928
+ shape.dup.each do |k, v|
929
+ shape[k] = v.first if v.is_a?(Array) and v.length == 1 and !['metadata'].include?(k)
930
+ end
931
+ shapes << shape
932
+ end
933
+
934
+ shapes_formatted = return_as_hash ? Hash[ shapes.map { |v| [ v[key_by_field], v ] } ] : shapes
935
+
936
+ shapes_response['shapes'] = shapes_formatted
937
+ shapes_response
938
+ end
939
+
940
+ # @param [Hash] args
941
+ # @param [Hash] options
942
+ # @option options [Boolean] :skip_if_shape_with_tag_exists
943
+ def item_transcode_shape(args = { }, options = { })
944
+ _response = { }
945
+ item_id = args[:item_id]
946
+ transcode_tag = args[:tag] || args[:transcode_tag] || 'lowres'
947
+ skip_if_tag_exists = options.fetch(:skip_if_shape_with_tag_exists, false)
948
+
949
+ if skip_if_tag_exists
950
+ item_shapes_response = item_shapes_get(:item_id => item_id, :tag => transcode_tag)
951
+ shape_ids = item_shapes_response['uri'] || [ ]
952
+ proxy_shape_id = shape_ids.last
953
+ _response[:tag_existed_on_shape] = !!proxy_shape_id
954
+ end
955
+
956
+ unless proxy_shape_id
957
+ logger.debug { "Generating Transcode of the Item. Tag: '#{transcode_tag}'" }
958
+ item_transcode_response = item_transcode(:item_id => item_id, :tag => transcode_tag)
959
+ _response[:item_transcode] = item_transcode_response
960
+
961
+ job_id = item_transcode_response['jobId']
962
+ _response[:job_id] = job_id
963
+ if options[:wait_for_transcode_job]
964
+ job_monitor_callback = options[:job_monitor_callback_function]
965
+ job_monitor_response = wait_for_job_completion(:job_id => job_id) do |env|
966
+ logger.debug { "Waiting for '#{transcode_tag}' Transcode Job to Complete. Time Elapsed: #{Time.now - env[:time_started]} seconds" }
967
+ job_monitor_callback.call(env) if job_monitor_callback
968
+ end
969
+
970
+ last_response = job_monitor_response[:last_response]
971
+ if last_response['status'] == 'FINISHED'
972
+ data = last_response['data']
973
+ data = Hash[ data.map { |d| [ d['key'], d['value'] ] } ]
974
+
975
+ proxy_shape_ids = data['shapeIds']
976
+ proxy_shape_id = proxy_shape_ids
977
+ end
978
+
979
+ # if wait_for_transcode_job
980
+ end
981
+
982
+ end
983
+
984
+ if proxy_shape_id
985
+ item_shape_files = item_shape_files_get(:item_id => item_id, :shape_id => proxy_shape_id)
986
+ proxy_file = (((item_shape_files || { })['file'] || [ ]).first || { })
987
+ proxy_file_uri = (proxy_file['uri'] || [ ]).first
988
+ _response[:file] = proxy_file
989
+ _response[:shape_id] = proxy_shape_id
990
+ _response[:file_uri] = proxy_file_uri
991
+ _response[:file_path] = URI.decode(URI(proxy_file_uri).path)
992
+ end
993
+
994
+ _response
995
+ end
996
+
997
+ # @param [Hash] args
998
+ # @option args [String] :file_path (Required)
999
+ # @option args [String] :metadata_file_path_field_id (Required)
1000
+ # @option args [Hash] :storage_path_map (Required)
1001
+ # @option args [String] :collection_id Required if :collection_name or :file_path_collection_name_position is not set
1002
+ # @option args [String] :collection_name Required if :collection_id or :file_path_collection_name_position is not set
1003
+ # @option args [Integer] :file_path_collection_name_position Required if :collection_id or :collection_name is not set
1004
+ # @option args [Hash] :placeholder_args ({ :container => 1, :video => 1 })
1005
+ def collection_file_add_using_path(args = { }, options = { })
1006
+ args = symbolize_keys(args, false)
1007
+ _response = { }
1008
+
1009
+ # 1. Receive a File Path
1010
+ file_path = args[:file_path]
1011
+ raise ArgumentError, ':file_path is a required argument.' unless file_path
1012
+
1013
+ metadata_file_path_field_id = args[:metadata_file_path_field_id]
1014
+ raise ArgumentError, ':metadata_file_path_field_id is a required argument.' unless metadata_file_path_field_id
1015
+
1016
+ # 2. Determine Storage ID
1017
+ storage_path_map = args[:storage_path_map] || args[:storage_map]
1018
+ raise ArgumentError, ':storage_path_map is a required argument.' unless storage_path_map
1019
+
1020
+ # Make sure the keys are strings
1021
+ storage_path_map = Hash[storage_path_map.map { |k,v| [k.to_s, v] }] if storage_path_map.is_a?(Hash)
1022
+
1023
+ volume_path, storage = storage_path_map.find { |path, _| file_path.start_with?(path) }
1024
+ raise "Unable to find match in storage path map for '#{file_path}'. Storage Map: #{storage_path_map.inspect}" unless volume_path
1025
+
1026
+ file_path_relative_to_storage_path = file_path.sub(volume_path, '')
1027
+ logger.debug { "File Path Relative to Storage Path: #{file_path_relative_to_storage_path}" }
1028
+
1029
+ storage = storage_get(:id => storage) if storage.is_a?(String)
1030
+ _response[:storage] = storage
1031
+ raise 'Error Retrieving Storage Record. Storage Id: #{' unless storage
1032
+
1033
+ storage_id = storage['id']
1034
+ storage_uri_raw = storage['method'].first['uri']
1035
+ storage_uri = URI.parse(storage_uri_raw)
1036
+
1037
+ vidispine_file_path = File.join(storage_uri.path, file_path_relative_to_storage_path)
1038
+ logger.debug { "Vidispine File Path: '#{vidispine_file_path}'" }
1039
+ _response[:vidispine_file_path] = vidispine_file_path
1040
+
1041
+ # 3 Get Collection
1042
+ collection_id = args[:collection_id]
1043
+ unless collection_id
1044
+ collection_name = args[:collection_name]
1045
+ unless collection_name
1046
+ file_path_collection_name_position = args[:file_path_collection_name_position]
1047
+ raise ArgumentError, ':collection_id, :collection_name, or :file_path_collection_name_position argument is required.' unless file_path_collection_name_position
1048
+
1049
+ file_path_split = (file_path_relative_to_storage_path.start_with?('/') ? file_path_relative_to_storage_path[1..-1] : file_path_relative_to_storage_path).split('/')
1050
+ collection_name = file_path_split[file_path_collection_name_position]
1051
+ raise ArgumentError, 'Unable to determine collection name from path.' unless collection_name
1052
+ logger.debug { "Using '#{collection_name}' as collection_name. File Path Array: #{file_path_split.inspect}" }
1053
+ end
1054
+ # Determine Collection
1055
+ collection = collection_create_if_not_exists(:collection_name => collection_name)
1056
+ collection_id = collection['id']
1057
+ else
1058
+ collection = collection_get(:collection_id => collection_id)
1059
+ raise ArgumentError, 'Collection not found.' unless collection
1060
+ end
1061
+ _response[:collection] = collection
1062
+ #return
1063
+
1064
+ # 4.1 Search for Item using File Path
1065
+ search_response = search(:content => 'metadata', :item_search_document => { :field => [ { :name => metadata_file_path_field_id, :value => [ { :value => vidispine_file_path } ] } ] } ) || { 'entry' => [ ] }
1066
+ _response[:search] = search_response
1067
+
1068
+ item = (search_response['entry'].first || { })['item']
1069
+ unless item
1070
+ storage_file_get_or_create_response = storage_file_get_or_create(storage_id, file_path_relative_to_storage_path, :extended_response => true)
1071
+ _response[:storage_file_get_or_create_response] = storage_file_get_or_create_response
1072
+ file = storage_file_get_or_create_response[:file]
1073
+
1074
+ item = placeholder = file['item']
1075
+
1076
+ unless item
1077
+ # 4.2 Create a Placeholder
1078
+ logger.debug { 'Creating Placeholder.' }
1079
+ #placeholder_args = args[:placeholder_args] ||= { :container => 1, :video => 1, :metadata_document => { :group => [ 'Film' ], :timespan => [ { :field => [ { :name => metadata_file_path_field_id, :value => [ { :value => vidispine_file_path } ] } ], :start => '-INF', :end => '+INF' } ] } }
1080
+ placeholder_args = args[:placeholder_args] ||= { :container => 1, :video => 1, :metadata_document => { :timespan => [ { :field => [ { :name => metadata_file_path_field_id, :value => [ { :value => vidispine_file_path } ] } ], :start => '-INF', :end => '+INF' } ] } }
1081
+ item = placeholder = import_placeholder(placeholder_args)
1082
+ end
1083
+ _response[:placeholder] = placeholder
1084
+ end
1085
+
1086
+ _response[:item] = item
1087
+ item_id = item['id']
1088
+
1089
+ # 5. Add Item to the Collection
1090
+ logger.debug { 'Adding Item to Collection.' }
1091
+ collection_object_add_response = collection_object_add(:collection_id => collection_id, :object_id => item_id)
1092
+ _response[:collection_object_add] = collection_object_add_response
1093
+
1094
+ # Item was already in the system so exit here
1095
+ return _response unless file
1096
+
1097
+ file_id = file['id']
1098
+ raise "File Id Not Found. #{file.inspect}" unless file_id
1099
+
1100
+ # 6. Add the file as the original shape
1101
+ logger.debug { 'Adding the file as the Original Shape.' }
1102
+ item_shape_import_response = item_shape_import(:item_id => item_id, :file_id => file_id, :tag => 'original')
1103
+ _response[:item_shape_import] = item_shape_import_response
1104
+
1105
+ job_id = item_shape_import_response['jobId']
1106
+ if job_id
1107
+ job_monitor_response = wait_for_job_completion(:job_id => job_id) { |env|
1108
+ logger.debug { "Waiting for Item Shape Import Job to Complete. Time Elapsed: #{Time.now - env[:time_started]} seconds" }
1109
+ }
1110
+ last_response = job_monitor_response[:last_response]
1111
+ raise "Error Adding file As Original Shape. Response: #{last_response.inspect}" unless last_response['status'] == 'FINISHED'
1112
+ _response[:item_shape_import_job] = job_monitor_response
1113
+ end
1114
+
1115
+ # 7. Generate the Transcode of the item
1116
+ transcode_tag = args[:transcode_tag] || 'lowres'
1117
+ logger.debug { 'Generating Transcode of the Item.' }
1118
+ item_transcode_response = item_transcode(:item_id => item_id, :tag => transcode_tag)
1119
+ _response[:item_transcode] = item_transcode_response
1120
+
1121
+ # 8. Generate the Thumbnails and Poster Frame
1122
+ create_thumbnails = args.fetch(:create_thumbnails, true)
1123
+ create_posters = args[:create_posters] || 3
1124
+ logger.debug { 'Generating Thumbnails(s) and Poster Frame.' }
1125
+ item_thumbnail_response = item_thumbnail(:item_id => item_id, :createThumbnails => create_thumbnails, :createPosters => create_posters)
1126
+ _response[:item_thumbnail] = item_thumbnail_response
1127
+
1128
+ _response
1129
+ end
1130
+
1131
+ # SEQUENCE THAT CREATES AN ITEM AND THE PROXY USING THE FILE ID
1132
+ # @deprecated
1133
+ #
1134
+ # @param [Hash] args
1135
+ # @option args [String] :original_file_path
1136
+ # @option args [String] :lowres_file_path
1137
+ # @option args [String] :storage_id
1138
+ # @option args [Hash] :placeholder_args ({ :container => 1, :video => 1 })
1139
+ # @option args [Boolean] :create_posters (False)
1140
+ # @option args [Boolean] :create_thumbnails (True)
1141
+ #
1142
+ # @return [Hash] :item_id, :original_file_id, :lowres_file_id
1143
+ def item_create_with_proxy_using_storage_file_paths(args = { }, options = { })
1144
+
1145
+ original_file_path = args[:original_file_path] || args[:original]
1146
+ lowres_file_path = args[:lowres_file_path] || args[:lowres]
1147
+
1148
+ placeholder_args = args[:placeholder_args] ||= { :container => 1, :video => 1 }
1149
+
1150
+ storage_id = args[:storage_id]
1151
+
1152
+ create_posters = args[:create_posters] #|| '300@NTSC'
1153
+ create_thumbnails = args.fetch(:create_thumbnails, true)
1154
+
1155
+ # Create a placeholder
1156
+ # /API/import/placeholder/?container=1&video=1
1157
+ logger.debug { "Creating Placeholder: #{placeholder_args.inspect}" }
1158
+ place_holder = import_placeholder(placeholder_args)
1159
+ item_id = place_holder['id']
1160
+
1161
+ raise 'Placeholder Create Failed.' unless success?
1162
+
1163
+ # /API/storage/VX-2/file/?path=storages/test/test_orginal2.mp4
1164
+ _original_file = original_file = storage_file_get(:storage_id => storage_id, :path => original_file_path)
1165
+ raise "Unexpected Response Format. Expecting Hash instead of #{original_file.class.name} #{original_file}" unless _original_file.is_a?(Hash)
1166
+
1167
+ original_file = original_file['file']
1168
+ begin
1169
+ original_file = original_file.first
1170
+ rescue => e
1171
+ raise "Error Getting File from Response. #{$!}\n#{original_file.inspect}\n#{_original_file.inspect}"
1172
+ end
1173
+ raise "File Not Found. '#{original_file_path}' in storage '#{storage_id}'" unless original_file
1174
+ original_file_id = original_file['id']
1175
+
1176
+ # /API/item/VX-98/shape?tag=original&fileId=[FileIDofOriginal]
1177
+ item_shape_import(:item_id => item_id, :tag => 'original', :file_id => original_file_id)
1178
+
1179
+ # /API/storage/VX-2/file/?path=storages/test/test_proxy2.mp4
1180
+ lowres_file = storage_file_get(:storage_id => storage_id, :path => lowres_file_path)
1181
+ lowres_file_id = lowres_file['file'].first['id']
1182
+
1183
+ # /API/item/VX-98/shape?tag=lowres&fileId=[FileIDofProxy]
1184
+ item_shape_import(:item_id => item_id, :tag => 'lowres', :file_id => lowres_file_id)
1185
+
1186
+ # /API/item/VX-98/thumbnail/?createThumbnails=true&createPoster
1187
+ item_thumbnail_args = { :item_id => item_id }
1188
+ item_thumbnail_args[:createThumbnails] = create_thumbnails
1189
+ item_thumbnail_args[:createPosters] = create_posters if create_posters
1190
+ item_thumbnail(item_thumbnail_args)
1191
+
1192
+ { :item_id => item_id, :original_file_id => original_file_id, :lowres_file_id => lowres_file_id }
1193
+ end
1194
+
1195
+ # SEQUENCE THAT CREATE AN ITEM AND THE PROXY USING THE FILE URI/PATH
1196
+ # @deprecated
1197
+ def item_create_with_proxy_using_file_uri(args = { }, options = { })
1198
+ original_file_uri = args[:original_file_uri] || args[:original]
1199
+ file_type = args[:file_type] || 'video'
1200
+
1201
+ import_tag = args[:import_tag] || 'lowres'
1202
+
1203
+ placeholder_args = args[:placeholder_args] ||= { :container => 1, :video => 1 }
1204
+
1205
+ storage_id = args[:storage_id]
1206
+
1207
+ # /API/import/placeholder/?container=1&video=1
1208
+ placeholder = import_placeholder(placeholder_args)
1209
+ placeholder_item_id = placeholder['id']
1210
+
1211
+ # /API/import/placeholder/VX-99/video/?uri=file%3A%2F%2F%2Fsrv%2Fmedia1%2Ftest_orginal2.mp4&tag=lowres
1212
+ item = import_placeholder_item(:item_id => placeholder_item_id, :type => file_type, :uri => original_file_uri, :tag => import_tag)
1213
+ item_id = item['file'].first['id']
1214
+
1215
+ # /API/item/VX-99/shape?tag=lowres&fileId=VX-136
1216
+ #item_shape_import(:item_id => item_id, :tag => nil, :file_id => nil)
1217
+
1218
+ # /API/storage/VX-2/file/?path=storages/test/test_orginal2.mp4
1219
+ #storage_file_get(:storage_id => storage_id)
1220
+ end
1221
+
1222
+ # @note THIS IS A CANTEMO SPECIFIC CALL
1223
+ def item_annotation_create(args = { }, options = { })
1224
+ # _args = args
1225
+ # item_id = _args[:item_id]
1226
+ #
1227
+ # in_point = _args[:in_point]
1228
+ # out_point = _args[:out_point]
1229
+ # title = _args[:title]
1230
+ #
1231
+ # body = { }
1232
+ # body[:title] = title if title
1233
+ # body[:inpoint] = in_point if in_point
1234
+ # body[:outpoint] = out_point if out_point
1235
+ #
1236
+ # http(:post, "v1/item/#{item_id}/annotation", :body => body)
1237
+
1238
+ _request = Requests::BaseRequest.new(
1239
+ args,
1240
+ {
1241
+ :http_path => 'v1/item/#{arguments[:item_id]}/annotation',
1242
+ :http_method => :post,
1243
+ :default_parameter_send_in_value => :body,
1244
+ :parameters => [
1245
+ { :name => :item_id, :aliases => [ :id ], :required => true, :send_in => :path },
1246
+
1247
+ :inpoint,
1248
+ :outpoint,
1249
+ { :name => :title, :default => '' },
1250
+ ]
1251
+ }.merge(options)
1252
+ )
1253
+ process_request(_request)
1254
+ end
1255
+
1256
+ # @note THIS IS A CANTEMO SPECIFIC CALL
1257
+ def item_annotation_get(args = { }, options = { })
1258
+ args = { :item_id => args } if args.is_a?(String)
1259
+ # item_id = args[:item_id]
1260
+ # http(:get, "v1/item/#{item_id}/annotation")
1261
+
1262
+ _request = Requests::BaseRequest.new(
1263
+ args,
1264
+ {
1265
+ :http_path => 'v1/item/#{arguments[:item_id]}/annotation',
1266
+ :parameters => [
1267
+ { :name => :item_id, :aliases => [ :id ], :required => true, :send_in => :path }
1268
+ ]
1269
+ }.merge(options)
1270
+ )
1271
+ process_request(_request)
1272
+ end
1273
+
1274
+ def items_search_extended(args = { }, options = { })
1275
+ _args = symbolize_keys(args, false)
1276
+ _data = Requests::BaseRequest.process_parameters([ { :name => :fields }, { :name => :item_search_document } ], _args)
1277
+ _args = _args.merge(_data[:arguments_out])
1278
+
1279
+ fields = _args.delete(:fields)
1280
+ _args[:item_search_document] ||= build_item_search_document(:fields => fields)
1281
+
1282
+ items_search(_args, options)
1283
+ end
1284
+ alias :item_search_extended :items_search_extended
1285
+
1286
+ # Will process a file path through a storage path map
1287
+ # @param [String] file_path
1288
+ # @param [Hash] storage_path_map (#storage_file_path_map_create)
1289
+ # @return [Hash] :relative_file_path, :storage_id, :storage_path_map, :volume_path
1290
+ def process_file_path_using_storage_map(file_path, storage_path_map = nil)
1291
+ logger.debug { "Method: #{__method__} Args: #{{:file_path => file_path, :storage_path_map => storage_path_map}.inspect}"}
1292
+
1293
+ storage_path_map = storage_file_path_map_create unless storage_path_map and !storage_path_map.empty?
1294
+
1295
+ volume_path, storage_id = storage_path_map.find { |path, _| file_path.start_with?(path) }
1296
+ file_path_relative_to_storage_path = file_path.sub(volume_path, '')
1297
+
1298
+ _response = { :relative_file_path => file_path_relative_to_storage_path,
1299
+ :storage_id => storage_id,
1300
+ :storage_path_map => storage_path_map,
1301
+ :volume_path => volume_path }
1302
+
1303
+ logger.debug { "Method: #{__method__} Response: #{_response.inspect}" }
1304
+ _response
1305
+ end
1306
+
1307
+
1308
+ def storage_file_copy_extended(args = { }, options = { })
1309
+ _args = symbolize_keys(args, false)
1310
+ _data = Requests::BaseRequest.process_parameters([ { :name => :use_source_filename }, { :name => :file_id }, { :name => :filename }, { :name => :source_storage_id } ], _args)
1311
+ _args = _args.merge(_data[:arguments_out])
1312
+
1313
+ # Get the source file name and set it as the destination file name
1314
+ if options[:use_source_filename]
1315
+ file_id = _args[:file_id]
1316
+ source_storage_id = _args[:source_storage_id]
1317
+ file = storage_file_get(:storage_id => source_storage_id, :file_id => file_id)
1318
+ args[:filename] = file[:filename]
1319
+ end
1320
+
1321
+ storage_file_copy(args, options)
1322
+ end
1323
+
1324
+ def storage_file_create_extended(args = { }, options = { })
1325
+ _args = symbolize_keys(args, false)
1326
+ _params = Requests::StorageFileCreate::PARAMETERS.concat [ { :name => :directory, :aliases => [ :dir ], :send_in => :none }, { :name => :storage_map, :send_in => :none } ]
1327
+ _data = Requests::BaseRequest.process_parameters(_params, _args)
1328
+ _args = _args.merge(_data[:arguments_out])
1329
+
1330
+ storage_path_map = _args.delete(:storage_map) { }
1331
+ if storage_path_map.empty?
1332
+ storage_path_map = storage_file_path_map_create
1333
+ else
1334
+ storage_path_map = Hash[storage_path_map.map { |k,v| [k.to_s, v] }] if storage_path_map.is_a?(Hash)
1335
+ end
1336
+
1337
+
1338
+ dir = _args.delete(:directory) { }
1339
+ if dir
1340
+ # raise ArgumentError, ':storage_map is a required argument.' unless storage_path_map
1341
+
1342
+ volume_path, storage = storage_path_map.find { |path, _| dir.start_with?(path) }
1343
+ raise "Unable to find match in storage path map for '#{dir}'. Storage Map: #{storage_path_map.inspect}" unless volume_path
1344
+
1345
+ dir_path_relative_to_storage = dir.sub(volume_path, '')
1346
+
1347
+ storage = storage_get(:id => storage) if storage.is_a?(String)
1348
+ raise 'Error Retrieving Storage Record' unless storage
1349
+
1350
+ storage_id = storage['id']
1351
+ _args[:storage_id] = storage_id
1352
+ storage_uri_raw = storage['method'].first['uri']
1353
+ storage_uri = URI.parse(storage_uri_raw)
1354
+
1355
+ vidispine_dir_path = File.join(storage_uri.path, dir_path_relative_to_storage)
1356
+ logger.debug { "Vidispine Dir Path: '#{vidispine_dir_path}'" }
1357
+
1358
+
1359
+ glob_path = dir.end_with?('*') ? vidispine_dir_path : File.join(vidispine_dir_path, '*')
1360
+ paths = Dir.glob(glob_path)
1361
+
1362
+
1363
+ return paths.map do |local_absolute_path|
1364
+ logger.debug { "Found Path: '#{local_absolute_path}'" }
1365
+ _path = local_absolute_path
1366
+ file_path_relative_to_storage_path = _path.sub(volume_path, '')
1367
+ logger.debug { "File Path Relative to Storage Path: #{file_path_relative_to_storage_path}" }
1368
+
1369
+ _args[:path] = file_path_relative_to_storage_path
1370
+ storage_file_create(_args, options)
1371
+ end
1372
+ end
1373
+
1374
+ storage_file_create(_args, options)
1375
+ end
1376
+
1377
+ # @param [Hash] args
1378
+ # @option args [String] :file_path
1379
+ # @option args [Hash] :storage_path_map
1380
+ # @option args [Boolean] :create_if_not_exists (True)
1381
+ # @option args [Boolean] :include_item (True)
1382
+ # @return [Hash|nil]
1383
+ def storage_file_get_using_file_path(args = { })
1384
+ _response = { }
1385
+ file_path = args[:file_path]
1386
+ return_extended_response = args.fetch(:extended_response, true)
1387
+ volume_path = args[:volume_path]
1388
+
1389
+ # The method type of the URI to lookup
1390
+ storage_method_type = (args[:storage_method_type] ||= 'file').to_s.downcase
1391
+ logger.debug { "Storage Method Type: '#{storage_method_type}'" }
1392
+
1393
+ storage_path_map = args[:storage_path_map]
1394
+ storage_path_map = storage_file_path_map_create(:storage_method => storage_method_type) unless storage_path_map && !storage_path_map.empty?
1395
+ logger.debug { "Storage Path Map: #{storage_path_map.inspect}" }
1396
+
1397
+ sm_volume_path, storage = storage_path_map.find { |path, _| file_path.start_with?(path) }
1398
+ raise "Unable to find match in storage path map for '#{file_path}'. Storage Map: #{storage_path_map.inspect}" unless sm_volume_path
1399
+
1400
+ storage = storage_get(:id => storage) if storage.is_a?(String)
1401
+ _response[:storage] = storage
1402
+ storage_id = storage['id']
1403
+ raise "Error Retrieving Storage Record. Storage: #{storage}" unless storage_id
1404
+ logger.debug { "Storage ID: #{storage_id}" }
1405
+
1406
+ ## Storage Determined Now Start Digging for the File
1407
+
1408
+ case storage_method_type
1409
+ when 'file', 'http', 's3'
1410
+ storage_uri_method = "#{storage_method_type}:"
1411
+ storage_uri_raw = (storage['method'].find do |v|
1412
+ ((v['lastSuccess'] || '') >= (v['lastFailure'] || '')) && (storage_method_type == 'any' || v['uri'].start_with?(storage_uri_method))
1413
+ end || { })['uri'] rescue nil
1414
+ raise "Error Getting URI from Storage Method. Storage: #{storage.inspect}" unless storage_uri_raw
1415
+
1416
+ if storage_method_type == 's3'
1417
+ # URI was returning URI::InvalidURIError: the scheme s3 does not accept registry part
1418
+ volume_path = storage_uri_raw.split('@').last.sub('s3://', '').split('?').first
1419
+ else
1420
+ storage_uri = URI.parse(storage_uri_raw)
1421
+ volume_path = storage_uri.path
1422
+ end
1423
+
1424
+ when 'vxa'
1425
+ vxa_local_path = storage['metadata']['field'].find { |m| m['key'] == 'vxaLocalPath' }['value']
1426
+ volume_path = vxa_local_path
1427
+
1428
+ else
1429
+ volume_path = sm_volume_path
1430
+
1431
+ end unless volume_path
1432
+ logger.debug { "Volume Path: '#{volume_path}'" }
1433
+
1434
+ file_path_relative_to_storage_path = file_path.sub(volume_path, '')
1435
+ logger.debug { "File Path Relative to Storage Path: '#{file_path_relative_to_storage_path}'" }
1436
+
1437
+ vidispine_file_path = File.join(volume_path, file_path_relative_to_storage_path)
1438
+ logger.debug { "Vidispine File Path: '#{vidispine_file_path}'" }
1439
+
1440
+ create_if_not_exists = args.fetch(:create_if_not_exists, true)
1441
+ include_item = args.fetch(:include_item, true)
1442
+ options_out = { :include_item => include_item }
1443
+ if create_if_not_exists
1444
+ storage_file_get_or_create_response = storage_file_get_or_create(storage_id, file_path_relative_to_storage_path, options_out.merge(:extended_response => true))
1445
+ _response[:storage_file_get_or_create_response] = storage_file_get_or_create_response
1446
+ file = storage_file_get_or_create_response[:file]
1447
+ else
1448
+ storage_file_get_response = storage_files_get({ :storage_id => storage_id, :path => file_path_relative_to_storage_path }.merge(options_out)) || { 'file' => [ ] }
1449
+ file = ((storage_file_get_response || { })['file'] || [ ]).first
1450
+ end
1451
+
1452
+ _response[:file] = file
1453
+
1454
+ return_extended_response ? _response : file
1455
+ end
1456
+
1457
+ # Will search for a relative file path on a storage and if not found will trigger a storage_file_create
1458
+ # @param [String] storage_id
1459
+ # @param [String] file_path_relative_to_storage_path
1460
+ # @return [Hash]
1461
+ def storage_file_get_or_create(storage_id, file_path_relative_to_storage_path, options = { })
1462
+ logger.debug { "Method: #{__method__} Args:#{{:storage_id => storage_id, :file_path_relative_to_storage_path => file_path_relative_to_storage_path, :options => options }.inspect}" }
1463
+ include_item = options.fetch(:include_item, true)
1464
+ creation_state = options[:creation_state] || 'CLOSED'
1465
+ storage_file_get_response = storage_files_get(:storage_id => storage_id, :path => file_path_relative_to_storage_path, :include_item => include_item) || { 'file' => [ ] }
1466
+ file = ((storage_file_get_response || { })['file'] || [ ]).first
1467
+ if file
1468
+ file_already_existed = true
1469
+ else
1470
+ file_already_existed = false
1471
+ # 4.1.1 Create the storage file record if it does not exist
1472
+ storage_file_create_response = file = storage_file_create(:storage_id => storage_id, :path => file_path_relative_to_storage_path, :state => creation_state)
1473
+ if (file || { })['fileAlreadyExists']
1474
+ file_already_existed = true
1475
+ _message = file['fileAlreadyExists']
1476
+ logger.warn { "Running Recreation of Existing File Work Around: #{_message}" }
1477
+ storage_file_get_response = file = storage_file_get(:storage_id => storage_id, :file_id => _message['fileId'], :include_item => include_item)
1478
+ end
1479
+ raise "Error Creating File on Storage. Response: #{response.inspect}" unless (file || { })['id']
1480
+ end
1481
+
1482
+ logger.debug { "Method: #{__method__} Response: #{file.inspect}" }
1483
+
1484
+ if options[:extended_response]
1485
+ return {
1486
+ :file => file,
1487
+ :file_already_existed => file_already_existed,
1488
+ :storage_file_get_response => storage_file_get_response,
1489
+ :storage_file_create_response => storage_file_create_response
1490
+ }
1491
+ end
1492
+ file
1493
+ end
1494
+
1495
+ # Generates a storage file path map from the current storages.
1496
+ # This is meant as a default, on most methods you can provide your own storage map that can be used to resolve
1497
+ # local paths to storage paths.
1498
+ #
1499
+ # @param [Hash] args
1500
+ # @option args [String] :storage_method ('file')
1501
+ # @option args [Hash] :storages_response (#storages_get)
1502
+ # @option args [Array] :storages
1503
+ #
1504
+ # @return [Hash]
1505
+ def storage_file_path_map_create(args = {})
1506
+ method = args[:storage_method] || 'file'
1507
+
1508
+ storages = args[:storages] || begin
1509
+ storages_response = args[:storages_response] || storages_get
1510
+ storages_response['storage']
1511
+ end
1512
+
1513
+ storage_file_path_map = { }
1514
+ storages.each do |storage|
1515
+ storage_methods = storage['method']
1516
+ file_storage_method = storage_methods.find { |m| m['uri'].start_with?("#{method}:") }
1517
+ next unless file_storage_method
1518
+ case method
1519
+ when 'vxa'
1520
+ md = Hash[ storage['metadata']['field'].map { |m| [ m['key'], m['value'] ] } ]
1521
+ address = md['vxaLocalPath']
1522
+ address.concat('/') if address && !(address.empty? || address.end_with?('/'))
1523
+ when 's3'
1524
+ # "s3://{access_key}:_VSENC__{encrypted_secret_access_key}@{bucket_name}/?region=us-east-1?sseAlgorithm=aws:kms?signer=AWSS3V4SignerType/"
1525
+ uri = file_storage_method['uri']
1526
+ user_info, bucket_info = uri.split('@')
1527
+ address, args = bucket_info.split('?').first
1528
+ address.chomp!('/') if address.end_with?('/')
1529
+ else
1530
+ uri = file_storage_method['uri']
1531
+ match = uri.match(/(.*):\/\/(.*)/)
1532
+ address = match[2]
1533
+ end
1534
+ storage_file_path_map[address] = storage['id']
1535
+ end
1536
+ storage_file_path_map
1537
+ end
1538
+
1539
+ # Waits for a job to complete
1540
+ #
1541
+ # @param [Hash] args
1542
+ # @option args [String] :job_id (Required)
1543
+ # @option args [Integer] :delay (15)
1544
+ # @option args [Integer] :timeout The timeout in seconds
1545
+ #
1546
+ # Accepts a block where the following is exposed:
1547
+ # :time_started [TIme]
1548
+ # :poll_interval [Integer]
1549
+ # :latest_response [Hash]
1550
+ # :job_status [String]
1551
+ # :continue_monitoring [Boolean] Can be set to false and job monitoring will exit
1552
+ # :delay [Integer]
1553
+ #
1554
+ # @return [Hash]
1555
+ # :last_response [Hash]
1556
+ # :time_started [Time]
1557
+ # :time_ended [Time] Includes the last poll interval
1558
+ # :timed_out [Boolean]
1559
+ def wait_for_job_completion(args = { })
1560
+ job_id = args[:job_id]
1561
+ raise ArgumentError, 'job_id is a required argument.' unless job_id
1562
+ delay = args[:delay] || 15
1563
+
1564
+ timeout = args[:timeout]
1565
+ time_started = Time.now
1566
+
1567
+ _response = { }
1568
+ continue_monitoring = true
1569
+ timed_out = false
1570
+ loop do
1571
+ _response = job_get(:job_id => job_id)
1572
+ break unless _response
1573
+
1574
+ job_status = _response['status']
1575
+ break if %w(FAILED_TOTAL FINISHED FINISHED_WARNING FINISHED_TOTAL ABORTED).include?(job_status)
1576
+
1577
+ break if timeout and (timed_out = ((Time.now - time_started) > timeout))
1578
+
1579
+ if block_given?
1580
+ yield_out = {
1581
+ :time_started => time_started,
1582
+ :poll_interval => delay,
1583
+ :latest_response => _response,
1584
+ :job_status => job_status,
1585
+ :continue_monitoring => continue_monitoring,
1586
+ :delay => delay
1587
+ }
1588
+ yield yield_out
1589
+ break unless continue_monitoring
1590
+ else
1591
+ logger.debug { "Waiting for job completion. Job: #{job_status} Poll Interval: #{delay}" }
1592
+ end
1593
+
1594
+ sleep(delay)
1595
+ end
1596
+ time_ended = Time.now
1597
+
1598
+ { :last_response => _response, :time_started => time_started, :time_ended => time_ended, :timed_out => timed_out }
1599
+ end
1600
+
1601
+ # Utilities
1602
+ end
1603
+
1604
+ # API
1605
+ end
1606
+
1607
+ # Vidispine
1608
+ end