tablo_connect 0.0.2
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/LICENSE +22 -0
- data/README.md +43 -0
- data/Rakefile +26 -0
- data/app/assets/fonts/tablo_connect/bootstrap/glyphicons-halflings-regular.eot +0 -0
- data/app/assets/fonts/tablo_connect/bootstrap/glyphicons-halflings-regular.svg +288 -0
- data/app/assets/fonts/tablo_connect/bootstrap/glyphicons-halflings-regular.ttf +0 -0
- data/app/assets/fonts/tablo_connect/bootstrap/glyphicons-halflings-regular.woff +0 -0
- data/app/assets/fonts/tablo_connect/bootstrap/glyphicons-halflings-regular.woff2 +0 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-animate.js +3928 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-animate.min.js +56 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-animate.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-aria.js +397 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-aria.min.js +14 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-aria.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-cookies.js +321 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-cookies.min.js +9 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-cookies.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-csp.css +21 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-loader.js +443 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-loader.min.js +10 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-loader.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-message-format.js +980 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-message-format.min.js +26 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-message-format.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-messages.js +685 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-messages.min.js +12 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-messages.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-mocks.js +2470 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-resource.js +675 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-resource.min.js +13 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-resource.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-route.js +991 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-route.min.js +15 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-route.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-sanitize.js +683 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-sanitize.min.js +16 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-sanitize.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-scenario.js +40324 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-touch.js +628 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-touch.min.js +13 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-touch.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/angular-ui-router.js +4370 -0
- data/app/assets/javascripts/tablo_connect/angular/angular.js +28904 -0
- data/app/assets/javascripts/tablo_connect/angular/angular.min.js +294 -0
- data/app/assets/javascripts/tablo_connect/angular/angular.min.js.map +8 -0
- data/app/assets/javascripts/tablo_connect/angular/errors.json +1 -0
- data/app/assets/javascripts/tablo_connect/angular/version.json +1 -0
- data/app/assets/javascripts/tablo_connect/angular/version.txt +1 -0
- data/app/assets/javascripts/tablo_connect/application.js +21 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/affix.js +162 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/alert.js +94 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/button.js +120 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/carousel.js +237 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/collapse.js +211 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/dropdown.js +165 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/modal.js +337 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/popover.js +108 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/scrollspy.js +172 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/tab.js +155 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/tooltip.js +514 -0
- data/app/assets/javascripts/tablo_connect/bootstrap/transition.js +59 -0
- data/app/assets/javascripts/tablo_connect/jquery/jquery.js +9210 -0
- data/app/assets/javascripts/tablo_connect/ng_app/app.js +39 -0
- data/app/assets/javascripts/tablo_connect/ng_app/controllers/episodes_controller.js +17 -0
- data/app/assets/javascripts/tablo_connect/ng_app/controllers/home_controller.js +9 -0
- data/app/assets/javascripts/tablo_connect/ng_app/controllers/movies_controller.js +17 -0
- data/app/assets/javascripts/tablo_connect/ng_app/controllers/shows_controller.js +9 -0
- data/app/assets/javascripts/tablo_connect/ng_app/filters/titleize_filter.js +17 -0
- data/app/assets/javascripts/tablo_connect/ng_app/filters/url_filter.js +19 -0
- data/app/assets/javascripts/tablo_connect/ng_app/modules/alerts/alerts.js +9 -0
- data/app/assets/javascripts/tablo_connect/ng_app/modules/alerts/directives/alerts_directive.js +19 -0
- data/app/assets/javascripts/tablo_connect/ng_app/modules/alerts/services/alerts_service.js +22 -0
- data/app/assets/javascripts/tablo_connect/ng_app/modules/alerts/templates/alerts.html +6 -0
- data/app/assets/javascripts/tablo_connect/ng_app/modules/tablo_sync/directives/tablo_sync_directive.js +31 -0
- data/app/assets/javascripts/tablo_connect/ng_app/modules/tablo_sync/services/tablo_sync_service.js +20 -0
- data/app/assets/javascripts/tablo_connect/ng_app/modules/tablo_sync/tablo_sync.js +9 -0
- data/app/assets/javascripts/tablo_connect/ng_app/modules/tablo_sync/templates/tablo_sync.html +4 -0
- data/app/assets/javascripts/tablo_connect/ng_app/services/tablo_service.js +98 -0
- data/app/assets/javascripts/tablo_connect/ng_app/templates/episodes.html +36 -0
- data/app/assets/javascripts/tablo_connect/ng_app/templates/index.html +3 -0
- data/app/assets/javascripts/tablo_connect/ng_app/templates/movies.html +33 -0
- data/app/assets/javascripts/tablo_connect/ng_app/templates/shows.html +13 -0
- data/app/assets/stylesheets/tablo_connect/_bootstrap.scss +56 -0
- data/app/assets/stylesheets/tablo_connect/_global.scss +19 -0
- data/app/assets/stylesheets/tablo_connect/_movies.scss +0 -0
- data/app/assets/stylesheets/tablo_connect/_shows.scss +30 -0
- data/app/assets/stylesheets/tablo_connect/application.scss +18 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_alerts.scss +73 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_badges.scss +68 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_breadcrumbs.scss +26 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_button-groups.scss +244 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_buttons.scss +168 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_carousel.scss +269 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_close.scss +36 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_code.scss +69 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_component-animations.scss +37 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_dropdowns.scss +216 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_forms.scss +611 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_glyphicons.scss +307 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_grid.scss +84 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_input-groups.scss +167 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_jumbotron.scss +52 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_labels.scss +66 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_list-group.scss +130 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_media.scss +66 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_mixins.scss +40 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_modals.scss +150 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_navbar.scss +662 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_navs.scss +242 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_normalize.scss +424 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_pager.scss +54 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_pagination.scss +89 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_panels.scss +271 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_popovers.scss +131 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_print.scss +101 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_progress-bars.scss +87 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_responsive-embed.scss +35 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_responsive-utilities.scss +179 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_scaffolding.scss +161 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_tables.scss +234 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_theme.scss +291 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_thumbnails.scss +38 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_tooltip.scss +101 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_type.scss +298 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_utilities.scss +55 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_variables.scss +872 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/_wells.scss +29 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_alerts.scss +14 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_background-variant.scss +12 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_border-radius.scss +18 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_buttons.scss +68 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_center-block.scss +7 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_clearfix.scss +22 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_forms.scss +88 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_gradients.scss +58 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_grid-framework.scss +81 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_grid.scss +122 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_hide-text.scss +21 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_image.scss +33 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_labels.scss +12 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_list-group.scss +32 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_nav-divider.scss +10 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_nav-vertical-align.scss +9 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_opacity.scss +8 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_pagination.scss +24 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_panels.scss +24 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_progress-bar.scss +10 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_reset-filter.scss +8 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_reset-text.scss +18 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_resize.scss +6 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_responsive-visibility.scss +21 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_size.scss +10 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_tab-focus.scss +9 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_table-row.scss +28 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_text-emphasis.scss +12 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_text-overflow.scss +8 -0
- data/app/assets/stylesheets/tablo_connect/bootstrap/mixins/_vendor-prefixes.scss +222 -0
- data/app/controllers/tablo_connect/application_controller.rb +4 -0
- data/app/controllers/tablo_connect/copy_controller.rb +75 -0
- data/app/controllers/tablo_connect/home_controller.rb +6 -0
- data/app/controllers/tablo_connect/movies_controller.rb +9 -0
- data/app/controllers/tablo_connect/shows_controller.rb +11 -0
- data/app/controllers/tablo_connect/sync_controller.rb +73 -0
- data/app/helpers/tablo_connect/application_helper.rb +4 -0
- data/app/helpers/tablo_connect/movies_helper.rb +4 -0
- data/app/helpers/tablo_connect/shows_helper.rb +4 -0
- data/app/models/tablo_connect/movie.rb +14 -0
- data/app/models/tablo_connect/show.rb +23 -0
- data/app/views/layouts/tablo_connect/_header.html.erb +20 -0
- data/app/views/layouts/tablo_connect/application.html.erb +19 -0
- data/app/views/tablo_connect/home/index.html.erb +1 -0
- data/config/routes.rb +10 -0
- data/db/migrate/20151101164611_create_tablo_connect_movies.rb +18 -0
- data/db/migrate/20151101165020_create_tablo_connect_shows.rb +21 -0
- data/lib/tablo_connect.rb +7 -0
- data/lib/tablo_connect/engine.rb +28 -0
- data/lib/tablo_connect/version.rb +3 -0
- data/lib/tasks/tablo_connect_tasks.rake +4 -0
- data/spec/controllers/tablo_connect/copy_controller_spec.rb +244 -0
- data/spec/controllers/tablo_connect/movies_controller_spec.rb +27 -0
- data/spec/controllers/tablo_connect/shows_controller_spec.rb +45 -0
- data/spec/controllers/tablo_connect/sync_controller_spec.rb +209 -0
- data/spec/factories/tablo_connect_movies.rb +11 -0
- data/spec/factories/tablo_connect_shows.rb +15 -0
- data/spec/javascript/angular-environment.js +1 -0
- data/spec/javascript/karma.conf.js +48 -0
- data/spec/javascript/unit/controllers/episodes_controller_spec.js +49 -0
- data/spec/javascript/unit/controllers/home_controller_spec.js +34 -0
- data/spec/javascript/unit/controllers/movies_controller_spec.js +55 -0
- data/spec/javascript/unit/controllers/shows_controller_spec.js +33 -0
- data/spec/javascript/unit/filters/titleize_filter_spec.js +25 -0
- data/spec/javascript/unit/filters/url_filter_spec.js +33 -0
- data/spec/javascript/unit/modules/alerts/directives/alerts_directive_spec.js +39 -0
- data/spec/javascript/unit/modules/alerts/services/alerts_service_spec.js +51 -0
- data/spec/javascript/unit/modules/tablo_sync/directives/tablo_sync_directive_spec.js +68 -0
- data/spec/javascript/unit/modules/tablo_sync/services/tablo_sync_service_spec.js +26 -0
- data/spec/javascript/unit/services/tablo_service_spec.js +28 -0
- data/spec/models/tablo_connect/movie_spec.rb +32 -0
- data/spec/models/tablo_connect/show_spec.rb +65 -0
- data/spec/spec_helper.rb +86 -0
- data/spec/test_app/README.rdoc +28 -0
- data/spec/test_app/Rakefile +6 -0
- data/spec/test_app/app/assets/javascripts/application.js +13 -0
- data/spec/test_app/app/assets/stylesheets/application.css +15 -0
- data/spec/test_app/app/controllers/application_controller.rb +5 -0
- data/spec/test_app/app/helpers/application_helper.rb +2 -0
- data/spec/test_app/app/views/layouts/application.html.erb +14 -0
- data/spec/test_app/bin/bundle +3 -0
- data/spec/test_app/bin/rails +4 -0
- data/spec/test_app/bin/rake +4 -0
- data/spec/test_app/bin/setup +29 -0
- data/spec/test_app/config.ru +4 -0
- data/spec/test_app/config/application.rb +32 -0
- data/spec/test_app/config/boot.rb +5 -0
- data/spec/test_app/config/database.yml +25 -0
- data/spec/test_app/config/environment.rb +5 -0
- data/spec/test_app/config/environments/development.rb +41 -0
- data/spec/test_app/config/environments/production.rb +79 -0
- data/spec/test_app/config/environments/test.rb +42 -0
- data/spec/test_app/config/initializers/assets.rb +11 -0
- data/spec/test_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/test_app/config/initializers/cookies_serializer.rb +3 -0
- data/spec/test_app/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/test_app/config/initializers/inflections.rb +16 -0
- data/spec/test_app/config/initializers/mime_types.rb +4 -0
- data/spec/test_app/config/initializers/session_store.rb +3 -0
- data/spec/test_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/test_app/config/locales/en.yml +23 -0
- data/spec/test_app/config/routes.rb +4 -0
- data/spec/test_app/config/secrets.yml +22 -0
- data/spec/test_app/db/schema.rb +49 -0
- data/spec/test_app/db/test.sqlite3 +0 -0
- data/spec/test_app/log/test.log +5561 -0
- data/spec/test_app/public/404.html +67 -0
- data/spec/test_app/public/422.html +67 -0
- data/spec/test_app/public/500.html +66 -0
- data/spec/test_app/public/favicon.ico +0 -0
- metadata +550 -0
@@ -0,0 +1,75 @@
|
|
1
|
+
module TabloConnect
|
2
|
+
class CopyController < ApplicationController
|
3
|
+
before_action :set_item
|
4
|
+
|
5
|
+
def index
|
6
|
+
pid = spawn(ffmpeg_copy)
|
7
|
+
Process.detach(pid)
|
8
|
+
|
9
|
+
@item.copy_status = :in_progress
|
10
|
+
@item.save
|
11
|
+
|
12
|
+
render json: {pid: pid, destination: destination_path}, status: :ok
|
13
|
+
end
|
14
|
+
|
15
|
+
def status
|
16
|
+
file_copy_status
|
17
|
+
render json: {copy_status: @item.copy_status}
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def set_item
|
23
|
+
model = case params[:type]
|
24
|
+
when 'movie'
|
25
|
+
TabloConnect::Movie
|
26
|
+
when 'show'
|
27
|
+
TabloConnect::Show
|
28
|
+
else
|
29
|
+
return head :not_found if @item.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
@item = model.find_by_tablo_id(params[:tablo_id])
|
33
|
+
head :not_found if @item.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def source_path
|
37
|
+
"#{TabloConnect.tablo_base_url}/pvr/#{@item.tablo_id}/pl/playlist.m3u8"
|
38
|
+
end
|
39
|
+
|
40
|
+
def destination_path
|
41
|
+
"#{TabloConnect.output_directory}/#{file_name}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def ffmpeg_copy
|
45
|
+
"#{TabloConnect.ffmpeg_path} -i \"#{source_path}\" -bsf:a aac_adtstoasc -vcodec copy -c copy #{destination_path}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def file_name
|
49
|
+
if params[:type] == 'movie'
|
50
|
+
"#{@item.title.gsub(/[^\w\.]/, ' ').squish.gsub(' ', '_')}.mp4"
|
51
|
+
elsif params[:type] == 'show'
|
52
|
+
"#{@item.show.gsub(/[^\w\.]/, ' ').squish.gsub(' ', '_')}.s#{@item.season.to_s.rjust(2, '0')}e#{@item.episode.to_s.rjust(2, '0')}_#{@item.tablo_id}.mp4"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def file_copy_status
|
57
|
+
update_copy_status(new_status) if new_status.to_s != @item.copy_status
|
58
|
+
end
|
59
|
+
|
60
|
+
def new_status
|
61
|
+
return :idle unless File.exists?(destination_path)
|
62
|
+
return :complete if file_unlocked?
|
63
|
+
:in_progress
|
64
|
+
end
|
65
|
+
|
66
|
+
def update_copy_status(new_status)
|
67
|
+
@item.copy_status = new_status
|
68
|
+
@item.save
|
69
|
+
end
|
70
|
+
|
71
|
+
def file_unlocked?
|
72
|
+
%x[lsof -F n].split("\n").grep(/#{destination_path}/).empty?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'open-uri'
|
2
|
+
require 'rest-client'
|
3
|
+
|
4
|
+
module TabloConnect
|
5
|
+
class SyncController < ApplicationController
|
6
|
+
def index
|
7
|
+
begin
|
8
|
+
item_ids = parse_items
|
9
|
+
delete_removed item_ids
|
10
|
+
update_items(item_ids)
|
11
|
+
head :ok
|
12
|
+
rescue => e
|
13
|
+
render json: {error: e.to_s}, status: :internal_server_error
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def parse_items
|
20
|
+
page = Nokogiri::HTML(open("#{TabloConnect.tablo_base_url}/pvr/"))
|
21
|
+
page.css('tbody a').map { |row|
|
22
|
+
row[:href].gsub('/', '').to_i if row[:href].gsub('/', '') =~ /\A\d+\z/
|
23
|
+
}.compact
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete_removed(new_ids)
|
27
|
+
old_ids = TabloConnect::Movie.select(:tablo_id).all + TabloConnect::Show.select(:tablo_id).all
|
28
|
+
delete_ids = old_ids.map {|i| i.tablo_id } - new_ids
|
29
|
+
|
30
|
+
TabloConnect::Movie.delete_by_tablo_id(delete_ids)
|
31
|
+
TabloConnect::Show.delete_by_tablo_id(delete_ids)
|
32
|
+
end
|
33
|
+
|
34
|
+
def update_items(tablo_ids)
|
35
|
+
tablo_ids.each do |tablo_id|
|
36
|
+
details = recording_details tablo_id
|
37
|
+
|
38
|
+
if details[:recMovie].present?
|
39
|
+
update_movie(tablo_id, details)
|
40
|
+
elsif details[:recEpisode].present?
|
41
|
+
update_show(tablo_id, details)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_movie(tablo_id, details)
|
47
|
+
item = TabloConnect::Movie.find_or_create_by(tablo_id: tablo_id)
|
48
|
+
item.title = details.try(:[], :recMovie).try(:[], :jsonForClient).try(:[], :title)
|
49
|
+
item.description = details.try(:[], :recMovie).try(:[], :jsonForClient).try(:[], :plot)
|
50
|
+
item.release_year = details.try(:[], :recMovie).try(:[], :jsonForClient).try(:[], :releaseYear)
|
51
|
+
item.air_date = details.try(:[], :recMovieAiring).try(:[], :jsonForClient).try(:[], :airDate)
|
52
|
+
item.image_id = details.try(:[], :recMovie).try(:[], :imageJson).try(:[], :images).try(:[], 0).try(:[], :imageID)
|
53
|
+
item.save!
|
54
|
+
end
|
55
|
+
|
56
|
+
def update_show(tablo_id, details)
|
57
|
+
item = TabloConnect::Show.find_or_create_by(tablo_id: tablo_id)
|
58
|
+
item.show = details.try(:[], :recEpisode).try(:[], :jsonFromTribune).try(:[], :program).try(:[], :title)
|
59
|
+
item.title = details.try(:[], :recEpisode).try(:[], :jsonFromTribune).try(:[], :program).try(:[], :episodeTitle)
|
60
|
+
item.description = details.try(:[], :recEpisode).try(:[], :jsonFromTribune).try(:[], :program).try(:[], :longDescription)
|
61
|
+
item.episode = details.try(:[], :recEpisode).try(:[], :jsonFromTribune).try(:[], :program).try(:[], :episodeNum)
|
62
|
+
item.season = details.try(:[], :recEpisode).try(:[], :jsonFromTribune).try(:[], :program).try(:[], :seasonNum)
|
63
|
+
item.rec_date = details.try(:[], :recEpisode).try(:[], :jsonForClient).try(:[], :airDate)
|
64
|
+
item.air_date = details.try(:[], :recSeries).try(:[], :jsonForClient).try(:[], :originalAirDate)
|
65
|
+
item.image_id = details.try(:[], :recSeries).try(:[], :imageJson).try(:[], :images).try(:[], 0).try(:[], :imageID)
|
66
|
+
item.save!
|
67
|
+
end
|
68
|
+
|
69
|
+
def recording_details(tablo_id)
|
70
|
+
JSON.parse(RestClient.get("#{TabloConnect.tablo_base_url}/pvr/#{tablo_id}/meta.txt")).with_indifferent_access
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module TabloConnect
|
2
|
+
class Movie < ActiveRecord::Base
|
3
|
+
validates_presence_of :tablo_id
|
4
|
+
validates_uniqueness_of :tablo_id
|
5
|
+
|
6
|
+
enum copy_status: [:idle, :in_progress, :complete]
|
7
|
+
|
8
|
+
scope :all_order_title, -> { self.all.order(:title) }
|
9
|
+
|
10
|
+
def self.delete_by_tablo_id(delete_ids)
|
11
|
+
self.where(tablo_id: delete_ids).delete_all
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module TabloConnect
|
2
|
+
class Show < ActiveRecord::Base
|
3
|
+
validates_presence_of :tablo_id
|
4
|
+
validates_uniqueness_of :tablo_id
|
5
|
+
|
6
|
+
enum copy_status: [:idle, :in_progress, :complete]
|
7
|
+
|
8
|
+
scope :shows, -> { group(:show).order(:show).count }
|
9
|
+
scope :show_episodes, ->(show) { where(show: show).order(:season, :episode, :rec_date, :title) }
|
10
|
+
|
11
|
+
def self.shows_with_image_id
|
12
|
+
self.shows.map { |show|
|
13
|
+
image_id = self.select(:image_id).distinct(:image_id).where({show: show[0]}).order(image_id: :desc).limit(1)[0].try(:[], :image_id)
|
14
|
+
|
15
|
+
{show: show[0], count: show[1], image_id: image_id}
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.delete_by_tablo_id(delete_ids)
|
20
|
+
self.where(tablo_id: delete_ids).delete_all
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<nav class="navbar navbar-default">
|
2
|
+
<div class="container-fluid">
|
3
|
+
<div class="navbar-header">
|
4
|
+
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-nav-collapse" aria-expanded="false">
|
5
|
+
<span class="sr-only">Toggle navigation</span>
|
6
|
+
<span class="icon-bar"></span>
|
7
|
+
<span class="icon-bar"></span>
|
8
|
+
<span class="icon-bar"></span>
|
9
|
+
</button>
|
10
|
+
<a class="navbar-brand" href="#/">Tablo Connect</a>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div class="collapse navbar-collapse" id="bs-nav-collapse">
|
14
|
+
<ul class="nav navbar-nav">
|
15
|
+
<li><a ui-sref="movies">Movies</a></li>
|
16
|
+
<li><a ui-sref="shows">Shows</a></li>
|
17
|
+
</ul>
|
18
|
+
</div>
|
19
|
+
</div>
|
20
|
+
</nav>
|
@@ -0,0 +1,19 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html ng-app="ac.tabloConnectApp">
|
3
|
+
<head>
|
4
|
+
<title>TabloConnect</title>
|
5
|
+
<%= stylesheet_link_tag "tablo_connect/application", media: "all" %>
|
6
|
+
<%= csrf_meta_tags %>
|
7
|
+
</head>
|
8
|
+
<body>
|
9
|
+
<%= render 'layouts/tablo_connect/header' %>
|
10
|
+
<div class="container-fluid">
|
11
|
+
<%= yield %>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<%= javascript_include_tag "tablo_connect/application" %>
|
15
|
+
<script>
|
16
|
+
angular.module('ac.tabloConnectApp').constant('tabloBaseUrl', '<%= "http://#{TabloConnect.tablo_ip}:#{TabloConnect.tablo_port}" %>')
|
17
|
+
</script>
|
18
|
+
</body>
|
19
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
<ui-view></ui-view>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
TabloConnect::Engine.routes.draw do
|
2
|
+
root to: "home#index"
|
3
|
+
|
4
|
+
get 'sync', to: 'sync#index'
|
5
|
+
get 'movies', to: 'movies#index'
|
6
|
+
get 'shows', to: 'shows#index'
|
7
|
+
get 'episodes/:show', to: 'shows#episodes'
|
8
|
+
get 'copy/:tablo_id/:type', to: 'copy#index'
|
9
|
+
get 'copy/:tablo_id/:type/status', to: 'copy#status'
|
10
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class CreateTabloConnectMovies < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :tablo_connect_movies do |t|
|
4
|
+
t.integer :tablo_id
|
5
|
+
t.string :title
|
6
|
+
t.text :description
|
7
|
+
t.integer :release_year
|
8
|
+
t.datetime :air_date
|
9
|
+
t.integer :image_id
|
10
|
+
t.integer :copy_status, default: 0
|
11
|
+
|
12
|
+
t.timestamps null: false
|
13
|
+
end
|
14
|
+
|
15
|
+
add_index :tablo_connect_movies, :tablo_id, :unique => true
|
16
|
+
add_index :tablo_connect_movies, :title
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class CreateTabloConnectShows < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :tablo_connect_shows do |t|
|
4
|
+
t.integer :tablo_id
|
5
|
+
t.string :show
|
6
|
+
t.string :title
|
7
|
+
t.text :description
|
8
|
+
t.integer :episode
|
9
|
+
t.integer :season
|
10
|
+
t.date :air_date
|
11
|
+
t.datetime :rec_date
|
12
|
+
t.integer :image_id
|
13
|
+
t.integer :copy_status, default: 0
|
14
|
+
|
15
|
+
t.timestamps null: false
|
16
|
+
end
|
17
|
+
|
18
|
+
add_index :tablo_connect_shows, :tablo_id, :unique => true
|
19
|
+
add_index :tablo_connect_shows, :show
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module TabloConnect
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace TabloConnect
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec
|
7
|
+
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
mattr_accessor :tablo_ip
|
13
|
+
self.tablo_ip = '192.168.1.99'
|
14
|
+
|
15
|
+
mattr_accessor :tablo_port
|
16
|
+
self.tablo_port = '180809'
|
17
|
+
|
18
|
+
mattr_accessor :ffmpeg_path
|
19
|
+
self.ffmpeg_path = '/usr/local/bin/ffmpeg'
|
20
|
+
|
21
|
+
mattr_accessor :output_directory
|
22
|
+
self.output_directory = '/tmp'
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.setup(&block)
|
26
|
+
yield self
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module TabloConnect
|
4
|
+
describe CopyController, type: :controller do
|
5
|
+
routes { TabloConnect::Engine.routes }
|
6
|
+
|
7
|
+
let!(:show) { FactoryGirl.create(:tablo_connect_show, show: 'Angle of Attack') }
|
8
|
+
|
9
|
+
describe "GET index" do
|
10
|
+
before do
|
11
|
+
allow(controller).to receive(:spawn).and_return(1234)
|
12
|
+
allow(controller).to receive(:destination_path).and_return('/tmp/output.mp4')
|
13
|
+
allow(Process).to receive(:detach)
|
14
|
+
end
|
15
|
+
|
16
|
+
context "when the item is not found" do
|
17
|
+
it "returns a 404" do
|
18
|
+
get :index, {tablo_id: 99999, type: 'some_type'}
|
19
|
+
expect(response.status).to eq 404
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context "when the item is found" do
|
24
|
+
it "returns a 200" do
|
25
|
+
get :index, {tablo_id: show.tablo_id, type: 'show'}
|
26
|
+
expect(response.status).to eq 200
|
27
|
+
end
|
28
|
+
|
29
|
+
it "calls spawn" do
|
30
|
+
expect(controller).to receive(:spawn)
|
31
|
+
get :index, {tablo_id: show.tablo_id, type: 'show'}
|
32
|
+
end
|
33
|
+
|
34
|
+
it "sets the copy_status to in_progress" do
|
35
|
+
item = TabloConnect::Show.find_by_tablo_id show.tablo_id
|
36
|
+
expect(item.copy_status).to eq('idle')
|
37
|
+
get :index, {tablo_id: show.tablo_id, type: 'show'}
|
38
|
+
item.reload
|
39
|
+
expect(item.copy_status).to eq('in_progress')
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns an object with a shows array" do
|
43
|
+
get :index, {tablo_id: show.tablo_id, type: 'show'}
|
44
|
+
json_body = JSON.parse(response.body).with_indifferent_access
|
45
|
+
expect(json_body).to match({pid: 1234, destination: '/tmp/output.mp4'})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "GET status" do
|
51
|
+
it "calls file_copy_status" do
|
52
|
+
expect(controller).to receive(:file_copy_status)
|
53
|
+
get :status, {tablo_id: show.tablo_id, type: 'show'}
|
54
|
+
end
|
55
|
+
|
56
|
+
it "returns a 200 status code" do
|
57
|
+
get :status, {tablo_id: show.tablo_id, type: 'show'}
|
58
|
+
expect(response.status).to eq 200
|
59
|
+
end
|
60
|
+
|
61
|
+
it "returns a JSON object with the copy_status" do
|
62
|
+
get :status, {tablo_id: show.tablo_id, type: 'show'}
|
63
|
+
json_body = JSON.parse(response.body).with_indifferent_access
|
64
|
+
expect(json_body[:copy_status]).to eq show.copy_status
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe ".set_item" do
|
69
|
+
before do
|
70
|
+
allow(controller).to receive(:head).and_return(double.as_null_object)
|
71
|
+
end
|
72
|
+
|
73
|
+
context "when the type is not show or movie" do
|
74
|
+
it "returns a 404" do
|
75
|
+
allow(controller).to receive(:params).and_return({tablo_id: show.tablo_id, type: 'invalid_type'})
|
76
|
+
expect(controller).to receive(:head).with(:not_found)
|
77
|
+
controller.send(:set_item)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
context "when the type is 'show' or 'movie'" do
|
82
|
+
context "when the type is 'movie'" do
|
83
|
+
it "uses the Movie model" do
|
84
|
+
allow(controller).to receive(:params).and_return({tablo_id: show.tablo_id, type: 'movie'})
|
85
|
+
expect(TabloConnect::Movie).to receive(:find_by_tablo_id).with(show.tablo_id)
|
86
|
+
controller.send(:set_item)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "when the type is 'show'" do
|
91
|
+
it "uses the Show model" do
|
92
|
+
allow(controller).to receive(:params).and_return({tablo_id: show.tablo_id, type: 'show'})
|
93
|
+
expect(TabloConnect::Show).to receive(:find_by_tablo_id).with(show.tablo_id)
|
94
|
+
controller.send(:set_item)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when the item is not found" do
|
99
|
+
it "returns a 404" do
|
100
|
+
allow(controller).to receive(:params).and_return({tablo_id: 99999, type: 'show'})
|
101
|
+
controller.send(:set_item)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when the item is found" do
|
106
|
+
it "sets the @item instance variable" do
|
107
|
+
allow(controller).to receive(:params).and_return({tablo_id: show.tablo_id, type: 'show'})
|
108
|
+
controller.send(:set_item)
|
109
|
+
item = controller.instance_variable_get(:@item)
|
110
|
+
expect(item).to match(show)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe ".source_path" do
|
117
|
+
it "builds the source_path with the tablo_id" do
|
118
|
+
controller.instance_variable_set(:@item, show)
|
119
|
+
path = controller.send(:source_path)
|
120
|
+
expect(path).to eq "#{TabloConnect.tablo_base_url}/pvr/#{show.tablo_id}/pl/playlist.m3u8"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe ".destination_path" do
|
125
|
+
it "builds the destination path with the file_name method" do
|
126
|
+
controller.instance_variable_set(:@item, show)
|
127
|
+
allow(controller).to receive(:file_name).and_return('output.mp4')
|
128
|
+
path = controller.send(:destination_path)
|
129
|
+
expect(path).to eq "#{TabloConnect.output_directory}/output.mp4"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe ".ffmpeg_copy" do
|
134
|
+
it "returns the ffmpeg copy command string" do
|
135
|
+
allow(controller).to receive(:source_path).and_return('/source/path/input.mp4')
|
136
|
+
allow(controller).to receive(:destination_path).and_return('/destination/path/output.mp4')
|
137
|
+
expect(controller.send(:ffmpeg_copy)).to eq "#{TabloConnect.ffmpeg_path} -i \"/source/path/input.mp4\" -bsf:a aac_adtstoasc -vcodec copy -c copy /destination/path/output.mp4"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe ".file_name" do
|
142
|
+
before do
|
143
|
+
controller.instance_variable_set(:@item, show)
|
144
|
+
end
|
145
|
+
|
146
|
+
context "when type is 'movie'" do
|
147
|
+
it "returns the unix safe movie file name" do
|
148
|
+
allow(controller).to receive(:params).and_return({type: 'movie'})
|
149
|
+
expect(controller.send(:file_name)).to eq('The_Harder_They_Fall.mp4')
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context "when type is 'show'" do
|
154
|
+
it "returns the unix safe show file name" do
|
155
|
+
allow(controller).to receive(:params).and_return({type: 'show'})
|
156
|
+
expect(controller.send(:file_name)).to eq("Angle_of_Attack.s01e01_#{show.tablo_id}.mp4")
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe ".file_copy_status" do
|
162
|
+
before do
|
163
|
+
controller.instance_variable_set(:@item, show)
|
164
|
+
end
|
165
|
+
|
166
|
+
context "when the file exists" do
|
167
|
+
before do
|
168
|
+
allow(controller).to receive(:destination_path).and_return(Rails.root.join('public', '404.html').to_s)
|
169
|
+
end
|
170
|
+
|
171
|
+
context "when the file is in use" do
|
172
|
+
it "updates the copy_status to :in_progress" do
|
173
|
+
allow(controller).to receive(:file_unlocked?).and_return(false)
|
174
|
+
controller.send(:file_copy_status)
|
175
|
+
expect(show.copy_status).to eq 'in_progress'
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
context "when the file is not in use" do
|
180
|
+
it "updates the copy_status to :complete" do
|
181
|
+
allow(controller).to receive(:file_unlocked?).and_return(true)
|
182
|
+
controller.send(:file_copy_status)
|
183
|
+
expect(show.copy_status).to eq 'complete'
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
context "when the new_status is equal to the current status" do
|
188
|
+
it "does not update the copy_status" do
|
189
|
+
allow(controller).to receive(:file_unlocked?).and_return(true)
|
190
|
+
show.copy_status = :complete
|
191
|
+
show.save
|
192
|
+
expect(show.copy_status).to eq 'complete'
|
193
|
+
expect(controller).to_not receive(:update_copy_status).with(:complete)
|
194
|
+
controller.send(:file_copy_status)
|
195
|
+
expect(show.copy_status).to eq 'complete'
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context "when the file does not exist" do
|
201
|
+
before do
|
202
|
+
allow(controller).to receive(:destination_path).and_return(Rails.root.join('public', 'non_existent.html').to_s)
|
203
|
+
end
|
204
|
+
|
205
|
+
it "updates the copy_status to :idle" do
|
206
|
+
show.copy_status = :in_progress
|
207
|
+
show.save!
|
208
|
+
expect(show.copy_status).to eq 'in_progress'
|
209
|
+
controller.send(:file_copy_status)
|
210
|
+
expect(show.copy_status).to eq 'idle'
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe ".new_status" do
|
216
|
+
context "when file does not exist" do
|
217
|
+
it "returns :idle" do
|
218
|
+
allow(File).to receive(:exists?).and_return(false)
|
219
|
+
expect(controller.send(:new_status)).to eq :idle
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context "when file exists" do
|
224
|
+
before do
|
225
|
+
allow(File).to receive(:exists?).and_return(true)
|
226
|
+
end
|
227
|
+
|
228
|
+
context "when file is not in use" do
|
229
|
+
it "returns :complete" do
|
230
|
+
allow(controller).to receive(:file_unlocked?).and_return(true)
|
231
|
+
expect(controller.send(:new_status)).to eq :complete
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
context "when file is in use" do
|
236
|
+
it "returns :in_progress" do
|
237
|
+
allow(controller).to receive(:file_unlocked?).and_return(false)
|
238
|
+
expect(controller.send(:new_status)).to eq :in_progress
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|