irecorder 0.0.7

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.
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'ftools'
4
+
5
+
6
+ desc "Install Application Menu"
7
+ task :install_menu do
8
+ menuDir = %x{ kde4-config --install xdgdata-apps }.strip
9
+ menuEntryFile = File.join(menuDir, 'irecorder.desktop')
10
+ open(menuEntryFile,'w') do |f|
11
+ f.write(<<-EOF
12
+ [Desktop Entry]
13
+ Name=iRecorder
14
+ Comment=BBC iPlayer like audio recorder with KDE GUI.
15
+ Exec=irecorder.rb %f
16
+ Icon=irecorder
17
+ Terminal=false
18
+ Type=Application
19
+ Categories=Qt;KDE;AudioVideo;Radio;News;Music;Player
20
+ MimeType=application/x-gem;
21
+ EOF
22
+ )
23
+ %x{ update-menus }
24
+ end
25
+ end
26
+
27
+ task :default => [ :install_menu ]
@@ -0,0 +1,271 @@
1
+ #
2
+ #
3
+ #
4
+ require 'rubygems'
5
+ require 'uri'
6
+ require 'net/http'
7
+ require 'open-uri'
8
+ require 'nokogiri'
9
+ require 'shellwords'
10
+ require 'fileutils'
11
+ require 'tmpdir'
12
+ require 'singleton'
13
+ require 'Qt'
14
+
15
+ # my libs
16
+ require "cache"
17
+ require "logwin"
18
+
19
+ UrlRegexp = URI.regexp(['rtsp','http'])
20
+
21
+ #
22
+ #
23
+ class BBCNet
24
+ RtspRegexp = URI.regexp(['rtsp'])
25
+ MmsRegexp = URI.regexp(['mms'])
26
+ DirectStreamRegexp = URI.regexp(['mms', 'rtsp', 'rtmp', 'rtmpt'])
27
+
28
+ class CacheMetaInfoDevice < CasheDevice::CacheDeviceBase
29
+ def initialize(cacheDuration = 40*60, cacheMax=200)
30
+ super(cacheDuration, cacheMax)
31
+ end
32
+
33
+ # @return : [ data, key ]
34
+ # key : key to restore data.
35
+ def directRead(pid)
36
+ data = BBCNet::MetaInfo.new(pid).update
37
+ [ data, data ]
38
+ end
39
+
40
+ def self.read(url)
41
+ pid = BBCNet.extractPid(url)
42
+ self.instance.read(pid)
43
+ end
44
+ end
45
+
46
+ #------------------------------------------------------------------------
47
+ # get stream metadata
48
+ # episode url => pid => xml playlist => version pid (vpid aka. identifier)
49
+ # => xml stream metadata => wma
50
+ #
51
+ class MetaInfo
52
+ def self.get(url)
53
+ pid = BBCNet.extractPid(url)
54
+ self.new(pid)
55
+ end
56
+
57
+ attr_reader :pid
58
+ Keys = [ :duration, :vpid, :group, :media, :onAirDate, :channel, :title, :summary, :aacLow, :aacStd, :real, :wma, :streams ]
59
+ def initialize(pid)
60
+ @pid = pid
61
+ Keys.each do |k|
62
+ s = ('@' + k.to_s).to_sym
63
+ self.instance_variable_set(s, nil)
64
+ self.class.class_eval %Q{
65
+ def #{k}
66
+ #{s}
67
+ end
68
+ }
69
+ end
70
+
71
+ @streams = []
72
+ end
73
+
74
+
75
+ #
76
+ # read duration, vpid, group, media, onAirDate, channel
77
+ # from XmlPlaylist
78
+ def readXmlPlaylist
79
+ return self if @vpid
80
+
81
+ res = BBCNet.read("http://www.bbc.co.uk/iplayer/playlist/#{@pid}")
82
+ # res = IO.read("../tmp/iplayer-playlist-me.xml")
83
+
84
+ doc = Nokogiri::XML(res)
85
+ item = doc.at_css("noItems")
86
+ raise "No Playlist " + item[:reason] if item
87
+
88
+ item = doc.at_css("item")
89
+ @media = item[:kind].gsub(/programme/i, '')
90
+ @duration = item[:duration].to_i
91
+ @vpid = item[:identifier]
92
+ @group = item[:group]
93
+ @onAirDate = BBCNet.getTime(item.at_css("broadcast").content.to_s)
94
+ @channel = item.at_css("service").content.to_s
95
+ @title = item.at_css("title").content.to_s
96
+ @summary = doc.at_css("summary").content.to_s
97
+ self
98
+ end
99
+
100
+
101
+ class StreamInfo
102
+ # example) 48, wma, time, audio, http://..
103
+ attr_accessor :bitrate, :encoding, :expires, :type, :indirectUrl
104
+ alias :kind :type
105
+
106
+ def url
107
+ @url ||= BBCNet.getDirectStreamUrl(@indirectUrl)
108
+ end
109
+ end
110
+
111
+ def readXmlStreamMeta
112
+ readXmlPlaylist unless @vpid
113
+
114
+ res = BBCNet.read("http://www.bbc.co.uk/mediaselector/4/mtis/stream/#{vpid}")
115
+ # res = IO.read("../tmp/iplayer-stream-meta-me.xml")
116
+
117
+ doc = Nokogiri::XML(res)
118
+ me = doc.css("media")
119
+ me.each do |m|
120
+ stmInf = StreamInfo.new
121
+ stmInf.encoding = m[:encoding] # wma
122
+ stmInf.bitrate = m[:bitrate].to_i # 48
123
+ expiresStr = m[:expires]
124
+ stmInf.expires = BBCNet.getTime(expiresStr) if expiresStr
125
+ stmInf.type = m[:kind] # audio
126
+
127
+ con = m.at_css("connection")
128
+ stmInf.indirectUrl = con[:href]
129
+ @streams <<= stmInf
130
+
131
+ case stmInf.encoding
132
+ when /\bwma\b/i
133
+ @wma = stmInf
134
+ when /\baac\b/i
135
+ if stmInf.bitrate < 64
136
+ @aacLow = stmInf
137
+ else
138
+ @aacStd = stmInf
139
+ end
140
+ when /\breal\b/i
141
+ @real = stmInf
142
+ end
143
+ end
144
+ self
145
+ end
146
+
147
+ alias :update :readXmlStreamMeta
148
+
149
+ end
150
+
151
+
152
+
153
+ def self.getTime(str)
154
+ tm = str.match(/(\d{4})-(\d\d)-(\d\d)\w(\d\d):(\d\d):(\d\d)/)
155
+ par = ((1..6).inject([]) do |a, n| a << tm[n].to_i end)
156
+ Time.gm( *par )
157
+ end
158
+
159
+
160
+ #------------------------------------------------------------------------
161
+ #
162
+ #
163
+
164
+ # convert epsode Url to console Url
165
+ def self.getPlayerConsoleUrl(url)
166
+ "http://www.bbc.co.uk/iplayer/console/" + extractPid(url)
167
+ end
168
+
169
+ # get PID from BBC episode Url
170
+ def self.extractPid(url)
171
+ case url
172
+ when %r!/(?:item|episode|programmes)/([a-z0-9]{8})!
173
+ $1
174
+ when %r!^[a-z0-9]{8}$!
175
+ url
176
+ when %r!\b(b[a-z0-9]{7}\b)!
177
+ $1
178
+ else
179
+ raise "No PID in Url '%s'" % url
180
+ end
181
+ end
182
+
183
+
184
+ # .asf/.ram => .wma/.ra
185
+ def self.getDirectStreamUrl(url)
186
+ old = ''
187
+ while url != old and not url[DirectStreamRegexp] do
188
+ old = url
189
+ res = BBCNet.read(url)
190
+ url = res[ DirectStreamRegexp ] || res[ UrlRegexp ] || old
191
+ $log.debug { "new url:#{url}, old url:#{old}" }
192
+ $log.debug { "no url in response '#{res}'" } if url[ UrlRegexp ]
193
+ end
194
+ url
195
+ end
196
+
197
+
198
+ def self.read(url)
199
+ header = { "User-Agent" => self.randomUserAgent }
200
+ if defined? @@proxy
201
+ header[:proxy] = @@proxy
202
+ end
203
+
204
+ uri = URI.parse(url)
205
+ res = Net::HTTP.start(uri.host, uri.port) do |http|
206
+ http.get(uri.request_uri, header)
207
+ end
208
+ res.body
209
+ end
210
+
211
+
212
+ def self.setProxy(url)
213
+ @@proxy = url
214
+ end
215
+
216
+ private
217
+ UserAgentList = [
218
+ 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/<RAND>.8 (KHTML, like Gecko) Chrome/2.0.178.0 Safari/<RAND>.8',
219
+ 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; YPC 3.2.0; SLCC1; .NET CLR 2.0.50<RAND>; .NET CLR 3.0.04<RAND>)',
220
+ 'Mozilla/5.0 (Macintosh; U; PPC Mac OS X 10_4_11; tr) AppleWebKit/<RAND>.4+ (KHTML, like Gecko) Version/4.0dp1 Safari/<RAND>.11.2',
221
+ 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50<RAND>; .NET CLR 3.5.30<RAND>; .NET CLR 3.0.30<RAND>; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8)',
222
+ 'Mozilla/6.0 (Windows; U; Windows NT 7.0; en-US; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.9 (.NET CLR 3.5.30<RAND>)',
223
+ ]
224
+ def self.randomUserAgent
225
+ ua = UserAgentList[ rand UserAgentList.length ]
226
+ ua.gsub(/<RAND>/, "%03d" % rand(1000))
227
+ end
228
+
229
+ end
230
+
231
+ module AudioFile
232
+ # return seconds of audio file duration.
233
+ def self.getDuration(file)
234
+ return 0 unless File.exist?(file)
235
+
236
+ case file[/\.\w+$/].downcase
237
+ when ".mp3"
238
+ cmd = "| exiftool -S -Duration %s" % file.shellescape
239
+ when ".wma"
240
+ cmd = "| exiftool -S -PlayDuration %s" % file.shellescape
241
+ end
242
+ msg = open(cmd) do |f| f.read end
243
+ a = msg.scan(/(?:(\d+):){0,2}(\d+)/)[0]
244
+ return 0 unless a
245
+ i = -1
246
+ a.reverse.inject(0) do |s, d|
247
+ i += 1
248
+ s + d.to_i * [ 1, 60, 3600 ][i]
249
+ end
250
+ end
251
+ end
252
+
253
+
254
+
255
+ if __FILE__ == $0 then
256
+ # puts AudioFile::getDuration(ARGV.shift)
257
+ # exit 0
258
+
259
+ $log = MyLogger.new(STDOUT)
260
+ pid = "b00mzvfq"
261
+ if ARGV.size > 0 then
262
+ pid = ARGV.shift
263
+ end
264
+ minfo = BBCNet::MetaInfo.new(pid)
265
+ minfo.readXmlStreamMeta
266
+ puts minfo.inspect
267
+
268
+ minfo.streams.each do |s|
269
+ puts "url : " + s.url
270
+ end
271
+ end
@@ -0,0 +1,121 @@
1
+ #
2
+ #
3
+ #
4
+ module CasheDevice
5
+ class CacheDeviceBase
6
+ include Singleton
7
+
8
+ class CachedData
9
+ attr_accessor :expireTime, :url, :key
10
+ end
11
+
12
+ attr_accessor :cacheDuration, :cacheMax
13
+ def initialize(cacheDuration = 26*60, cacheMax=10)
14
+ @cacheDuration = cacheDuration
15
+ @cache = Hash.new
16
+ @cacheLRU = [] # Least Recently Used
17
+ @cacheMax = cacheMax
18
+ end
19
+
20
+ # @return : data, key
21
+ # key : key to restore data.
22
+ def directRead(url)
23
+ raise "Implement directRead method."
24
+ end
25
+
26
+ # @return : data
27
+ # restore data from key.
28
+ def restoreCache(key)
29
+ key
30
+ end
31
+
32
+ def read(url)
33
+ startTime = Time.now
34
+ cached = @cache[url]
35
+ if cached and cached.expireTime > startTime then
36
+ @cacheLRU.delete(cached)
37
+ @cacheLRU.push(cached)
38
+ data = restoreCache(cached.key)
39
+ $log.debug { "cached %s: Time %f sec" %
40
+ [self.class.name, (Time.now - startTime).to_f] }
41
+ return data
42
+ end
43
+ if @cacheLRU.size >= @cacheMax then
44
+ oldest = @cacheLRU.shift
45
+ @cache.delete(oldest)
46
+ end
47
+ cached = CachedData.new
48
+ cached.url = url
49
+ cached.expireTime = startTime + @cacheDuration
50
+ data, cached.key = directRead(url)
51
+ @cache[url] = cached
52
+ @cacheLRU.push(cached)
53
+ $log.debug {"direct read %s: Time %f sec" %
54
+ [self.class.name, (Time.now - startTime).to_f] }
55
+ data
56
+ end
57
+
58
+
59
+ def self.read(url)
60
+ self.instance.read(url)
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+ #
67
+ # practical implementations.
68
+ #
69
+ class CacheRssDevice < CasheDevice::CacheDeviceBase
70
+ def initialize(cacheDuration = 12*60, cacheMax=6)
71
+ super(cacheDuration, cacheMax)
72
+ end
73
+
74
+ # @return : [ data, key ]
75
+ # key : key to restore data.
76
+ def directRead(url)
77
+ data = Nokogiri::XML(CacheHttpDiskDevice.read(url))
78
+ [ data, data ]
79
+ end
80
+ end
81
+
82
+
83
+ class CacheHttpDiskDevice < CasheDevice::CacheDeviceBase
84
+ def initialize(cacheDuration = 12*60, cacheMax=50)
85
+ super(cacheDuration, cacheMax)
86
+ @tmpdir = Dir.tmpdir + '/bbc_cache'
87
+ FileUtils.mkdir_p(@tmpdir)
88
+ end
89
+
90
+ # @return : data
91
+ # restore data from key.
92
+ def restoreCache(key)
93
+ IO.read(key)
94
+ end
95
+
96
+ # @return : [ data, key ]
97
+ # key : key to restore data.
98
+ def directRead(url)
99
+ $log.misc { "directRead(): " + self.class.name }
100
+ tmpfname = tempFileName(url)
101
+
102
+ if File.exist?(tmpfname) then
103
+ $log.misc { "File ctime : " + File.ctime(tmpfname).to_s}
104
+ $log.misc { "expire time : " + (File.ctime(tmpfname) + @cacheDuration).to_s }
105
+ $log.misc { "Now Time : " + Time.now.to_s }
106
+ end
107
+
108
+ if File.exist?(tmpfname) and
109
+ File.ctime(tmpfname) + @cacheDuration > Time.now then
110
+ data = IO.read(tmpfname)
111
+ else
112
+ data = BBCNet.read(url)
113
+ open(tmpfname, "w") do |f| f.write(data) end
114
+ end
115
+ [ data, tmpfname ]
116
+ end
117
+
118
+ def tempFileName(url)
119
+ File.join(@tmpdir, url.scan(%r{(?:iplayer/)[\w\/]+$}).first.gsub!(/iplayer\//,'').gsub!(%r|/|, '_'))
120
+ end
121
+ end
@@ -0,0 +1,441 @@
1
+ require 'fileutils'
2
+
3
+ require "bbcnet.rb"
4
+
5
+ #-------------------------------------------------------------------
6
+ #
7
+ #
8
+ class OkCancelDialog < KDE::Dialog
9
+ def initialize(parent)
10
+ super(parent)
11
+ setButtons( KDE::Dialog::Ok | KDE::Dialog::Cancel )
12
+ @textEdit = Qt::Label.new do |w|
13
+ w.wordWrap= true
14
+ end
15
+ setMainWidget(@textEdit)
16
+ end
17
+
18
+ attr_reader :textEdit
19
+
20
+ def self.ask(parent, text, title = text)
21
+ @@dialog ||= self.new(parent)
22
+ @@dialog.textEdit.text = text
23
+ @@dialog.caption = title
24
+ @@dialog.exec
25
+ end
26
+ end
27
+
28
+
29
+ #-------------------------------------------------------------------
30
+ #
31
+ #
32
+ #
33
+ class DownloadProcess < Qt::Process
34
+ attr_reader :sourceUrl, :fileName
35
+ attr_accessor :taskItem
36
+
37
+ #
38
+ DEBUG_DOWNLOAD = false
39
+
40
+ # @stage
41
+ DOWNLOAD = 0
42
+ CONVERT = 1
43
+ FINISHED = 2
44
+
45
+ # @status
46
+ INITIAL = 0
47
+ RUNNING = 1
48
+ ERROR = 2
49
+ DONE = 3
50
+ def statusMessage
51
+ case @status
52
+ when INITIAL, RUNNING, DONE
53
+ %w{ Downloading Converting Finished }[@stage]
54
+ when ERROR
55
+ "Error : " + %w{ Download Convert Finish }[@stage]
56
+ else
57
+ "???"
58
+ end
59
+ end
60
+
61
+ def status=(st)
62
+ @status = st
63
+ @taskItem.status = statusMessage if @taskItem
64
+ $log.misc { "status:#{status}" }
65
+ end
66
+
67
+ #--------------------------
68
+ # check status
69
+ def running?
70
+ @status == RUNNING
71
+ end
72
+
73
+ def error?
74
+ @status == ERROR
75
+ end
76
+
77
+ #--------------------------
78
+ # check stage
79
+ def finished?
80
+ @stage == FINISHED
81
+ end
82
+
83
+ def rawDownloaded?
84
+ @stage >= CONVERT
85
+ end
86
+
87
+ class Command
88
+ attr_accessor :app, :args, :msg
89
+ def initialize(app, args, msg)
90
+ @app = app
91
+ @args = args
92
+ @msg = msg
93
+ end
94
+ end
95
+
96
+ attr_reader :sourceUrl, :rawFileName
97
+ attr_reader :rawFilePath, :outFilePath
98
+
99
+ def initialize(parent, metaInfo, fName)
100
+ super(parent)
101
+ @metaInfo = metaInfo
102
+ @parent = parent
103
+ @taskItem = nil
104
+ @startTime = Time.new
105
+ @sourceUrl = @metaInfo.wma.url
106
+ @rawFileName = fName
107
+ @rawFilePath = File.join(IRecSettings.rawDownloadDir, fName)
108
+ mkdirSavePath(@rawFilePath)
109
+ @outFileName = @rawFileName.gsub(/\.\w+$/i, '.mp3')
110
+ @outFilePath = File.join(IRecSettings.downloadDir, @outFileName)
111
+ mkdirSavePath(@outFilePath)
112
+ $log.debug { "@rawFilePath : #{@rawFilePath }" }
113
+ $log.debug { "@outFilePath : #{@outFilePath}" }
114
+
115
+ @stage = DOWNLOAD
116
+ @status = INITIAL
117
+
118
+ connect(self, SIGNAL('finished(int,QProcess::ExitStatus)'), self, SLOT('taskFinished(int,QProcess::ExitStatus)') )
119
+ end
120
+
121
+ def decideFinish?
122
+ if File.exist?(@outFilePath) then
123
+ # check outFile validity.
124
+ return ! outFileError?
125
+ end
126
+ return false
127
+ end
128
+
129
+ def decideConvert?
130
+ if File.exist?(@rawFilePath) then
131
+ # check rawFile validity.
132
+ return ! rawFileError?
133
+ end
134
+ return false
135
+ end
136
+
137
+ def decideStartTask
138
+ return FINISHED if decideFinish?
139
+ return CONVERT if decideConvert?
140
+ return DOWNLOAD
141
+ end
142
+
143
+ def beginTask
144
+ startTask = decideStartTask
145
+
146
+ # ask whether proceed or commence from start.
147
+ case startTask
148
+ when FINISHED
149
+ ret = OkCancelDialog.ask(nil, \
150
+ i18n('File %s is already exist. Download it anyway ?') % @outFileName)
151
+ if ret == Qt::Dialog::Accepted then
152
+ startTask = DOWNLOAD
153
+ end
154
+ when CONVERT
155
+ ret = OkCancelDialog.ask(nil, \
156
+ i18n('Raw file %s is already exist. Download it anyway ?') % @rawFileName)
157
+ if ret == Qt::Dialog::Accepted then
158
+ startTask = DOWNLOAD
159
+ end
160
+ end
161
+
162
+ # initialize task
163
+ case startTask
164
+ when DOWNLOAD
165
+ beginDownload
166
+ when CONVERT
167
+ beginConvert
168
+ when FINISHED
169
+ allTaskFinished
170
+ end
171
+ end
172
+
173
+
174
+ def retryTask
175
+ $log.debug { "retry." }
176
+ if error? then
177
+ # retry
178
+ case @stage
179
+ when DOWNLOAD
180
+ @startTime = Time.new
181
+ beginDownload
182
+ when CONVERT
183
+ @startTime = Time.new
184
+ beginConvert
185
+ end
186
+ else
187
+ $log.warn { "cannot retry the successfully finished or running process." }
188
+ end
189
+ end
190
+
191
+ def retryDownload
192
+ $log.debug { "retry from download." }
193
+ if error? then
194
+ # retry
195
+ @startTime = Time.new
196
+ beginDownload
197
+ else
198
+ $log.warn { "cannot retry the successfully finished or running process." }
199
+ end
200
+ end
201
+
202
+ def cancelTask
203
+ if running? then
204
+ self.terminate
205
+ self.status = ERROR
206
+ errMsg = "Stopped " + %w{ Download Convert ? }[@stage]
207
+ $log.error { [ errMsg ] }
208
+ passiveMessage(errMsg)
209
+ end
210
+ end
211
+
212
+ def removeData
213
+ cancelTask
214
+ begin
215
+ File.delete(@rawFilePath)
216
+ File.delete(@outFilePath)
217
+ rescue => e
218
+ $log.info { e }
219
+ end
220
+ end
221
+
222
+ def updateLapse
223
+ taskItem.updateTime(lapse)
224
+ end
225
+
226
+ def lapse
227
+ Time.now - @startTime
228
+ end
229
+
230
+ def errorStop(exitCode, exitStatus)
231
+ self.status = ERROR
232
+ errMsg = makeErrorMsg
233
+ $log.error { [ errMsg, "exitCode=#{exitCode}, exitStatus=#{exitStatus}" ] }
234
+ passiveMessage(errMsg)
235
+ end
236
+
237
+ slots 'taskFinished(int,QProcess::ExitStatus)'
238
+ def taskFinished(exitCode, exitStatus)
239
+ checkReadOutput
240
+ if error? || ((exitCode.to_i.nonzero? || exitStatus.to_i.nonzero?) && checkErroredStatus) then
241
+ errorStop(exitCode, exitStatus)
242
+ else
243
+ $log.info {
244
+ [ "Successed to download a File '%#2$s'",
245
+ "Successed to convert a File '%#2$s'", "?" ][@stage] %
246
+ [ @sourceUrl, @rawFilePath ]
247
+ }
248
+ if @stage == CONVERT then
249
+ passiveMessage(i18n("Download, Convert Complete. '%#1$s'") % [@outFilePath])
250
+ end
251
+ nextTask
252
+ end
253
+ end
254
+
255
+ def updateView
256
+ if running? then
257
+ # update Lapse time
258
+ updateLapse
259
+
260
+ # dump IO message buffer
261
+ checkReadOutput
262
+ end
263
+ end
264
+
265
+
266
+ protected
267
+ # increment stage
268
+ def nextTask
269
+ @stage += 1
270
+ case @stage
271
+ when DOWNLOAD
272
+ beginDownload
273
+ when CONVERT
274
+ beginConvert
275
+ else
276
+ removeRawFile
277
+ allTaskFinished
278
+ end
279
+ end
280
+
281
+ def beginDownload
282
+ $log.info { " DownloadProcess : beginDownload." }
283
+ @stage = DOWNLOAD
284
+ @downNG = true
285
+ self.status = RUNNING
286
+ @currentCommand = makeMPlayerDownloadCmd
287
+
288
+ $log.info { @currentCommand.msg }
289
+ start(@currentCommand.app, @currentCommand.args)
290
+ end
291
+
292
+ def makeMPlayerDownloadCmd
293
+ # make MPlayer Downlaod comand
294
+ cmdMsg = "mplayer -noframedrop -dumpfile %s -dumpstream %s" %
295
+ [@rawFilePath.shellescape, @sourceUrl.shellescape]
296
+ cmdApp = "mplayer"
297
+ cmdArgs = ['-noframedrop', '-dumpfile', @rawFilePath, '-dumpstream', @sourceUrl]
298
+
299
+ # debug code.
300
+ if DEBUG_DOWNLOAD then
301
+ if rand > 0.4 then
302
+ cmdApp = APP_DIR + "/mytests/sleepjob.rb"
303
+ cmdArgs = %w{ touch a/b/ }
304
+ else
305
+ cmdApp = APP_DIR + "/mytests/sleepjob.rb"
306
+ cmdArgs = %w{ touch } << @rawFilePath.shellescape
307
+ end
308
+ end
309
+
310
+ Command.new( cmdApp, cmdArgs, cmdMsg )
311
+ end
312
+
313
+ def beginConvert
314
+ @stage = CONVERT
315
+ self.status = RUNNING
316
+
317
+ cmdMsg = "nice -n 19 ffmpeg -i %s -f mp3 %s" %
318
+ [ @rawFilePath.shellescape, @outFilePath.shellescape]
319
+ cmdApp = "nice"
320
+ cmdArgs = [ '-n', '19', 'ffmpeg', '-i', @rawFilePath, '-f', 'mp3', @outFilePath ]
321
+
322
+
323
+ # debug code.
324
+ if DEBUG_DOWNLOAD then
325
+ if rand > 0.4 then
326
+ cmdApp = APP_DIR + "/mytests/sleepjob.rb"
327
+ cmdArgs = %w{ touch a/b/ }
328
+ else
329
+ cmdApp = APP_DIR + "/mytests/sleepjob.rb"
330
+ cmdArgs = %w{ cp -f } + [ @rawFilePath.shellescape, @outFilePath.shellescape ]
331
+ end
332
+ end
333
+
334
+ @currentCommand = Command.new( cmdApp, cmdArgs, cmdMsg )
335
+ $log.info { @currentCommand.msg }
336
+ start(@currentCommand.app, @currentCommand.args)
337
+ end
338
+
339
+
340
+ def removeRawFile
341
+ unless IRecSettings.leaveRawFile then
342
+ begin
343
+ File.delete(@rawFilePath)
344
+ rescue => e
345
+ $log.error { e }
346
+ end
347
+ end
348
+ end
349
+
350
+ def checkOutput(msg)
351
+ msgSum = msg.join(' ')
352
+ @downNG &&= false if msgSum =~ /Everything done/i
353
+ end
354
+
355
+ # check and read output
356
+ def checkReadOutput
357
+ msg = readAllStandardOutput.data .reject do |l| l.empty? end
358
+ checkOutput(msg)
359
+ $log.info { msg }
360
+ end
361
+
362
+
363
+ def rawFileError?
364
+ begin
365
+ $log.debug { "check duration for download." }
366
+ rawDuration = AudioFile.getDuration(@rawFilePath)
367
+ isError = rawDuration < @metaInfo.duration - 100
368
+ if isError then
369
+ $log.warn { [ "duration check error",
370
+ " rawDuration : #{rawDuration}" ] }
371
+ end
372
+ return isError if isError
373
+ $log.debug { "check file size for download." }
374
+ isError = File.size(@rawFilePath) < @metaInfo.duration * 5500
375
+ if isError then
376
+ $log.warn { [ "duration check error",
377
+ " File.size(@rawFilePath) :#{File.size(@rawFilePath)}",
378
+ " @metaInfo.duration : #{@metaInfo.duration}" ] }
379
+ end
380
+ return isError
381
+ rescue => e
382
+ $log.warn { e }
383
+ return true
384
+ end
385
+ end
386
+
387
+ def outFileError?
388
+ begin
389
+ $log.debug { "check duration for convert." }
390
+ outDuration = AudioFile.getDuration(@outFilePath)
391
+ isError = outDuration < @metaInfo.duration - 3*60 - 10
392
+ if isError then
393
+ $log.warn { [ "duration check error",
394
+ " outDuration : #{outDuration}" ] }
395
+ end
396
+ return isError
397
+ rescue => e
398
+ $log.warn { e }
399
+ return true
400
+ end
401
+ end
402
+
403
+ # return error or not
404
+ def checkErroredStatus
405
+ case @stage
406
+ when DOWNLOAD
407
+ return @downNG unless @downNG
408
+ rawFileError?
409
+ when CONVERT
410
+ outFileError?
411
+ else
412
+ true
413
+ end
414
+ end
415
+
416
+
417
+
418
+
419
+ protected
420
+ def allTaskFinished
421
+ @stage = FINISHED
422
+ self.status = DONE
423
+ end
424
+
425
+ def makeErrorMsg
426
+ [ "Failed to download a File '%#2$s'",
427
+ "Failed to convert a File '%#2$s'", "?"][@stage] %
428
+ [ @sourceUrl, @rawFilePath ]
429
+ end
430
+
431
+
432
+ def mkdirSavePath(fName)
433
+ dir = File.dirname(fName)
434
+ unless File.exist? dir
435
+ $log.info{ "mkdir : " + dir }
436
+ FileUtils.mkdir_p(dir)
437
+ end
438
+ end
439
+ end
440
+
441
+