irecorder 0.0.5-linux → 0.0.6-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.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 ruby.twiddler@gmail.com
1
+ Copyright (c) 2010 ruby.twiddler
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "irecorder"), to deal
data/README CHANGED
@@ -3,7 +3,7 @@ You can browse BBC Radio programmes and click to download stream file.
3
3
  files will be converted to mp3 files automatically.
4
4
  irecorder allow to play without any other browser or play on your prefered browser.
5
5
 
6
- == KDE ruby
6
+ KDE ruby
7
7
  ==============
8
8
  irecorder require kdebindings.
9
9
  please check your distro package to install it.
@@ -11,7 +11,7 @@ or you can compile from source.
11
11
  http://download.kde.org/download.php?url=stable/4.5.0/src/kdebindings-4.5.0.tar.bz2
12
12
 
13
13
 
14
- == Installation
14
+ Installation
15
15
  ==================
16
16
  install most important package kdebindings4.
17
17
  on Mandriva Linux use urpmi command.
@@ -36,7 +36,7 @@ launch irecorder just type irecorder.rb
36
36
  # irecorder.rb
37
37
 
38
38
 
39
- == Build Gem
39
+ Build Gem
40
40
  =================
41
41
  if you want to gem package from source, use rake command.
42
42
 
data/Rakefile CHANGED
@@ -6,10 +6,10 @@ require 'rake/gempackagetask'
6
6
 
7
7
  spec = Gem::Specification.new do |s|
8
8
  s.name = "irecorder"
9
- s.version = "0.0.5"
9
+ s.version = "0.0.6"
10
10
  s.author = "ruby.twiddler"
11
11
  s.email = "ruby.twiddler at gmail.com"
12
- s.homepage = "http://wiki.github.com/rubytwiddler/irecorder/"
12
+ s.homepage = "http://github.com/rubytwiddler/irecorder/wiki"
13
13
  s.platform = "linux"
14
14
  s.summary = "BBC iPlayer like audio recorder with KDE GUI."
15
15
  s.files = FileList["{bin,lib}/**/*"].to_a
@@ -17,7 +17,7 @@ spec = Gem::Specification.new do |s|
17
17
  s.executables = [ 'irecorder.rb' ]
18
18
  s.license = "MIT-LICENSE"
19
19
  s.require_path = "lib"
20
- s.requirements = %w{ korundum4 qtwebkit kio }
20
+ s.requirements = %w{ korundum4 qtwebkit kio ktexteditor }
21
21
  s.add_runtime_dependency( 'nokogiri', '>= 1.4.0' )
22
22
  s.description = <<-EOF
23
23
  BBC iPlayer like audio recorder with KDE GUI.
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # 2010 by ruby.twiddler@gmail.com
4
4
  #
5
- # iPlayer interface
5
+ # iPlayer like stream recorder
6
6
  # record real/wma (rtsp/mms) audio stream
7
7
  #
8
8
 
@@ -12,14 +12,13 @@ require 'ftools'
12
12
  APP_NAME = File.basename(__FILE__).sub(/\.rb/, '')
13
13
  APP_DIR = File::dirname(File.expand_path(File.dirname(__FILE__)))
14
14
  LIB_DIR = File::join(APP_DIR, "lib")
15
- APP_VERSION = "0.0.5"
15
+ APP_VERSION = "0.0.6"
16
16
 
17
17
  # standard libs
18
18
  require 'rubygems'
19
19
  require 'uri'
20
20
  require 'net/http'
21
21
  require 'open-uri'
22
- require 'rss'
23
22
  require 'shellwords'
24
23
  require 'fileutils'
25
24
  require 'singleton'
@@ -48,9 +47,6 @@ require "settings"
48
47
  # Main Window Class
49
48
  #
50
49
  class MainWindow < KDE::MainWindow
51
- slots :startDownload, :updateTask, :getList, :reloadStyleSheet, :clearStyleSheet
52
- slots :mediaFilterChanged, :playProgramme
53
- slots 'programmeCellClicked(int,int)'
54
50
 
55
51
  GroupName = "MainWindow"
56
52
 
@@ -72,7 +68,7 @@ class MainWindow < KDE::MainWindow
72
68
  # BBCNet.setProxy('http://194.36.10.154:3127')
73
69
  # initialize values
74
70
  $log = MyLogger.new(@logWin)
75
- $log.level = MyLogger::INFO
71
+ $log.level = MyLogger::DEBUG
76
72
  $log.info { 'Log Start.' }
77
73
 
78
74
  # assign from config file.
@@ -132,20 +128,42 @@ class MainWindow < KDE::MainWindow
132
128
 
133
129
 
134
130
  # Help menu
