simple_pvr 1.0.0 → 1.1.0

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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +1 -4
  5. data/Gemfile.lock +72 -61
  6. data/README.md +51 -33
  7. data/bin/pvr_server +14 -6
  8. data/changelog.txt +11 -0
  9. data/development_server +14 -6
  10. data/features/channel_overview.feature +1 -1
  11. data/features/recordings.feature +30 -0
  12. data/features/step_definitions/pvr_steps.rb +34 -0
  13. data/features/support/env.rb +1 -2
  14. data/features/support/paths.rb +2 -0
  15. data/lib/simple_pvr.rb +2 -0
  16. data/lib/simple_pvr/model/database_initializer.rb +8 -0
  17. data/lib/simple_pvr/model/programme.rb +12 -2
  18. data/lib/simple_pvr/model/programme_actor.rb +14 -0
  19. data/lib/simple_pvr/model/programme_category.rb +14 -0
  20. data/lib/simple_pvr/model/programme_director.rb +13 -0
  21. data/lib/simple_pvr/model/programme_presenter.rb +13 -0
  22. data/lib/simple_pvr/model/recording.rb +12 -0
  23. data/lib/simple_pvr/programme_icon_fetcher.rb +15 -0
  24. data/lib/simple_pvr/pvr_initializer.rb +4 -4
  25. data/lib/simple_pvr/recorder.rb +4 -3
  26. data/lib/simple_pvr/recording_manager.rb +44 -24
  27. data/lib/simple_pvr/recording_planner.rb +3 -3
  28. data/lib/simple_pvr/scheduler.rb +12 -4
  29. data/lib/simple_pvr/server/base_controller.rb +12 -13
  30. data/lib/simple_pvr/server/channels_controller.rb +5 -5
  31. data/lib/simple_pvr/server/programmes_controller.rb +2 -2
  32. data/lib/simple_pvr/server/schedules_controller.rb +4 -4
  33. data/lib/simple_pvr/server/secured_controller.rb +71 -0
  34. data/lib/simple_pvr/server/shows_controller.rb +13 -8
  35. data/lib/simple_pvr/server/status_controller.rb +1 -1
  36. data/lib/simple_pvr/server/upcoming_recordings_controller.rb +1 -1
  37. data/lib/simple_pvr/version.rb +1 -1
  38. data/lib/simple_pvr/xmltv_reader.rb +37 -4
  39. data/public/css/typeahead.js-bootstrap.css +83 -0
  40. data/public/index.html +45 -37
  41. data/public/js/angular/http-auth-interceptor.js +122 -0
  42. data/public/js/app.js +4 -37
  43. data/public/js/controllers.js +22 -14
  44. data/public/js/directives.js +102 -0
  45. data/public/js/filters.js +0 -31
  46. data/public/js/services.js +64 -0
  47. data/public/js/typeahead/typeahead.min.js +7 -0
  48. data/public/partials/about.html +15 -13
  49. data/public/partials/channels.html +41 -36
  50. data/public/partials/programme.html +20 -4
  51. data/public/partials/programmeListing.html +14 -13
  52. data/public/partials/schedule.html +91 -86
  53. data/public/partials/schedules.html +28 -16
  54. data/public/partials/search.html +10 -11
  55. data/public/partials/show.html +26 -9
  56. data/public/partials/shows.html +5 -6
  57. data/public/partials/status.html +1 -1
  58. data/public/templates/loginDialog.html +30 -0
  59. data/public/templates/logoutLink.html +1 -0
  60. data/public/templates/titleSearch.html +5 -3
  61. data/simple_pvr.gemspec +6 -4
  62. data/spec/resources/dummyImage.png +1 -0
  63. data/spec/resources/programmes-with-categories.xmltv +27 -0
  64. data/spec/resources/programmes-with-credits.xmltv +24 -0
  65. data/spec/resources/programmes-with-icons.xmltv +25 -0
  66. data/spec/resources/programmes-with-presenters.xmltv +19 -0
  67. data/spec/resources/{programs-without-icon.xmltv → programmes-without-icon.xmltv} +0 -0
  68. data/spec/resources/{programs.xmltv → programmes.xmltv} +0 -4
  69. data/spec/simple_pvr/ffmpeg_spec.rb +24 -22
  70. data/spec/simple_pvr/hdhomerun_spec.rb +69 -67
  71. data/spec/simple_pvr/model/channel_spec.rb +101 -101
  72. data/spec/simple_pvr/model/programme_spec.rb +104 -104
  73. data/spec/simple_pvr/model/schedule_spec.rb +74 -74
  74. data/spec/simple_pvr/programme_icon_fetcher_spec.rb +25 -0
  75. data/spec/simple_pvr/pvr_initializer_spec.rb +40 -38
  76. data/spec/simple_pvr/recorder_spec.rb +37 -26
  77. data/spec/simple_pvr/recording_manager_spec.rb +160 -133
  78. data/spec/simple_pvr/recording_planner_spec.rb +213 -211
  79. data/spec/simple_pvr/scheduler_spec.rb +189 -172
  80. data/spec/simple_pvr/server/secured_controller_spec.rb +118 -0
  81. data/spec/simple_pvr/xmltv_reader_spec.rb +89 -41
  82. data/test/karma.conf.js +7 -4
  83. data/test/unit/filtersSpec.js +0 -36
  84. metadata +79 -63
  85. data/public/css/bootstrap-responsive.min.css +0 -9
  86. data/public/css/bootstrap.min.css +0 -9
  87. data/public/img/glyphicons-halflings-white.png +0 -0
  88. data/public/img/glyphicons-halflings.png +0 -0
  89. data/public/js/angular/angular-resource.min.js +0 -10
  90. data/public/js/angular/angular.min.js +0 -162
  91. data/public/js/bootstrap/bootstrap.min.js +0 -6
  92. data/public/js/jquery/jquery.min.js +0 -5
  93. data/test/lib/angular/angular-mocks.js +0 -1768
