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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +1 -4
- data/Gemfile.lock +72 -61
- data/README.md +51 -33
- data/bin/pvr_server +14 -6
- data/changelog.txt +11 -0
- data/development_server +14 -6
- data/features/channel_overview.feature +1 -1
- data/features/recordings.feature +30 -0
- data/features/step_definitions/pvr_steps.rb +34 -0
- data/features/support/env.rb +1 -2
- data/features/support/paths.rb +2 -0
- data/lib/simple_pvr.rb +2 -0
- data/lib/simple_pvr/model/database_initializer.rb +8 -0
- data/lib/simple_pvr/model/programme.rb +12 -2
- data/lib/simple_pvr/model/programme_actor.rb +14 -0
- data/lib/simple_pvr/model/programme_category.rb +14 -0
- data/lib/simple_pvr/model/programme_director.rb +13 -0
- data/lib/simple_pvr/model/programme_presenter.rb +13 -0
- data/lib/simple_pvr/model/recording.rb +12 -0
- data/lib/simple_pvr/programme_icon_fetcher.rb +15 -0
- data/lib/simple_pvr/pvr_initializer.rb +4 -4
- data/lib/simple_pvr/recorder.rb +4 -3
- data/lib/simple_pvr/recording_manager.rb +44 -24
- data/lib/simple_pvr/recording_planner.rb +3 -3
- data/lib/simple_pvr/scheduler.rb +12 -4
- data/lib/simple_pvr/server/base_controller.rb +12 -13
- data/lib/simple_pvr/server/channels_controller.rb +5 -5
- data/lib/simple_pvr/server/programmes_controller.rb +2 -2
- data/lib/simple_pvr/server/schedules_controller.rb +4 -4
- data/lib/simple_pvr/server/secured_controller.rb +71 -0
- data/lib/simple_pvr/server/shows_controller.rb +13 -8
- data/lib/simple_pvr/server/status_controller.rb +1 -1
- data/lib/simple_pvr/server/upcoming_recordings_controller.rb +1 -1
- data/lib/simple_pvr/version.rb +1 -1
- data/lib/simple_pvr/xmltv_reader.rb +37 -4
- data/public/css/typeahead.js-bootstrap.css +83 -0
- data/public/index.html +45 -37
- data/public/js/angular/http-auth-interceptor.js +122 -0
- data/public/js/app.js +4 -37
- data/public/js/controllers.js +22 -14
- data/public/js/directives.js +102 -0
- data/public/js/filters.js +0 -31
- data/public/js/services.js +64 -0
- data/public/js/typeahead/typeahead.min.js +7 -0
- data/public/partials/about.html +15 -13
- data/public/partials/channels.html +41 -36
- data/public/partials/programme.html +20 -4
- data/public/partials/programmeListing.html +14 -13
- data/public/partials/schedule.html +91 -86
- data/public/partials/schedules.html +28 -16
- data/public/partials/search.html +10 -11
- data/public/partials/show.html +26 -9
- data/public/partials/shows.html +5 -6
- data/public/partials/status.html +1 -1
- data/public/templates/loginDialog.html +30 -0
- data/public/templates/logoutLink.html +1 -0
- data/public/templates/titleSearch.html +5 -3
- data/simple_pvr.gemspec +6 -4
- data/spec/resources/dummyImage.png +1 -0
- data/spec/resources/programmes-with-categories.xmltv +27 -0
- data/spec/resources/programmes-with-credits.xmltv +24 -0
- data/spec/resources/programmes-with-icons.xmltv +25 -0
- data/spec/resources/programmes-with-presenters.xmltv +19 -0
- data/spec/resources/{programs-without-icon.xmltv → programmes-without-icon.xmltv} +0 -0
- data/spec/resources/{programs.xmltv → programmes.xmltv} +0 -4
- data/spec/simple_pvr/ffmpeg_spec.rb +24 -22
- data/spec/simple_pvr/hdhomerun_spec.rb +69 -67
- data/spec/simple_pvr/model/channel_spec.rb +101 -101
- data/spec/simple_pvr/model/programme_spec.rb +104 -104
- data/spec/simple_pvr/model/schedule_spec.rb +74 -74
- data/spec/simple_pvr/programme_icon_fetcher_spec.rb +25 -0
- data/spec/simple_pvr/pvr_initializer_spec.rb +40 -38
- data/spec/simple_pvr/recorder_spec.rb +37 -26
- data/spec/simple_pvr/recording_manager_spec.rb +160 -133
- data/spec/simple_pvr/recording_planner_spec.rb +213 -211
- data/spec/simple_pvr/scheduler_spec.rb +189 -172
- data/spec/simple_pvr/server/secured_controller_spec.rb +118 -0
- data/spec/simple_pvr/xmltv_reader_spec.rb +89 -41
- data/test/karma.conf.js +7 -4
- data/test/unit/filtersSpec.js +0 -36
- metadata +79 -63
- data/public/css/bootstrap-responsive.min.css +0 -9
- data/public/css/bootstrap.min.css +0 -9
- data/public/img/glyphicons-halflings-white.png +0 -0
- data/public/img/glyphicons-halflings.png +0 -0
- data/public/js/angular/angular-resource.min.js +0 -10
- data/public/js/angular/angular.min.js +0 -162
- data/public/js/bootstrap/bootstrap.min.js +0 -6
- data/public/js/jquery/jquery.min.js +0 -5
- data/test/lib/angular/angular-mocks.js +0 -1768
|
@@ -3,7 +3,7 @@ module SimplePvr
|
|
|
3
3
|
def self.reload
|
|
4
4
|
planner = self.new
|
|
5
5
|
planner.read
|
|
6
|
-
|
|
6
|
+
Model::Schedule.cleanup
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
def initialize
|
|
@@ -123,8 +123,8 @@ module SimplePvr
|
|
|
123
123
|
end
|
|
124
124
|
end
|
|
125
125
|
|
|
126
|
-
def add_recording(title, channel, start_time, duration, programme
|
|
127
|
-
@recordings <<
|
|
126
|
+
def add_recording(title, channel, start_time, duration, programme)
|
|
127
|
+
@recordings << Model::Recording.new(channel, title, start_time, duration, programme)
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
# Given a time of day as string, e.g. "10:35", returns an integer which can be used to compare with other
|
data/lib/simple_pvr/scheduler.rb
CHANGED
|
@@ -4,7 +4,9 @@ module SimplePvr
|
|
|
4
4
|
|
|
5
5
|
def initialize
|
|
6
6
|
@number_of_tuners = 2
|
|
7
|
-
@
|
|
7
|
+
@current_recordings = [nil] * @number_of_tuners
|
|
8
|
+
@recorders = {}
|
|
9
|
+
@upcoming_recordings = []
|
|
8
10
|
@mutex = Mutex.new
|
|
9
11
|
end
|
|
10
12
|
|
|
@@ -16,7 +18,7 @@ module SimplePvr
|
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
end
|
|
19
|
-
|
|
21
|
+
|
|
20
22
|
def recordings=(recordings)
|
|
21
23
|
@mutex.synchronize do
|
|
22
24
|
@upcoming_recordings = recordings.sort_by {|r| r.start_time }.find_all {|r| !r.expired? }
|
|
@@ -82,8 +84,14 @@ module SimplePvr
|
|
|
82
84
|
end
|
|
83
85
|
|
|
84
86
|
def stop_current_recordings_not_relevant_anymore
|
|
85
|
-
@current_recordings.each do |recording|
|
|
86
|
-
|
|
87
|
+
@current_recordings.find_all {|r| r != nil }.each do |recording|
|
|
88
|
+
similar_recording = @upcoming_recordings.find {|r| recording.similar_to(r) }
|
|
89
|
+
if similar_recording
|
|
90
|
+
# It's (probably) the same show, so we continue recording and update with new information
|
|
91
|
+
similar_recording.update_with(recording)
|
|
92
|
+
else
|
|
93
|
+
stop_recording(recording)
|
|
94
|
+
end
|
|
87
95
|
end
|
|
88
96
|
end
|
|
89
97
|
|
|
@@ -8,16 +8,6 @@ module SimplePvr
|
|
|
8
8
|
class BaseController < Sinatra::Base
|
|
9
9
|
include ERB::Util
|
|
10
10
|
|
|
11
|
-
http_username, http_password = ENV['username'], ENV['password']
|
|
12
|
-
if http_username && http_password
|
|
13
|
-
PvrLogger.info('Securing server with Basic HTTP Authentication')
|
|
14
|
-
use Rack::Auth::Basic, 'Restricted Area' do |username, password|
|
|
15
|
-
[username, password] == [http_username, http_password]
|
|
16
|
-
end
|
|
17
|
-
else
|
|
18
|
-
PvrLogger.info('Beware: Unsecured server. Do not expose to the rest of the world!')
|
|
19
|
-
end
|
|
20
|
-
|
|
21
11
|
configure do
|
|
22
12
|
set :public_folder, File.dirname(__FILE__) + '/../../../public/'
|
|
23
13
|
mime_type :webm, 'video/webm'
|
|
@@ -34,23 +24,32 @@ module SimplePvr
|
|
|
34
24
|
title: programme.title,
|
|
35
25
|
subtitle: programme.subtitle,
|
|
36
26
|
description: programme.description,
|
|
27
|
+
directors: programme.directors.map { |director| director.name },
|
|
28
|
+
presenters: programme.presenters.map { |presenter| presenter.name },
|
|
29
|
+
actors: programme.actors.map { |actor| { role_name: actor.role_name, actor_name: actor.actor_name } },
|
|
30
|
+
categories: programme.categories.map { |category| category.name },
|
|
37
31
|
start_time: programme.start_time,
|
|
38
32
|
is_scheduled: PvrInitializer.scheduler.scheduled?(programme),
|
|
39
33
|
episode_num: programme.episode_num,
|
|
34
|
+
icon_url: programme.icon_url,
|
|
40
35
|
is_outdated: programme.outdated?
|
|
41
36
|
}
|
|
42
37
|
end
|
|
43
38
|
|
|
44
39
|
def recording_hash(show_id, recording)
|
|
45
|
-
path = PvrInitializer.recording_manager.
|
|
40
|
+
path = PvrInitializer.recording_manager.directory_for_show_and_recording(show_id, recording.id)
|
|
46
41
|
{
|
|
47
|
-
id: recording.
|
|
42
|
+
id: recording.id,
|
|
48
43
|
show_id: show_id,
|
|
49
|
-
episode: recording.episode,
|
|
50
44
|
subtitle: recording.subtitle,
|
|
51
45
|
description: recording.description,
|
|
46
|
+
directors: recording.directors,
|
|
47
|
+
presenters: recording.presenters,
|
|
48
|
+
actors: recording.actors,
|
|
49
|
+
categories: recording.categories,
|
|
52
50
|
start_time: recording.start_time,
|
|
53
51
|
channel_name: recording.channel,
|
|
52
|
+
has_icon: recording.has_icon,
|
|
54
53
|
has_thumbnail: recording.has_thumbnail,
|
|
55
54
|
has_webm: recording.has_webm,
|
|
56
55
|
local_file_url: 'file://' + File.join(path, 'stream.ts')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SimplePvr
|
|
2
2
|
module Server
|
|
3
|
-
class ChannelsController <
|
|
3
|
+
class ChannelsController < SecuredController
|
|
4
4
|
get '/' do
|
|
5
5
|
Model::Channel.all_with_current_programmes.map do |channel_with_current_programmes|
|
|
6
6
|
channel_with_current_programmes_hash(channel_with_current_programmes)
|
|
@@ -8,11 +8,11 @@ module SimplePvr
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
get '/:id' do |id|
|
|
11
|
-
channel_with_current_programmes_hash(Model::Channel.with_current_programmes(id)).to_json
|
|
11
|
+
channel_with_current_programmes_hash(Model::Channel.with_current_programmes(id.to_i)).to_json
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
post '/:id/hide' do |id|
|
|
15
|
-
channel = Model::Channel.get(id)
|
|
15
|
+
channel = Model::Channel.get(id.to_i)
|
|
16
16
|
channel.hidden = true
|
|
17
17
|
channel.save
|
|
18
18
|
{
|
|
@@ -23,7 +23,7 @@ module SimplePvr
|
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
post '/:id/show' do |id|
|
|
26
|
-
channel = Model::Channel.get(id)
|
|
26
|
+
channel = Model::Channel.get(id.to_i)
|
|
27
27
|
channel.hidden = false
|
|
28
28
|
channel.save
|
|
29
29
|
{
|
|
@@ -42,7 +42,7 @@ module SimplePvr
|
|
|
42
42
|
end
|
|
43
43
|
previous_date = this_date.advance(days: -7)
|
|
44
44
|
next_date = this_date.advance(days: 7)
|
|
45
|
-
channel = Model::Channel.get(channel_id)
|
|
45
|
+
channel = Model::Channel.get(channel_id.to_i)
|
|
46
46
|
|
|
47
47
|
days = (0..6).map do |date_advanced|
|
|
48
48
|
from_date = this_date.advance(days: date_advanced)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SimplePvr
|
|
2
2
|
module Server
|
|
3
|
-
class ProgrammesController <
|
|
3
|
+
class ProgrammesController < SecuredController
|
|
4
4
|
get '/title_search' do
|
|
5
5
|
Model::Programme.titles_containing(params['query']).to_json
|
|
6
6
|
end
|
|
@@ -10,7 +10,7 @@ module SimplePvr
|
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
get '/:id' do |id|
|
|
13
|
-
programme = Model::Programme.get(id)
|
|
13
|
+
programme = Model::Programme.get(id.to_i)
|
|
14
14
|
programme_hash(programme).to_json
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SimplePvr
|
|
2
2
|
module Server
|
|
3
|
-
class SchedulesController <
|
|
3
|
+
class SchedulesController < SecuredController
|
|
4
4
|
# Must come before the "post '/:id'" below, or it won't get hit :-)
|
|
5
5
|
post '/reload' do
|
|
6
6
|
reload_schedules
|
|
@@ -20,13 +20,13 @@ module SimplePvr
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
get '/:id' do |id|
|
|
23
|
-
schedule_map(Model::Schedule.get(id)).to_json
|
|
23
|
+
schedule_map(Model::Schedule.get(id.to_i)).to_json
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
post '/:id' do |id|
|
|
27
27
|
parameters = JSON.parse(request.body.read)
|
|
28
28
|
channel_id = parameters['channel'] != nil ? parameters['channel']['id'].to_i : 0
|
|
29
|
-
schedule = Model::Schedule.get(id)
|
|
29
|
+
schedule = Model::Schedule.get(id.to_i)
|
|
30
30
|
schedule.title = parameters['title']
|
|
31
31
|
schedule.channel = channel_id > 0 ? Model::Channel.get(channel_id) : nil
|
|
32
32
|
|
|
@@ -51,7 +51,7 @@ module SimplePvr
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
delete '/:id' do |id|
|
|
54
|
-
Model::Schedule.get(id).destroy
|
|
54
|
+
Model::Schedule.get(id.to_i).destroy
|
|
55
55
|
reload_schedules
|
|
56
56
|
''
|
|
57
57
|
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require 'base64'
|
|
2
|
+
|
|
3
|
+
module SimplePvr
|
|
4
|
+
module Server
|
|
5
|
+
class SecuredController < BaseController
|
|
6
|
+
if ENV['username'] && ENV['password']
|
|
7
|
+
PvrLogger.info('Server secured with Basic HTTP Authentication')
|
|
8
|
+
else
|
|
9
|
+
PvrLogger.info('Beware: Unsecured server. Do not expose to the rest of the world!')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
before do
|
|
13
|
+
return unless security_enabled
|
|
14
|
+
return if username_and_password_from_request == [http_username, http_password]
|
|
15
|
+
|
|
16
|
+
# We don't want AJAX calls to pop up the browser's own log-in dialog, so we
|
|
17
|
+
# give AJAX calls a special scheme. See
|
|
18
|
+
# http://stackoverflow.com/questions/86105/how-can-i-supress-the-browsers-authentication-dialog
|
|
19
|
+
# and
|
|
20
|
+
# http://loudvchar.blogspot.ca/2010/11/avoiding-browser-popup-for-401.html
|
|
21
|
+
scheme = is_ajax_call ? 'xBasic' : 'Basic'
|
|
22
|
+
|
|
23
|
+
halt 401, {
|
|
24
|
+
'Content-Type' => 'text/plain',
|
|
25
|
+
'Content-Length' => '0',
|
|
26
|
+
'WWW-Authenticate' => "#{scheme} realm=\"SimplePVR\""
|
|
27
|
+
}, []
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def http_username
|
|
31
|
+
ENV['username']
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def http_password
|
|
35
|
+
ENV['password']
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
def security_enabled
|
|
40
|
+
http_username && http_password
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def username_and_password_from_request
|
|
44
|
+
authorization = encoded_credentials_from_http_basic_authentication || encoded_credentials_from_cookie
|
|
45
|
+
if authorization
|
|
46
|
+
username_and_password = Base64.decode64(authorization)
|
|
47
|
+
if username_and_password =~ /(.*):(.*)/
|
|
48
|
+
username, password = $1, $2
|
|
49
|
+
return [username, password]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
[nil, nil]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def encoded_credentials_from_http_basic_authentication
|
|
56
|
+
authorization = env['HTTP_AUTHORIZATION']
|
|
57
|
+
if authorization =~ /Basic ([a-zA-Z0-9\+\/]*[=]{0,2})/
|
|
58
|
+
return $1
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def encoded_credentials_from_cookie
|
|
63
|
+
request.cookies['basicCredentials']
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def is_ajax_call
|
|
67
|
+
env['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module SimplePvr
|
|
2
2
|
module Server
|
|
3
|
-
class ShowsController <
|
|
3
|
+
class ShowsController < SecuredController
|
|
4
4
|
get '/' do
|
|
5
5
|
shows = PvrInitializer.recording_manager.shows
|
|
6
6
|
shows.map do |show|
|
|
@@ -24,7 +24,7 @@ module SimplePvr
|
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
get '/:show_id/recordings/?' do |show_id|
|
|
27
|
-
recordings = PvrInitializer.recording_manager.
|
|
27
|
+
recordings = PvrInitializer.recording_manager.recordings_of(show_id)
|
|
28
28
|
recordings.map {|recording| recording_hash(show_id, recording) }.to_json
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -33,28 +33,33 @@ module SimplePvr
|
|
|
33
33
|
recording_hash(show_id, recording).to_json
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
-
delete '/:show_id/recordings/:
|
|
37
|
-
PvrInitializer.recording_manager.
|
|
36
|
+
delete '/:show_id/recordings/:recording_id' do |show_id, recording_id|
|
|
37
|
+
PvrInitializer.recording_manager.delete_show_recording(show_id, recording_id)
|
|
38
38
|
''
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
get '/:show_id/recordings/:recording_id/icon' do |show_id, recording_id|
|
|
42
|
+
path = PvrInitializer.recording_manager.directory_for_show_and_recording(show_id, recording_id)
|
|
43
|
+
send_file File.join(path, 'icon')
|
|
44
|
+
end
|
|
45
|
+
|
|
41
46
|
get '/:show_id/recordings/:recording_id/thumbnail.png' do |show_id, recording_id|
|
|
42
|
-
path = PvrInitializer.recording_manager.
|
|
47
|
+
path = PvrInitializer.recording_manager.directory_for_show_and_recording(show_id, recording_id)
|
|
43
48
|
send_file File.join(path, 'thumbnail.png')
|
|
44
49
|
end
|
|
45
50
|
|
|
46
51
|
get '/:show_id/recordings/:recording_id/stream.ts' do |show_id, recording_id|
|
|
47
|
-
path = PvrInitializer.recording_manager.
|
|
52
|
+
path = PvrInitializer.recording_manager.directory_for_show_and_recording(show_id, recording_id)
|
|
48
53
|
send_file File.join(path, 'stream.ts')
|
|
49
54
|
end
|
|
50
55
|
|
|
51
56
|
get '/:show_id/recordings/:recording_id/stream.webm' do |show_id, recording_id|
|
|
52
|
-
path = PvrInitializer.recording_manager.
|
|
57
|
+
path = PvrInitializer.recording_manager.directory_for_show_and_recording(show_id, recording_id)
|
|
53
58
|
send_file File.join(path, 'stream.webm'), type: :webm
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
post '/:show_id/recordings/:recording_id/transcode' do |show_id, recording_id|
|
|
57
|
-
path = PvrInitializer.recording_manager.
|
|
62
|
+
path = PvrInitializer.recording_manager.directory_for_show_and_recording(show_id, recording_id)
|
|
58
63
|
Ffmpeg.transcode_to_webm(path)
|
|
59
64
|
''
|
|
60
65
|
end
|
data/lib/simple_pvr/version.rb
CHANGED
|
@@ -50,7 +50,9 @@ module SimplePvr
|
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
def add_programme(channel_name, programme)
|
|
53
|
-
title_node, subtitle_node, description_node, episode_num_node = nil
|
|
53
|
+
title_node, subtitle_node, description_node, episode_num_node, icon_node, credits_node = nil
|
|
54
|
+
category_nodes = []
|
|
55
|
+
|
|
54
56
|
programme.children.each do |child|
|
|
55
57
|
case child.name
|
|
56
58
|
when 'title'
|
|
@@ -58,9 +60,15 @@ module SimplePvr
|
|
|
58
60
|
when 'sub-title'
|
|
59
61
|
subtitle_node = child
|
|
60
62
|
when 'desc'
|
|
61
|
-
|
|
63
|
+
description_node = child
|
|
62
64
|
when 'episode-num'
|
|
63
|
-
|
|
65
|
+
episode_num_node = child
|
|
66
|
+
when 'icon'
|
|
67
|
+
icon_node = child
|
|
68
|
+
when 'category'
|
|
69
|
+
category_nodes << child
|
|
70
|
+
when 'credits'
|
|
71
|
+
credits_node = child
|
|
64
72
|
end
|
|
65
73
|
end
|
|
66
74
|
|
|
@@ -70,8 +78,33 @@ module SimplePvr
|
|
|
70
78
|
episode_num = episode_num_node ? episode_num_node.text : ''
|
|
71
79
|
start_time = Time.parse(programme[:start])
|
|
72
80
|
stop_time = Time.parse(programme[:stop])
|
|
81
|
+
icon_url = icon_node ? icon_node['src'] : nil
|
|
73
82
|
|
|
74
|
-
Programme.add(channel_from_name(channel_name), title, subtitle, description, start_time, stop_time - start_time, episode_num)
|
|
83
|
+
programme = Programme.add(channel_from_name(channel_name), title, subtitle, description, start_time, stop_time - start_time, episode_num, icon_url)
|
|
84
|
+
|
|
85
|
+
if programme
|
|
86
|
+
add_categories(programme, category_nodes)
|
|
87
|
+
add_credits(programme, credits_node) if credits_node
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def add_categories(programme, category_nodes)
|
|
92
|
+
category_nodes.each do |child|
|
|
93
|
+
programme.categories.create(language: child[:language], name: child.text)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def add_credits(programme, credits_node)
|
|
98
|
+
credits_node.children.each do |child|
|
|
99
|
+
case child.name
|
|
100
|
+
when 'director'
|
|
101
|
+
programme.directors.create(name: child.text)
|
|
102
|
+
when 'presenter'
|
|
103
|
+
programme.presenters.create(name: child.text)
|
|
104
|
+
when 'actor'
|
|
105
|
+
programme.actors.create(role_name: child[:role], actor_name: child.text)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
75
108
|
end
|
|
76
109
|
|
|
77
110
|
def channel_from_name(channel_name)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/* From https://github.com/ashleydw/typeahead.js-bootstrap.css */
|
|
2
|
+
|
|
3
|
+
.twitter-typeahead {
|
|
4
|
+
width: 100%;
|
|
5
|
+
position: relative;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.twitter-typeahead .tt-query,
|
|
9
|
+
.twitter-typeahead .tt-hint {
|
|
10
|
+
margin-bottom: 0;
|
|
11
|
+
width: 100%;
|
|
12
|
+
height: 34px;
|
|
13
|
+
position: absolute;
|
|
14
|
+
top: 0;
|
|
15
|
+
left: 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.twitter-typeahead .tt-hint {
|
|
19
|
+
color: #a1a1a1;
|
|
20
|
+
z-index: 1;
|
|
21
|
+
padding: 6px 12px;
|
|
22
|
+
border: 1px solid transparent;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.twitter-typeahead .tt-query {
|
|
26
|
+
z-index: 2;
|
|
27
|
+
border-radius: 4px !important;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.input-group-addon + .twitter-typeahead > .tt-query {
|
|
31
|
+
border-top-left-radius: 0!important;
|
|
32
|
+
border-bottom-left-radius: 0!important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.input-group-appended > .twitter-typeahead > .tt-query {
|
|
36
|
+
border-top-right-radius: 0!important;
|
|
37
|
+
border-bottom-right-radius: 0!important;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.tt-dropdown-menu {
|
|
41
|
+
min-width: 160px;
|
|
42
|
+
margin-top: 2px;
|
|
43
|
+
padding: 5px 0;
|
|
44
|
+
background-color: #fff;
|
|
45
|
+
border: 1px solid #ccc;
|
|
46
|
+
border: 1px solid rgba(0, 0, 0, .2);
|
|
47
|
+
*border-right-width: 2px;
|
|
48
|
+
*border-bottom-width: 2px;
|
|
49
|
+
-webkit-border-radius: 6px;
|
|
50
|
+
-moz-border-radius: 6px;
|
|
51
|
+
border-radius: 6px;
|
|
52
|
+
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
|
53
|
+
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
|
54
|
+
box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
|
|
55
|
+
-webkit-background-clip: padding-box;
|
|
56
|
+
-moz-background-clip: padding;
|
|
57
|
+
background-clip: padding-box;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.tt-suggestion {
|
|
61
|
+
display: block;
|
|
62
|
+
padding: 3px 20px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.tt-suggestion.tt-is-under-cursor {
|
|
66
|
+
color: #fff;
|
|
67
|
+
background-color: #0081c2;
|
|
68
|
+
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
|
|
69
|
+
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
|
|
70
|
+
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
|
|
71
|
+
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
|
|
72
|
+
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
|
|
73
|
+
background-repeat: repeat-x;
|
|
74
|
+
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.tt-suggestion.tt-is-under-cursor a {
|
|
78
|
+
color: #fff;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.tt-suggestion p {
|
|
82
|
+
margin: 0;
|
|
83
|
+
}
|