irecorder 0.0.7

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