SurfCustomCalabash 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +68 -4
- data/SurfCustomCalabash.gemspec +3 -3
- data/bin/SurfCustomCalabash +54 -0
- data/bin/surf-calabash-console.rb +62 -0
- data/bin/surf-calabash-gen.rb +17 -0
- data/bin/surf-calabash-helpers.rb +34 -0
- data/lib/SurfCustomCalabash/CommonMethods.rb +3 -3
- data/lib/SurfCustomCalabash/DroidMethods.rb +2 -1
- data/lib/SurfCustomCalabash/IosMethods.rb +2 -2
- data/lib/SurfCustomCalabash/version.rb +1 -1
- data/sources/Backdoors list b/data/sources/Backdoors → list +0 -0
- data/sources/Gemfile +27 -0
- data/sources/Scripts/and.sh +5 -0
- data/sources/Scripts/ca.sh +5 -0
- data/sources/Scripts/ci.sh +5 -0
- data/sources/Scripts/data +7 -0
- data/sources/Scripts/fa.sh +6 -0
- data/sources/Scripts/fi.sh +5 -0
- data/sources/Scripts/get_apk.rb +68 -0
- data/sources/Scripts/get_scenarios.rb +193 -0
- data/sources/Scripts/import_scenarios.rb +172 -0
- data/sources/Scripts/ios.sh +5 -0
- data/sources/Scripts/parallel_android_smoke.sh +4 -0
- data/sources/Scripts/parallel_android_test.sh +4 -0
- data/sources/Scripts/ra.sh +12 -0
- data/sources/Scripts/ri.sh +9 -0
- data/sources/Scripts/smoke_and.sh +5 -0
- data/sources/Scripts/smoke_ios.sh +5 -0
- data/sources/Scripts/ta.sh +5 -0
- data/sources/Scripts/ti.sh +5 -0
- data/sources/Scripts/to_exec.sh +19 -0
- data/sources/Scripts/update.sh +24 -0
- data/sources/Scripts/update_exec.rb +96 -0
- data/sources/TestFolder/small.gif +0 -0
- data/sources/ci/JenkinsfileUiTestAndroid.groovy +22 -0
- data/sources/ci/JenkinsfileUiTestIos.groovy +23 -0
- data/sources/config/cucumber.yml +24 -0
- data/sources/features/android/pages/standard/DroidCommon.rb +33 -0
- data/sources/features/android/pages/standard/Init_android.rb +8 -0
- data/sources/features/android/pages/test.rb +43 -0
- data/sources/features/android/support/app_life_cycle_hooks.rb +147 -0
- data/sources/features/android/support/log_hooks_and.rb +40 -0
- data/sources/features/android/support/video_hooks_and.rb +110 -0
- data/sources/features/ios/pages/standard/Init_ios.rb +5 -0
- data/sources/features/ios/pages/standard/IosCommon.rb +30 -0
- data/sources/features/ios/pages/test.rb +37 -0
- data/sources/features/ios/support/01_launch.rb +194 -0
- data/sources/features/ios/support/log_hooks_ios.rb +40 -0
- data/sources/features/ios/support/video_hooks_ios.rb +58 -0
- data/sources/features/net/net.rb +28 -0
- data/sources/features/net/screentest_api.rb +186 -0
- data/sources/features/scenarios/eng.feature +8 -0
- data/sources/features/scenarios/rus.feature +8 -0
- data/sources/features/step_definitions/test.rb +43 -0
- data/sources/features/support/credentials.rb +11 -0
- data/sources/features/support/env.rb +8 -0
- data/sources/features/support/hooks.rb +21 -0
- data/sources/features/support/localization.rb +8 -0
- data/sources/find_id.rb +95 -0
- data/sources/group_steps.rb +39 -0
- data/sources/irbrcs/android/irbrc +105 -0
- data/sources/irbrcs/ios/irbrc +132 -0
- data/sources/start.sh +134 -0
- metadata +63 -6
- data/bin/console +0 -14
- data/bin/setup +0 -8
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'timeout'
|
3
|
+
require 'process'
|
4
|
+
require 'calabash-android/operations'
|
5
|
+
require 'calabash-android/env'
|
6
|
+
require 'streamio-ffmpeg'
|
7
|
+
|
8
|
+
def adb_command
|
9
|
+
"\"#{Calabash::Android::Dependencies.adb_path}\""
|
10
|
+
end
|
11
|
+
|
12
|
+
class ScreenRecord
|
13
|
+
|
14
|
+
# определяем поддерживает ли устройсвто запись через adb или scrcpy
|
15
|
+
def self.get_adb_or_scr
|
16
|
+
if Open3.capture2e("#{adb_command} shell screenrecord --help").first.include? "Options:"
|
17
|
+
return "adb"
|
18
|
+
elsif Open3.capture2e("scrcpy --help").first.include? "Options:"
|
19
|
+
return "scrcpy"
|
20
|
+
else
|
21
|
+
return "adb and scrcpy not install"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class AndroidVideoCapture
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@video_file = "video_#{Time.now.strftime('%d_%m-%H:%M:%S')}.mp4"
|
30
|
+
|
31
|
+
# определяем поддерживает ли устройсвто запись через adb или scrcpy
|
32
|
+
@command = ScreenRecord.get_adb_or_scr
|
33
|
+
|
34
|
+
# качество видео при конвертации
|
35
|
+
@quality = "low"
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :video_file, :quality
|
39
|
+
|
40
|
+
# запускаем запись видео
|
41
|
+
def start_capture
|
42
|
+
if @command == "adb"
|
43
|
+
@screenrecord = Open3.popen2e("#{adb_command} shell")
|
44
|
+
# для улучшения качества видео, увеличить битрейт или убрать параметр совсем, по умолчанию пишется в хорошем качестве
|
45
|
+
@screenrecord[0] << "screenrecord --verbose --bit-rate 1000000 /sdcard/#{@video_file} &\n"
|
46
|
+
@screenrecord[0] << "CPID=$!\n"
|
47
|
+
elsif @command == "scrcpy"
|
48
|
+
@screenrecord = Open3.popen2e("scrcpy -m 1024 -b 2M --max-fps 10 -Nr #{@video_file}")
|
49
|
+
@pid = @screenrecord[2].pid
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# останавливаем запись видео
|
54
|
+
def stop_capture
|
55
|
+
|
56
|
+
if @command == "adb"
|
57
|
+
@screenrecord[0] << "if ps | grep screenrecord | grep $CPID; then kill -2 $CPID ; fi\n"
|
58
|
+
elsif @command == "scrcpy"
|
59
|
+
%x{kill -SIGINT #{@pid}}
|
60
|
+
end
|
61
|
+
|
62
|
+
begin
|
63
|
+
Timeout.timeout(5) { @screenrecord[2].value }
|
64
|
+
rescue Timeout::Error
|
65
|
+
Process.detach(@screenrecord[2].pid)
|
66
|
+
end
|
67
|
+
|
68
|
+
@screenrecord[0].close
|
69
|
+
@screenrecord[1].close
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
# удаляем видео с устройства если была запись через adb
|
74
|
+
def dispose_capture
|
75
|
+
system "#{adb_command} shell rm -f /sdcard/#{@video_file}" if @command == "adb"
|
76
|
+
end
|
77
|
+
|
78
|
+
# сбрасываем видео с устройства в папку с тестами
|
79
|
+
def acquire_capture
|
80
|
+
if @command == "adb"
|
81
|
+
system "#{adb_command} pull /sdcard/#{@video_file} ."
|
82
|
+
dispose_capture
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# конвертируем видео
|
87
|
+
def convert_video(path_to_input, path_to_output, quality = @quality)
|
88
|
+
movie = FFMPEG::Movie.new(path_to_input)
|
89
|
+
|
90
|
+
Kernel.puts "size #{movie.size}"
|
91
|
+
|
92
|
+
options = {video_codec: "h264", frame_rate: '10', resolution: "320x180"}
|
93
|
+
transcoder_options = {preserve_aspect_ratio: :width}
|
94
|
+
|
95
|
+
case quality.downcase
|
96
|
+
when "low"
|
97
|
+
options.merge!({video_bitrate: 100})
|
98
|
+
when "medium"
|
99
|
+
options.merge!({video_bitrate: 500})
|
100
|
+
when "high"
|
101
|
+
options.merge!({resolution: "720x480", video_bitrate: 1000})
|
102
|
+
else
|
103
|
+
return p "Please set video quality: high, medium or low"
|
104
|
+
end
|
105
|
+
|
106
|
+
Timeout.timeout(60) {
|
107
|
+
movie.transcode(path_to_output, options, transcoder_options)
|
108
|
+
}
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'calabash-cucumber/ibase'
|
2
|
+
require 'SurfCustomCalabash/IosMethods'
|
3
|
+
|
4
|
+
|
5
|
+
class IosCommon < Calabash::IBase
|
6
|
+
|
7
|
+
# system method
|
8
|
+
def initialize(driver)
|
9
|
+
@driver = driver
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing (sym, *args, &block)
|
13
|
+
@driver.send sym, *args, &block
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.element(name, &block)
|
17
|
+
define_method(name.to_s, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
alias :value :element
|
22
|
+
alias :action :element
|
23
|
+
alias :trait :element
|
24
|
+
end
|
25
|
+
|
26
|
+
# custom methods
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require_relative 'standard/IosCommon'
|
2
|
+
# all screens your application screens should be like this. Inheritance from IosCommon is essential!
|
3
|
+
|
4
|
+
class Test < IosCommon
|
5
|
+
|
6
|
+
def initialize(driver)
|
7
|
+
@driver = driver
|
8
|
+
|
9
|
+
|
10
|
+
##### elements on the screen #######
|
11
|
+
# locators and their x-platform nickname should be like that
|
12
|
+
|
13
|
+
@trait = "SegmentedPageControllerButton"
|
14
|
+
|
15
|
+
@auth_field = "* id:'auth_fld'"
|
16
|
+
|
17
|
+
@main_ok_button = "* id:'bigSpaceBackground'"
|
18
|
+
|
19
|
+
super(driver)
|
20
|
+
end
|
21
|
+
|
22
|
+
# all elements above should have getter for mention them from step definitions
|
23
|
+
attr_reader *Test.new(self).instance_variables.map {|s| s[1..-1]}
|
24
|
+
|
25
|
+
# another way to describe elements
|
26
|
+
def tape
|
27
|
+
"* id:'feed_rv'"
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
#### methods ####
|
32
|
+
def authorize(data)
|
33
|
+
tap_on(@auth_field)
|
34
|
+
wait_for_keyboard
|
35
|
+
enter_text(data)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'calabash-cucumber/launcher'
|
2
|
+
require 'calabash-cucumber/cucumber'
|
3
|
+
require_relative '../../support/env'
|
4
|
+
require_relative '../../../features/net/screentest_api'
|
5
|
+
require 'fileutils'
|
6
|
+
|
7
|
+
|
8
|
+
module Calabash::Launcher
|
9
|
+
@@launcher = nil
|
10
|
+
|
11
|
+
def self.launcher
|
12
|
+
@@launcher ||= Calabash::Cucumber::Launcher.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.launcher=(launcher)
|
16
|
+
@@launcher = launcher
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
Before ('@reinstall') do
|
22
|
+
ENV['RESET_BETWEEN_SCENARIOS'] = '1'
|
23
|
+
end
|
24
|
+
|
25
|
+
Before('@screentest') do |scenario|
|
26
|
+
|
27
|
+
clear_file_with_stack_trace
|
28
|
+
@scenario_name = scenario.name
|
29
|
+
create_feature(@scenario_name)
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
Before do |scenario|
|
35
|
+
|
36
|
+
begin
|
37
|
+
calabash_exit
|
38
|
+
rescue HTTPClient::ReceiveTimeoutError => e
|
39
|
+
Kernel.puts(e)
|
40
|
+
end
|
41
|
+
|
42
|
+
ENV['SCREENSHOT_PATH'] = './reports/'
|
43
|
+
|
44
|
+
scenario_tags = scenario.source_tag_names
|
45
|
+
|
46
|
+
@start_record = true
|
47
|
+
@start_log = false
|
48
|
+
|
49
|
+
# Scenario name
|
50
|
+
@scenario_name = scenario.name
|
51
|
+
|
52
|
+
launcher = Calabash::Launcher.launcher
|
53
|
+
options = {
|
54
|
+
# Add launch options here.
|
55
|
+
}
|
56
|
+
|
57
|
+
launcher.relaunch(:timeout => 300)
|
58
|
+
ENV['RESET_BETWEEN_SCENARIOS'] = ''
|
59
|
+
|
60
|
+
if scenario_tags.include?('@record')
|
61
|
+
@start_record = true
|
62
|
+
end
|
63
|
+
|
64
|
+
@video_capture = nil
|
65
|
+
|
66
|
+
# start record video
|
67
|
+
if @start_record
|
68
|
+
@video_capture = IosCapture.new
|
69
|
+
@video_capture.start_capture
|
70
|
+
@need_convert = false
|
71
|
+
# set video quality: low, medium, high
|
72
|
+
@video_capture.quality = "low"
|
73
|
+
end
|
74
|
+
|
75
|
+
@write_log = nil
|
76
|
+
|
77
|
+
# start write logs
|
78
|
+
if @start_log
|
79
|
+
@write_log = IosLogs.new
|
80
|
+
@write_log.start_log
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
After do
|
85
|
+
# Calabash can shutdown the app cleanly by calling the app life cycle methods
|
86
|
+
# in the UIApplicationDelegate. This is really nice for CI environments, but
|
87
|
+
# not so good for local development.
|
88
|
+
#
|
89
|
+
# See the documentation for QUIT_APP_AFTER_SCENARIO for a nice debugging workflow
|
90
|
+
#
|
91
|
+
# http://calabashapi.xamarin.com/ios/file.ENVIRONMENT_VARIABLES.html#label-QUIT_APP_AFTER_SCENARIO
|
92
|
+
# http://calabashapi.xamarin.com/ios/Calabash/Cucumber/Core.html#console_attach-instance_method
|
93
|
+
|
94
|
+
begin
|
95
|
+
calabash_exit
|
96
|
+
rescue HTTPClient::ReceiveTimeoutError => e
|
97
|
+
Kernel.puts(e)
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
def file_name(ext)
|
103
|
+
"iOS_#{Time.now.strftime('%d_%m-%H:%M:%S')}.#{ext}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_folder
|
107
|
+
@path = "#{ENV['SCREENSHOT_PATH']}#{@scenario_name}_#{Time.now.strftime('%d_%m-%H:%M:%S')}/"
|
108
|
+
# Dir.mkdir(@path) unless File.directory?(@path)
|
109
|
+
FileUtils.mkdir_p @path
|
110
|
+
end
|
111
|
+
|
112
|
+
def convert
|
113
|
+
convert_file = "video_#{file_name('mp4')}"
|
114
|
+
@video_capture.convert_video(@video_capture.video_file, convert_file)
|
115
|
+
delete_file(@video_capture.video_file)
|
116
|
+
@video_capture.video_file = convert_file
|
117
|
+
end
|
118
|
+
|
119
|
+
def embed_video
|
120
|
+
unless @video_capture.nil?
|
121
|
+
if File.exist?(@video_capture.video_file)
|
122
|
+
convert if @need_convert
|
123
|
+
sleep(2)
|
124
|
+
embed(@video_capture.video_file, 'video/mp4', "video #{@video_capture.video_file}")
|
125
|
+
FileUtils.mv(@video_capture.video_file, @path)
|
126
|
+
@video_capture.video_file = @path + @video_capture.video_file
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def delete_file(file_name)
|
132
|
+
if File.exist?(file_name)
|
133
|
+
File.delete(file_name)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def embed_logs
|
138
|
+
unless @write_log.nil?
|
139
|
+
if File.exist?(@write_log.log_file)
|
140
|
+
embed(@write_log.log_file, 'log/txt', "log #{@write_log.log_file}")
|
141
|
+
FileUtils.mv(@write_log.log_file, @path)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
After do |scenario|
|
147
|
+
|
148
|
+
@video_capture.stop_capture unless @video_capture.nil?
|
149
|
+
|
150
|
+
@write_log.stop_log unless @write_log.nil?
|
151
|
+
|
152
|
+
scenario_tags = scenario.source_tag_names
|
153
|
+
|
154
|
+
if scenario_tags.include?('@record') && scenario.passed?
|
155
|
+
create_folder
|
156
|
+
embed_video
|
157
|
+
# Kernel.puts("file name #{@video_capture.video_file}")
|
158
|
+
elsif scenario.passed?
|
159
|
+
delete_file(@write_log.log_file) unless @write_log.nil?
|
160
|
+
delete_file(@video_capture.video_file) unless @video_capture.nil?
|
161
|
+
end
|
162
|
+
|
163
|
+
if scenario.failed?
|
164
|
+
|
165
|
+
create_folder
|
166
|
+
mark_feature_as_not_finished if scenario_tags.include?('@screentest')
|
167
|
+
|
168
|
+
pretty = JSON.pretty_generate(query("*"))
|
169
|
+
parsed = JSON.parse(pretty)
|
170
|
+
|
171
|
+
for i in 0..query("*").count-1
|
172
|
+
id = parsed[i]['id'].to_s
|
173
|
+
text = parsed[i]['text'].to_s
|
174
|
+
label = parsed[i]['label'].to_s
|
175
|
+
if id != ''
|
176
|
+
File.open("#{@path}id_#{file_name('txt')}", 'a', crlf_newline: true) { |f| f.puts(id.encode(crlf_newline: true))}
|
177
|
+
end
|
178
|
+
if text != ''
|
179
|
+
File.open("#{@path}text_#{file_name('txt')}", 'a', crlf_newline: true) { |f| f.puts(text.encode(crlf_newline: true))}
|
180
|
+
end
|
181
|
+
if label != ''
|
182
|
+
File.open("#{@path}label_#{file_name('txt')}", 'a', crlf_newline: true) { |f| f.puts(label.encode(crlf_newline: true))}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
screenshot(prefix: @path, name: "#{file_name('png')}")
|
187
|
+
|
188
|
+
embed_video
|
189
|
+
embed_logs
|
190
|
+
end
|
191
|
+
|
192
|
+
send_video(@video_capture.video_file) if scenario_tags.include?('@record')
|
193
|
+
|
194
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'timeout'
|
3
|
+
require 'process'
|
4
|
+
|
5
|
+
def adb_command
|
6
|
+
"\"#{Calabash::Android::Dependencies.adb_path}\""
|
7
|
+
end
|
8
|
+
|
9
|
+
class IosLogs
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@log_file = "#{ENV['SCREENSHOT_PATH']}log_#{Time.now.strftime('%d_%m-%H:%M:%S')}.txt"
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :log_file
|
16
|
+
|
17
|
+
def start_log
|
18
|
+
@logcat = Open3.popen2e("xcrun simctl spawn booted log stream")
|
19
|
+
@pid = @logcat[2].pid
|
20
|
+
end
|
21
|
+
|
22
|
+
def stop_log
|
23
|
+
@stoplog = Open3.popen2e("kill -SIGINT #{@pid}")
|
24
|
+
|
25
|
+
begin
|
26
|
+
Timeout.timeout(10) { File.open(@log_file, 'w') { |f| f << @logcat[1].readlines.join("\n") } }
|
27
|
+
rescue Timeout::Error
|
28
|
+
Kernel.puts("Error write log file")
|
29
|
+
@logcat[0].close
|
30
|
+
@logcat[1].close
|
31
|
+
end
|
32
|
+
|
33
|
+
@stoplog[0].close
|
34
|
+
@stoplog[1].close
|
35
|
+
|
36
|
+
@logcat[0].close
|
37
|
+
@logcat[1].close
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'timeout'
|
3
|
+
require 'streamio-ffmpeg'
|
4
|
+
|
5
|
+
class IosCapture
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@video_file = "video_#{Time.now.strftime('%d_%m-%H_%M_%S')}.mp4"
|
9
|
+
|
10
|
+
@quality = "low"
|
11
|
+
|
12
|
+
# h264 or hevc
|
13
|
+
@codec = "hevc"
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_accessor :video_file, :quality
|
17
|
+
|
18
|
+
def start_capture(codec = @codec)
|
19
|
+
@screenrecord = Open3.popen2e("xcrun simctl io booted recordVideo --codec=#{codec} #{@video_file} \n")
|
20
|
+
# @screenrecord = Open3.popen2e("xcrun simctl io booted recordVideo #{@video_file} \n")
|
21
|
+
@pid = @screenrecord[2].pid
|
22
|
+
Kernel.puts("start record")
|
23
|
+
end
|
24
|
+
|
25
|
+
def stop_capture
|
26
|
+
@stoprecord = Open3.popen2e("kill -SIGINT #{@pid}")
|
27
|
+
|
28
|
+
@screenrecord[0].close
|
29
|
+
@screenrecord[1].close
|
30
|
+
|
31
|
+
@stoprecord[0].close
|
32
|
+
@stoprecord[1].close
|
33
|
+
|
34
|
+
Kernel.puts("video stopped")
|
35
|
+
end
|
36
|
+
|
37
|
+
def convert_video(path_to_input, path_to_output, quality = @quality)
|
38
|
+
movie = FFMPEG::Movie.new(path_to_input)
|
39
|
+
|
40
|
+
options = {video_codec: "h264", frame_rate: '10', resolution: "320x180"}
|
41
|
+
transcoder_options = {preserve_aspect_ratio: :width}
|
42
|
+
|
43
|
+
case quality.downcase
|
44
|
+
when "low"
|
45
|
+
options.merge!({video_bitrate: 100})
|
46
|
+
when "medium"
|
47
|
+
options.merge!({video_bitrate: 500})
|
48
|
+
when "high"
|
49
|
+
options.merge!({resolution: "720x480", video_bitrate: 1000})
|
50
|
+
else
|
51
|
+
return p "Please set video quality: high, medium or low"
|
52
|
+
end
|
53
|
+
|
54
|
+
Timeout.timeout(60) {
|
55
|
+
movie.transcode(path_to_output, options, transcoder_options)
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|