@@ -11,7 +11,7 @@ Background:
11
11
  | Animals |
12
12
  And the following programmes:
13
13
  | title | subtitle | channel | day |
14
- | Blast from past | Prehistoric documentary | Channel 1 | -1 |
14
+ | Blast from past | Prehistoric documentary | Channel 1 | -1 |
15
15
  | Bonderøven | Danish documentary | Channel 1 | 0 |
16
16
  | Blood and Bone | American action movie from 2009 | Channel 1 | 0 |
17
17
  | Noddy | Children's programme | Channel 2 | 0 |
@@ -0,0 +1,30 @@
1
+ Feature: Channel overview
2
+ In order to actually use the system for anything
3
+ As a user
4
+ I need to be able to view and administer recorded shows
5
+
6
+ Background:
7
+ Given the following recorded shows:
8
+ | name |
9
+ | Bonderøven |
10
+ | Noddy |
11
+ | Star Trek |
12
+ And the following recordings:
13
+ | name | id |
14
+ | Bonderøven | 1234567 |
15
+ | Bonderøven | 1234567-1 |
16
+ | Bonderøven | 1234678 |
17
+ | Star Trek | 2345678 |
18
+
19
+ Scenario: Shows overview shows all recorded shows
20
+ Given I am on the shows page
21
+ Then I should see "Bonderøven"
22
+ And I should see "Noddy"
23
+ And I should see "Star Trek"
24
+
25
+ Scenario: Show details shows all recordings of show
26
+ Given I am on the shows page
27
+ # The following does not work with Poltergeist/PhantomJS - prints infinitely many
28
+ # AngularJS errors :-(
29
+ #And I see the details for show "Bonderøven"
30
+ #Then I should see 3 recordings
@@ -1,4 +1,5 @@
1
1
  require 'timeout'
2
+ require 'pry'
2
3
 
3
4
  RSpec::Matchers.define :become do |expected|
4
5
  match do |block|
@@ -40,6 +41,24 @@ Given /the following channels\:/ do |channel_table|
40
41
  end
41
42
  end
42
43
 
44
+ Given /the following recorded shows:/ do |shows_table|
45
+ recordings_directory = Dir.pwd + '/features/recordings'
46
+ FileUtils.rm_rf(recordings_directory)
47
+ FileUtils.makedirs(recordings_directory)
48
+
49
+ shows_table.hashes.each do |show|
50
+ FileUtils.makedirs(recordings_directory + '/' + show['name'])
51
+ end
52
+ end
53
+
54
+ Given /the following recordings:/ do |recordings_table|
55
+ recordings_directory = Dir.pwd + '/features/recordings'
56
+
57
+ recordings_table.hashes.each do |recording|
58
+ FileUtils.makedirs(recordings_directory + '/' + recording['name'] + '/' + recording['id'])
59
+ end
60
+ end
61
+
43
62
  Given /I have navigated to the week overview for channel "(.*)"/ do |channel|