135
- about = i18n(<<-ABOUT
136
- #{APP_NAME} #{APP_VERSION}
137
-
138
- BBC iPlayer like audio (mms/rtsp) stream recorder.
139
- ABOUT
140
- )
141
- helpMenu = KDE::HelpMenu.new(self, about)
131
+ aboutDlg = KDE::AboutApplicationDialog.new($about)
132
+ openAboutAction = KDE::Action.new(KDE::Icon.new('irecorder'),
133
+ i18n('About iRecorder'), self)
134
+ connect(openAboutAction, SIGNAL(:triggered), aboutDlg, SLOT(:exec))
135
+
136
+ #
137
+ openDocUrlAction = KDE::Action.new(KDE::Icon.new('help-contents'),
138
+ i18n('Open Document Wiki'), self)
139
+ connect(openDocUrlAction, SIGNAL(:triggered), self, SLOT(:openDocUrl))
140
+
141
+ #
142
+ openReportIssueUrlAction = KDE::Action.new(
143
+ KDE::Icon.new('tools-report-bug'), i18n('Report Bug'), self)
144
+ connect(openReportIssueUrlAction, SIGNAL(:triggered), self,
145
+ SLOT(:openReportIssueUrl))
146
+
147
+ #
148
+ openSourceAction = KDE::Action.new(KDE::Icon.new('document-open-folder'),
149
+ i18n('Open Source Folder'), self)
150
+ connect(openSourceAction, SIGNAL(:triggered), self, SLOT(:openSource))
151
+
152
+ helpMenu = KDE::Menu.new(i18n('&Help'), self)
153
+ helpMenu.addAction(openDocUrlAction)
154
+ # helpMenu.addAction(openHomeUrlAction)
155
+ helpMenu.addAction(openReportIssueUrlAction)
156
+ # helpMenu.addAction(openRDocAction)
157
+ helpMenu.addAction(openSourceAction)
158
+ helpMenu.addSeparator
159
+ helpMenu.addAction(openAboutAction)
142
160
 
143
161
  # insert menus in MenuBar
144
162
  menu = KDE::MenuBar.new
145
163
  menu.addMenu( fileMenu )
146
164
  menu.addMenu( settingsMenu )
147
165
  menu.addSeparator
148
- menu.addMenu( helpMenu.menu )
166
+ menu.addMenu( helpMenu )
149
167
  setMenuBar(menu)
150
168
  end
151
169
 
@@ -194,6 +212,7 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
194
212
 
195
213
  #-------------------------------------------------------------
196
214
  #
215
+ TvType, RadioType, RadioCategoryType = [-1,0,1] # TvType = -1 (hide)
197
216
  TVChannelRssTbl = [
198
217
  ['BBC One', 'bbc_one' ],
199
218
  ['BBC Two', 'bbc_two' ],
@@ -252,11 +271,15 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
252
271
  w.objectName = 'channelToolBox'
253
272
  end
254
273
 
274
+ # default value
275
+ toolBox.currentIndex = 2
276
+
255
277
  # TV & Radio Channels selector
256
- @tvChannelListBox = KDE::ListWidget.new
257
- # TV Channels
258
- @tvChannelListBox.addItems( TVChannelRssTbl.map do |w| w[0] end )
259
- toolBox.addItem( @tvChannelListBox, 'TV Channels' )
278
+ # @tvChannelListBox = KDE::ListWidget.new
279
+ # # TV Channels
280
+ # @tvChannelListBox.addItems( TVChannelRssTbl.map do |w| w[0] end )
281
+ # toolBox.addItem( @tvChannelListBox, 'TV Channels' )
282
+
260
283
 
261
284
  # Radio Channels
262
285
  @radioChannelListBox = KDE::ListWidget.new
@@ -266,7 +289,7 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
266
289
  # Category selector
267
290
  @categoryListBox = KDE::ListWidget.new
268
291
  @categoryListBox.addItems( CategoryRssTbl.map do |w| w[0] end )
269
- toolBox.addItem( @categoryListBox, 'Categories' )
292
+ toolBox.addItem( @categoryListBox, 'Radio Categories' )
270
293
 
271
294
  toolBox
272
295
  end
@@ -339,6 +362,8 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
339
362
  connect(w,SIGNAL('textChanged(const QString &)'),
340
363
  @programmeTable, SLOT('filterChanged(const QString &)'))
341
364
  w.setClearButtonShown(true)
365
+ connect(@programmeTable, SIGNAL('filterRequest(const QString &)'),
366
+ w, SLOT('setText(const QString &)'))
342
367
  end
343
368
  )
344
369
  end
@@ -346,35 +371,20 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
346
371
  vbxw.addWidget( @listTitleLabel = Qt::Label.new('') )
347
372
  vbxw.addWidget(@programmeTable)
348
373
 
349
- # 'Start Download' Button
350
- @tvFilterBtn = KDE::PushButton.new(i18n("TV")) do |w|
351
- w.objectName = 'mediaButton'
352
- w.checkable = true
353
- w.autoExclusive = true
354
- connect( w, SIGNAL(:clicked), self, SLOT(:mediaFilterChanged) )
355
- end
356
-
357
- @radioFilterBtn = KDE::PushButton.new(i18n("Radio")) do |w|
358
- w.objectName = 'mediaButton'
359
- w.checkable = true
360
- w.autoExclusive = true
361
- w.checked = true
362
- connect( w, SIGNAL(:clicked), self, SLOT(:mediaFilterChanged) )
363
- end
364
-
365
374
  playIcon = KDE::Icon.new(':images/play-22.png')
366
375
  playBtn = KDE::PushButton.new( playIcon, i18n("Play")) do |w|
367
376
  w.objectName = 'playButton'
368
377
  connect( w, SIGNAL(:clicked), self, SLOT(:playProgramme) )
369
378
  end
370
379
 
380
+ # 'Start Download' Button
371
381
  downloadIcon = KDE::Icon.new(':images/download-22.png')
372
382
  downloadBtn = KDE::PushButton.new( downloadIcon, i18n("Download")) do |w|
373
383
  w.objectName = 'downloadButton'
374
384
  connect( w, SIGNAL(:clicked), self, SLOT(:startDownload) )
