irecorder 0.0.5-linux → 0.0.6-linux

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