44
63
  visit path_to('the channel overview page')
45
64
  fill_in('channel_filter', :with => channel)
@@ -97,6 +116,17 @@ Then /I should see the programme title suggestion "(.*)"/ do |suggestion|
97
116
  page.should have_text(text)
98
117
  end
99
118
 
119
+ When /I see the details for show "(.*)"/ do |show_name|
120
+ div = find(:xpath, "//h2[text()='#{show_name}']").find(:xpath, '..')
121
+ within(div) do
122
+ click_on('Episodes')
123
+ end
124
+ end
125
+
126
+ Then /I should see (\d*) recordings/ do |number_of_recordings|
127
+ expect { page.all(:xpath, "//button[text()='Delete recording']").length }.to become(number_of_recordings.to_i)
128
+ end
129
+
100
130
  Then /I should see "(.*)" in the page contents/ do |text|
101
131
  within('#contents') do
102
132
  page.should have_text(text)
@@ -141,6 +171,10 @@ Then /I wait (\d*) seconds/ do |seconds|
141
171
  sleep seconds.to_i
142
172
  end
143
173
 
174
+ Then /I start the debugger/ do
175
+ binding.pry
176
+ end
177
+
144
178
  def find_or_create_channel_with_name(name)
145
179
  channel = SimplePvr::Model::Channel.first(name: name)
146
180
  channel ? channel : SimplePvr::Model::Channel.add(name, 0, 0)
@@ -10,9 +10,8 @@ SimplePvr::PvrInitializer.setup_for_integration_test
10
10
  SimplePvr::RecordingPlanner.reload
11
11
 
12
12
  Capybara.app = eval "Rack::Builder.new {( " + SimplePvr::PvrInitializer.rack_maps_file + ")}"
13
- Capybara.default_driver = (ENV['capybara_driver'] || 'selenium').to_sym
13
+ Capybara.default_driver = (ENV['capybara_driver'] || 'poltergeist').to_sym # 'selenium' can be nice too
14
14
  Capybara.default_wait_time = 30
15
- Capybara.ignore_hidden_elements = true # AngularJS shows and hides elements all the time, so this is important
16
15
 
17
16
  class SimplePvrWorld
18
17
  include Capybara::DSL
@@ -7,6 +7,8 @@ module NavigationHelpers
7
7
  '/channels'
8
8
  when 'the status page'
9
9
  '/status'
10
+ when 'the shows page'
11
+ '/shows'
10
12
  else
11
13
  raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
12
14
  "Now, go and add a mapping in #{__FILE__}"
@@ -9,10 +9,12 @@ require File.dirname(__FILE__) + '/simple_pvr/recording_planner'
9
9
  require File.dirname(__FILE__) + '/simple_pvr/recorder'
10
10
  require File.dirname(__FILE__) + '/simple_pvr/scheduler'
11
11
  require File.dirname(__FILE__) + '/simple_pvr/recording_manager'
12
+ require File.dirname(__FILE__) + '/simple_pvr/programme_icon_fetcher'
12
13
  require File.dirname(__FILE__) + '/simple_pvr/ffmpeg'
13
14
  require File.dirname(__FILE__) + '/simple_pvr/xmltv_reader'
14
15
 
15
16
  require File.dirname(__FILE__) + '/simple_pvr/server/base_controller'
17
+ require File.dirname(__FILE__) + '/simple_pvr/server/secured_controller'
16
18
  require File.dirname(__FILE__) + '/simple_pvr/server/app_controller'
17
19
  require File.dirname(__FILE__) + '/simple_pvr/server/channels_controller'
18
20
  require File.dirname(__FILE__) + '/simple_pvr/server/programmes_controller'
@@ -3,6 +3,10 @@ require 'dm-migrations'
3
3
  require 'active_support/time_with_zone'
4
4
  require File.dirname(__FILE__) + '/channel'
5
5
  require File.dirname(__FILE__) + '/programme'
6
+ require File.dirname(__FILE__) + '/programme_category'
7
+ require File.dirname(__FILE__) + '/programme_director'
8
+ require File.dirname(__FILE__) + '/programme_presenter'
9
+ require File.dirname(__FILE__) + '/programme_actor'
6
10
  require File.dirname(__FILE__) + '/schedule'
