irecorder 0.0.4-linux

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) 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