vidispine 1.4.0

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