irecorder 0.0.4-linux

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2009 ruby.twiddler@gmail.com
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,37 @@
1
+ BBC iPlayer like audio recorder with KDE GUI.
2
+ You can browse BBC Radio programmes and click to download stream file.
3
+ file will be converted mp3 files automatically.
4
+ iplayer allow to play without any other browser or play on your prefered browser.
5
+
6
+ Installation
7
+ ============
8
+ install most important package kdebindings4.
9
+ on Mandriva Linux use urpmi command.
10
+
11
+ # sudo urpmi ruby-kde4 ruby-qt4
12
+
13
+ install mplayer ffmpeg exiftool packages if necessary
14
+
15
+ # sudo urpmi mplayer ffmpeg perl-Image-ExifTool
16
+
17
+ install nokogiri gem.
18
+ # sudo gem install nokogiri
19
+
20
+ Finally you can install irecorder.
21
+
22
+ # sudo gem install irecorder
23
+ or directly specify your downloaded gem file.
24
+ # sudo gem install -l irecorder-0.0.x.gem
25
+
26
+ launch irecorder just type irecorder.rb
27
+
28
+ # irecorder.rb
29
+
30
+
31
+ Build Gem
32
+ ============
33
+ if you want to gem package from source, use rake command.
34
+
35
+ # rake gem
36
+
37
+
@@ -0,0 +1,67 @@
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.4"
10
+ s.author = "ruby.twiddler"
11
+ s.email = "ruby.twiddler at gmail.com"
12
+ s.platform = "linux"
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.license = "MIT-LICENSE"
18
+ s.require_path = "lib"
19
+ s.requirements = %w{ korundum4 qtwebkit kio }
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
+ iplayer allow to play without any other browser or on your prefered media player
26
+ like mplayer.
27
+ irecorder require kdebindings.
28
+ svn://anonsvn.kde.org/home/kde/branches/KDE/4.4/kdebindings
29
+ http://websvn.kde.org/branches/KDE/4.4/kdebindings/
30
+ please check your distro package to install it.
31
+ EOF
32
+ s.has_rdoc = false
33
+ s.extra_rdoc_files = ["README"]
34
+ end
35
+
36
+ package = Rake::GemPackageTask.new(spec) do |pkg|
37
+ pkg.need_tar = true
38
+ end
39
+
40
+ desc "install as gem package"
41
+ task :installgem => :gem do
42
+ system("gem install -r pkg/" + package.gem_file )
43
+ end
44
+
45
+ desc "install for rpm"
46
+ task :install4rpm do
47
+ def installToDir(file, dir, options={})
48
+ # puts "copy #{file} => #{dir}"
49
+ FileUtils.mkdir_p(File.join(dir, File.dirname(file)))
50
+ FileUtils.install(file, File.join(dir, File.dirname(file)), options)
51
+ end
52
+
53
+ prefix = ENV['prefix']
54
+ conf = Config::CONFIG
55
+ destDir = conf['sitelibdir'].sub(/#{conf['prefix']}/, prefix)
56
+ destDir = File.join(destDir, spec.name)
57
+
58
+ files = FileList["{bin,lib}/**/*"].to_a
59
+ files += %w{ resources/bbcstyle.qss }
60
+ files.each do |f|
61
+ # install files.
62
+ installToDir(f, destDir)
63
+ end
64
+
65
+ installToDir('bin/irecorder', prefix, :mode => 0755)
66
+ end
67
+
@@ -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,994 @@
1
+ #!/usr/bin/ruby
2
+ #
3
+ # 2010 by ruby.twiddler@gmail.com
4
+ #
5
+ # IPlayer interface
6
+ # record real/wma (rtsp/mms) audio/video 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.3"
16
+
17
+ # standard libs
18
+ require 'rubygems'
19
+ require 'uri'
20
+ require 'net/http'
21
+ require 'open-uri'
22
+ require 'rss'
23
+ require 'shellwords'
24
+ require 'fileutils'
25
+ require 'singleton'
26
+
27
+ # additional libs
28
+ require 'korundum4'
29
+ require 'qtwebkit'
30
+
31
+ #
32
+ # my libraries and programs
33
+ #
34
+ $:.unshift(LIB_DIR)
35
+ require "bbcnet"
36
+ require "mylibs"
37
+ require "logwin"
38
+ require "taskwin"
39
+ require "download"
40
+ require "settings"
41
+
42
+
43
+
44
+
45
+
46
+ #---------------------------------------------------------------------------------------------
47
+ #---------------------------------------------------------------------------------------------
48
+
49
+
50
+
51
+
52
+
53
+ #---------------------------------------------------------------------------------------------
54
+ #
55
+ #
56
+ class ProgrammeTableWidget < Qt::TableWidget
57
+ slots 'filterChanged(const QString &)'
58
+
59
+ #
60
+ #
61
+ class Programme
62
+ attr_reader :titleItem, :categoriesItem, :updatedItem
63
+ attr_reader :content, :link
64
+
65
+ def initialize(title, categories, updated, content, link)
66
+ @titleItem = Item.new(title)
67
+ @categoriesItem = Item.new(categories)
68
+ @updatedItem = Item.new(updated)
69
+ @content = content
70
+ @link = link
71
+ end
72
+
73
+ def title
74
+ @titleItem.text
75
+ end
76
+
77
+ def categories
78
+ @categoriesItem.text
79
+ end
80
+
81
+ def updated
82
+ @updatedItem.text
83
+ end
84
+ end
85
+
86
+ #
87
+ #
88
+ class Item < Qt::TableWidgetItem
89
+ def initialize(text)
90
+ super(text)
91
+ self.flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled
92
+ self.toolTip = text
93
+ end
94
+ end
95
+
96
+
97
+ #------------------------------------------------------------------------
98
+ #
99
+ #
100
+ attr_accessor :mediaFilter
101
+
102
+ def initialize()
103
+ super(0, 3)
104
+
105
+ setHorizontalHeaderLabels(['Title', 'Category', 'Updated'])
106
+ self.horizontalHeader.stretchLastSection = true
107
+ self.selectionBehavior = Qt::AbstractItemView::SelectRows
108
+ self.alternatingRowColors = true
109
+ self.sortingEnabled = true
110
+ sortByColumn(2, Qt::DescendingOrder )
111
+
112
+ @mediaFilter = ''
113
+
114
+ # Hash table : key column_0_item => Programme entry.
115
+ @table = Hash.new
116
+ end
117
+
118
+ def addEntry( row, title, categories, updated, content, link )
119
+ entry = Programme.new(title, categories, updated, content, link)
120
+ setItem( row, 0, entry.titleItem )
121
+ setItem( row, 1, entry.categoriesItem )
122
+ setItem( row, 2, entry.updatedItem )
123
+ @table[entry.titleItem] = entry
124
+ end
125
+
126
+ # return Programme object.
127
+ def [](row)
128
+ @table[item(row,0)]
129
+ end
130
+
131
+
132
+ #
133
+ # slot : called when filterLineEdit text is changed.
134
+ #
135
+ public
136
+
137
+ def filterChanged(text)
138
+ return unless text
139
+
140
+ text += ' ' + @mediaFilter unless @mediaFilter.empty?
141
+
142
+ regxs = text.split(/[,\s]+/).map do |w|
143
+ /#{Regexp.escape(w.strip)}/i
144
+ end
145
+ rowCount.times do |r|
146
+ i0 = item(r,0)
147
+ i1 = item(r,1)
148
+ i2 = item(r,2)
149
+ txt = ((i0 && i0.text) || '') + ((i1 && i1.text) || '') + ((i2 && i2.text) || '')
150
+ if regxs.all? do |rx| rx =~ txt end then
151
+ showRow(r)
152
+ else
153
+ hideRow(r)
154
+ end
155
+ end
156
+ end
157
+
158
+ GroupName = "ProgrammeTable"
159
+ def writeSettings
160
+ config = $config.group(GroupName)
161
+ config.writeEntry('Header', horizontalHeader.saveState)
162
+ end
163
+
164
+ def readSettings
165
+ config = $config.group(GroupName)
166
+ horizontalHeader.restoreState(config.readEntry('Header', horizontalHeader.saveState))
167
+ end
168
+
169
+ protected
170
+ def contextMenuEvent(e)
171
+ item = itemAt(e.pos)
172
+ menu = createPopup
173
+ execPopup(menu, e.globalPos, item)
174
+ end
175
+
176
+ def createPopup()
177
+ menu = Qt::Menu.new
178
+ insertPlayerActions(menu)
179
+ menu
180
+ end
181
+
182
+ def execPopup(menu, pos, item)
183
+ action = menu.exec(pos)
184
+ if action then
185
+ action.data
186
+ # $log.code { "execute : '#{action.vData}'" }
187
+ cmd, exe = action.vData.split(/@/, 2)
188
+ # $log.code { "cmd(#{cmd}), exe(#{exe})" }
189
+ case cmd
190
+ when 'play'
191
+ playMedia(exe, item)
192
+ else
193
+ # self.method(cmd).call(item)
194
+ end
195
+ end
196
+ menu.deleteLater
197
+ end
198
+
199
+ def insertPlayerActions(menu)
200
+ Mime::services('.wma').each do |s|
201
+ if s.exec then
202
+ exeName = s.exec[/\w+/]
203
+ a = menu.addAction(KDE::Icon.new(exeName), 'Play with ' + exeName)
204
+ a.setVData('play@' + s.exec)
205
+ end
206
+ end
207
+ end
208
+
209
+ def playMedia(exe, item)
210
+ begin
211
+ prog = self[item.row]
212
+ url = prog.content[UrlRegexp] # String[] method extract only 1st one.
213
+
214
+ $log.info { "episode Url : #{url}" }
215
+ minfo = BBCNet::MetaInfo.get(url).update
216
+ url = minfo.wma.url
217
+
218
+ cmd, args = exe.split(/\s+/, 2)
219
+ args = args.split(/\s+/).map do |a|
220
+ a.gsub(/%\w/, url)
221
+ end
222
+ # $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
223
+ proc = Qt::Process.new(self)
224
+ proc.start(cmd, args)
225
+
226
+ rescue => e
227
+ $log.error { e }
228
+ KDE::MessageBox::information(self, i18n("There is not direct stream for this programme."))
229
+ end
230
+ end
231
+ end
232
+
233
+
234
+
235
+
236
+ #---------------------------------------------------------------------------------------------
237
+ #---------------------------------------------------------------------------------------------
238
+ #
239
+ # Main Window Class
240
+ #
241
+ class MainWindow < KDE::MainWindow
242
+ slots :startDownload, :updateTask, :getList, :reloadStyleSheet, :clearStyleSheet
243
+ slots :mediaFilterChanged, :playProgramme
244
+ slots 'programmeCellClicked(int,int)'
245
+
246
+ GroupName = "MainWindow"
247
+
248
+ #
249
+ #
250
+ #
251
+ def initialize
252
+ super(nil)
253
+ setCaption(APP_NAME)
254
+
255
+
256
+ $app.styleSheet = IO.read(APP_DIR + '/resources/bbcstyle.qss')
257
+
258
+ createWidgets
259
+ createMenu
260
+ createDlg
261
+
262
+ # default values
263
+ # BBCNet.setProxy('http://194.36.10.154:3127')
264
+ # initialize values
265
+ $log = MyLogger.new(@logWin)
266
+ $log.level = MyLogger::INFO
267
+ $log.info { 'Log Start.' }
268
+
269
+ # assign from config file.
270
+ readSettings
271
+ setAutoSaveSettings(GroupName)
272
+
273
+ initializeTaskTimer
274
+ end
275
+
276
+
277
+
278
+ protected
279
+ def initializeTaskTimer
280
+ # Task Timer
281
+ @timer = Qt::Timer.new(self)
282
+ connect( @timer, SIGNAL(:timeout), self, SLOT(:updateTask) )
283
+ @timer.start(1000) # 1000 msec = 1 sec
284
+ end
285
+
286
+
287
+ #
288
+ # make menus for MainWindow
289
+ #
290
+ def createMenu
291
+
292
+ # File menu
293
+ recordAction = KDE::Action.new(KDE::Icon.new('arrow-down'), i18n('Start &Download'), self)
294
+ reloadStyleAction = KDE::Action.new(KDE::Icon.new('view-refresh'), i18n('&Reload StyleSheet'), self)
295
+ reloadStyleAction.setShortcut(KDE::Shortcut.new('Ctrl+R'))
296
+ clearStyleAction = KDE::Action.new(KDE::Icon.new('list-remove'), i18n('&Clear StyleSheet'), self)
297
+ clearStyleAction.setShortcut(KDE::Shortcut.new('Ctrl+L'))
298
+ quitAction = KDE::Action.new(KDE::Icon.new('exit'), i18n('&Quit'), self)
299
+ quitAction.setShortcut(KDE::Shortcut.new('Ctrl+Q'))
300
+ fileMenu = KDE::Menu.new('&File', self)
301
+ fileMenu.addAction(recordAction)
302
+ fileMenu.addAction(reloadStyleAction)
303
+ fileMenu.addAction(clearStyleAction)
304
+ fileMenu.addAction(quitAction)
305
+
306
+ # connect actions
307
+ connect(recordAction, SIGNAL(:triggered), self, SLOT(:startDownload))
308
+ connect(reloadStyleAction, SIGNAL(:triggered), self, SLOT(:reloadStyleSheet))
309
+ connect(clearStyleAction, SIGNAL(:triggered), self, SLOT(:clearStyleSheet))
310
+ connect(quitAction, SIGNAL(:triggered), self, SLOT(:close))
311
+
312
+ # settings menu
313
+ playerDockAction = @playerDock.toggleViewAction
314
+ playerDockAction.text = i18n('Show Player')
315
+ configureAppAction = KDE::Action.new(KDE::Icon.new('configure'),
316
+ i18n("Configure #{APP_NAME}"), self)
317
+ settingsMenu = KDE::Menu.new(i18n('&Settings'), self)
318
+ settingsMenu.addAction(playerDockAction)
319
+ settingsMenu.addSeparator
320
+ settingsMenu.addAction(configureAppAction)
321
+ # connect actions
322
+ connect(configureAppAction, SIGNAL(:triggered), self, SLOT(:configureApp))
323
+
324
+
325
+ # Help menu
326
+ about = i18n(<<-ABOUT
327
+ #{APP_NAME} #{APP_VERSION}
328
+
329
+ BBC iPlayer like audio (mms/rtsp) stream recorder.
330
+ ABOUT
331
+ )
332
+ helpMenu = KDE::HelpMenu.new(self, about)
333
+
334
+ # insert menus in MenuBar
335
+ menu = KDE::MenuBar.new
336
+ menu.addMenu( fileMenu )
337
+ menu.addMenu( settingsMenu )
338
+ menu.addSeparator
339
+ menu.addMenu( helpMenu.menu )
340
+ setMenuBar(menu)
341
+ end
342
+
343
+
344
+
345
+
346
+ #-------------------------------------------------------------
347
+ #
348
+ # create Widgets for MainWindow
349
+ #
350
+ def createWidgets
351
+ @topTab = KDE::TabWidget.new
352
+
353
+ @mainTabPage = Qt::Splitter.new
354
+ @topTab.addTab(@mainTabPage, 'Channels')
355
+
356
+ @mainTabPage.addWidget(createChannelAreaWidget)
357
+
358
+ # Main Tab page. programme table area
359
+ @progTableFrame = Qt::Splitter.new(Qt::Vertical)
360
+ @progTableFrame.addWidget(createProgrammeAreaWidget)
361
+ @progTableFrame.addWidget(createProgrammeSummaryWidget)
362
+ @mainTabPage.addWidget(@progTableFrame)
363
+
364
+ # parameter : Qt::Splitter.setStretchFactor( int index, int stretch )
365
+ @mainTabPage.setStretchFactor( 0, 0 )
366
+ @mainTabPage.setStretchFactor( 1, 1 )
367
+
368
+ # dock
369
+ createPlayerDock
370
+
371
+
372
+ # Top Tab - Task Page
373
+ @taskWin = TaskWindow.new
374
+ @topTab.addTab(@taskWin, 'Task')
375
+
376
+ # Top Tab - Log Page
377
+ @logWin = LogWindow.new
378
+ @topTab.addTab(@logWin, 'Log')
379
+
380
+
381
+ # set Top Widget & Layout
382
+ setCentralWidget(@topTab)
383
+ end
384
+
385
+
386
+ #-------------------------------------------------------------
387
+ #
388
+ TVChannelRssTbl = [
389
+ ['BBC One', 'bbc_one' ],
390
+ ['BBC Two', 'bbc_two' ],
391
+ ['BBC Three', 'bbc_three' ],
392
+ ['BBC Four', 'bbc_four' ],
393
+ ['CBBC', 'cbbc'],
394
+ ['CBeebies', 'cbeebies'],
395
+ ['BBC News Channel', 'bbc_news24'],
396
+ ['BBC Parliament', 'bbc_parliament'],
397
+ ['BBC HD', 'bbc_hd'],
398
+ ['BBC ALBA', 'bbc_alba'] ]
399
+
400
+ RadioChannelRssTbl = [
401
+ ['BBC Radio 1', 'bbc_radio_one'],
402
+ ['BBC 1Xtra', 'bbc_1xtra'],
403
+ ['BBC Radio 2', 'bbc_radio_two'],
404
+ ['BBC Radio 3', 'bbc_radio_three'],
405
+ ['BBC Radio 4', 'bbc_radio_four'],
406
+ ['BBC Radio 5 live', 'bbc_radio_five_live'],
407
+ ['BBC Radio 5 live sports extra', 'bbc_radio_five_live_sports_extra'],
408
+ ['BBC 6 Music', 'bbc_6music'],
409
+ ['BBC Radio 7', 'bbc_7'],
410
+ ['BBC Asian Network', 'bbc_asian_network'],
411
+ ['BBC World service', 'bbc_world_service'],
412
+ ['BBC Radio Scotland', 'bbc_alba/scotland'],
413
+ ['BBC Radio Nan Gàidheal', 'bbc_radio_nan_gaidheal'],
414
+ ['BBC Radio Ulster', 'bbc_radio_ulster'],
415
+ ['BBC Radio Foyle', 'bbc_radio_foyle'],
416
+ ['BBC Radio Wales', 'bbc_radio_wales'],
417
+ ['BBC Radio Cymru', 'bbc_radio_cymru'] ]
418
+
419
+ CategoryRssTbl = [
420
+ ['Children\'s', 'childrens'],
421
+ ['Comedy', 'comedy'],
422
+ ['Drama', 'drama'],
423
+ ['Entertainment', 'entertainment'],
424
+ ['Factual', 'factual'],
425
+ ['Films', 'films'],
426
+ ['Music', 'music'],
427
+ ['News', 'news'],
428
+ ['Learning', 'learning'],
429
+ ['Religion & Ethics', 'religion_and_ethics'],
430
+ ['Sport', 'sport'],
431
+ ['Sign Zone', 'signed'],
432
+ ['Audio Described', 'audiodescribed'],
433
+ ['Northern Ireland', 'northern_ireland'],
434
+ ['Scotland', 'scotland'],
435
+ ['Wales', 'wales'] ]
436
+
437
+ CategoryNameTbl = CategoryRssTbl.map do |c|
438
+ c[0][/[\w\s\'&]+/].gsub(/\'/, '').gsub(/&/, ' and ')
439
+ end
440
+ #
441
+ def createChannelListToolBox
442
+ toolBox = Qt::ToolBox.new do |w|
443
+ w.objectName = 'channelToolBox'
444
+ end
445
+
446
+ # TV & Radio Channels selector
447
+ @tvChannelListBox = KDE::ListWidget.new
448
+ # TV Channels
449
+ @tvChannelListBox.addItems( TVChannelRssTbl.map do |w| w[0] end )
450
+ toolBox.addItem( @tvChannelListBox, 'TV Channels' )
451
+
452
+ # Radio Channels
453
+ @radioChannelListBox = KDE::ListWidget.new
454
+ @radioChannelListBox.addItems( RadioChannelRssTbl.map do |w| w[0] end )
455
+ toolBox.addItem( @radioChannelListBox, 'Radio Channels' )
456
+
457
+ # Category selector
458
+ @categoryListBox = KDE::ListWidget.new
459
+ @categoryListBox.addItems( CategoryRssTbl.map do |w| w[0] end )
460
+ toolBox.addItem( @categoryListBox, 'Categories' )
461
+
462
+ toolBox
463
+ end
464
+
465
+ #-------------------------------------------------------------
466
+ #
467
+ # [ All / Highlights / Most Popular ] selector
468
+ #
469
+ def createListTypeButtons
470
+ @listTypeGroup = Qt::ButtonGroup.new
471
+ allBtn = KDE::PushButton.new('All') do |w|
472
+ w.objectName = 'switchButton'
473
+ w.checkable = true
474
+ w.autoExclusive = true
475
+ connect(w, SIGNAL(:clicked), self, SLOT(:getList))
476
+ end
477
+
478
+ highlightsBtn = KDE::PushButton.new('Highlights') do |w|
479
+ w.objectName = 'switchButton'
480
+ w.setMinimumWidth(80)
481
+ w.checkable = true
482
+ w.autoExclusive = true
483
+ connect(w, SIGNAL(:clicked), self, SLOT(:getList))
484
+ end
485
+
486
+ mostBtn = KDE::PushButton.new('Most Popular') do |w|
487
+ w.objectName = 'switchButton'
488
+ w.checkable = true
489
+ w.autoExclusive = true
490
+ connect(w, SIGNAL(:clicked), self, SLOT(:getList))
491
+ end
492
+ listTypeHLayout = Qt::HBoxLayout.new
493
+ listTypeHLayout.addWidget(allBtn, 54) # 2nd parameter is stretch.
494
+ listTypeHLayout.addWidget(highlightsBtn, 180)
495
+ listTypeHLayout.addWidget(mostBtn,235)
496
+
497
+ @listTypeGroup.addButton(allBtn, 0)
498
+ @listTypeGroup.addButton(highlightsBtn, 1)
499
+ @listTypeGroup.addButton(mostBtn, 2)
500
+ @listTypeGroup.exclusive = true
501
+
502
+ listTypeHLayout
503
+ end
504
+
505
+
506
+ #-------------------------------------------------------------
507
+ #
508
+ # Left Side Channel ToolBox & ListType Buttons
509
+ def createChannelAreaWidget
510
+ VBoxLayoutWidget.new do |vb|
511
+ @channelTypeToolBox = createChannelListToolBox
512
+ vb.addWidget(@channelTypeToolBox)
513
+ vb.addLayout(createListTypeButtons)
514
+ end
515
+ end
516
+
517
+ #-------------------------------------------------------------
518
+ #
519
+ #
520
+ def createProgrammeAreaWidget
521
+ VBoxLayoutWidget.new do |vbxw|
522
+ @programmeTable = ProgrammeTableWidget.new do |w|
523
+ connect(w, SIGNAL('cellClicked(int,int)'),
524
+ self, SLOT('programmeCellClicked(int,int)'))
525
+ end
526
+ vbxw.addWidget(HBoxLayoutWidget.new do |hw|
527
+ hw.addWidget(Qt::Label.new(i18n('Look for:')))
528
+ hw.addWidget(
529
+ @filterLineEdit = KDE::LineEdit.new do |w|
530
+ connect(w,SIGNAL('textChanged(const QString &)'),
531
+ @programmeTable, SLOT('filterChanged(const QString &)'))
532
+ w.setClearButtonShown(true)
533
+ end
534
+ )
535
+ end
536
+ )
537
+ vbxw.addWidget(@programmeTable)
538
+
539
+ # 'Start Download' Button
540
+ @tvFilterBtn = KDE::PushButton.new(i18n("TV")) do |w|
541
+ w.objectName = 'mediaButton'
542
+ w.checkable = true
543
+ w.autoExclusive = true
544
+ connect( w, SIGNAL(:clicked), self, SLOT(:mediaFilterChanged) )
545
+ end
546
+
547
+ @radioFilterBtn = KDE::PushButton.new(i18n("Radio")) do |w|
548
+ w.objectName = 'mediaButton'
549
+ w.checkable = true
550
+ w.autoExclusive = true
551
+ w.checked = true
552
+ connect( w, SIGNAL(:clicked), self, SLOT(:mediaFilterChanged) )
553
+ end
554
+
555
+ playBtn = KDE::PushButton.new( KDE::Icon.new('media-playback-start'), i18n("Play")) do |w|
556
+ w.objectName = 'playButton'
557
+ connect( w, SIGNAL(:clicked), self, SLOT(:playProgramme) )
558
+ end
559
+
560
+ downloadBtn = KDE::PushButton.new( KDE::Icon.new('arrow-down'), i18n("Download")) do |w|
561
+ w.objectName = 'downloadButton'
562
+ connect( w, SIGNAL(:clicked), self, SLOT(:startDownload) )
563
+ end
564
+
565
+ vbxw.addWidgets( @tvFilterBtn, @radioFilterBtn, nil, playBtn, nil, downloadBtn, nil )
566
+ end
567
+ end
568
+
569
+ #-------------------------------------------------------------
570
+ #
571
+ #
572
+ def createProgrammeSummaryWidget
573
+ @programmeSummaryWebView = Qt::WebView.new do |w|
574
+ w.page.linkDelegationPolicy = Qt::WebPage::DelegateAllLinks
575
+ end
576
+ end
577
+
578
+ #-------------------------------------------------------------
579
+ #
580
+ #
581
+ def createPlayerDock
582
+ @playerDock = Qt::DockWidget.new(self) do |w|
583
+ w.objectName = 'playerDock'
584
+ w.windowTitle = i18n('Player')
585
+ w.allowedAreas = Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::BottomDockWidgetArea
586
+ w.floating = true
587
+ w.hide
588
+ end
589
+ @playerWevView = Qt::WebView.new do |w|
590
+ w.page.linkDelegationPolicy = Qt::WebPage::DelegateAllLinks
591
+ end
592
+
593
+ webSettings = Qt::WebSettings::globalSettings
594
+ webSettings.setAttribute(Qt::WebSettings::PluginsEnabled, true)
595
+
596
+
597
+ @playerDock.setWidget(@playerWevView)
598
+ self.addDockWidget(Qt::RightDockWidgetArea, @playerDock)
599
+
600
+ @playerDock
601
+ end
602
+
603
+ #-------------------------------------------------------------
604
+ #
605
+ #
606
+ def createDlg
607
+ @settingsDlg = SettingsDlg.new(self)
608
+ end
609
+
610
+
611
+ slots :configureApp
612
+ # slot
613
+ def configureApp
614
+ @settingsDlg.exec
615
+ end
616
+
617
+
618
+ #-------------------------------------------------------------
619
+ #
620
+ # virtual function slot
621
+ def closeEvent(event)
622
+ writeSettings
623
+ super(event)
624
+ end
625
+
626
+ def readSettings
627
+ config = $config.group(GroupName)
628
+ @mainTabPage.restoreState(config.readEntry('MainTabPageState', @mainTabPage.saveState))
629
+ @progTableFrame.restoreState(config.readEntry('ProgTableFrame',
630
+ @progTableFrame.saveState))
631
+
632
+ @programmeTable.readSettings
633
+ @taskWin.readSettings
634
+ end
635
+
636
+ def writeSettings
637
+ config = $config.group(GroupName)
638
+ config.writeEntry('MainTabPageState', @mainTabPage.saveState)
639
+ config.writeEntry('ProgTableFrame', @progTableFrame.saveState)
640
+
641
+ @programmeTable.writeSettings
642
+ @taskWin.writeSettings
643
+ end
644
+
645
+ # ------------------------------------------------------------------------
646
+ # slot :
647
+ def reloadStyleSheet
648
+ styleStr = IO.read(APP_DIR + '/resources/bbcstyle.qss')
649
+ $app.styleSheet = styleStr
650
+ $app.styleSheet = styleStr
651
+ $log.info { 'Reloaded StyleSheet.' }
652
+ end
653
+
654
+ # slot :
655
+ def clearStyleSheet
656
+ $app.styleSheet = nil
657
+ $log.info { 'Cleared StyleSheet.' }
658
+ end
659
+
660
+
661
+ # slot :
662
+ def programmeCellClicked(row, column)
663
+ prog = @programmeTable[row]
664
+ color = "#%06x" % (@programmeSummaryWebView.palette.color(Qt::Palette::Text).rgb & 0xffffff)
665
+
666
+ html = <<-EOF
667
+ <font color="#{color}">
668
+ #{prog.content}
669
+ </font>
670
+ EOF
671
+
672
+ @programmeSummaryWebView.setHtml(html)
673
+ end
674
+
675
+ # slot :
676
+ def mediaFilterChanged
677
+ setMediaFilter
678
+ @programmeTable.filterChanged(@filterLineEdit.text)
679
+ end
680
+
681
+
682
+ # slot
683
+ def playProgramme
684
+ items = @programmeTable.selectedItems
685
+ return unless items.size > 0
686
+
687
+ def makeProcCommand(command, url)
688
+ cmd, args = command.split(/\s+/, 2)
689
+ args = args.split(/\s+/).map do |a|
690
+ a.gsub(/%\w/, url)
691
+ end
692
+ [ cmd, args ]
693
+ end
694
+
695
+ def getIplayerUrl(prog)
696
+ # big type console
697
+ url = prog.link
698
+ $log.info { "big console Url : #{url}" }
699
+
700
+ # old type console
701
+ url = prog.content[UrlRegexp] # String[] method extract only 1st one.
702
+ $log.info { "episode Url : #{url}" }
703
+ url = BBCNet.getPlayerConsoleUrl(url)
704
+ $log.info { "old console Url : #{url}" }
705
+
706
+ if IRecSettings.playerTypeBeta then
707
+ url.sub!(%r{www}, 'beta')
708
+ $log.info { "new console Url : #{url}" }
709
+ end
710
+ url
711
+ end
712
+
713
+ prog = @programmeTable[items[0].row]
714
+ webPlayerCommand = IRecSettings.webPlayerCommand
715
+ directPlayerCommand = IRecSettings.directPlayerCommand
716
+
717
+ begin
718
+ if IRecSettings.useInnerPlayer then
719
+ url = getIplayerUrl(prog)
720
+
721
+ @playerDock.show
722
+ @playerWevView.setUrl(Qt::Url.new(url))
723
+ elsif IRecSettings.useWebPlayer and webPlayerCommand then
724
+ $log.info { "Play on web browser" }
725
+ url = getIplayerUrl(prog)
726
+ cmd, args = makeProcCommand(webPlayerCommand, url)
727
+ $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
728
+ proc = Qt::Process.new(self)
729
+ proc.start(cmd, args)
730
+
731
+ elsif IRecSettings.useDirectPlayer and directPlayerCommand then
732
+ $log.info { "Play direct" }
733
+ url = prog.content[UrlRegexp] # String[] method extract only 1st one.
734
+
735
+ $log.info { "episode Url : #{url}" }
736
+ minfo = BBCNet::MetaInfo.get(url).update
737
+ $log.debug { "#{minfo.inspect}" }
738
+ url = minfo.wma.url
739
+
740
+ cmd, args = makeProcCommand(directPlayerCommand, url)
741
+
742
+ $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
743
+ proc = Qt::Process.new(self)
744
+ proc.start(cmd, args)
745
+ end
746
+ rescue => e
747
+ $log.error { e }
748
+ KDE::MessageBox::information(self, i18n("There is not direct stream for this programme."))
749
+ end
750
+ end
751
+
752
+
753
+ # ------------------------------------------------------------------------
754
+ #
755
+ # slot: called when 'Get List' Button clicked signal invoked.
756
+ #
757
+ public
758
+ def getList
759
+ feedAdr = getFeedAdr
760
+ return if feedAdr.nil?
761
+
762
+ $log.info{ "feeding from '#{feedAdr}'" }
763
+
764
+ begin
765
+ makeTablefromRss( BBCNet.read(feedAdr) )
766
+ rescue IOError, OpenURI::HTTPError => e
767
+ $log.error { e }
768
+ end
769
+ mediaFilterChanged
770
+ end
771
+
772
+ #
773
+ # get feed address
774
+ #
775
+ protected
776
+ def getFeedAdr
777
+ @channelType = @channelTypeToolBox.currentIndex
778
+
779
+ channelStr = nil
780
+ case @channelType
781
+ when 0
782
+ # get TV channel
783
+ @channelIndex = @tvChannelListBox.currentRow
784
+ channelStr = TVChannelRssTbl[ @channelIndex ][1]
785
+ when 1
786
+ # get Radio channel
787
+ @channelIndex = @radioChannelListBox.currentRow
788
+ channelStr = RadioChannelRssTbl[ @channelIndex ][1]
789
+ when 2
790
+ # get Category
791
+ @channelIndex = @categoryListBox.currentRow
792
+ channelStr = 'categories/' + CategoryRssTbl[ @channelIndex ][1]
793
+ end
794
+
795
+ return nil if channelStr.nil?
796
+
797
+ list = %w[ list highlights popular ][@listTypeGroup.checkedId]
798
+
799
+ "http://feeds.bbc.co.uk/iplayer/#{channelStr}/#{list}"
800
+ end
801
+
802
+
803
+ protected
804
+ def makeTablefromRss(rssRaw)
805
+ rss = RSS::Parser.parse(rssRaw)
806
+ sortFlag = @programmeTable.sortingEnabled
807
+ @programmeTable.sortingEnabled = false
808
+ @programmeTable.hide
809
+ @programmeTable.clearContents
810
+ @filterLineEdit.clear
811
+ @programmeTable.rowCount = rss.entries.size
812
+ setMediaFilter
813
+
814
+ # ['Title', 'Category', 'Updated' ]
815
+ rss.entries.each_with_index do |i, r|
816
+ title = i.title.content.to_s
817
+ updated = i.updated.content.to_s
818
+ contents = i.content.content
819
+ link = i.links.find do |l| l.rel == 'self' end.href
820
+ categories = i.categories.map do |c| c.term end.join(',')
821
+ $log.misc { title }
822
+ @programmeTable.addEntry( r, title, categories, updated, contents, link )
823
+ end
824
+
825
+ @programmeTable.sortingEnabled = sortFlag
826
+ @programmeTable.show
827
+ end
828
+
829
+ def setMediaFilter
830
+ @programmeTable.mediaFilter =
831
+ case @channelType
832
+ when 2
833
+ @tvFilterBtn.enabled = true
834
+ @radioFilterBtn.enabled = true
835
+ @tvFilterBtn.checked ? 'tv' : 'radio'
836
+ else
837
+ @tvFilterBtn.enabled = false
838
+ @radioFilterBtn.enabled = false
839
+ ''
840
+ end
841
+ end
842
+
843
+
844
+ # ------------------------------------------------------------------------
845
+ #
846
+ # slot : when 'Download' Button pressed.
847
+ #
848
+ # Start Downloading
849
+ #
850
+ public
851
+ def startDownload
852
+ rowsSet = {} # use Hash as Set.
853
+ @programmeTable.selectedItems.each do |i| rowsSet[i.row] = true end
854
+
855
+ rowsSet.keys.sort.map do |r|
856
+ begin
857
+ prog = @programmeTable[r]
858
+ url = prog.content[UrlRegexp] # String[] method extract only 1st one.
859
+
860
+ $log.info { "episode Url : #{url}" }
861
+ minfo = BBCNet::MetaInfo.get(url).update
862
+ url = minfo.wma.url
863
+
864
+ fName = getSaveName(prog, 'wma')
865
+ $log.info { "save name : #{fName}" }
866
+
867
+ startDownOneFile(minfo, fName)
868
+ rescue Timeout::Error, StandardError => e
869
+ $log.error { e }
870
+ KDE::MessageBox::information(self, i18n("There is not direct stream for programme '%s'.") % [prog.title])
871
+ end
872
+ end
873
+ end
874
+
875
+
876
+ private
877
+ #
878
+ def getSaveName(prog, ext='wma')
879
+ tags = prog.categories.split(/,/)
880
+ dir = getSaveSubDirName(tags)
881
+ $log.debug { "save dir : #{dir}" }
882
+
883
+ dir + '/' + getSaveBaseName(prog.title, tags, ext)
884
+ end
885
+
886
+ def getSaveBaseName(title, tags, ext)
887
+ s = IRecSettings
888
+ head = s.fileAddHeadStr
889
+ head += getMediaName(tags) + ' ' if s.fileAddMediaName
890
+ head += getChannelName + ' ' if s.fileAddChannelName and getChannelName
891
+ head += getGenreName(tags) + ' ' if s.fileAddGenreName and getGenreName(tags)
892
+ head += "- " unless head.empty?
893
+ baseName = head + title + '.' + ext
894
+ baseName.gsub(%r{[\/]}, '-')
895
+ end
896
+
897
+ #
898
+ def getSaveSubDirName(tags)
899
+ s = IRecSettings
900
+ dir = []
901
+ dir << getMediaName(tags) if s.dirAddMediaName
902
+ dir << getChannelName if s.dirAddChannelName and getChannelName
903
+ dir << getGenreName(tags) if s.dirAddGenreName and getGenreName(tags)
904
+ File.join(dir.compact)
905
+ end
906
+
907
+ # media [TV,Radio,iPod]
908
+ def getMediaName(tags)
909
+ tags.find do |t|
910
+ %w(radio tv ipod).include?(t.downcase)
911
+ end
912
+ end
913
+
914
+ # channel [BBC Radio 4, ..]
915
+ def getChannelName
916
+ title = getChannelTitle
917
+ end
918
+
919
+ def getChannelTitle
920
+ case @channelType
921
+ when 0
922
+ # get TV channel
923
+ TVChannelRssTbl[ @channelIndex ][0]
924
+ when 1
925
+ # get Radio channel
926
+ RadioChannelRssTbl[ @channelIndex ][0]
927
+ else
928
+ nil
929
+ end
930
+ end
931
+
932
+ # Main Genre [Drama, Comedy, ..]
933
+ def getGenreName(tags)
934
+ CategoryNameTbl.find do |cat|
935
+ tags.find do |t|
936
+ cat =~ /#{t}/i
937
+ end
938
+ end
939
+ end
940
+
941
+
942
+ # other genre [Sitcom, SF, etc]
943
+ def getSubGenreName(tags)
944
+ end
945
+
946
+
947
+
948
+
949
+
950
+ #-------------------------------------------------------------------
951
+ # parameter
952
+ # metaInfo : source stream MetaInfo of download file.
953
+ # fName : save file name.
954
+ #
955
+ # start Dwnload One file.
956
+ protected
957
+ def startDownOneFile(metaInfo, fName)
958
+ process = DownloadProcess.new(self, metaInfo, fName)
959
+ process.taskItem = @taskWin.addTask(process)
960
+ process.beginTask
961
+ end
962
+
963
+
964
+
965
+
966
+ #
967
+ # slot : periodically called to update task view.
968
+ #
969
+ protected
970
+ def updateTask
971
+ @taskWin.each do |task|
972
+ task.process.updateView
973
+ end
974
+ end
975
+ end
976
+
977
+
978
+ #
979
+ # main start
980
+ #
981
+
982
+ about = KDE::AboutData.new(APP_NAME, APP_NAME, KDE::ki18n(APP_NAME), APP_VERSION)
983
+ KDE::CmdLineArgs.init(ARGV, about)
984
+ # options = KDE::CmdLineOptions.new()
985
+ # options.add( "+url", KDE::ki18n( "The url to record)" ),"")
986
+
987
+ $app = KDE::Application.new
988
+ args = KDE::CmdLineArgs.parsedArgs()
989
+ $config = KDE::Global::config
990
+ win = MainWindow.new
991
+ $app.setTopWidget(win)
992
+
993
+ win.show
994
+ $app.exec