375
385
  end
376
386
 
377
- vbxw.addWidgets( @tvFilterBtn, @radioFilterBtn, nil, playBtn, nil, downloadBtn, nil )
387
+ vbxw.addWidgets( nil, playBtn, nil, downloadBtn, nil )
378
388
  end
379
389
  end
380
390
 
@@ -468,7 +478,7 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
468
478
  end
469
479
 
470
480
  # ------------------------------------------------------------------------
471
- # slot :
481
+ slots :reloadStyleSheet
472
482
  def reloadStyleSheet
473
483
  styleStr = IO.read(APP_DIR + '/resources/bbcstyle.qss')
474
484
  $app.styleSheet = styleStr
@@ -476,7 +486,7 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
476
486
  $log.info { 'Reloaded StyleSheet.' }
477
487
  end
478
488
 
479
- # slot :
489
+ slots :clearStyleSheet
480
490
  def clearStyleSheet
481
491
  $app.styleSheet = nil
482
492
  $log.info { 'Cleared StyleSheet.' }
@@ -484,6 +494,7 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
484
494
 
485
495
 
486
496
  # slot :
497
+ slots 'programmeCellClicked(int,int)'
487
498
  def programmeCellClicked(row, column)
488
499
  prog = @programmeTable[row]
489
500
  color = "#%06x" % (@programmeSummaryWebView.palette.color(Qt::Palette::Text).rgb & 0xffffff)
@@ -500,25 +511,21 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
500
511
  @programmeSummaryWebView.setHtml(html)
501
512
  end
502
513
 
503
- # slot :
504
- def mediaFilterChanged
505
- setMediaFilter
506
- @programmeTable.filterChanged(@filterLineEdit.text)
507
- end
508
514
 
509
515
 
510
- # slot
516
+ def makeProcCommand(command, url)
517
+ cmd, args = command.split(/\s+/, 2)
518
+ args = args.split(/\s+/).map do |a|
519
+ a.gsub(/%\w/, url)
520
+ end
521
+ [ cmd, args ]
522
+ end
523
+
524
+ slots :playProgramme
511
525
  def playProgramme
512
526
  items = @programmeTable.selectedItems
513
527
  return unless items.size > 0
514
528
 
515
- def makeProcCommand(command, url)
516
- cmd, args = command.split(/\s+/, 2)
517
- args = args.split(/\s+/).map do |a|
518
- a.gsub(/%\w/, url)
519
- end
520
- [ cmd, args ]
521
- end
522
529
 
523
530
  def getIplayerUrl(prog)
524
531
  # big type console
@@ -563,6 +570,7 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
563
570
  $log.info { "episode Url : #{url}" }
564
571
  minfo = BBCNet::CacheMetaInfoDevice.read(url)
565
572
  $log.debug { "#{minfo.inspect}" }
573
+ raise "No stream Url" unless minfo.wma
566
574
  url = minfo.wma.url
567
575
 
568
576
  cmd, args = makeProcCommand(directPlayerCommand, url)
@@ -572,17 +580,51 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
572
580
  proc.start(cmd, args)
573
581
  end
574
582
  rescue => e
575
- $log.error { e }
576
- KDE::MessageBox::information(self, i18n("There is not direct stream for this programme."))
583
+ if e.kind_of? RuntimeError
584
+ $log.info { e.message }
585
+ else
586
+ $log.error { e }
587
+ end
588
+ # some messages must be treated.
589
+ # already expired.
590
+ # some xml error.
591
+ # no url
592
+ passiveMessage(i18n("There is no direct stream for this programme.\n %s" %[prog.title]))
577
593
  end
578
594
  end
579
595
 
596
+ slots :openDocUrl
597
+ def openDocUrl
598
+ openUrlDocument('http://github.com/rubytwiddler/irecorder/wiki')
599
+ end
600
+
601
+ slots :openReportIssueUrl
602
+ def openReportIssueUrl
603
+ openUrlDocument('http://github.com/rubytwiddler/irecorder/issues')
604
+ end
605
+
606
+ slots :openSource
607
+ def openSource
608
+ cmd = 'dolphin'
609
+ args = [ APP_DIR ]
610
+ proc = Qt::Process.new(self)
611
+ proc.start(cmd, args)
612
+ end
613
+
614
+ def openUrlDocument(url)
615
+ webPlayerCommand = IRecSettings.webPlayerCommand
616
+ cmd, args = makeProcCommand(webPlayerCommand, url)
617
+ $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
618
+ proc = Qt::Process.new(self)
619
+ proc.start(cmd, args)
620
+ end
580
621
 
581
622
  # ------------------------------------------------------------------------
582
623
  #
583
624
  # slot: called when 'Get List' Button clicked signal invoked.
584
625
  #
585
626
  public
627
+ slots :getList
586
628
  def getList
587
629
  feedAdr = getFeedAdr
588
630
  return if feedAdr.nil?
@@ -591,10 +633,9 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
591
633
 
592
634
  begin
593
635
  makeTablefromRss( CacheRssDevice.read(feedAdr) )
594
- rescue IOError, OpenURI::HTTPError => e
636
+ rescue => e
595
637
  $log.error { e }
596
638
  end
597
- mediaFilterChanged
598
639
  setListTitle
599
640
  end
600
641
 
@@ -608,18 +649,18 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
608
649
 
609
650
  channelStr = nil
610
651
  case @channelType