7
11
  require File.dirname(__FILE__) + '/recording'
8
12
 
@@ -19,6 +23,10 @@ module SimplePvr
19
23
 
20
24
  def self.clear
21
25
  Schedule.destroy
26
+ ProgrammeCategory.destroy
27
+ ProgrammePresenter.destroy
28
+ ProgrammeDirector.destroy
29
+ ProgrammeActor.destroy
22
30
  Programme.destroy
23
31
  Channel.destroy
24
32
  end
@@ -11,8 +11,13 @@ module SimplePvr
11
11
  property :start_time, DateTime
12
12
  property :duration, Integer
13
13
  property :episode_num, String, index: true
14
+ property :icon_url, String, :length => 255
14
15
 
15
16
  belongs_to :channel
17
+ has n, :categories, 'ProgrammeCategory'
18
+ has n, :directors, 'ProgrammeDirector'
19
+ has n, :presenters, 'ProgrammePresenter'
20
+ has n, :actors, 'ProgrammeActor'
16
21
 
17
22
  def end_time
18
23
  @start_time.advance(seconds: duration)
@@ -23,10 +28,14 @@ module SimplePvr
23
28
  end
24
29
 
25
30
  def self.clear
31
+ ProgrammeCategory.destroy
32
+ ProgrammeDirector.destroy
33
+ ProgrammePresenter.destroy
34
+ ProgrammeActor.destroy
26
35
  Programme.destroy
27
36
  end
28
37
 
29
- def self.add(channel, title, subtitle, description, start_time, duration, episode_num)
38
+ def self.add(channel, title, subtitle, description, start_time, duration, episode_num, icon_url=nil)
30
39
  channel.programmes.create(
31
40
  channel: channel,
32
41
  title: title,
@@ -34,7 +43,8 @@ module SimplePvr
34
43
  description: description,
35
44
  start_time: start_time,
36
45
  duration: duration.to_i,
37
- episode_num: episode_num)
46
+ episode_num: episode_num,
47
+ icon_url: icon_url)
38
48
  end
39
49
 
40
50
  def self.with_title(title)
@@ -0,0 +1,14 @@
1
+ module SimplePvr
2
+ module Model
3
+ class ProgrammeActor
4
+ include DataMapper::Resource
5
+ storage_names[:default] = 'programme_actors'
6
+
7
+ property :id, Serial
8
+ property :role_name, String, :length => 255
9
+ property :actor_name, String, :length => 255
10
+
11
+ belongs_to :programme
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ module SimplePvr
2
+ module Model
3
+ class ProgrammeCategory
4
+ include DataMapper::Resource
5
+ storage_names[:default] = 'programme_categories'
6
+
7
+ property :id, Serial
8
+ property :language, String, :length => 4
9
+ property :name, String, :length => 255
10
+
11
+ belongs_to :programme
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module SimplePvr
2
+ module Model
3
+ class ProgrammeDirector
4
+ include DataMapper::Resource
5
+ storage_names[:default] = 'programme_directors'
6
+
7
+ property :id, Serial
8
+ property :name, String, :length => 255
9
+
10
+ belongs_to :programme
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module SimplePvr
2
+ module Model
3
+ class ProgrammePresenter
4
+ include DataMapper::Resource
5
+ storage_names[:default] = 'programme_presenters'
6
+
7
+ property :id, Serial
8
+ property :name, String, :length => 255
9
+
10
+ belongs_to :programme
11
+ end
12
+ end
13
+ end
@@ -26,6 +26,18 @@ module SimplePvr
26
26
  def inspect
27
27
  "'#{@show_name}' from '#{@channel.name}' at '#{@start_time}' with duration #{@duration} and programme #{@programme}"
28
28
  end
29
+
30
+ def similar_to(other)
31
+ other != nil &&
32
+ other.channel == @channel &&
33
+ other.show_name == @show_name &&
34
+ other.start_time == @start_time
35
+ end
36
+
37
+ def update_with(other)
38
+ @duration = other.duration
39
+ @programme = other.programme
40
+ end
29
41
 
30
42
  def ==(other)
31
43
  other != nil &&
@@ -0,0 +1,15 @@
1
+ require 'open-uri'
2
+
3
+ module SimplePvr
4
+ class ProgrammeIconFetcher
5
+ def self.fetch(url, destination_file)
6
+ Thread.new do
7
+ File.open(destination_file, "wb") do |saved_file|
8
+ open(url, 'rb') do |read_file|
9
+ saved_file.write(read_file.read)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,13 +2,13 @@ module SimplePvr
2
2
  class PvrInitializer
