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,19 @@
1
+ Copyright (c) 2010 ruby.twiddler
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "irecorder"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,45 @@
1
+ BBC iPlayer like audio recorder with KDE GUI.
2
+ You can browse BBC Radio programmes and click to download stream file.
3
+ files will be converted to mp3 files automatically.
4
+ irecorder allow to play without any other browser or play on your prefered browser.
5
+
6
+ KDE ruby
7
+ ==============
8
+ irecorder require kdebindings.
9
+ Please check your distro package to install it.
10
+ or you can compile from source.
11
+ http://download.kde.org/download.php?url=stable/4.5.0/src/kdebindings-4.5.0.tar.bz2
12
+
13
+
14
+ Installation
15
+ ==================
16
+ Install most important package kdebindings4.
17
+ on Mandriva Linux use urpmi command.
18
+
19
+ # sudo urpmi ruby-kde4 ruby-qt4
20
+
21
+ Install mplayer ffmpeg exiftool packages if necessary
22
+
23
+ # sudo urpmi mplayer ffmpeg perl-Image-ExifTool
24
+
25
+ Install nokogiri gem.
26
+ # sudo gem install nokogiri
27
+
28
+ Finally you can install irecorder.
29
+
30
+ # sudo gem install irecorder
31
+ or directly specify your downloaded gem file.
32
+ # sudo gem install -l irecorder-0.0.x.gem
33
+
34
+ To launch irecorder just type irecorder.rb
35
+
36
+ # irecorder.rb
37
+
38
+
39
+ Build Gem
40
+ =================
41
+ If you want to gem package from source, use rake command.
42
+
43
+ # rake gem
44
+
45
+
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require 'rbconfig'
4
+ require 'rubygems'
5
+ require 'rake/gempackagetask'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = "irecorder"
9
+ s.version = "0.0.7"
10
+ s.author = "ruby.twiddler"
11
+ s.email = "ruby.twiddler at gmail.com"
12
+ s.homepage = "http://github.com/rubytwiddler/irecorder/wiki"
13
+ s.summary = "BBC iPlayer like audio recorder with KDE GUI."
14
+ s.files = FileList["{bin,lib}/**/*"].to_a
15
+ s.files += %w{ README MIT-LICENSE Rakefile resources/bbcstyle.qss }
16
+ s.executables = [ 'irecorder.rb' ]
17
+ s.require_path = "lib"
18
+ s.extensions = [ 'ext/Rakefile' ]
19
+ s.requirements = %w{ korundum4 qtwebkit kio ktexteditor }
20
+ s.add_runtime_dependency( 'nokogiri', '>= 1.4.0' )
21
+ s.description = <<-EOF
22
+ BBC iPlayer like audio recorder with KDE GUI.
23
+ You can browse BBC Radio programmes and click to download stream file.
24
+ files will be converted to mp3 files automatically.
25
+ irecorder allow to play without any other browser or play on your prefered browser.
26
+ like mplayer.
27
+ irecorder require kdebindings.
28
+ EOF
29
+ s.has_rdoc = false
30
+ s.extra_rdoc_files = ["README"]
31
+ end
32
+
33
+ package = Rake::GemPackageTask.new(spec) do |pkg|
34
+ pkg.need_tar = true
35
+ end
36
+
37
+ desc "install as gem package"
38
+ task :installgem => :gem do
39
+ system("gem install -r pkg/" + package.gem_file )
40
+ end
41
+
42
+ desc "install for rpm"
43
+ task :install4rpm do
44
+ def installToDir(file, dir, options={})
45
+ # puts "copy #{file} => #{dir}"
46
+ FileUtils.mkdir_p(File.join(dir, File.dirname(file)))
47
+ FileUtils.install(file, File.join(dir, File.dirname(file)), options)
48
+ end
49
+
50
+ prefix = ENV['prefix']
51
+ conf = Config::CONFIG
52
+ destDir = conf['sitelibdir'].sub(/#{conf['prefix']}/, prefix)
53
+ destDir = File.join(destDir, spec.name)
54
+
55
+ files = FileList["{bin,lib}/**/*"].to_a
56
+ files += %w{ resources/bbcstyle.qss }
57
+ files.each do |f|
58
+ # install files.
59
+ installToDir(f, destDir)
60
+ end
61
+
62
+ installToDir('bin/irecorder', prefix, :mode => 0755)
63
+ end
64
+
65
+
66
+ resouce_files = [ 'resources/irecorder.qrc' ] + FileList['resources/images/*.png']
67
+ desc "package resources"
68
+ file 'lib/irecorder_resource.rb' => resouce_files do
69
+ %x{ rbrcc resources/irecorder.qrc >lib/irecorder_resource.rb }
70
+ end
71
+
72
+ task :resource => 'lib/irecorder_resource.rb'
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rbconfig'
4
+ require 'ftools'
5
+
6
+ conf = Config::CONFIG
7
+ sitelibdir = conf['sitelibdir']
8
+ name = File.basename(__FILE__)
9
+ load( File.join(sitelibdir, "#{name}/bin/#{name}.rb") )
@@ -0,0 +1,885 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # 2010 by ruby.twiddler@gmail.com
4
+ #
5
+ # iPlayer like stream recorder
6
+ # record real/wma (rtsp/mms) audio stream
7
+ #
8
+
9
+ $KCODE = 'UTF8'
10
+ require 'ftools'
11
+
12
+ APP_NAME = File.basename(__FILE__).sub(/\.rb/, '')
13
+ APP_DIR = File::dirname(File.expand_path(File.dirname(__FILE__)))
14
+ LIB_DIR = File::join(APP_DIR, "lib")
15
+ APP_VERSION = "0.0.7"
16
+
17
+ # standard libs
18
+ require 'rubygems'
19
+ require 'uri'
20
+ require 'net/http'
21
+ require 'open-uri'
22
+ require 'shellwords'
23
+ require 'fileutils'
24
+ require 'singleton'
25
+
26
+ # additional libs
27
+ require 'korundum4'
28
+ require 'qtwebkit'
29
+
30
+ #
31
+ # my libraries and programs
32
+ #
33
+ $:.unshift(LIB_DIR)
34
+ require "irecorder_resource"
35
+ require "bbcnet"
36
+ require "mylibs"
37
+ require "logwin"
38
+ require "taskwin"
39
+ require "download"
40
+ require "programmewin"
41
+ require "settings"
42
+
43
+
44
+ #---------------------------------------------------------------------------------------------
45
+ #---------------------------------------------------------------------------------------------
46
+ #
47
+ # Main Window Class
48
+ #
49
+ class MainWindow < KDE::MainWindow
50
+
51
+ GroupName = "MainWindow"
52
+
53
+ #
54
+ #
55
+ #
56
+ def initialize
57
+ super(nil)
58
+ setCaption(APP_NAME)
59
+
60
+
61
+ $app.styleSheet = IO.read(APP_DIR + '/resources/bbcstyle.qss')
62
+ @actions = KDE::ActionCollection.new(self)
63
+
64
+ createWidgets
65
+ createMenu
66
+ createDlg
67
+
68
+ # default values
69
+ # BBCNet.setProxy('http://194.36.10.154:3127')
70
+ # initialize values
71
+ $log = MyLogger.new(@logWin)
72
+ $log.level = MyLogger::DEBUG
73
+ $log.info { 'Log Start.' }
74
+
75
+ # assign from config file.
76
+ readSettings
77
+ @actions.readSettings
78
+ setAutoSaveSettings(GroupName)
79
+
80
+ initializeTaskTimer
81
+ end
82
+
83
+
84
+
85
+ protected
86
+ def initializeTaskTimer
87
+ # Task Timer
88
+ @timer = Qt::Timer.new(self)
89
+ connect( @timer, SIGNAL(:timeout), self, SLOT(:updateTask) )
90
+ @timer.start(1000) # 1000 msec = 1 sec
91
+ end
92
+
93
+
94
+ #
95
+ # make menus for MainWindow
96
+ #
97
+ def createMenu
98
+
99
+ # File menu
100
+ recordAction = @actions.addNew(i18n('Start Download'), self, \
101
+ { :icon => 'arrow-down', :triggered => :startDownload })
102
+ reloadStyleAction = @actions.addNew(i18n('&Reload StyleSheet'), self, \
103
+ { :icon => 'view-refresh', :shortCut => 'Ctrl+R', :triggered => :reloadStyleSheet })
104
+ clearStyleAction = @actions.addNew(i18n('&Clear StyleSheet'), self, \
105
+ { :icon => 'list-remove', :shortCut => 'Ctrl+L', :triggered => :clearStyleSheet })
106
+ quitAction = @actions.addNew(i18n('&Quit'), self, \
107
+ { :icon => 'application-exit', :shortCut => 'Ctrl+Q', :triggered => :close })
108
+
109
+ fileMenu = KDE::Menu.new('&File', self)
110
+ fileMenu.addAction(recordAction)
111
+ fileMenu.addAction(reloadStyleAction)
112
+ fileMenu.addAction(clearStyleAction)
113
+ fileMenu.addAction(quitAction)
114
+
115
+
116
+ # settings menu
117
+ playerDockAction = @playerDock.toggleViewAction
118
+ playerDockAction.text = i18n('Show Player')
119
+ configureAppAction = @actions.addNew(i18n('Configure %s') % APP_NAME, self, \
120
+ { :icon => 'configure', :shortCut => 'F2', :triggered => :configureApp })
121
+
122
+ settingsMenu = KDE::Menu.new(i18n('&Settings'), self)
123
+ settingsMenu.addAction(playerDockAction)
124
+ settingsMenu.addSeparator
125
+ settingsMenu.addAction(configureAppAction)
126
+
127
+
128
+ # Help menu
129
+ aboutDlg = KDE::AboutApplicationDialog.new($about)
130
+ openAboutAction = @actions.addNew(i18n('About %s') % APP_NAME, self, \
131
+ { :icon => 'irecorder', :triggered =>[aboutDlg, :exec] })
132
+ openDocUrlAction = @actions.addNew(i18n('Open Document Wiki'), self, \
133
+ { :icon => 'help-contents', :triggered =>:openDocUrl})
134
+ openReportIssueUrlAction = @actions.addNew(i18n('Report Bug'), self, \
135
+ { :icon => 'tools-report-bug', :triggered =>:openReportIssueUrl })
136
+ openRdocAction = @actions.addNew(i18n('Open Rdoc'), self, \
137
+ { :icon => 'help-contents', :triggered =>:openRdoc })
138
+ openSourceAction = @actions.addNew(i18n('Open Source Folder'), self, \
139
+ { :icon => 'document-open-folder', :triggered =>:openSource })
140
+
141
+
142
+ helpMenu = KDE::Menu.new(i18n('&Help'), self)
143
+ helpMenu.addAction(openDocUrlAction)
144
+ helpMenu.addAction(openReportIssueUrlAction)
145
+ helpMenu.addAction(openRdocAction)
146
+ helpMenu.addAction(openSourceAction)
147
+ helpMenu.addSeparator
148
+ helpMenu.addAction(openAboutAction)
149
+
150
+ # insert menus in MenuBar
151
+ menu = KDE::MenuBar.new
152
+ menu.addMenu( fileMenu )
153
+ menu.addMenu( settingsMenu )
154
+ menu.addSeparator
155
+ menu.addMenu( helpMenu )
156
+ setMenuBar(menu)
157
+ end
158
+
159
+
160
+
161
+
162
+ #-------------------------------------------------------------
163
+ #
164
+ # create Widgets for MainWindow
165
+ #
166
+ def createWidgets
167
+ @topTab = KDE::TabWidget.new
168
+
169
+ @mainTabPage = Qt::Splitter.new
170
+ @topTab.addTab(@mainTabPage, 'Channels')
171
+
172
+ @mainTabPage.addWidget(createChannelAreaWidget)
173
+
174
+ # Main Tab page. programme table area
175
+ @progTableFrame = Qt::Splitter.new(Qt::Vertical)
176
+ @progTableFrame.addWidget(createProgrammeAreaWidget)
177
+ @progTableFrame.addWidget(createProgrammeSummaryWidget)
178
+ @mainTabPage.addWidget(@progTableFrame)
179
+
180
+ # parameter : Qt::Splitter.setStretchFactor( int index, int stretch )
181
+ @mainTabPage.setStretchFactor( 0, 0 )
182
+ @mainTabPage.setStretchFactor( 1, 1 )
183
+
184
+ # dock
185
+ createPlayerDock
186
+
187
+
188
+ # Top Tab - Task Page
189
+ @taskWin = TaskWindow.new
190
+ @topTab.addTab(@taskWin, 'Task')
191
+
192
+ # Top Tab - Log Page
193
+ @logWin = LogWindow.new
194
+ @topTab.addTab(@logWin, 'Log')
195
+
196
+
197
+ # set Top Widget & Layout
198
+ setCentralWidget(@topTab)
199
+ end
200
+
201
+
202
+ #-------------------------------------------------------------
203
+ #
204
+ TvType, RadioType, RadioCategoryType = [-1,0,1] # TvType = -1 (hide)
205
+ TVChannelRssTbl = [
206
+ ['BBC One', 'bbc_one' ],
207
+ ['BBC Two', 'bbc_two' ],
208
+ ['BBC Three', 'bbc_three' ],
209
+ ['BBC Four', 'bbc_four' ],
210
+ ['CBBC', 'cbbc'],
211
+ ['CBeebies', 'cbeebies'],
212
+ ['BBC News Channel', 'bbc_news24'],
213
+ ['BBC Parliament', 'bbc_parliament'],
214
+ ['BBC HD', 'bbc_hd'],
215
+ ['BBC ALBA', 'bbc_alba'] ]
216
+
217
+ RadioChannelRssTbl = [
218
+ ['BBC Radio 1', 'bbc_radio_one'],
219
+ ['BBC 1Xtra', 'bbc_1xtra'],
220
+ ['BBC Radio 2', 'bbc_radio_two'],
221
+ ['BBC Radio 3', 'bbc_radio_three'],
222
+ ['BBC Radio 4', 'bbc_radio_four'],
223
+ ['BBC Radio 5 live', 'bbc_radio_five_live'],
224
+ ['BBC Radio 5 live sports extra', 'bbc_radio_five_live_sports_extra'],
225
+ ['BBC 6 Music', 'bbc_6music'],
226
+ ['BBC Radio 7', 'bbc_7'],
227
+ ['BBC Asian Network', 'bbc_asian_network'],
228
+ ['BBC World service', 'bbc_world_service'],
229
+ ['BBC Radio Scotland', 'bbc_alba/scotland'],
230
+ ['BBC Radio Nan Gàidheal', 'bbc_radio_nan_gaidheal'],
231
+ ['BBC Radio Ulster', 'bbc_radio_ulster'],
232
+ ['BBC Radio Foyle', 'bbc_radio_foyle'],
233
+ ['BBC Radio Wales', 'bbc_radio_wales'],
234
+ ['BBC Radio Cymru', 'bbc_radio_cymru'] ]
235
+
236
+ CategoryRssTbl = [
237
+ ['Children\'s', 'childrens'],
238
+ ['Comedy', 'comedy'],
239
+ ['Drama', 'drama'],
240
+ ['Entertainment', 'entertainment'],
241
+ ['Factual', 'factual'],
242
+ ['Films', 'films'],
243
+ ['Music', 'music'],
244
+ ['News', 'news'],
245
+ ['Learning', 'learning'],
246
+ ['Religion & Ethics', 'religion_and_ethics'],
247
+ ['Sport', 'sport'],
248
+ ['Sign Zone', 'signed'],
249
+ ['Audio Described', 'audiodescribed'],
250
+ ['Northern Ireland', 'northern_ireland'],
251
+ ['Scotland', 'scotland'],
252
+ ['Wales', 'wales'] ]
253
+
254
+ CategoryNameTbl = CategoryRssTbl.map do |c|
255
+ c[0][/[\w\s\'&]+/].gsub(/\'/, '').gsub(/&/, ' and ')
256
+ end
257
+ #
258
+ def createChannelListToolBox
259
+ toolBox = Qt::ToolBox.new do |w|
260
+ w.objectName = 'channelToolBox'
261
+ end
262
+
263
+ # default value
264
+ toolBox.currentIndex = 2
265
+
266
+ # TV & Radio Channels selector
267
+ # @tvChannelListBox = KDE::ListWidget.new
268
+ # # TV Channels
269
+ # @tvChannelListBox.addItems( TVChannelRssTbl.map do |w| w[0] end )
270
+ # toolBox.addItem( @tvChannelListBox, 'TV Channels' )
271
+
272
+
273
+ # Radio Channels
274
+ @radioChannelListBox = KDE::ListWidget.new
275
+ @radioChannelListBox.addItems( RadioChannelRssTbl.map do |w| w[0] end )
276
+ toolBox.addItem( @radioChannelListBox, 'Radio Channels' )
277
+
278
+ # Category selector
279
+ @categoryListBox = KDE::ListWidget.new
280
+ @categoryListBox.addItems( CategoryRssTbl.map do |w| w[0] end )
281
+ toolBox.addItem( @categoryListBox, 'Radio Categories' )
282
+
283
+ toolBox
284
+ end
285
+
286
+ #-------------------------------------------------------------
287
+ #
288
+ # [ All / Highlights / Most Popular ] selector
289
+ #
290
+ def createListTypeButtons
291
+ @listTypeGroup = Qt::ButtonGroup.new
292
+ allBtn = KDE::PushButton.new('All') do |w|
293
+ w.objectName = 'switchButton'
294
+ w.checkable = true
295
+ w.autoExclusive = true
296
+ connect(w, SIGNAL(:clicked), self, SLOT(:getList))
297
+ end
298
+
299
+ highlightsBtn = KDE::PushButton.new('Highlights') do |w|
300
+ w.objectName = 'switchButton'
301
+ w.setMinimumWidth(80)
302
+ w.checkable = true
303
+ w.autoExclusive = true
304
+ connect(w, SIGNAL(:clicked), self, SLOT(:getList))
305
+ end
306
+
307
+ mostBtn = KDE::PushButton.new('Most Popular') do |w|
308
+ w.objectName = 'switchButton'
309
+ w.checkable = true
310
+ w.autoExclusive = true
311
+ connect(w, SIGNAL(:clicked), self, SLOT(:getList))
312
+ end
313
+ listTypeHLayout = Qt::HBoxLayout.new
314
+ listTypeHLayout.addWidget(allBtn, 54) # 2nd parameter is stretch.
315
+ listTypeHLayout.addWidget(highlightsBtn, 180)
316
+ listTypeHLayout.addWidget(mostBtn,235)
317
+
318
+ @listTypeGroup.addButton(allBtn, 0)
319
+ @listTypeGroup.addButton(highlightsBtn, 1)
320
+ @listTypeGroup.addButton(mostBtn, 2)
321
+ @listTypeGroup.exclusive = true
322
+
323
+ listTypeHLayout
324
+ end
325
+
326
+
327
+ #-------------------------------------------------------------
328
+ #
329
+ # Left Side Channel ToolBox & ListType Buttons
330
+ def createChannelAreaWidget
331
+ VBoxLayoutWidget.new do |vb|
332
+ @channelTypeToolBox = createChannelListToolBox
333
+ vb.addWidget(@channelTypeToolBox)
334
+ vb.addLayout(createListTypeButtons)
335
+ end
336
+ end
337
+
338
+ #-------------------------------------------------------------
339
+ #
340
+ #
341
+ def createProgrammeAreaWidget
342
+ VBoxLayoutWidget.new do |vbxw|
343
+ @programmeTable = ProgrammeTableWidget.new do |w|
344
+ connect(w, SIGNAL('cellClicked(int,int)'),
345
+ self, SLOT('programmeCellClicked(int,int)'))
346
+ end
347
+ vbxw.addWidget(HBoxLayoutWidget.new do |hw|
348
+ hw.addWidget(Qt::Label.new(i18n('Look for:')))
349
+ hw.addWidget(
350
+ @filterLineEdit = KDE::LineEdit.new do |w|
351
+ connect(w,SIGNAL('textChanged(const QString &)'),
352
+ @programmeTable, SLOT('filterChanged(const QString &)'))
353
+ w.setClearButtonShown(true)
354
+ connect(@programmeTable, SIGNAL('filterRequest(const QString &)'),
355
+ w, SLOT('setText(const QString &)'))
356
+ end
357
+ )
358
+ end
359
+ )
360
+ vbxw.addWidget( @listTitleLabel = Qt::Label.new('') )
361
+ vbxw.addWidget(@programmeTable)
362
+
363
+ playIcon = KDE::Icon.new(':images/play-22.png')
364
+ playBtn = KDE::PushButton.new( playIcon, i18n("Play")) do |w|
365
+ w.objectName = 'playButton'
366
+ connect( w, SIGNAL(:clicked), self, SLOT(:playProgramme) )
367
+ end
368
+
369
+ # 'Start Download' Button
370
+ downloadIcon = KDE::Icon.new(':images/download-22.png')
371
+ downloadBtn = KDE::PushButton.new( downloadIcon, i18n("Download")) do |w|
372
+ w.objectName = 'downloadButton'
373
+ connect( w, SIGNAL(:clicked), self, SLOT(:startDownload) )
374
+ end
375
+
376
+ vbxw.addWidgets( nil, playBtn, nil, downloadBtn, nil )
377
+ end
378
+ end
379
+
380
+ #-------------------------------------------------------------
381
+ #
382
+ #
383
+ def createProgrammeSummaryWidget
384
+ @programmeSummaryWebView = Qt::WebView.new do |w|
385
+ w.page.linkDelegationPolicy = Qt::WebPage::DelegateAllLinks
386
+ end
387
+ end
388
+
389
+ #-------------------------------------------------------------
390
+ #
391
+ #
392
+ def createPlayerDock
393
+ @playerDock = Qt::DockWidget.new(self) do |w|
394
+ w.objectName = 'playerDock'
395
+ w.windowTitle = i18n('Player')
396
+ w.allowedAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea
397
+ w.floating = true
398
+ w.hide
399
+ end
400
+ @playerWevView = Qt::WebView.new do |w|
401
+ w.page.linkDelegationPolicy = Qt::WebPage::DelegateAllLinks
402
+ end
403
+
404
+ webSettings = Qt::WebSettings::globalSettings
405
+ webSettings.setAttribute(Qt::WebSettings::PluginsEnabled, true)
406
+
407
+
408
+ @playerDock.setWidget(@playerWevView)
409
+ self.addDockWidget(Qt::RightDockWidgetArea, @playerDock)
410
+
411
+ @playerDock
412
+ end
413
+
414
+ #-------------------------------------------------------------
415
+ #
416
+ #
417
+ def createDlg
418
+ @settingsDlg = SettingsDlg.new(self)
419
+ end
420
+
421
+
422
+ slots :configureApp
423
+ # slot
424
+ def configureApp
425
+ @settingsDlg.exec
426
+ end
427
+
428
+
429
+ #-------------------------------------------------------------
430
+ #
431
+ # virtual function slot
432
+ def closeEvent(event)
433
+ writeSettings
434
+ super(event)
435
+ $config.sync # important! qtruby can't invoke destructor properly.
436
+ end
437
+
438
+ def readSettings
439
+ config = $config.group(GroupName)
440
+ @mainTabPage.restoreState(config.readEntry('MainTabPageState', @mainTabPage.saveState))
441
+ @progTableFrame.restoreState(config.readEntry('ProgTableFrame',
442
+ @progTableFrame.saveState))
443
+ @channelTypeToolBox.currentIndex = config.readEntry('ChannelType', @channelTypeToolBox.currentIndex)
444
+
445
+ @programmeTable.readSettings
446
+ @taskWin.readSettings
447
+ end
448
+
449
+ def writeSettings
450
+ config = $config.group(GroupName)
451
+ config.writeEntry('MainTabPageState', @mainTabPage.saveState)
452
+ config.writeEntry('ProgTableFrame', @progTableFrame.saveState)
453
+ config.writeEntry('ChannelType', @channelTypeToolBox.currentIndex)
454
+
455
+ @programmeTable.writeSettings
456
+ @taskWin.writeSettings
457
+ # dumpConfig(GroupName)
458
+ end
459
+
460
+ def dumpConfig(group)
461
+ puts "dump #{group} config"
462
+ entries = KDE::Global.config.entryMap(group)
463
+ confGroup = KDE::Global.config.group(group)
464
+ entries.each do |key, val|
465
+ puts " key(#{key}) = #{val.inspect}"
466
+ end
467
+ end
468
+
469
+ # ------------------------------------------------------------------------
470
+ slots :reloadStyleSheet
471
+ def reloadStyleSheet
472
+ styleStr = IO.read(APP_DIR + '/resources/bbcstyle.qss')
473
+ $app.styleSheet = styleStr
474
+ $app.styleSheet = styleStr
475
+ $log.info { 'Reloaded StyleSheet.' }
476
+ end
477
+
478
+ slots :clearStyleSheet
479
+ def clearStyleSheet
480
+ $app.styleSheet = nil
481
+ $log.info { 'Cleared StyleSheet.' }
482
+ end
483
+
484
+
485
+ # slot :
486
+ slots 'programmeCellClicked(int,int)'
487
+ def programmeCellClicked(row, column)
488
+ prog = @programmeTable[row]
489
+ color = "#%06x" % (@programmeSummaryWebView.palette.color(Qt::Palette::Text).rgb & 0xffffff)
490
+
491
+ html = <<-EOF
492
+ <style type="text/css">
493
+ img { float: left; margin: 4px; }
494
+ </style>
495
+ <font color="#{color}">
496
+ #{prog.content}
497
+ </font>
498
+ EOF
499
+
500
+ @programmeSummaryWebView.setHtml(html)
501
+ end
502
+
503
+
504
+
505
+ def makeProcCommand(command, url)
506
+ cmd, args = command.split(/\s+/, 2)
507
+ args = args.split(/\s+/).map do |a|
508
+ a.gsub(/%\w/, url)
509
+ end
510
+ [ cmd, args ]
511
+ end
512
+
513
+ slots :playProgramme
514
+ def playProgramme
515
+ items = @programmeTable.selectedItems
516
+ return unless items.size > 0
517
+
518
+
519
+ def getIplayerUrl(prog)
520
+ # big type console
521
+ url = prog.link
522
+ $log.info { "big console Url : #{url}" }
523
+
524
+ # old type console
525
+ url = prog.content[UrlRegexp] # String[] method extract only 1st one.
526
+ $log.info { "episode Url : #{url}" }
527
+ url = BBCNet.getPlayerConsoleUrl(url)
528
+ $log.info { "old console Url : #{url}" }
529
+
530
+ if IRecSettings.playerTypeBeta then
531
+ url.sub!(%r{www}, 'beta')
532
+ $log.info { "new console Url : #{url}" }
533
+ end
534
+ url
535
+ end
536
+
537
+ prog = @programmeTable[items[0].row]
538
+ webPlayerCommand = IRecSettings.webPlayerCommand
539
+ directPlayerCommand = IRecSettings.directPlayerCommand
540
+
541
+ begin
542
+ if IRecSettings.useInnerPlayer then
543
+ url = getIplayerUrl(prog)
544
+
545
+ @playerDock.show
546
+ @playerWevView.setUrl(Qt::Url.new(url))
547
+ elsif IRecSettings.useWebPlayer and webPlayerCommand then
548
+ $log.info { "Play on web browser" }
549
+ url = getIplayerUrl(prog)
550
+ cmd, args = makeProcCommand(webPlayerCommand, url)
551
+ $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
552
+ proc = Qt::Process.new(self)
553
+ proc.start(cmd, args)
554
+
555
+ elsif IRecSettings.useDirectPlayer and directPlayerCommand then
556
+ $log.info { "Play direct" }
557
+ url = prog.content[UrlRegexp] # String[] method extract only 1st one.
558
+
559
+ $log.info { "episode Url : #{url}" }
560
+ minfo = BBCNet::CacheMetaInfoDevice.read(url)
561
+ $log.debug { "#{minfo.inspect}" }
562
+ raise "No stream Url" unless minfo.wma
563
+ url = minfo.wma.url
564
+
565
+ cmd, args = makeProcCommand(directPlayerCommand, url)
566
+
567
+ $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
568
+ proc = Qt::Process.new(self)
569
+ proc.start(cmd, args)
570
+ end
571
+ rescue => e
572
+ if e.kind_of? RuntimeError
573
+ $log.info { e.message }
574
+ else
575
+ $log.error { e }
576
+ end
577
+ # some messages must be treated.
578
+ # already expired.
579
+ # some xml error.
580
+ # no url
581
+ passiveMessage(i18n("There is no direct stream for this programme.\n %s" %[prog.title]))
582
+ end
583
+ end
584
+
585
+ slots :openDocUrl
586
+ def openDocUrl
587
+ openUrlDocument('http://github.com/rubytwiddler/irecorder/wiki')
588
+ end
589
+
590
+ slots :openReportIssueUrl
591
+ def openReportIssueUrl
592
+ openUrlDocument('http://github.com/rubytwiddler/irecorder/issues')
593
+ end
594
+
595
+ slots :openRdoc
596
+ def openRdoc
597
+ @@gemPath ||= %x{ gem environment gempath }.split(/:/)
598
+ relPath = '/doc/' + APP_NAME + '-' + APP_VERSION + '/rdoc/index.html'
599
+ topPath = @@gemPath.find do |p|
600
+ File.exist?(p + relPath)
601
+ end
602
+ return unless topPath
603
+ openUrlDocument(topPath + relPath)
604
+ end
605
+
606
+ slots :openSource
607
+ def openSource
608
+ openDirectory(APP_DIR)
609
+ end
610
+
611
+ def openUrlDocument(url)
612
+ webPlayerCommand = IRecSettings.webPlayerCommand
613
+ cmd, args = makeProcCommand(webPlayerCommand, url)
614
+ $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
615
+ proc = Qt::Process.new(self)
616
+ proc.start(cmd, args)
617
+ end
618
+
619
+ # ------------------------------------------------------------------------
620
+ #
621
+ # slot: called when 'Get List' Button clicked signal invoked.
622
+ #
623
+ public
624
+ slots :getList
625
+ def getList
626
+ feedAdr = getFeedAdr
627
+ return if feedAdr.nil?
628
+
629
+ $log.info{ "feeding from '#{feedAdr}'" }
630
+
631
+ begin
632
+ makeTablefromRss( CacheRssDevice.read(feedAdr) )
633
+ rescue => e
634
+ $log.error { e }
635
+ end
636
+ setListTitle
637
+ end
638
+
639
+
640
+ #
641
+ # get feed address
642
+ #
643
+ protected
644
+ def getFeedAdr
645
+ @channelType = @channelTypeToolBox.currentIndex
646
+
647
+ channelStr = nil
648
+ case @channelType
649
+ when TvType
650
+ # get TV channel
651
+ @channelIndex = @tvChannelListBox.currentRow
652
+ channelStr = TVChannelRssTbl[ @channelIndex ][1]
653
+ when RadioType
654
+ # get Radio channel
655
+ @channelIndex = @radioChannelListBox.currentRow
656
+ channelStr = RadioChannelRssTbl[ @channelIndex ][1]
657
+ when RadioCategoryType
658
+ # get Category
659
+ @channelIndex = @categoryListBox.currentRow
660
+ channelStr = 'categories/' + CategoryRssTbl[ @channelIndex ][1] + '/radio'
661
+ end
662
+
663
+ return nil if channelStr.nil?
664
+
665
+ @listType = @listTypeGroup.checkedId
666
+ list = %w[ list highlights popular ][@listType]
667
+
668
+ "http://feeds.bbc.co.uk/iplayer/#{channelStr}/#{list}"
669
+ end
670
+
671
+
672
+ def getCategoryTitle
673
+ CategoryRssTbl[ @channelIndex ][0]
674
+ end
675
+
676
+ def setListTitle
677
+ names = []
678
+ if getChannelTitle
679
+ names << getChannelTitle
680
+ else
681
+ names << CategoryRssTbl[ @channelIndex ][0]
682
+ end
683
+ names << %w[ All Highlights Popular ][@listType]
684
+ @listTitleLabel.text = names.join(' / ')
685
+ end
686
+
687
+ protected
688
+ def makeTablefromRss(rss)
689
+ @programmeTable.clearContents
690
+ @programmeTable.rowCount = 0
691
+ @filterLineEdit.clear
692
+ entries = rss.css('entry')
693
+ return unless rss and entries and entries.size
694
+
695
+ sortFlag = @programmeTable.sortingEnabled
696
+ @programmeTable.sortingEnabled = false
697
+ @programmeTable.hide
698
+ @programmeTable.rowCount = entries.size
699
+
700
+ # ['Title', 'Category', 'Updated' ]
701
+ entries.each_with_index do |i, r|
702
+ title = i.at_css('title').content
703
+ updated = i.at_css('updated').content
704
+ contents = i.at_css('content').content
705
+ linkItem = i.css('link').find do |l| l['rel'] == 'self' end
706
+ link = linkItem ? linkItem['href'] : nil
707
+ categories = i.css('category').map do |c| c['term'] end.join(',')
708
+ $log.misc { title }
709
+ @programmeTable.addEntry( r, title, categories, updated, contents, link )
710
+ end
711
+
712
+ @programmeTable.sortingEnabled = sortFlag
713
+ @programmeTable.show
714
+ end
715
+
716
+
717
+
718
+ # ------------------------------------------------------------------------
719
+ #
720
+ # slot : when 'Download' Button pressed.
721
+ #
722
+ # Start Downloading
723
+ #
724
+ public
725
+ slots :startDownload
726
+ def startDownload
727
+ rowsSet = {} # use Hash as Set.
728
+ @programmeTable.selectedItems.each do |i| rowsSet[i.row] = true end
729
+
730
+ titles = {}
731
+ rowsSet.keys.map do |r|
732
+ @programmeTable[r]
733
+ end .each do |p| titles[p.title] = p end
734
+
735
+ titles.each_value do |prog|
736
+ begin
737
+ url = prog.content[UrlRegexp] # String[] method extract only 1st one.
738
+
739
+ $log.info { "episode Url : #{url}" }
740
+ minfo = BBCNet::CacheMetaInfoDevice.read(url)
741
+ url = minfo.wma.url
742
+
743
+ fName = getSaveName(prog, 'wma')
744
+ $log.info { "save name : #{fName}" }
745
+
746
+ startDownOneFile(minfo, fName)
747
+
748
+ passiveMessage(i18n("Start Download programme '%s'") % [prog.title])
749
+
750
+ rescue Timeout::Error, StandardError => e
751
+ if e.kind_of? RuntimeError
752
+ $log.info { e.message }
753
+ else
754
+ $log.error { e }
755
+ end
756
+ passiveMessage(i18n("There is no direct stream for this programme.\n%s" %[prog.title]))
757
+ end
758
+ end
759
+ end
760
+
761
+
762
+ protected
763
+ #
764
+ def getSaveName(prog, ext='wma')
765
+ tags = prog.categories.split(/,/)
766
+ dir = getSaveSubDirName(tags)
767
+ $log.debug { "save dir : #{dir}" }
768
+
769
+ dir + '/' + getSaveBaseName(prog.title, tags, ext)
770
+ end
771
+
772
+ def getSaveBaseName(title, tags, ext)
773
+ s = IRecSettings
774
+ head = s.fileAddHeadStr
775
+ head += getMediaName(tags) + ' ' if s.fileAddMediaName
776
+ head += getChannelName + ' ' if s.fileAddChannelName and getChannelName
777
+ head += getGenreName(tags) + ' ' if s.fileAddGenreName and getGenreName(tags)
778
+ head += "- " unless head.empty?
779
+ baseName = head + title + '.' + ext
780
+ baseName.gsub(%r{[\/]}, '-')
781
+ end
782
+
783
+ #
784
+ def getSaveSubDirName(tags)
785
+ s = IRecSettings
786
+ dir = []
787
+ dir << getMediaName(tags) if s.dirAddMediaName
788
+ dir << getChannelName if s.dirAddChannelName and getChannelName
789
+ dir << getGenreName(tags) if s.dirAddGenreName and getGenreName(tags)
790
+ File.join(dir.compact)
791
+ end
792
+
793
+ # media [TV,Radio,iPod]
794
+ def getMediaName(tags)
795
+ tags.find do |t|
796
+ %w(radio tv ipod).include?(t.downcase)
797
+ end
798
+ end
799
+
800
+ # channel [BBC Radio 4, ..]
801
+ def getChannelName
802
+ getChannelTitle
803
+ end
804
+
805
+ def getChannelTitle
806
+ case @channelType
807
+ when TvType
808
+ # get TV channel
809
+ TVChannelRssTbl[ @channelIndex ][0]
810
+ when RadioType
811
+ # get Radio channel
812
+ RadioChannelRssTbl[ @channelIndex ][0]
813
+ else
814
+ nil
815
+ end
816
+ end
817
+
818
+ # Main Genre [Drama, Comedy, ..]
819
+ def getGenreName(tags)
820
+ CategoryNameTbl.find do |cat|
821
+ tags.find do |t|
822
+ cat =~ /#{t}/i
823
+ end
824
+ end
825
+ end
826
+
827
+
828
+ # other genre [Sitcom, SF, etc]
829
+ def getSubGenreName(tags)
830
+ end
831
+
832
+
833
+
834
+
835
+
836
+ #-------------------------------------------------------------------
837
+ # parameter
838
+ # metaInfo : source stream MetaInfo of download file.
839
+ # fName : save file name.
840
+ #
841
+ # start Dwnload One file.
842
+ protected
843
+ def startDownOneFile(metaInfo, fName)
844
+ process = DownloadProcess.new(self, metaInfo, fName)
845
+ process.taskItem = @taskWin.addTask(process)
846
+ process.beginTask
847
+ end
848
+
849
+
850
+
851
+
852
+ #
853
+ # slot : periodically called to update task view.
854
+ #
855
+ protected
856
+ slots :updateTask
857
+ def updateTask
858
+ @taskWin.each do |task|
859
+ task.process.updateView
860
+ end
861
+ end
862
+ end
863
+
864
+
865
+ #
866
+ # main start
867
+ #
868
+
869
+ $about = KDE::AboutData.new(APP_NAME, APP_NAME, KDE::ki18n(APP_NAME), APP_VERSION,
870
+ KDE::ki18n('BBC iRecorder KDE')
871
+ )
872
+ $about.setProgramIconName(':images/irecorder-22.png')
873
+ $about.addLicenseTextFile(APP_DIR + '/MIT-LICENSE')
874
+ KDE::CmdLineArgs.init(ARGV, $about)
875
+ # options = KDE::CmdLineOptions.new()
876
+ # options.add( "+url", KDE::ki18n( "The url to record)" ),"")
877
+
878
+ $app = KDE::Application.new
879
+ args = KDE::CmdLineArgs.parsedArgs()
880
+ $config = KDE::Global::config
881
+ win = MainWindow.new
882
+ $app.setTopWidget(win)
883
+
884
+ win.show
885
+ $app.exec