611
- when 0
652
+ when TvType
612
653
  # get TV channel
613
654
  @channelIndex = @tvChannelListBox.currentRow
614
655
  channelStr = TVChannelRssTbl[ @channelIndex ][1]
615
- when 1
656
+ when RadioType
616
657
  # get Radio channel
617
658
  @channelIndex = @radioChannelListBox.currentRow
618
659
  channelStr = RadioChannelRssTbl[ @channelIndex ][1]
619
- when 2
660
+ when RadioCategoryType
620
661
  # get Category
621
662
  @channelIndex = @categoryListBox.currentRow
622
- channelStr = 'categories/' + CategoryRssTbl[ @channelIndex ][1]
663
+ channelStr = 'categories/' + CategoryRssTbl[ @channelIndex ][1] + '/radio'
623
664
  end
624
665
 
625
666
  return nil if channelStr.nil?
@@ -648,23 +689,25 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
648
689
 
649
690
  protected
650
691
  def makeTablefromRss(rss)
651
- # rss = RSS::Parser.parse(rssRaw)
692
+ @programmeTable.clearContents
693
+ @programmeTable.rowCount = 0
694
+ @filterLineEdit.clear
695
+ entries = rss.css('entry')
696
+ return unless rss and entries and entries.size
697
+
652
698
  sortFlag = @programmeTable.sortingEnabled
653
699
  @programmeTable.sortingEnabled = false
654
700
  @programmeTable.hide
655
- @programmeTable.clearContents
656
- @filterLineEdit.clear
657
- @programmeTable.rowCount = rss.entries.size
658
- setMediaFilter
701
+ @programmeTable.rowCount = entries.size
659
702
 
660
703
  # ['Title', 'Category', 'Updated' ]
661
- rss.entries.each_with_index do |i, r|
662
- title = i.title.content.to_s
663
- updated = i.updated.content.to_s
664
- contents = i.content.content
665
- linkItem = i.links.find do |l| l.rel == 'self' end
666
- link = linkItem ? linkItem.href : nil
667
- categories = i.categories.map do |c| c.term end.join(',')
704
+ entries.each_with_index do |i, r|
705
+ title = i.at_css('title').content
706
+ updated = i.at_css('updated').content
707
+ contents = i.at_css('content').content
708
+ linkItem = i.css('link').find do |l| l['rel'] == 'self' end
709
+ link = linkItem ? linkItem['href'] : nil
710
+ categories = i.css('category').map do |c| c['term'] end.join(',')
668
711
  $log.misc { title }
669
712
  @programmeTable.addEntry( r, title, categories, updated, contents, link )
670
713
  end
@@ -673,19 +716,6 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
673
716
  @programmeTable.show
674
717
  end
675
718
 
676
- def setMediaFilter
677
- @programmeTable.mediaFilter =
678
- case @channelType
679
- when 2
680
- @tvFilterBtn.enabled = true
681
- @radioFilterBtn.enabled = true
682
- @tvFilterBtn.checked ? 'tv' : 'radio'
683
- else
684
- @tvFilterBtn.enabled = false
685
- @radioFilterBtn.enabled = false
686
- ''
687
- end
688
- end
689
719
 
690
720
 
691
721
  # ------------------------------------------------------------------------
@@ -695,13 +725,18 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
695
725
  # Start Downloading
696
726
  #
697
727
  public
728
+ slots :startDownload
698
729
  def startDownload
699
730
  rowsSet = {} # use Hash as Set.
700
731
  @programmeTable.selectedItems.each do |i| rowsSet[i.row] = true end
701
732
 
702
- rowsSet.keys.sort.map do |r|
733
+ titles = {}
734
+ rowsSet.keys.map do |r|
735
+ @programmeTable[r]
736
+ end .each do |p| titles[p.title] = p end
737
+
738
+ titles.each_value do |prog|
703
739
  begin
704
- prog = @programmeTable[r]
705
740
  url = prog.content[UrlRegexp] # String[] method extract only 1st one.
706
741
 
707
742
  $log.info { "episode Url : #{url}" }
@@ -712,15 +747,22 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
712
747
  $log.info { "save name : #{fName}" }
713
748
 
714
749
  startDownOneFile(minfo, fName)
750
+
751
+ passiveMessage(i18n("Start Download programme '%s'") % [prog.title])
752
+
715
753
  rescue Timeout::Error, StandardError => e
716
- $log.error { e }
717
- KDE::MessageBox::information(self, i18n("There is not direct stream for programme '%s'.") % [prog.title])
754
+ if e.kind_of? RuntimeError
755
+ $log.info { e.message }
756
+ else
757
+ $log.error { e }
758
+ end
759
+ passiveMessage(i18n("There is no direct stream for this programme.\n%s" %[prog.title]))
718
760
  end
719
761
  end
720
762
  end
721
763
 
722
764
 
723
- private
765
+ protected
724
766
  #
725
767
  def getSaveName(prog, ext='wma')
726
768
  tags = prog.categories.split(/,/)
@@ -765,10 +807,10 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
765
807
 
766
808
  def getChannelTitle
767
809
  case @channelType
768
- when 0
810
+ when TvType
769
811
  # get TV channel
770
812
  TVChannelRssTbl[ @channelIndex ][0]
771
- when 1
813
+ when RadioType
772
814
  # get Radio channel
773
815
  RadioChannelRssTbl[ @channelIndex ][0]
774
816
  else