3
3
  def self.setup
4
4
  Model::DatabaseInitializer.setup
5
- setup_with_hdhomerun(HDHomeRun.new)
5
+ setup_with_hdhomerun_and_recording_directory(HDHomeRun.new, Dir.pwd + '/recordings')
6
6
  @hdhomerun.scan_for_channels if Model::Channel.all.empty?
7
7
  end
8
8
 
9
9
  def self.setup_for_integration_test
10
10
  Model::DatabaseInitializer.prepare_for_test
11
- setup_with_hdhomerun(HDHomeRunFake.new)
11
+ setup_with_hdhomerun_and_recording_directory(HDHomeRunFake.new, Dir.pwd + '/features/recordings')
12
12
  end
13
13
 
14
14
  def self.hdhomerun
@@ -37,9 +37,9 @@ module SimplePvr
37
37
  end
38
38
 
39
39
  private
40
- def self.setup_with_hdhomerun(hdhomerun)
40
+ def self.setup_with_hdhomerun_and_recording_directory(hdhomerun, recording_directory)
41
41
  @hdhomerun = hdhomerun
42
- @recording_manager = RecordingManager.new
42
+ @recording_manager = RecordingManager.new(recording_directory)
43
43
  @scheduler = Scheduler.new
44
44
  @scheduler.start
45
45
  end
@@ -1,6 +1,4 @@
1
1
  require 'fileutils'
2
- require File.dirname(__FILE__) + '/hdhomerun'
3
- require File.dirname(__FILE__) + '/pvr_logger'
4
2
 
5
3
  module SimplePvr
6
4
  class Recorder
@@ -11,7 +9,10 @@ module SimplePvr
11
9
  def start!
12
10
  @directory = PvrInitializer.recording_manager.create_directory_for_recording(@recording)
13
11
  PvrInitializer.hdhomerun.start_recording(@tuner, @recording.channel.frequency, @recording.channel.channel_id, @directory)
14
-
12
+
13
+ icon_url = @recording.programme.icon_url
14
+ ProgrammeIconFetcher.fetch(icon_url, "#{@directory}/icon") if icon_url
15
+
15
16
  PvrLogger.info "Started recording #{@recording.show_name} in #{@directory}"
16
17
  end
17
18
 
@@ -1,9 +1,9 @@
1
1
  module SimplePvr
2
- RecordingMetadata = Struct.new(:has_thumbnail, :has_webm, :show_name, :episode, :channel, :subtitle, :description, :start_time, :duration)
2
+ RecordingMetadata = Struct.new(:id, :has_icon, :has_thumbnail, :has_webm, :show_name, :channel, :subtitle, :description, :categories, :directors, :presenters, :actors, :start_time, :duration)
3
3
 
4
4
  class RecordingManager
5
- def initialize(recordings_directory=nil)
6
- @recordings_directory = recordings_directory || Dir.pwd + '/recordings'
5
+ def initialize(recordings_directory)
6
+ @recordings_directory = recordings_directory
7
7
  end
8
8
 
9
9
  def shows
@@ -14,45 +14,53 @@ module SimplePvr
14
14
  FileUtils.rm_rf(directory_for_show(show_name))
15
15
  end
16
16
 
17
- def episodes_of(show_name)
18
- episodes = Dir.new(directory_for_show(show_name)).entries - ['.', '..']
19
- episodes.sort.map do |episode|
20
- metadata_for(show_name, episode)
17
+ def recordings_of(show_name)
18
+ recordings = Dir.new(directory_for_show(show_name)).entries - ['.', '..']
19
+ recordings.sort.map do |recording_id|
20
+ metadata_for(show_name, recording_id)
21
21
  end
22
22
  end
23
23
 
24
- def metadata_for(show_name, episode)
25
- metadata_file_name = directory_for_show_and_episode(show_name, episode) + '/metadata.yml'
24
+ def metadata_for(show_name, recording_id)
25
+ metadata_file_name = directory_for_show_and_recording(show_name, recording_id) + '/metadata.yml'
26
26
  metadata = File.exists?(metadata_file_name) ? YAML.load_file(metadata_file_name) : {}
27
27
 