@@ -814,6 +856,7 @@ BBC iPlayer like audio (mms/rtsp) stream recorder.
814
856
  # slot : periodically called to update task view.
815
857
  #
816
858
  protected
859
+ slots :updateTask
817
860
  def updateTask
818
861
  @taskWin.each do |task|
819
862
  task.process.updateView
@@ -826,9 +869,12 @@ end
826
869
  # main start
827
870
  #
828
871
 
829
- about = KDE::AboutData.new(APP_NAME, APP_NAME, KDE::ki18n(APP_NAME), APP_VERSION)
830
- about.setProgramIconName(':images/irecorder-22.png')
831
- KDE::CmdLineArgs.init(ARGV, about)
872
+ $about = KDE::AboutData.new(APP_NAME, APP_NAME, KDE::ki18n(APP_NAME), APP_VERSION,
873
+ KDE::ki18n('BBC iRecorder KDE')
874
+ )
875
+ $about.setProgramIconName(':images/irecorder-22.png')
876
+ $about.addLicenseTextFile(APP_DIR + '/MIT-LICENSE')
877
+ KDE::CmdLineArgs.init(ARGV, $about)
832
878
  # options = KDE::CmdLineOptions.new()
833
879
  # options.add( "+url", KDE::ki18n( "The url to record)" ),"")
834
880
 
@@ -5,7 +5,6 @@ require 'rubygems'
5
5
  require 'uri'
6
6
  require 'net/http'
7
7
  require 'open-uri'
8
- require 'rss'
9
8
  require 'nokogiri'
10
9
  require 'shellwords'
11
10
  require 'fileutils'
@@ -25,8 +24,8 @@ class BBCNet
25
24
  DirectStreamRegexp = URI.regexp(['mms', 'rtsp', 'rtmp', 'rtmpt'])
26
25
 
27
26
  class CacheMetaInfoDevice < CasheDevice::CacheDeviceBase
28
- def initialize(expireDuration = 40*60, cacheMax=200)
29
- super(expireDuration, cacheMax)
27
+ def initialize(cacheDuration = 40*60, cacheMax=200)
28
+ super(cacheDuration, cacheMax)
30
29
  end
31
30
 
32
31
  # return : [ data, key ]
@@ -81,6 +80,9 @@ class BBCNet
81
80
  # res = IO.read("../tmp/iplayer-playlist-me.xml")
82
81
 
83
82
  doc = Nokogiri::XML(res)
83
+ item = doc.at_css("noItems")
84
+ raise "No Playlist " + item[:reason] if item
85
+
84
86
  item = doc.at_css("item")
85
87
  @media = item[:kind].gsub(/programme/i, '')
86
88
  @duration = item[:duration].to_i
@@ -158,11 +160,6 @@ class BBCNet
158
160
  #
159
161
 
160
162
  # convert epsode Url to console Url
161
- # example
162
- # from
163
- # http://www.bbc.co.uk/iplayer/episode/b007jpkt/Miss_Marple_A_Caribbean_Mystery_Episode_1/
164
- # to
165
- # http://www.bbc.co.uk/iplayer/console/b007jpkt
166
163
  def self.getPlayerConsoleUrl(url)
167
164
  "http://www.bbc.co.uk/iplayer/console/" + extractPid(url)
168
165
  end
@@ -188,7 +185,7 @@ class BBCNet
188
185
  while url != old and not url[DirectStreamRegexp] do
189
186
  old = url
190
187
  res = BBCNet.read(url)
191
- url = res[ DirectStreamRegexp ] or res[ UrlRegexp ] or old
188
+ url = res[ DirectStreamRegexp ] || res[ UrlRegexp ] || old
192
189
  $log.debug { "new url:#{url}, old url:#{old}" }
193
190
  $log.debug { "no url in response '#{res}'" } if url[ UrlRegexp ]
194
191
  end
@@ -9,9 +9,9 @@ module CasheDevice
9
9
  attr_accessor :expireTime, :url, :key
10
10
  end
11
11
 
12
- attr_accessor :expireDuration, :cacheMax
13
- def initialize(expireDuration = 26*60, cacheMax=10)
14
- @expireDuration = expireDuration # 12 minutes
12
+ attr_accessor :cacheDuration, :cacheMax
13
+ def initialize(cacheDuration = 26*60, cacheMax=10)
14
+ @cacheDuration = cacheDuration # 12 minutes
15
15
  @cache = Hash.new
16
16
  @cacheLRU = [] # Least Recently Used
17
17
  @cacheMax = cacheMax
@@ -46,7 +46,7 @@ module CasheDevice
46
46
  end
47
47
  cached = CachedData.new
48
48
  cached.url = url
49
- cached.expireTime = startTime + @expireDuration
49
+ cached.expireTime = startTime + @cacheDuration
50
50
  data, cached.key = directRead(url)
51
51
  @cache[url] = cached
52
52
  @cacheLRU.push(cached)
@@ -67,22 +67,22 @@ end
67
67
  # practical implementations.
68
68
  #
69
69
  class CacheRssDevice < CasheDevice::CacheDeviceBase
70
- def initialize(expireDuration = 12*60, cacheMax=6)
71
- super(expireDuration, cacheMax)
70
+ def initialize(cacheDuration = 12*60, cacheMax=6)
71
+ super(cacheDuration, cacheMax)
72
72
  end
73
73
 
74
74
  # return : [ data, key ]
75
75
  # key : key to restore data.
76
76
  def directRead(url)
77
- data = RSS::Parser.parse(CacheHttpDiskDevice.read(url))
77
+ data = Nokogiri::XML(CacheHttpDiskDevice.read(url))
78
78
  [ data, data ]
79
79
  end
80
80
  end
81
81
 
82
82
 
83
83
  class CacheHttpDiskDevice < CasheDevice::CacheDeviceBase
84
- def initialize(expireDuration = 12*60, cacheMax=30)
85
- super(expireDuration, cacheMax)
84
+ def initialize(cacheDuration = 12*60, cacheMax=50)
85
+ super(cacheDuration, cacheMax)
86
86
  @tmpdir = Dir.tmpdir + '/bbc_cache'
87
87
  FileUtils.mkdir_p(@tmpdir)
88
88
  end
@@ -96,11 +96,17 @@ class CacheHttpDiskDevice < CasheDevice::CacheDeviceBase
96
96
  # return : [ data, key ]
97
97
  # key : key to restore data.
98
98
  def directRead(url)
99
- puts "directRead(): " + self.class.name
99
+ $log.debug { "directRead(): " + self.class.name }
100
100
  tmpfname = tempFileName(url)
101
101
 
102
+ if File.exist?(tmpfname) then
103
+ $log.debug { "File ctime : " + File.ctime(tmpfname).to_s}
104
+ $log.debug { "expire time : " + (File.ctime(tmpfname) + @cacheDuration).to_s }
105
+ $log.debug { "Now Time : " + Time.now.to_s }
106
+ end
107
+
102
108
  if File.exist?(tmpfname) and
103
- File.ctime(tmpfname) + @expireDuration > Time.now then
109
+ File.ctime(tmpfname) + @cacheDuration > Time.now then
104
110
  data = IO.read(tmpfname)
105
111
  else
106
112
  data = BBCNet.read(url)
@@ -110,6 +116,6 @@ class CacheHttpDiskDevice < CasheDevice::CacheDeviceBase
110
116
  end
111
117
 
112
118
  def tempFileName(url)