28
- thumbnail_file_name = directory_for_show_and_episode(show_name, episode) + '/thumbnail.png'
28
+ icon_file_name = directory_for_show_and_recording(show_name, recording_id) + '/icon'
29
+ has_icon = File.exists?(icon_file_name)
30
+
31
+ thumbnail_file_name = directory_for_show_and_recording(show_name, recording_id) + '/thumbnail.png'
29
32
  has_thumbnail = File.exists?(thumbnail_file_name)
30
33
 
31
- webm_file_name = directory_for_show_and_episode(show_name, episode) + '/stream.webm'
34
+ webm_file_name = directory_for_show_and_recording(show_name, recording_id) + '/stream.webm'
32
35
  has_webm = File.exists?(webm_file_name)
33
36
 
34
37
  RecordingMetadata.new(
38
+ recording_id,
39
+ has_icon,
35
40
  has_thumbnail,
36
41
  has_webm,
37
42
  show_name,
38
- episode,
39
43
  metadata[:channel],
40
44
  metadata[:subtitle],
41
45
  metadata[:description],
46
+ metadata[:categories] || [],
47
+ metadata[:directors] || [],
48
+ metadata[:presenters] || [],
49
+ metadata[:actors] || [],
42
50
  metadata[:start_time],
43
51
  metadata[:duration])
44
52
  end
45
53
 
46
- def delete_show_episode(show_name, episode)
47
- FileUtils.rm_rf(@recordings_directory + '/' + show_name + '/' + episode)
54
+ def delete_show_recording(show_name, recording_id)
55
+ FileUtils.rm_rf(@recordings_directory + '/' + show_name + '/' + recording_id)
48
56
  end
49
57
 
50
58
  def create_directory_for_recording(recording)
51
59
  show_directory = directory_for_show(recording.show_name)
52
60
  ensure_directory_exists(show_directory)
53
61
 
54
- new_sequence_number = next_sequence_number_for(show_directory)
55
- recording_directory = "#{show_directory}/#{new_sequence_number}"
62
+ recording_subdirectory = subdirectory_for_recording(recording, show_directory)
63
+ recording_directory = "#{show_directory}/#{recording_subdirectory}"
56
64
  ensure_directory_exists(recording_directory)
57
65
 
58
66
  create_metadata(recording_directory, recording)
@@ -60,8 +68,8 @@ module SimplePvr
60
68
  recording_directory
61
69
  end
62
70
 
63
- def directory_for_show_and_episode(show_name, episode)
64
- directory_for_show(show_name) + '/' + episode
71
+ def directory_for_show_and_recording(show_name, recording_id)
72
+ directory_for_show(show_name) + '/' + recording_id
65
73
  end
66
74
 
67
75
  private
@@ -75,9 +83,16 @@ module SimplePvr
75
83
  FileUtils.makedirs(directory) unless File.exists?(directory)
76
84
  end
77
85
 
78
- def next_sequence_number_for(base_directory)
79
- largest_current_sequence_number = Dir.new(base_directory).map {|dir_name| dir_name.to_i }.max
80
- 1 + largest_current_sequence_number
86
+ def subdirectory_for_recording(recording, base_directory)
87
+ start_time_millis = recording.start_time.to_i
88
+ path = "#{base_directory}/#{start_time_millis}"
89
+ return "#{start_time_millis}" unless File.exists?(path)
90
+
91
+ sequence_number = 1
92
+ while File.exists?("#{path}-#{sequence_number}")
93
+ sequence_number += 1
94
+ end
95
+ "#{start_time_millis}-#{sequence_number}"
81
96
  end
82
97
 
83
98
  def create_metadata(directory, recording)
@@ -88,10 +103,15 @@ module SimplePvr
88
103
  duration: recording.duration
89
104
  }
90
105
 
91
- if recording.programme
106
+ programme = recording.programme
107
+ if programme
92
108
  metadata.merge!({
93
- subtitle: recording.programme.subtitle,
94
- description: recording.programme.description
109
+ subtitle: programme.subtitle,
110
+ description: programme.description,
111
+ categories: programme.categories.map { |category| category.name },
112
+ directors: programme.directors.map { |director| director.name },
113
+ presenters: programme.presenters.map { |presenter| presenter.name },
114
+ actors: programme.actors.map { |actor| { role_name: actor.role_name, actor_name: actor.actor_name } }
95
115
  })
96
116
  end
97
117