113
- File.join(@tmpdir, url.scan(%r{\w+/\w+$}).first.gsub(%r|/|, '_'))
119
+ File.join(@tmpdir, url.scan(%r{(?:iplayer/)[\w\/]+$}).first.gsub!(/iplayer\//,'').gsub!(%r|/|, '_'))
114
120
  end
115
121
  end
@@ -7,7 +7,6 @@ require "bbcnet.rb"
7
7
  #
8
8
  #
9
9
  class DownloadProcess < Qt::Process
10
- slots 'taskFinished(int,QProcess::ExitStatus)'
11
10
  attr_reader :sourceUrl, :fileName
12
11
  attr_accessor :taskItem
13
12
 
@@ -136,13 +135,12 @@ class DownloadProcess < Qt::Process
136
135
  def cancelTask
137
136
  if running? then
138
137
  self.terminate
138
+ taskFinished(1,0)
139
139
  end
140
140
  end
141
141
 
142
142
  def removeData
143
- if running? then
144
- self.terminate
145
- end
143
+ cancelTask
146
144
  begin
147
145
  File.delete(@rawFilePath)
148
146
  File.delete(@outFilePath)
@@ -159,18 +157,23 @@ class DownloadProcess < Qt::Process
159
157
  Time.now - @startTime
160
158
  end
161
159
 
162
- # slot :
160
+ slots 'taskFinished(int,QProcess::ExitStatus)'
163
161
  def taskFinished(exitCode, exitStatus)
164
162
  checkReadOutput
165
163
  if (exitCode.to_i.nonzero? || exitStatus.to_i.nonzero?) && checkErroredStatus then
166
164
  self.status = ERROR
167
- $log.error { [ makeErrorMsg, "exitCode=#{exitCode}, exitStatus=#{exitStatus}" ] }
165
+ errMsg = makeErrorMsg
166
+ $log.error { [ errMsg, "exitCode=#{exitCode}, exitStatus=#{exitStatus}" ] }
167
+ passiveMessage(errMsg)
168
168
  else
169
169
  $log.info {
170
170
  [ "Successed to download a File '%#2$s'",
171
171
  "Successed to convert a File '%#2$s'", ][@stage] %
172
172
  [ @sourceUrl, @rawFilePath ]
173
173
  }
174
+ if @stage == CONVERT then
175
+ passiveMessage(i18n("Download, Convert Complete. '%#1$s'") % [@outFilePath])
176
+ end
174
177
  nextTask
175
178
  end
176
179
  end
@@ -222,10 +225,10 @@ class DownloadProcess < Qt::Process
222
225
  # debug code.
223
226
  if DEBUG_DOWNLOAD then
224
227
  if rand > 0.4 then
225
- cmdApp = "test/sleepjob.rb"
228
+ cmdApp = APP_DIR + "/mytests/sleepjob.rb"
226
229
  cmdArgs = %w{ touch a/b/ }
227
230
  else
228
- cmdApp = "test/sleepjob.rb"
231
+ cmdApp = APP_DIR + "/mytests/sleepjob.rb"
229
232
  cmdArgs = %w{ touch } << @rawFilePath.shellescape
230
233
  end
231
234
  end
@@ -246,10 +249,10 @@ class DownloadProcess < Qt::Process
246
249
  # debug code.
247
250
  if DEBUG_DOWNLOAD then
248
251
  if rand > 0.4 then
249
- cmdApp = "test/sleepjob.rb"
252
+ cmdApp = APP_DIR + "/mytests/sleepjob.rb"
250
253
  cmdArgs = %w{ touch a/b/ }
251
254
  else
252
- cmdApp = "test/sleepjob.rb"
255
+ cmdApp = APP_DIR + "/mytests/sleepjob.rb"
253
256
  cmdArgs = %w{ cp -f } + [ @rawFilePath.shellescape, @outFilePath.shellescape ]
254
257
  end
255
258
  end
@@ -300,7 +303,15 @@ class DownloadProcess < Qt::Process
300
303
  when CONVERT
301
304
  begin
302
305
  $log.debug { "check duration for convert." }
303
- AudioFile.getDuration(@outFilePath) < AudioFile.getDuration(@rawFilePath) - 10
306
+ outDuration = AudioFile.getDuration(@outFilePath)
307
+ rawDuration = AudioFile.getDuration(@rawFilePath)
308
+ isError = outDuration < rawDuration - 3*60 - 10
309
+ if isError then
310
+ $log.warn { [ "duration check error",
311
+ " outDuration : #{outDuration}",
312
+ " rawDuration : #{rawDuration}" ] }
313
+ end
314
+ isError
304
315
  rescue => e
305
316
  $log.warn { e }
306
317
  true
@@ -105,6 +105,11 @@ class HBoxLayoutWidget < Qt::Widget
105
105
  end
106
106
  end
107
107
 
108
+ def passiveMessage(text)
109
+ %x{ kdialog --passivepopup #{text.shellescape} }
110
+ end
111
+
112
+
108
113
  #--------------------------------------------------------------------------
109
114
  #
110
115
  #
@@ -3,8 +3,6 @@
3
3
  #
4
4
  #
5
5
  class ProgrammeTableWidget < Qt::TableWidget
6
- slots 'filterChanged(const QString &)'
7
-
8
6
  #
9
7
  #
10
8
  class Programme
@@ -46,7 +44,6 @@ class ProgrammeTableWidget < Qt::TableWidget
46
44
  #------------------------------------------------------------------------
47
45
  #
48
46
  #
49
- attr_accessor :mediaFilter
50
47
 
51
48
  def initialize()
52
49
  super(0, 3)
@@ -58,7 +55,6 @@ class ProgrammeTableWidget < Qt::TableWidget
58
55
  self.sortingEnabled = true
59
56
  sortByColumn(2, Qt::DescendingOrder )
60
57
 
61
- @mediaFilter = ''
62
58
 
63
59
  # Hash table : key column_0_item => Programme entry.
64
60
  @table = Hash.new
@@ -82,12 +78,10 @@ class ProgrammeTableWidget < Qt::TableWidget
82
78
  # slot : called when filterLineEdit text is changed.
83
79
  #
84
80
  public
85
-
81
+ slots 'filterChanged(const QString &)'
86
82
  def filterChanged(text)
87
83
  return unless text
88
84
 
89
- text += ' ' + @mediaFilter unless @mediaFilter.empty?
90
-
91
85
  regxs = text.split(/[,\s]+/).map do |w|
92
86
  /#{Regexp.escape(w.strip)}/i
93
87
  end
@@ -117,47 +111,47 @@ class ProgrammeTableWidget < Qt::TableWidget
117
111
 
118
112
  protected
119
113
  def contextMenuEvent(e)
120
- item = itemAt(e.pos)
114
+ prog = self[itemAt(e.pos).row]
121
115
  menu = createPopup
122
- execPopup(menu, e.globalPos, item)
116
+ action = menu.exec(e.globalPos)
117
+ action and execPopup(action, prog)
118
+ menu.deleteLater
123
119
  end
124
120
 
125
121
  def createPopup()
126
122
  menu = Qt::Menu.new
123
+ a = menu.addAction(KDE::Icon.new('search'), i18n('Search Same Programme'))
124
+ a.setVData('searchSame@')
125
+ a = menu.addAction(KDE::Icon.new('search'), i18n('Search Same Category tags'))
126
+ a.setVData('searchSameTags@')
127
+ menu.addSeparator
127
128
  insertPlayerActions(menu)
128
129
  menu
129
130
  end
130
131
 
131
- def execPopup(menu, pos, item)
132
- action = menu.exec(pos)
133
- if action then
134
- action.data
135
- # $log.code { "execute : '#{action.vData}'" }
136
- cmd, exe = action.vData.split(/@/, 2)
137
- # $log.code { "cmd(#{cmd}), exe(#{exe})" }
138
- case cmd
139
- when 'play'
140
- playMedia(exe, item)
141
- else
142
- # self.method(cmd).call(item)
143
- end
132
+ def execPopup(action, item)
133
+ $log.code { "execute : '#{action.vData}'" }
134
+ cmd, exe = action.vData.split(/@/, 2)
135
+ $log.code { "cmd(#{cmd}), exe(#{exe})" }
136
+ if cmd == 'play'
137
+ playMedia(exe, item)
138
+ elsif self.respond_to?(cmd)
139
+ self.method(cmd).call(item)
140
+ else
141
+ $log.warn { "No method #{cmd} in contextmenu." }
144
142
  end
145
- menu.deleteLater
146
143
  end
147
144
 
148
145
  def insertPlayerActions(menu)
149
146
  Mime::services('.wma').each do |s|
150
- if s.exec then
151
- exeName = s.exec[/\w+/]
152
- a = menu.addAction(KDE::Icon.new(exeName), 'Play with ' + exeName)
153
- a.setVData('play@' + s.exec)
154
- end
147
+ exeName = s.exec[/\w+/]
148
+ a = menu.addAction(KDE::Icon.new(exeName), 'Play with ' + exeName)
149
+ a.setVData('play@' + s.exec)
155
150
  end
156
151
  end
157
152
 
158
- def playMedia(exe, item)
153
+ def playMedia(exe, prog)
159
154
  begin
160
- prog = self[item.row]
161
155
  url = prog.content[UrlRegexp] # String[] method extract only 1st one.
162
156
 
163
157
  $log.info { "episode Url : #{url}" }
@@ -168,7 +162,7 @@ class ProgrammeTableWidget < Qt::TableWidget
168
162
  args = args.split(/\s+/).map do |a|
169
163
  a.gsub(/%\w/, url)
170
164
  end
171
- # $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
165
+ $log.debug { "execute cmd '#{cmd}', args '#{args.inspect}'" }
172
166
  proc = Qt::Process.new(self)
173
167
  proc.start(cmd, args)
174
168
 
@@ -177,6 +171,15 @@ class ProgrammeTableWidget < Qt::TableWidget
177
171
  KDE::MessageBox::information(self, i18n("There is not direct stream for this programme."))
178
172
  end
179
173
  end
174
+
175
+ signals 'filterRequest(const QString &)'
176
+ def searchSame(prog)
177
+ emit filterRequest( prog.title.sub(/:.*/, '') )
178
+ end
179
+
180
+ def searchSameTags(prog)
181
+ emit filterRequest( prog.categories )
182
+ end
180
183
  end
181
184
 
182
185
 
@@ -1,4 +1,4 @@
1
- #---------------------------------------------------------------------------------------------------
1
+ #-------------------------------------------------------------------------------------------
2
2
  #
3
3
  # Task Window
4
4
  #
@@ -107,7 +107,6 @@ class TaskWindow < Qt::Widget
107
107
  # context menu : right click popup menu.
108
108
  protected
109
109
  def contextMenuEvent(e)
110
- $log.misc { "right button is clicked." }
111
110
  wItem = itemAt(e.pos)
112
111
  if wItem
113
112
  openContextPopup(e.globalPos, wItem)
@@ -121,7 +120,6 @@ class TaskWindow < Qt::Widget
121
120
  def openContextPopup(pos, wItem)
122
121
  poRow = wItem.row
123
122
  poColumn = wItem.column
124
- $log.misc { "right clicked item (row:#{poRow}, column:#{poColumn})" }
125
123
  process = taskItemAtRow(wItem.row).process
126
124
 
127
125
  menu = Qt::Menu.new
@@ -182,7 +180,9 @@ class TaskWindow < Qt::Widget
182
180
  if process.rawDownloaded? then
183
181
  createPlayers(menu, i18n('Play File with'), "playMP3@", '.mp3')
184
182
  end
185
- createPlayers(menu, i18n('Play Temp File with'), "playTemp@", '.wma')
183
+ if !process.finished? or File.exist?(process.rawFilePath) then
184
+ createPlayers(menu, i18n('Play Temp File with'), "playTemp@", '.wma')
185
+ end
186
186
  end
187
187
  end
188
188
 
@@ -250,9 +250,10 @@ class TaskWindow < Qt::Widget
250
250
  # contextMenu Event
251
251
  def removeTaskData(process, wItem)
252
252
  if process.running? then
253
- process.removeData
254
- $log.info { "task and data removed." }
253
+ process.cancelTask
255
254
  end
255
+ process.removeData
256
+ $log.info { "task and data removed." }
256
257
  ti = taskItemAtRow(wItem.row)
257
258
  deleteItem(ti)
258
259
  end
@@ -294,6 +295,7 @@ class TaskWindow < Qt::Widget
294
295
  def initialize(text)
295
296
  super(text)
296
297
  self.flags = Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled
298
+ self.toolTip = text
297
299
  end
298
300
  end
299
301
 
@@ -24,6 +24,11 @@
24
24
  }
25
25
 
26
26
 
27
+ QLabel a {
28
+ text-decoration: underline; color: rgb(84, 174, 255);
29
+ }
30
+
31
+
27
32
  /*
28
33
  Popup & Menu
29
34
  */
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: irecorder
3
3
  version: !ruby/object:Gem::Version
4
- hash: 21
4
+ hash: 19
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 5
10
- version: 0.0.5
9
+ - 6
10
+ version: 0.0.6
11
11
  platform: linux
12
12
  authors:
13
13
  - ruby.twiddler
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-08-19 00:00:00 +09:00
18
+ date: 2010-09-02 00:00:00 +09:00
19
19
  default_executable:
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -66,7 +66,7 @@ files:
66
66
  - Rakefile
67
67
  - resources/bbcstyle.qss
68
68
  has_rdoc: true
69
- homepage: http://wiki.github.com/rubytwiddler/irecorder/
69
+ homepage: http://github.com/rubytwiddler/irecorder/wiki
70
70
  licenses:
71
71
  - MIT-LICENSE
72
72
  post_install_message:
@@ -96,6 +96,7 @@ requirements:
96
96
  - korundum4
97
97
  - qtwebkit
98
98
  - kio
99
+ - ktexteditor
99
100
  rubyforge_project:
100
101
  rubygems_version: 1.3.7
101
102
  signing_key: